4

私は、GooglePlayで見ることができるいくつかの人気のあるAndroidアプリケーションに統合されるライブラリプロジェクトを開発しています。

ユーザーが2つ以上のアプリケーションをインストールでき、それぞれが私のライブラリを統合できると仮定します。ライブラリには、環境の状態の変化を検出するために使用される特定のコードがあります。状態は単に私のサーバーに送信されます。問題は、環境状態の処理に多くのCPUパワーが必要ですが、短時間であるということです。処理のサイクルは、適切なIntentServiceを起動する「非ウェイクアップ」ブロードキャストを使用してAlarmManagerによって開始されます。

私の目標は、アプリケーションに統合された1つのインスタンスだけが作業を実行できるようにライブラリを実装することです。つまり、1つのライブラリモジュールのみが「アクティブ」として機能する必要があります。ユーザーのデバイスにインストールされているアプリケーションがさらにある場合は、それらが重複しないようにする必要があります。

それを達成する方法は?ある種のパーミッション検証とクロスパッケージ検出について考えていましたが、それを実装する方法を想像することはできませんでした。

4

5 に答える 5

2

ネットワークで使用されている(または以前より頻繁に使用されていた)CSMA/CD衝突検出技術に関連するものを試してみます。

特定のインスタンスがアンインストールされるかどうかわからないため、常に作業を行うために特定のインスタンスにコミットする必要はありません。したがって、代わりに、毎回新たに決定を下します(いつでもどちらがそれを行うかは実際には問題ではないため)。

解決するのは簡単な問題ではないため、少し複雑になりますが、誰かがこのソリューションを一般化して誰でも使用できるようにするというアイデアが好きです(これを使って何をするのかをオープンソースにしますか?)。

最初のブロードキャストが到着したら、あなた聴いているカスタムブロードキャスト(特定のアプリからのものとして識別されます)を送信します。たとえば、1秒以内に同じブロードキャストが他に受信されない場合は、先に進んで作業を行ってください。ライブラリの他のインスタンスが作業を実行することをいとわないためです。

少なくとも1つの他のライブラリからメッセージを受け取った場合(あなたが聞いたすべてのライブラリを追跡します)、ランダムな時間待ちます。その時間内に他の図書館から「やるよ」というメッセージが届いたら、すぐに「やるよ」というメッセージを送ります。そうでない場合は、「やります」というメッセージを送信し、最初からメッセージを受信した他のすべてのライブラリが「OK、やります」というメッセージを送信するのを待ちます。その後、作業を​​行います。

「I'lldoit」メッセージを送信したが、別のライブラリからも「I'll do it」メッセージを受け取った場合は、プロセスを最初からやり直してください。各ライブラリが「やる」を送信するためにランダムな時間を待つという事実は、このような衝突がめったに起こらないことを意味し、それらが連続して複数回発生することは確かにないはずです。

私はあなたがそれを実現することができるようにこれを十分に説明したことを望みます。そうでない場合は、説明を求めるか、ネットワーキングの世界でこれがどのように行われているかを確認してください。私が説明しようとしているのは、「衝突検出」と呼ばれるもののようなものです。たとえば、https://en.wikipedia.org/wiki/CSMA/CDで参照されています。

于 2012-06-19T16:29:59.900 に答える
1

私の目標は、アプリケーションに統合された1つのインスタンスだけが作業を実行できるようにライブラリを実装することです。

それはかなり複雑になり、結果は信頼できない可能性があります。

イアンのテーマのバリエーションをお勧めします。問題の定義を「N分/時間/何でも毎回だけ作業を実行したい」に変更します。作業が最後に行われたのはいつか(外部ストレージ上のファイル、Webサービスからの要求など)を検出するためのバックグラウンドジョブの手段を用意し、早すぎる場合はその作業をスキップします。そうすれば、ライブラリにインストールされているアプリの数、インストールされている順序、またはアンインストールされているタイミングは関係ありません。

于 2012-06-18T15:33:08.217 に答える
0

デバイスのANDROID_ID(または電話のある種の一意の識別子)を使用してサーバーに登録できず、ライブラリの別のインスタンスがそのデバイスですでに実行されている場合は、何もしないでください。

次のコードでデバイス識別子を取得できます

Secure.getString(context.getContentResolver(), Secure.ANDROID_ID);
于 2012-06-15T18:20:02.773 に答える
0

ContentProviderアプリがデータを共有するためのフレンドリーな方法ではありませんか?1行のSQLiteテーブルを使用して、アトミックタイムスタンプを実装できます。アラームマネージャスキームを、ライブラリの初期化中に作成され、ContentProvider数秒ごとにポーリングするスレッドに置き換えます。CPは、「はい、環境状態を送信してください」と応答します。これは、現在のデータ/時刻でテーブルがすでに更新されているか、「いいえ、まだ」ではないことを意味します。プロバイダーは、テーブルとシステムクロックを調べて、いつ「はい」と言うかを決定します。

于 2012-06-22T03:33:51.590 に答える
0

私はいくつかの追加の調査を行い、満足のいく解決策を見つけることができました。ここに来る:

ライブラリは、それを統合する各アプリケーションが既知のアクションで放送受信機を公開するような方法で開発する必要があります。com.mylib.ACTION_DETECT。

ライブラリには、AIDLインターフェイスを公開する追加のサービスが必要です。これは、ライブラリの現在のインスタンスをアクティブにできるかどうかを判断するのに役立ちます。AIDLには、getVersion()、isActive()、getUUID()などの便利なメソッドを含めることができます。

決定を下すためのパターンは次のとおりです。現在のインスタンスのバージョン番号が大きい場合、他のインスタンスはアクティブになります。現在のインスタンスのバージョンが低い場合は、それ自体が非アクティブ化されるか、すでに非アクティブ化されている場合は非アクティブ化されたままになります。現在のインスタンスのバージョンが他のインスタンスと等しい場合、他のインスタンスがアクティブでなく、他のライブラリのuuidが低い場合(compareToメソッドを使用)-それ自体がアクティブになります。他の条件では、それ自体が非アクティブになります。このクロスチェックにより、各ライブラリが独自に決定を下すことが保証されます。各ライブラリは、他のアプリの他のライブラリインスタンスの公開されたAIDL支援サービスから必要なデータをフェッチするため、あいまいなケースはありません。

次のステップは、新しいパッケージが削除または追加されるたびに、またはライブラリを使用するアプリケーションが最初に開始されるたびに開始されるIntentServiceを準備することです。IntentServiceは、com.mylib.ACTION_DETECTを実装するブロードキャストレシーバーのすべてのパッケージを照会します。次に、検出されたパッケージを繰り返し処理し(独自のパッケージを拒否します)、他のインスタンスのAIDLでバックアップされたサービスにバインドします(AIDLサービスのクラス名は常に同じで、アプリケーションパッケージのみが異なります)。バインディングを完了した後(明確な状況があります)、適用されたパターンの結果が「ポジティブ」(インスタンスのバージョンがより良いか、より高いuuidであるか、すでにアクティブになっている)の場合、他のインスタンスが「ネガティブ」であると判断し、非アクティブ化したことを意味します。もちろん、パターンはバインドされた各AIDLサービスに適用する必要があります。

英語が下手でしたことをお詫びします。

動作するConfictAvoidanceソリューションのコード: バインディングをサポートするIntentServiceクラスであるため、上記のAIDLでサポートされたサービスでもあります。競合チェックを開始するBroadcastReceiverもあります。

public class ConflictAvoidance extends IntentService
{
    private static final String TAG = ConflictAvoidance.class.getSimpleName();
    private static final String PREFERENCES = "mylib_sdk_prefs";
    private static final int VERSION = 1;
    private static final String KEY_BOOLEAN_PRIME_CHECK_DONE = "key_bool_prime_check_done";
    private static final String KEY_BOOLEAN_ACTIVE = "key_bool_active";
    private static final String KEY_LONG_MUUID = "key_long_muuid";
    private static final String KEY_LONG_LUUID = "key_long_luuid";
    private WakeLock mWakeLock;
    private SharedPreferences mPrefs;

    public ConflictAvoidance()
    {
        super(TAG);
    }

    private final IRemoteSDK.Stub mBinder = new IRemoteSDK.Stub()
    {
        @Override
        public boolean isActive() throws RemoteException
        {
            return mPrefs.getBoolean(KEY_BOOLEAN_ACTIVE, false);
        }

        @Override
        public long[] getUUID() throws RemoteException
        {
            return getLongUUID();
        }

        @Override
        public int getSdkVersion() throws RemoteException
        {
            return 1;
        }
    };

    @Override
    public IBinder onBind(Intent intent)
    {
        return mBinder;
    }

    @Override
    public void onCreate()
    {
        //#ifdef DEBUG
        Log.i(TAG, "onCreate()");
        //#endif
        mWakeLock = ((PowerManager) getSystemService(POWER_SERVICE)).newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
        mWakeLock.acquire();
        mPrefs = getSharedPreferences(PREFERENCES, MODE_PRIVATE);
        super.onCreate();
    }

    @Override
    public void onDestroy()
    {
        //#ifdef DEBUG
        Log.i(TAG, "onDestroy()");
        //#endif
        mWakeLock.release();
        super.onDestroy();
    }

    @Override
    protected void onHandleIntent(Intent arg)
    {
        //#ifdef DEBUG
        Log.d(TAG, "Conflict check");
        //#endif
        final String packageName = getPackageName();
        //#ifdef DEBUG
        Log.v(TAG, "Current package name: %s", packageName);
        //#endif
        final ArrayList<String> packages = new ArrayList<String>(20);
        final PackageManager man = getPackageManager();
        //#ifdef DEBUG
        Log.v(TAG, "Querying receivers: com.mylib.android.sdk.ACTION_DETECT_LIB");
        //#endif
        final List<ResolveInfo> receivers = man.queryBroadcastReceivers(new Intent("com.mylib.android.sdk.ACTION_DETECT_LIB"), 0);
        for (ResolveInfo receiver : receivers)
        {
            if (receiver.activityInfo != null)
            {
                final String otherPackageName = receiver.activityInfo.packageName;
                //#ifdef DEBUG
                Log.v(TAG, "Checking package: %s", otherPackageName);
                //#endif
                if (!packageName.equals(otherPackageName))
                {
                    packages.add(otherPackageName);
                }
            }
        }
        if (packages.isEmpty())
        {
            //#ifdef DEBUG
            Log.i(TAG, "No other libraries found");
            //#endif
            setup(true);
        }
        else
        {
            //#ifdef DEBUG
            Log.v(TAG, "Querying other packages");
            //#endif
            final UUID uuid = getUUID();
            for (String pkg : packages)
            {
                final Intent intent = new Intent();
                intent.setClassName(pkg, "com.mylib.android.sdk.utils.ConflictAvoidance");
                final RemoteConnection conn = new RemoteConnection(uuid);
                try
                {
                    if (bindService(intent, conn, BIND_AUTO_CREATE))
                    {
                        if (!conn.canActivateItself())
                        {
                            setup(false);
                            return;
                        }
                    }
                }
                finally
                {
                    unbindService(conn);
                }
            }
            setup(true);
        }
    }

    private UUID getUUID()
    {
        final long[] uuid = getLongUUID();
        return new UUID(uuid[0], uuid[1]);
    }

    private synchronized long[] getLongUUID()
    {
        if (mPrefs.contains(KEY_LONG_LUUID) && mPrefs.contains(KEY_LONG_MUUID))
        {
            return new long[] { mPrefs.getLong(KEY_LONG_MUUID, 0), mPrefs.getLong(KEY_LONG_LUUID, 0) };
        }
        else
        {
            final long[] uuid = new long[2];
            final UUID ruuid = UUID.randomUUID();
            uuid[0] = ruuid.getMostSignificantBits();
            uuid[1] = ruuid.getLeastSignificantBits();
            mPrefs.edit().putLong(KEY_LONG_MUUID, uuid[0]).putLong(KEY_LONG_LUUID, uuid[1]).commit();
            return uuid;
        }
    }

    private void setup(boolean active)
    {
        //#ifdef DEBUG
        Log.v(TAG, "setup(active:%b)", active);
        //#endif
        mPrefs.edit().putBoolean(KEY_BOOLEAN_ACTIVE, active).putBoolean(KEY_BOOLEAN_PRIME_CHECK_DONE, true).commit();
    }

    public static StatusInfo getStatusInfo(Context context)
    {
        final SharedPreferences prefs = context.getSharedPreferences(PREFERENCES, MODE_PRIVATE);
        return new StatusInfo(prefs.getBoolean(KEY_BOOLEAN_ACTIVE, false), prefs.getBoolean(KEY_BOOLEAN_PRIME_CHECK_DONE, false));
    }

    public static class DetectionReceiver extends BroadcastReceiver
    {
        @Override
        public void onReceive(Context context, Intent intent)
        {
            context.startService(new Intent(context, ConflictAvoidance.class));         
        }       
    }

    public static class StatusInfo
    {
        public final boolean isActive;
        public final boolean primeCheckDone;

        public StatusInfo(boolean isActive, boolean primeCheckDone)
        {
            this.isActive = isActive;
            this.primeCheckDone = primeCheckDone;
        }       
    }

    protected static class RemoteConnection implements ServiceConnection
    {
        private final ConditionVariable var = new ConditionVariable(false);
        private final UUID mUuid;
        private final AtomicReference<IRemoteSDK> mSdk = new AtomicReference<IRemoteSDK>();

        public RemoteConnection(UUID uuid)
        {
            super();
            this.mUuid = uuid;
        }

        @Override
        public void onServiceConnected(ComponentName name, IBinder service)
        {
            //#ifdef DEBUG
            Log.v(TAG, "RemoteConnection.onServiceConnected(%s)", name.getPackageName());
            //#endif
            mSdk.set(IRemoteSDK.Stub.asInterface(service));
            var.open();
        }

        @Override
        public void onServiceDisconnected(ComponentName name)
        {
            //#ifdef DEBUG
            Log.w(TAG, "RemoteConnection.onServiceDisconnected(%s)", name);
            //#endif
            var.open();
        }

        public boolean canActivateItself()
        {
            //#ifdef DEBUG
            Log.v(TAG, "RemoteConnection.canActivateItself()");
            //#endif
            var.block(30000);
            final IRemoteSDK sdk = mSdk.get();
            if (sdk != null)
            {
                try
                {
                    final int version = sdk.getSdkVersion();
                    final boolean active = sdk.isActive();
                    final UUID uuid;
                    {
                        final long[] luuid = sdk.getUUID();
                        uuid = new UUID(luuid[0], luuid[1]);
                    }
                    //#ifdef DEBUG
                    Log.v(TAG, "Other library: ver: %d, active: %b, uuid: %s", version, active, uuid);
                    //#endif
                    if (VERSION > version)
                    {
                        return true;
                    }
                    else if (VERSION < version)
                    {
                        return false;
                    }
                    else
                    {
                        if (active)
                        {
                            return false;
                        }
                        else
                        {
                            return mUuid.compareTo(uuid) == 1;
                        }
                    }
                }
                catch (Exception e)
                {
                    return false;
                }
            }
            else
            {
                return false;
            }
        }
    }

}

AIDLファイル:

package com.mylib.android.sdk;

interface IRemoteSDK
{
    boolean isActive();
    long[] getUUID();
    int getSdkVersion();
}

マニフェストのサンプル:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.mylib.android.sdk"
    android:versionCode="1"
    android:versionName="1.0" >
    <uses-sdk
        android:minSdkVersion="4"
        android:targetSdkVersion="4" />
        <service
            android:name="com.mylib.android.sdk.utils.ConflictAvoidance"
            android:exported="true" />
        <receiver android:name="com.mylib.android.sdk.utils.ConflictAvoidance$DetectionReceiver" >
            <intent-filter>
                <action android:name="com.mylib.android.sdk.ACTION_DETECT_LIB" />
            </intent-filter>
            <intent-filter>
                <action android:name="android.intent.action.PACKAGE_ADDED" />
                <action android:name="android.intent.action.PACKAGE_REMOVED" />
                <action android:name="android.intent.action.PACKAGE_DATA_CLEARED" />
                <action android:name="android.intent.action.PACKAGE_REPLACED" />
                <data android:scheme="package" />
            </intent-filter>
        </receiver>
    </application>
</manifest>

アクション:

<action android:name="com.mylib.android.sdk.ACTION_DETECT_LIB" />

これは、ライブラリで他のアプリを検出するために使用される一般的なアクションです。

ログの使用法は奇妙に見えるかもしれませんが、デバッグ時のStringBuffersのオーバーヘッドを減らすために、フォーマットをサポートするカスタムラッパーを使用しています。

于 2012-06-23T13:03:27.490 に答える