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

Posted

技术标签:

【中文标题】如何在两个不同的 Android 应用程序之间共享 SharedPreferences 文件?【英文标题】:How can I share a SharedPreferences file across two different android apps? 【发布时间】:2012-06-14 00:26:49 【问题描述】:

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

我尝试关注this tutorial,这让我很好地了解了如何在一个应用程序中设置可以被另一个应用程序读取的偏好。本质上,我通过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 并且我收到警告关于无法编辑 pref_name.xml.bak 的 logcat。

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

【问题讨论】:

为什么,当你说它们总是安装在一起时,它们需要是两个不同的应用程序吗?看起来很头疼,所以大概你正在设想一个好处,但我想知道是否有一种方法可以实现这一目标,而无需实际拥有单独的应用程序。 我希望能够安装这两个应用程序,设置首选项,然后卸载前端 UI,但让服务在后台继续运行。这也允许我升级一个应用程序而无需重新安装两者。这绝对不是必要的(我什至可能不会使用它),但这是我按照我设计的方式实现它的想法之一。 【参考方案1】:

最好为文件设置私有模式。应用需要使用相同的证书集签名才能共享此文件。

将两个应用中的 sharedUserId 设置为相同。

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

从其他包中获取上下文:

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

照常从 SharedPreference 中获取项目。您现在可以访问它了。

【讨论】:

不确定为什么这不是公认的答案。这是正确的方法,并且完美无缺。 另外,您可能需要将 Context.MODE_PRIVATE 替换为 Context.CONTEXT_RESTRICTED 这很容易实现,但是,不幸的是,如果您在开发的后期添加它,升级您的应用程序将无法正常工作(您必须重新安装)。此外,如果您使用应用内计费,它很可能无法正常工作。 @Omer 和 Miro 我怎样才能成功地设置我的应用程序,以便它们都可以读取和写入相同的首选项文件(存储在其中一个的数据文件夹中)?你能做到吗? 是的,如果两个应用程序都使用相同的证书签名并在清单中声明了相同的 sharedUserID,这是可能的。【参考方案2】:

首先,我应该注意,这不是官方支持的,尽管将来可能会在 Android 中添加一种受支持的方法(即它不会是这种方法)(这两种说法的来源:见第二段this link)。

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

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

注意:所有这些都可以在 2.2 和 2.3.3 的模拟器上运行——我还没有在设备或安卓版本上进行过广泛的测试。

在将拥有偏好的应用程序中要做的事情(上面的应用程序 A):

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

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 文件并使其公开可读/可写。我选择这样做的方式是在我的整个班级中定义三个变量。

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

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

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() 之后),您将使用三个字符串中的每一个调用此函数。

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

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

1.) 写入文件 这甚至更简单。我在这个活动上做了一个按钮,并在它的 onClick 方法中设置了代码,这会将一些东西保存到共享首选项文件中。请记住,应用 B 的包必须是应用 A 的包的子级。我们将基于 App A 的上下文创建一个上下文,然后在该上下文上调用 getSharedPreferences。

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,它将检索包含此应用程序更改的文件。

【讨论】:

“虽然它可能在未来”——Android SDK 永远不会支持命令行二进制文件的使用。就个人而言,我不会用 10 米长的杆子接触这项技术。如果您想在应用之间共享数据,请使用真正的 API,例如实现由您的 SharedPreferences 支持的 ContentProvider 我无法预测 android 团队会做什么(除非他们告诉我)。但是,我相当确定如果他们实现了这一点,它会更干净,并且不需要运行ddchmod。事实上,这甚至可以通过其他一些技术实现——如果是这样,我希望有人会发布答案。这就是我让它工作的方式。 两个应用可以在同一个共享首选项上写入数据吗?

以上是关于如何在两个不同的 Android 应用程序之间共享 SharedPreferences 文件?的主要内容,如果未能解决你的问题,请参考以下文章

如何在heroku上的两个不同应用程序之间共享worker?

如何在不同子域之间共享本地存储?

如何在不同图表之间共享 pyqtgraph 图?

我们如何在两个不同的服务之间共享数据

如何在两个 iOS 应用之间共享应用内购买数据?

Google BigQuery:如何查询两个不同值之间的共享值计数?