¿Cómo asegurarse de que el código de proyecto de la biblioteca de android se ejecuta sólo en una de las aplicaciones instaladas que lo integran?

Estoy desarrollando un proyecto de biblioteca que se integrará en algunas aplicaciones Android populares que se pueden ver en Google Play.

Supongamos que el usuario puede tener dos o más aplicaciones instaladas y cada una puede integrar mi biblioteca. La biblioteca tiene algún código específico utilizado para detectar cambios en el estado del entorno. El estado se envía simplemente a mi servidor. El problema es que el procesamiento del estado del entorno requiere mucha CPU, pero en un corto período de tiempo. Los ciclos de procesamiento son iniciados por AlarmManager, usando transmisiones "no despertar" que lanzan IntentService adecuado.

Mi objetivo es implementar la biblioteca de tal manera, que sólo una instancia integrada en la aplicación puede hacer el trabajo. Quiero decir que sólo un módulo de biblioteca debe actuar como "activo". Si hay más aplicaciones instaladas en el dispositivo del usuario, entonces no deben superponerse.

¿Cómo lograrlo? Estaba pensando en algún tipo de validación de permisos, y la detección de paquetes cruzados, pero no podía imaginar cómo implementarlo.

5 Solutions collect form web for “¿Cómo asegurarse de que el código de proyecto de la biblioteca de android se ejecuta sólo en una de las aplicaciones instaladas que lo integran?”

Me gustaría probar algo relacionado con la técnica de detección de colisión CSMA / CD que se utiliza (o solía ser utilizado con más frecuencia) en la red.

Usted no quiere comprometerse a una instancia específica para estar siempre haciendo el trabajo, ya que no sabe si se desinstalarían. Así que en su lugar, tomar la decisión de nuevo cada vez (ya que realmente no importa lo que lo hace en un momento dado).

Se vuelve un poco complicado, porque no es un problema trivial para resolver, pero me gusta la idea de alguien tal vez la generalización de esta solución para cualquier persona a utilizar (open-source lo que haces con esto?).

Cuando llegue la emisión inicial, envíe una transmisión personalizada (identificada como proveniente de su aplicación) que también está escuchando. Si no recibes ninguna otra de esa misma emisión dentro de, digamos, un segundo, entonces adelante y haz el trabajo, ya que no debe haber otros ejemplos de tu biblioteca dispuestos a hacer el trabajo.

Si obtiene un mensaje de al menos otra biblioteca (mantenga un registro de todas las que escucha), espere una cantidad de tiempo aleatoria. Si recibe un mensaje de otra biblioteca que dice "Lo haré" dentro de esa cantidad de tiempo, inmediatamente envíe un mensaje que significa "bien, lo haces". Si no lo hace , envíe un mensaje diciendo "Lo haré" y espere a que cada otra biblioteca a la que recibió un mensaje le envíe un mensaje de "bien, lo haga" al principio. Luego haz el trabajo.

Si envías un mensaje "Lo haré", pero obtendrás un mensaje "Lo haré" de otra biblioteca, y comenzarás el proceso. El hecho de que cada biblioteca espera un tiempo aleatorio para enviar el "lo haré" significa que rara vez habrá colisiones como esta, y ciertamente no debe suceder a menudo varias veces en una fila.

Espero haber explicado esto lo suficientemente bien como para que eso suceda. Si no es así, pida aclaraciones o mire cómo se hace esto en el mundo de las redes. Lo que estoy tratando de describir es como lo que se llama "Collision Detection", por ejemplo, como se hace referencia aquí: https://en.wikipedia.org/wiki/CSMA/CD

Mi objetivo es implementar la biblioteca de tal manera, que sólo una instancia integrada en la aplicación puede hacer el trabajo.

Eso va a ser bastante complicado, y los resultados probablemente no serán confiables.

Recomendaría una variación en el tema de Ian. Cambie la definición de su problema para que sea "Quiero que el trabajo sólo se haga cada N minutos / horas / lo que sea". Tener algunos medios del trabajo de fondo para detectar cuando el trabajo fue hecho por última vez (archivo en el almacenamiento externo, la solicitud de su servicio web, lo que sea), y luego omitir ese trabajo si es demasiado pronto. De esta forma, no importa cuántas aplicaciones estén instaladas con su biblioteca, en qué orden están instaladas o cuándo se desinstalan.

¿Por qué no puedes usar ANDROID_ID del dispositivo (o algún tipo de identificador único para el teléfono), registrarlo con el servidor y si otra instancia de la biblioteca ya se está ejecutando en ese dispositivo, no hacer nada.

Puede obtener un identificador de dispositivo mediante el siguiente fragmento de código

Secure.getString(context.getContentResolver(), Secure.ANDROID_ID); 

¿No es ContentProvider la forma amigable para que las aplicaciones compartan datos? Puede utilizar una tabla SQLite de una fila para implementar una marca de tiempo atómica. Reemplace el esquema del administrador de alarmas por un subproceso creado durante la inicialización de la biblioteca que realiza sondeos en el ContentProvider cada pocos segundos. El CP responde "sí, envíe el estado del medio ambiente", lo que significa que ya ha actualizado la tabla con los datos actuales / hora, o "no, todavía no". El proveedor está consultando la tabla y el reloj del sistema para decidir cuándo decir que sí.

He hecho algunas investigaciones adicionales, y he logrado encontrar una solución satisfactoria. Aquí viene:

Una biblioteca tiene que ser desarrollada de una manera, que cada aplicación que la integra – publica el receptor de la difusión con la acción sabida, eg. Com.mylib.ACTION_DETECT.

La biblioteca tiene que tener servicio adicional, que publica alguna interfaz de AIDL, que ayuda a tomar la decisión – si la instancia actual de la biblioteca se puede hacer activa. El AIDL puede tener algunos métodos útiles, por ejemplo getVersion (), isActive (), getUUID ().

El patrón para tomar la decisión es: si la instancia actual tiene un número de versión superior, ese otro – se convertirá en activo. Si la instancia actual tiene una versión inferior, se desactivará o permanecerá desactivada si ya está desactivada. Si instancia actual tiene versión igual a otra instancia, entonces si la otra instancia no está activa, y el uuid de otra biblioteca es menor (a través del método compareTo) – se activará a sí mismo. En otra condición – se desactivará. Esta comprobación cruzada garantiza que cada biblioteca tomará una decisión por sí sola – no habrá casos ambiguos, ya que cada biblioteca obtendrá los datos requeridos del Servicio de AIDL respaldado publicado de otras instancias de librería en otras aplicaciones.

El siguiente paso es preparar un IntentService, que se inicia cada vez que se elimina o agrega un nuevo paquete, o se inicia la aplicación con la biblioteca por primera vez. IntentService consulta todos los paquetes para receptores de difusión, que implementan com.mylib.ACTION_DETECT. Luego itera a través de paquetes detectados (rechazando su propio paquete), y se enlaza con el servicio respaldado por AIDL de cada otra instancia (el nombre de clase del servicio AIDL será siempre el mismo, sólo el paquete de aplicación sería diferente). Después de completar la encuadernación – tenemos una situación clara – si se aplican los resultados del patrón "positivo" (nuestra instancia tiene una versión mejor uuid uuid, o ha sido ya activa), entonces implica que otras instancias figuraron a sí mismos como "negativo", y desactivado ellos mismos . Por supuesto, el patrón debe aplicarse en cada servicio AIDL vinculado.

Me disculpo por mi mal inglés.

Código de trabajo ConfictAvoidance solución: IntentService clase, que apoya vinculante, por lo que también es AIDL respaldado servicio mencionado anteriormente. También hay BroadcastReceiver, que inicia comprobaciones de conflictos.

 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; } } } } 

Archivo AIDL:

 package com.mylib.android.sdk; interface IRemoteSDK { boolean isActive(); long[] getUUID(); int getSdkVersion(); } 

Muestra de manifiesto:

 <?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> 

Acción:

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

Es la acción común, que se utiliza para detectar otras aplicaciones con la biblioteca.

El uso del registro puede parecer extraño, pero utilizo un contenedor personalizado, que admite el formateo, para reducir la sobrecarga de StringBuffers al depurar.

FlipAndroid es un fan de Google para Android, Todo sobre Android Phones, Android Wear, Android Dev y Aplicaciones para Android Aplicaciones.