如何在两个不同的 Android 应用程序之间共享 SharedPreferences 文件?

如何在两个不同的 Android 应用程序之间共享 SharedPreferences 文件?

问题描述:

我已经为此苦苦挣扎了一段时间.基本上,我希望有两个应用程序(将始终安装在一起)共享首选项,其中一个只是在后台运行并需要使用首选项的服务(应该拥有首选项,但只有真的 需要读取它们),另一个应用程序是前端 UI 应用程序,它需要能够写入另一个应用程序拥有的首选项文件.该服务将在后台执行操作(这可能由首选项决定)并且 UI 将允许用户编辑首选项并查看来自该服务的一些信息.但是,它们将是不同的包/应用程序.

I've been struggling with this for a while. Basically, I want to have two applications (which will always be installed together) share preferences, with one of them being just a service which runs in the background and needs to use the preferences (should own the preferences but only really needs to read them) and the other app being a front-end UI app which needs to be able to write to the preferences file owned by the other app. The service will be doing things in the background (which may be determined by the preferences) and the UI will allow the user to edit the preferences and view some information from the service. However, they will be different packages/apps.

我尝试遵循 本教程,它给了我关于如何在一个应用程序中设置可供另一个应用程序读取的首选项的好主意.本质上,我通过 myContext = createPackageContext("com.example.package",Context.MODE_WORLD_READABLE + Context.MODE_WORLD_WRITEABLE); 创建一个新的上下文,然后调用 myContext.getSharedPreferences("pref_name",Context.MODE_WORLD_READABLE + Context.MODE_WORLD_WRITEABLE); 但是,我无法从外部应用程序成功写入首选项 - (SharedPreferences.Editor).commit() 返回 false 并且我在 logcat 中收到关于无法的警告编辑 pref_name.xml.bak.

I tried following this tutorial which gave me a pretty good idea of how to have preferences in one app which can be read by another. Essentially, I create a new context through myContext = createPackageContext("com.example.package",Context.MODE_WORLD_READABLE + Context.MODE_WORLD_WRITEABLE); and then call myContext.getSharedPreferences("pref_name", Context.MODE_WORLD_READABLE + Context.MODE_WORLD_WRITEABLE); However, I can't write to the preferences successfully from the outside app - (SharedPreferences.Editor).commit() returns false and I get a warning in logcat about being unable to edit pref_name.xml.bak.

如何成功设置我的应用程序,以便它们都可以读取和写入同一个首选项文件(存储在其中一个的数据文件夹中)?

How can I successfully set up my applications so both of them can read and write to the same preferences file (which is stored in the data folder of one of them)?

首先,我应该注意这不是官方支持的,尽管可能有一种受支持的方法来做到这一点(即它不会是这种方法)添加到未来的 Android(两种声明的来源:请参阅 此链接的第二段).

First, I should note that this is not officially supported, although there may be a supported way to do this (i.e. it would NOT be this method) added to Android in the future (source for both claims: see second paragraph of this link).

同样,这是不受支持的,并且很可能不稳定.我主要是作为一个实验来做这件事,看看它是否可能;如果您打算将此方法实际合并到应用程序中,请格外小心.

但是,如果满足一些要求,似乎可以在应用程序之间共享首选项.首先,如果您希望 App B 能够访问 App A 的首选项,App B 的包名称必须是 App A 包名称的子项(例如 App A:com.example.pkg App B:com.example.pkg.stuff).此外,他们不能同时访问文件(我假设适用于在活动之间访问它们的相同规则,如果您想确保原子访问,则必须使用额外的保护措施,例如 .wait() 和 .notify(),但我不会在这里讨论).

However, it appears to be possible to share preferences between applications if a few requirements are met. First, if you want App B to be able to access App A's preferences the package name of App B must be a child of App A's package name (e.g. App A: com.example.pkg App B: com.example.pkg.stuff). Additionally, they can't be wanting to access the file at the same time (I assume the same rules apply as for accessing them between activities, if you want to ensure atomic access you'll have to use additional safeguards such as .wait() and .notify(), but I won't go into that here).

注意:所有这些都适用于 2.2 和 2.3.3 的模拟器 - 我没有在设备或安卓版本上进行广泛的测试.




在将拥有首选项的应用中要做的事情(上面的应用 A):

1.) 声明 SharedPreferences 文件
这相当简单.只需为您的共享首选项文件和类中的编辑器声明几个变量,并在您的 onCreate 方法中实例化它们.您现在可以在首选项中放置一个字符串,您将使用它来确保其他应用程序可以正确读取它.

1.) Declare the SharedPreferences file
This is fairly simple. Simply declare a couple variables for your sharedpreferences file and the editor in your class and instantiate them in your onCreate method. You can put a string in the preferences now that you will use to make sure the other app can read it properly.

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.) 设置备份文件getSharedPreferences 方法似乎检查 .bak 文件以从中加载首选项.这就是为什么它在文档中说它不能跨多个进程工作;为了最大限度地减少 I/O,它会在您抓取它们时加载一次首选项,并且仅在您关闭应用程序/活动时才备份它们.但是,如果您从外部应用程序调用它,您将收到一条警告,提示您对该文件夹(这是第一个应用程序的数据文件夹)没有正确的文件权限.为了解决这个问题,我们将自己创建 .bak 文件并使其公开可读/可写.我选择这样做的方式是在我的整个班级中定义三个变量.

2.) Set up the backup file The getSharedPreferences method appears to check for a .bak file to load the preferences from. This is why it says in the documentation that it will not work across multiple processes; to minimize I/O, it loads the prefs ONCE when you grab them and only backs them up when you close your application/activity. However, if you call this from an outside application you will get a warning about not having the right file permissions for the folder (which is the first app's data folder). To fix this we are going to create the .bak file ourselves and make it publicly readable/writable. The way I chose to do this was to define three variables in my overall class.

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

并在我的主类中创建一个函数,将这些作为参数并执行它们

and make a function in my main class which would take these as arguments and execute them

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

它不是非常漂亮或好,但它有效.现在,在您的 onCreate 方法中(在 editor.commit() 之后),您将使用三个字符串中的每一个调用此函数.

It's not terribly pretty or good but it works. Now, in your onCreate method (right after editor.commit())you will call this function with each of the three strings.

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

这将复制文件并使外部程序可以访问主 .xml 和 .xml.bak 文件.您还应该在 onDestroy() 中调用这三个方法以确保在您的应用程序退出时正确备份数据库,并在您调用应用程序其他地方的 getSharedPreferences 之前另外调用它们(否则它将加载 .bak 文件,该文件如果另一个进程一直在编辑主 .xml 文件,则可能已过期).不过,这就是您在此应用程序中需要做的全部工作.您可以在此活动的其他地方调用 getSharedPreferences,它将从 .xml 文件中获取所有数据,然后您可以调用 getdatatype("key") 方法并检索它.


访问文件中要做的事情(上面的应用 B)

This will copy the file and make both the main .xml and the .xml.bak files accessible to outside programs. You should also call these three methods in your onDestroy() to make sure the database is backed up properly when your app exits, and additionally call them right before you call getSharedPreferences elsewhere in your application (as otherwise it will load the .bak file which is likely out of date if another process has been editing the main .xml file). That's all you need to do in this application though, though. You can call getSharedPreferences elsewhere in this activity and it will grab all the data from the .xml file, allowing you to then call the getdatatype("key") methods and retrieve it.


Things to do in the accessing file(s) (App B from above)

1.) 写入文件
这更简单.我在此活动上制作了一个按钮,并在它的 onClick 方法中设置了代码,该方法会将某些内容保存到共享首选项文件中.请记住,App B 的包必须是 App A 包的子代.我们将根据 App A 的上下文创建一个上下文,然后在该上下文上调用 getSharedPreferences.

1.) Write to the file
This is even simpler. I made a button on this activity and set up code in it's onClick method which would save something to the shared preferences file. Remember that App B's package must be a child of App A's package. We will be creating a context based on App A's context and then calling getSharedPreferences on that context.

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

这会抓取我在其他应用程序中设置的字符串,并在此应用程序的文本视图中显示它(或错误消息).此外,它会在首选项文件中设置一个新字符串并提交更改.运行后,如果您的其他应用程序调用 getSharedPreferences,它将检索文件,包括来自该应用程序的更改.

This grabs the string I set in the other application and displays it (or an error message) in a textview in this application. Additionally it sets a new string in the preferences file and commits the changes. After this runs, if your other application calls getSharedPreferences it will retrieve the file including the changes from this app.