我正在开发一个库项目,该项目将集成到一些流行的Android应用程序中,可以在Google Play中看到。
假设用户可以安装两个或更多应用程序,并且每个应用程序都可以集成我的库。该库具有一些用于检测环境状态变化的特定代码。状态只是发送到我的服务器。问题是环境状态处理需要很多CPU能力,但需要很短的时间。处理循环由AlarmManager启动,使用“非唤醒”广播启动正确的IntentService。
我的目标是以这种方式实现库,只有一个集成到应用程序中的实例才能完成工作。我的意思是只有一个库模块应该充当“活动”。如果用户设备上安装了更多应用程序 - 那么它们不应重叠。
如何实现呢?我正在考虑某种权限验证和跨包检测,但无法想象如何实现它。
答案 0 :(得分:2)
我会尝试与CSMA / CD碰撞检测技术相关的东西,这种技术在网络中使用(或者经常使用)。
您不希望提交特定实例以始终执行工作,因为您不知道是否会卸载该实例。因此,每次都要重新做出决定(因为无论在任何给定时间,它都无关紧要。)
它有点复杂,因为它不是一个需要解决的微不足道的问题,但我喜欢有人可能将这个解决方案推广给任何人使用的想法(开源你用这个做什么?)。
当初始广播到达时,发送您也正在收听的自定义广播(标识为来自您的特定应用)。如果你在一秒钟之内没有收到任何其他相同的广播,那么请继续进行工作,因为你的图书馆必须没有其他实例愿意开展这项工作。
如果您做从至少一个其他库中获取消息(跟踪您听到的所有消息),请等待一段随机时间。如果您在这段时间内收到来自另一个图书馆的消息说“我会这样做”,那么请立即发出一条消息,意思是“好的,你这样做”。如果你不,那么发出一条消息说“我会这样做”,并等待你从一开始收到消息的每个其他库发送一个“好吧,你做到了“ 信息。然后做好工作。
如果您发送“我会做”的消息,但也从另一个库获得“我会做”消息,然后开始执行该过程。每个库等待随机时间发送“我会这样做”的事实意味着很少会发生这样的冲突,并且它们肯定不会经常连续多次发生。
我希望我已经很好地解释了这一点,你可以实现它。如果没有,请询问澄清,或者看看在网络世界中如何做到这一点。我想要描述的就是所谓的“碰撞检测”,例如:https://en.wikipedia.org/wiki/CSMA/CD
答案 1 :(得分:1)
我的目标是以这种方式实现库,只有一个集成到应用程序中的实例才能完成工作。
这将是相当复杂的,结果可能不可靠。
我会推荐Ian主题的变体。将问题的定义更改为“我希望工作只能每N分钟/小时/无论什么时候完成”。有一些后台作业的方法来检测上次完成工作的时间(外部存储上的文件,Web服务的请求,等等),如果太快,则跳过该工作。这样,与您的库安装了多少个应用程序,安装它们的顺序或卸载它们无关紧要。
答案 2 :(得分:0)
为什么不能使用设备的ANDROID_ID(或电话的某种唯一标识符),向服务器注册,如果该设备的另一个实例已在该设备上运行,则不执行任何操作。
您可以通过以下代码获取设备标识符
Secure.getString(context.getContentResolver(), Secure.ANDROID_ID);
答案 3 :(得分:0)
不是ContentProvider
应用分享数据的友好方式吗?您可以使用一行SQLite表来实现原子时间戳。将警报管理器方案替换为库初始化期间创建的线程,该线程每隔几秒轮询一次ContentProvider
。 CP回复'是,请发送环境状态',这意味着它已经用当前数据/时间或'否,尚未'更新了表。提供商正在咨询表格和系统时钟,以决定何时说是。
答案 4 :(得分:0)
我做了一些额外的研究,并设法找到了令人满意的解决方案。 它来了:
必须以某种方式开发库,每个应用程序集成它 - 发布具有已知操作的广播接收器,例如。 com.mylib.ACTION_DETECT。
库必须有额外的服务,它发布一些AIDL接口,这有助于做出决定 - 如果库的当前实例可以被激活。 AIDL可以有一些有用的方法,例如getVersion(),isActive(),getUUID()。
决策模式是:如果当前实例具有更高版本号,那么另一个 - 它将变为活动状态。如果当前实例具有较低版本 - 它将自行停用,或者如果已停用则保持停用状态。如果当前实例与其他实例具有相同的版本,那么如果其他实例未激活,并且其他库的uuid较低(通过compareTo方法) - 它将自动激活。在其他条件 - 它将自行停用。这种交叉检查确保每个库都能自行做出决策 - 不存在歧义,因为每个库都将从其他应用程序中其他libary实例的已发布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开销。