2012-06-14 21 views
17

He estado luchando con esto por un tiempo. Básicamente, quiero tener dos aplicaciones (que siempre se instalarán juntas) compartir preferencias, con una de ellas solo un servicio que se ejecuta en segundo plano y necesita usar las preferencias (debe poseer las preferencias pero solo realmente necesita léelos) y la otra aplicación es una aplicación de interfaz de usuario frontal que debe poder escribir en el archivo de preferencias propiedad de la otra aplicación. El servicio hará cosas en segundo plano (que puede estar determinado por las preferencias) y la IU le permitirá al usuario editar las preferencias y ver cierta información del servicio. Sin embargo, serán diferentes paquetes/aplicaciones.¿Cómo puedo compartir un archivo SharedPreferences en dos aplicaciones diferentes de Android?

Intenté seguir this tutorial que me dio una muy buena idea de cómo tener preferencias en una aplicación que puede leer otra. Esencialmente, creo un nuevo contexto a través del myContext = createPackageContext("com.example.package",Context.MODE_WORLD_READABLE + Context.MODE_WORLD_WRITEABLE); y luego llamo al myContext.getSharedPreferences("pref_name", Context.MODE_WORLD_READABLE + Context.MODE_WORLD_WRITEABLE); Sin embargo, no puedo escribir correctamente en las preferencias desde la aplicación externa - (SharedPreferences.Editor). .commit() devuelve falso y aparece una advertencia en logcat sobre que no puedo para editar pref_name.xml.bak.

¿Cómo puedo configurar correctamente mis aplicaciones para que ambas puedan leer y escribir en el mismo archivo de preferencias (que está almacenado en la carpeta de datos de uno de ellos)?

+0

¿Por qué, cuando dices que estos siempre se instalan juntos, tienen que ser dos aplicaciones diferentes? Parece un gran dolor de cabeza, por lo que presumiblemente está visualizando un beneficio, pero me pregunto si podría haber una forma de lograrlo sin tener aplicaciones separadas. –

+2

Quería poder instalar ambas aplicaciones, configurar las preferencias y luego desinstalar la interfaz de usuario frontend pero dejar que el servicio continúe en segundo plano. Esto también me permite actualizar una aplicación sin volver a instalar ambas. Definitivamente no es necesario (y puede que ni siquiera lo use) pero fue una de mis ideas para implementar esto de la manera que lo diseñé. – matt5784

Respuesta

6

En primer lugar, debo tener en cuenta que esto no es oficialmente compatible, aunque puede haber una forma admitida de hacerlo (es decir, NO sería este método) agregado a Android en el futuro (fuente para ambas afirmaciones: ver segundo párrafo de this link).

De nuevo, esto no es compatible y es muy posiblemente inestable. Principalmente hice esto como un experimento para ver si era posible; tenga extrema precaución si planea incorporar este método en una aplicación.

Sin embargo, parece que es posible compartir preferencias entre las aplicaciones si se cumplen algunos requisitos. Primero, si desea que la aplicación B pueda acceder a las preferencias de la aplicación A, el nombre del paquete de la aplicación B debe ser hijo del nombre del paquete de la aplicación A (por ejemplo, aplicación A: com.example.pkg, aplicación B: com.example.pkg.stuff). Además, no pueden querer acceder al archivo al mismo tiempo (supongo que se aplican las mismas reglas que para acceder a ellos entre actividades, si desea garantizar el acceso atómico, deberá usar protecciones adicionales como .wait () y .notify(), pero no entraré en eso aquí).

Nota: todo esto funciona en el emulador en 2.2 y 2.3.3- No he probado exhaustivamente en dispositivos o versiones de Android.




Cosas que hacer en la aplicación que se va a poseer las preferencias (Ap A desde arriba):

1.) Declarar el archivo de SharedPreferences
Esto es bastante sencillo . Simplemente declare un par de variables para su archivo de preferencias compartidas y el editor de su clase y cree una instancia en su método onCreate. Puede poner una cadena en las preferencias ahora que usará para asegurarse de que la otra aplicación pueda leerla correctamente.

public class stuff extends Activity { 
    SharedPreferences mPrefs = null; 
    SharedPreferences.Editor mEd= null; 
    @Override 
    public void onCreate(Bundle savedInstanceState){ 
     super.onCreate(savedInstanceState); 
     setContentView(R.layout.main); 
     mPrefs = (getApplicationContext()).getSharedPreferences("svcprefs", Context.MODE_WORLD_READABLE + Context.MODE_WORLD_WRITEABLE); 
     mEd = mPrefs.edit(); 
     mEd.putString("test", "original send from prefs owner"); 
     mEd.commit(); 

2.) Configurar el archivo de copia de seguridad El método getSharedPreferences aparece para comprobar si hay un archivo .bak para cargar las preferencias de.Es por eso que dice en la documentación que no funcionará en múltiples procesos; para minimizar la E/S, carga los prefs UNA VEZ cuando los agarra y solo los respalda cuando cierra su aplicación/actividad. Sin embargo, si llama a esto desde una aplicación externa, recibirá una advertencia acerca de no tener los permisos de archivo correctos para la carpeta (que es la carpeta de datos de la primera aplicación). Para solucionarlo, crearemos el archivo .bak nosotros mismos y lo haremos público/legible. La forma en que elegí hacer esto fue definir tres variables en mi clase general.

final String[] copyToBackup = { "dd", "if=/data/data/com.example.pkg/shared_prefs/prefs.xml", "of=/data/data/com.example.pkg/shared_prefs/prefs.xml.bak", "bs=1024" }; 
final String[] mainFixPerm = {"chmod", "666", "/data/data/com.example.pkg/shared_prefs/prefs.xml"}; 
final String[] bakFixPerm = {"chmod", "666", "/data/data/com.example.pkg/shared_prefs/prefs.xml.bak"}; 

y hacer una función en mi clase principal que llevaría éstos como argumentos y ejecutarlos

public void execCommand(String[] arg0){ 
    try { 
     final Process pr = Runtime.getRuntime().exec(arg0); 
     final int retval = pr.waitFor(); 
     if (retval != 0) { 
      System.err.println("Error:" + retval); 
     } 
    } 
    catch (Exception e) {} 
} 

No es terriblemente bonita o buena, pero funciona. Ahora, en su método onCreate (justo después de editor.commit()) llamará a esta función con cada una de las tres cadenas.

execCommand(copyToBackup); 
execCommand(mainFixPerm); 
execCommand(bakFixPerm); 

Esto copiará el archivo y hacer que tanto el .xml principal y los archivos .xml.bak accesibles a programas externos. También debe llamar a estos tres métodos en su onDestroy() para asegurarse de que la base de datos se respalda correctamente cuando se cierra su aplicación, y además llamarlos directamente antes de llamar a getSharedPreferences en otra parte de su aplicación (de lo contrario cargará el archivo .bak que es probable que esté desactualizado si otro proceso ha estado editando el archivo principal .xml). Sin embargo, eso es todo lo que necesitas hacer en esta aplicación. Puede llamar a getSharedPreferences en otra parte de esta actividad y tomará todos los datos del archivo .xml, lo que le permite llamar a los métodos getdatatype ("clave") y recuperarlos.


Cosas que hacer en el archivo (s) accede (Ap B desde arriba)

1.) escribir en el fichero
Esto es aún más simple. Inicié un botón en esta actividad y configuré el código en su método onClick que guardará algo en el archivo de preferencias compartidas. Recuerde que el paquete de la aplicación B debe ser un elemento secundario del paquete de la aplicación A. Crearemos un contexto basado en el contexto de la Aplicación A y luego llamaremos a getSharedPreferences en ese contexto.

prefsbutton.setOnClickListener(new View.OnClickListener() { 
    public void onClick(View v) { 
     Context myContext = null; 
     try { 
      // App A's context 
      myContext = createPackageContext("com.example.pkg", Context.MODE_WORLD_READABLE + Context.MODE_WORLD_WRITEABLE); 
     } catch (NameNotFoundException e) {e.printStackTrace();} 

     testPrefs = myContext.getSharedPreferences("svcprefs", Context.MODE_WORLD_READABLE + Context.MODE_WORLD_WRITEABLE); 
     testEd = testPrefs.edit(); 
     String valueFromPrefs = testPrefs.getString("test", "read failure"); 
     TextView test1 = (TextView)findViewById(R.id.tvprefs); 
     test1.setText(valueFromPrefs); 
     testEd.putString("test2", "testback"); 
     boolean edit_success = testEd.commit(); 

Esta agarra la cadena puse en la otra aplicación y la muestra (o un mensaje de error) en un TextView en esta aplicación. Además, establece una nueva cadena en el archivo de preferencias y confirma los cambios. Después de que esto se ejecute, si su otra aplicación llama a getSharedPreferences, recuperará el archivo, incluidos los cambios de esta aplicación.

+2

"aunque puede ser en el futuro": el uso de binarios de línea de comandos nunca se admitirá en el SDK de Android. Personalmente, no tocaría esta técnica con un poste de 10 metros. Si desea compartir datos entre aplicaciones, use una API real, como la implementación de un 'ContentProvider' respaldado por' SharedPreferences'. – CommonsWare

+0

No pude predecir qué hará el equipo de android (excepto cuando me digan). Sin embargo, estoy ** completamente ** seguro de que si implementaran esto sería mucho más limpio y no requeriría ejecutar 'dd' o' chmod'. De hecho, esto puede incluso ser posible a través de alguna otra técnica, y si es así, espero que alguien publique una respuesta. Así es como lo hice funcionar. – matt5784

+0

¿Es posible que dos aplicaciones escriban datos en las mismas preferencias compartidas? –

34

Es mejor configurar el modo privado para el archivo. La aplicación debe estar firmada con el mismo conjunto de certificados para compartir este archivo.

Establezca sharedUserId en ambas aplicaciones para que sean iguales.

<manifest xmlns:android="http://schemas.android.com/apk/res/android" 
package="com.example.hello" 
android:versionCode="1" 
android:versionName="1.0" 
android:sharedUserId="com.example"> 
.... 

obtener el contexto de otro paquete:

mContext = context.createPackageContext(
         "com.example.otherapp", 
         Context.MODE_PRIVATE); 
mPrefs = mContext.getSharedPreferences("sameFileNameHere", Activity.MODE_PRIVATE); 

obtener elementos como de costumbre a partir SharedPreference. Puedes acceder ahora.

+2

No estoy seguro de por qué esta no es la respuesta aceptada. Esta es la forma correcta de hacerlo, y funciona sin problemas. –

+1

También es posible que tenga que reemplazar 'Context.MODE_PRIVATE' con' Context.CONTEXT_RESTRICTED'. –

+0

Esto es fácil de implementar, sin embargo, desafortunadamente, si agrega esto en una etapa posterior del desarrollo, la actualización de su aplicación no funcionará (tiene que volver a instalarla). Además, si usa la facturación integrada en la aplicación, es muy probable que no funcione. – lenooh

Cuestiones relacionadas