私はいくつかの追加の調査を行い、満足のいく解決策を見つけることができました。ここに来る:
ライブラリは、それを統合する各アプリケーションが既知のアクションで放送受信機を公開するような方法で開発する必要があります。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のオーバーヘッドを減らすために、フォーマットをサポートするカスタムラッパーを使用しています。