我进行了一些额外的研究,并设法找到了一个令人满意的解决方案。它来了:
必须以某种方式开发库,即每个集成它的应用程序 - 发布具有已知动作的广播接收器,例如。com.mylib.ACTION_DETECT。
库必须有额外的服务,发布一些 AIDL 接口,这有助于做出决策 - 如果可以激活库的当前实例。AIDL 可以有一些有用的方法,例如 getVersion()、isActive()、getUUID()。
做出决定的模式是:如果当前实例具有更高的版本号,则另一个 - 它将变为活动状态。如果当前实例的版本较低 - 它会自行停用,如果已经停用,则保持停用状态。如果当前实例与其他实例具有相同的版本,那么如果其他实例未处于活动状态,并且其他库的 uuid 较低(通过 compareTo 方法) - 它会自行激活。在其他情况下 - 它会自行停用。这种交叉检查确保每个库都将自行做出决定 - 不会出现模棱两可的情况,因为每个库将从其他应用程序中其他库实例的已发布 AIDL 支持的服务中获取所需的数据。
下一步是准备一个 IntentService,它在每次删除或添加新包时启动,或者第一次启动带有库的应用程序。IntentService 查询所有包以查找实现 com.mylib.ACTION_DETECT 的广播接收器。然后它遍历检测到的包(拒绝它自己的包),并绑定到其他实例的 AIDL 支持的服务(AIDL 服务的类名将始终相同,只有应用程序包会不同)。完成绑定后——我们有明确的情况——如果应用模式的结果是“积极的”(我们的实例有更好的版本或更高的 uuid,或者已经处于活动状态),那么这意味着其他实例认为自己是“消极的”,并自行停用. 当然,该模式必须应用于每个绑定的 AIDL 服务。
我为我糟糕的英语道歉。
工作冲突避免解决方案的代码:
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 开销。