17

Od pewnego czasu walczę z tym. Zasadniczo chcę mieć dwie aplikacje (które zawsze będą instalowane razem) preferencje udostępniania, z których jedna jest tylko usługą, która działa w tle i musi korzystać z preferencji (powinna posiadać preferencje, ale tylko naprawdę musi być przeczytaj je), a druga aplikacja jest aplikacją interfejsu użytkownika, która musi być w stanie napisać do pliku preferencji posiadanego przez inną aplikację. Usługa będzie działała w tle (co może być uzależnione od preferencji), a interfejs użytkownika umożliwia edycję preferencji i wyświetlanie niektórych informacji z usługi. Będą to jednak różne pakiety/aplikacje.Jak mogę udostępnić plik SharedPreferences w dwóch różnych aplikacjach na Androida?

Próbowałem podążać za this tutorial, co dało mi całkiem niezły pomysł, jak mieć preferencje w jednej aplikacji, które mogą być odczytane przez inną. Zasadniczo, tworzę nowy kontekst przez myContext = createPackageContext("com.example.package",Context.MODE_WORLD_READABLE + Context.MODE_WORLD_WRITEABLE);, a następnie zadzwonię pod numer myContext.getSharedPreferences("pref_name", Context.MODE_WORLD_READABLE + Context.MODE_WORLD_WRITEABLE); Jednak nie mogę poprawnie pisać do preferencji z aplikacji zewnętrznej - (SharedPreferences.Editor) .commit() zwraca false i otrzymuję ostrzeżenie w logcat o tym, że nie jestem w stanie edytować plik pref_name.xml.bak.

W jaki sposób mogę pomyślnie skonfigurować moje aplikacje, aby oba z nich mogły czytać i pisać do tego samego pliku preferencji (który jest przechowywany w folderze danych jednego z nich)?

+0

Dlaczego, kiedy mówisz, że są one zawsze instalowane razem, czy muszą to być dwie różne aplikacje? Wydaje się, że masz dużo bólu głowy, więc prawdopodobnie spodziewasz się korzyści, ale zastanawiam się, czy istnieje sposób, aby to osiągnąć, bez posiadania osobnych aplikacji. –

+2

Chciałem móc zainstalować obie aplikacje, ustawić preferencje, a następnie odinstalować interfejs użytkownika interfejsu, ale pozwól, aby usługa działała nadal w tle. Umożliwia to również aktualizację jednej aplikacji bez ponownej instalacji obu. Z pewnością nie jest to niepotrzebne (i może nawet go nie używam), ale był to jeden z moich pomysłów na wdrożenie tego w taki sposób, w jaki go zaprojektowałem. – matt5784

Odpowiedz

6

Po pierwsze, powinienem zauważyć, że nie jest to oficjalnie obsługiwane, chociaż może istnieć obsługiwany sposób, aby to zrobić (tj. NIE byłby to ta metoda) dodany do Androida w przyszłości (źródło obu roszczeń: patrz akapit drugi z this link).

Ponownie, jest to nieobsługiwane i bardzo możliwe, że jest niestabilne. Zrobiłem to przede wszystkim jako eksperyment, aby sprawdzić, czy było to możliwe; zachowaj szczególną ostrożność, jeśli zamierzasz włączyć tę metodę do aplikacji.

Jednak wydaje się, że możliwe jest współdzielenie preferencji między aplikacjami, jeśli kilka wymagań zostanie spełnionych. Po pierwsze, jeśli chcesz, aby aplikacja B miała dostęp do preferencji aplikacji A, nazwa pakietu aplikacji B musi być dzieckiem nazwy pakietu aplikacji (np. Aplikacja A: com.example.pkg aplikacja B: com.example.pkg.stuff). Ponadto nie mogą chcieć uzyskać dostępu do pliku w tym samym czasie (zakładam, że mają zastosowanie te same reguły, co w przypadku uzyskiwania dostępu do nich między działaniami, jeśli chcesz zapewnić dostęp atomowy, musisz użyć dodatkowych zabezpieczeń, takich jak .wait () i .notify(), ale nie będę tego tutaj omawiał).

Uwaga: wszystko to działa na emulatorze w wersjach 2.2 i 2.3.3 - Nie testowałem na dużą skalę na różnych urządzeniach i wersjach Androida.




Atrakcje w aplikacji, która ma zamiar właścicielem preferencje (app od góry):

1.) deklaruje plik SharedPreferences
Jest to dość proste . Po prostu zadeklaruj kilka zmiennych dla swojego pliku sharedpreferences i edytora w swojej klasie i stwórz je w swojej metodzie onCreate. Możesz umieścić ciąg w preferencjach, których teraz użyjesz, aby upewnić się, że druga aplikacja może go poprawnie odczytać.

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.) Skonfiguruj plik kopii zapasowej Sposób getSharedPreferences wydaje się sprawdzać na .bak plik załadować preferencje.Dlatego w dokumentacji mówi się, że nie będzie działać w wielu procesach; aby zminimalizować I/O, ładuje prefsesy RAZ, gdy je przechwytujesz i tworzy kopie zapasowe tylko wtedy, gdy zamkniesz aplikację/działanie. Jeśli jednak wywołasz to z zewnętrznej aplikacji, otrzymasz ostrzeżenie o braku odpowiednich uprawnień do pliku dla folderu (który jest folderem danych pierwszej aplikacji). Aby to naprawić, sami utworzymy plik .bak i udostępnimy go publicznie do odczytu/zapisu. Sposób, w jaki zdecydowałem się to zrobić, to zdefiniowanie trzech zmiennych w mojej ogólnej klasie.

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

i utworzyć funkcję w moim głównej klasy, który weźmie je jako argumenty i realizują je

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) {} 
} 

To nie jest strasznie ładna i dobra, ale to działa. Teraz, w metodzie onCreate (zaraz po editor.commit()) wywołasz tę funkcję za pomocą każdego z trzech ciągów.

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

Spowoduje to skopiowanie pliku i udostępnienie zarówno głównych plików .xml, jak i .xml.bak programom zewnętrznym. Powinieneś również wywołać te trzy metody w swoim onDestroy(), aby upewnić się, że baza danych jest poprawnie zapisana w kopii zapasowej po wyjściu z aplikacji i dodatkowo wywołać je tuż przed wywołaniem getSharedPreferences w innym miejscu aplikacji (w przeciwnym razie załaduje plik .bak, który jest prawdopodobnie nieaktualny, jeśli inny proces edytował główny plik .xml). Ale to wszystko, co musisz zrobić w tej aplikacji. Możesz wywołać getSharedPreferences w innym miejscu tego działania, a pobierze on wszystkie dane z pliku .xml, co pozwoli ci wywołać metody getdatatype ("key") i je pobrać.


Atrakcje w pliku uzyskującego dostęp (ów) (App B od góry)

1.) Napisz do pliku
To jest jeszcze prostsze. Zrobiłem przycisk na tej czynności i ustawiłem kod w jego metodzie onClick, która zapisałaby coś w udostępnionym pliku preferencji. Pamiętaj, że pakiet aplikacji B musi być dzieckiem pakietu aplikacji. Będziemy tworzyć kontekst oparty na kontekście aplikacji A, a następnie wywoływać getSharedPreferences w tym kontekście.

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(); 

Ten chwyta ciąg ja ustawiony w innej aplikacji i wyświetla je (lub komunikat o błędzie) w TextView w tej aplikacji. Dodatkowo ustawia nowy ciąg w pliku preferencji i zatwierdza zmiany. Po tym uruchomieniu, jeśli inne aplikacje wywoła getSharedPreferences, pobierze plik zawierający zmiany z tej aplikacji.

+2

"chociaż może być w przyszłości" - używanie plików binarnych z wiersza poleceń nigdy nie będzie obsługiwane w zestawie SDK systemu Android. Osobiście nie dotknęłbym tej techniki 10-metrowym biegunem. Jeśli chcesz udostępniać dane między aplikacjami, użyj prawdziwego API, takiego jak implementacja 'ContentProvider', który jest wspierany przez' SharedPreferences'. – CommonsWare

+0

Nie mogłem przewidzieć, co zrobi zespół androidów (chyba, że ​​mi powiedzą). Jednak jestem ** całkiem ** pewny, czy zaimplementowali to, to byłoby znacznie czystsze i nie wymagałoby uruchamiania 'dd' lub' chmod'. W rzeczywistości może to być możliwe dzięki innej technice - a jeśli tak, mam nadzieję, że ktoś napisze odpowiedź. Tak po prostu działa. – matt5784

+0

Czy dwie aplikacje mogą zapisywać dane na tej samej zasadzie sharedprefrences? –

34

Lepiej jest ustawić tryb prywatny dla pliku. Aplikacja musi być podpisana przy użyciu tego samego zestawu certyfikatów, aby udostępnić ten plik.

Ustaw sharedUserId w obu aplikacjach na takie same.

<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"> 
.... 

Get Context z innego pakietu:

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

Get pozycje jak zwykle od SharedPreference. Możesz uzyskać do niego dostęp teraz.

+2

Nie wiem, dlaczego nie jest to zaakceptowana odpowiedź. To jest właściwy sposób, aby to zrobić i działa bezbłędnie. –

+1

Może również zajść potrzeba zamiany 'Context.MODE_PRIVATE' na' Context.CONTEXT_RESTRICTED'. –

+0

Jest to łatwe do wdrożenia, niestety, jeśli dodasz to na późniejszym etapie prac, uaktualnienie aplikacji nie będzie działać (musisz ponownie zainstalować). Dodatkowo, jeśli korzystasz z rozliczeń w aplikacji, najprawdopodobniej nie będzie działać. – lenooh