Android跨应用数据共享方案咨询:替代已废弃的SharedPreferences与Context.MODE_WORLD_READABLE
嘿,这个问题我太熟了——自从MODE_WORLD_READABLE被谷歌废弃后,确实得换更安全合规的跨应用数据共享方案,毕竟现在Android对隐私和权限管控越来越严格了。下面给你列几个实用的替代方案,都是当前Android开发中常用且推荐的:
1. ContentProvider(官方推荐的标准方案)
这是Android官方主推的跨应用数据共享方式,安全可控,能精细粒度地控制访问权限,我在多个项目里都用过,非常稳妥。
实现步骤:
- 在提供数据的应用中,自定义一个
ContentProvider,重写query()等核心方法(按需实现insert/update/delete),把你要共享的数据(比如从SharedPreferences或数据库读取)包装成Cursor返回。 - 在Manifest中注册这个Provider,同时声明自定义权限(或使用系统权限),限制只有授权应用才能访问。
- 访问数据的应用需要在Manifest中申请对应权限,然后通过
ContentResolver来查询数据。
代码示例:
提供方自定义ContentProvider
public class MyDataProvider extends ContentProvider { // 定义ContentProvider的Uri,访问方会用这个Uri来查询数据 public static final Uri CONTENT_URI = Uri.parse("content://com.example.dataprovider/mydata"); @Override public boolean onCreate() { // 初始化数据源,比如SharedPreferences return true; } @Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { // 从SharedPreferences读取数据,用MatrixCursor包装成Cursor返回 SharedPreferences sp = getContext().getSharedPreferences("app_shared_data", Context.MODE_PRIVATE); MatrixCursor cursor = new MatrixCursor(new String[]{"key", "value"}); // 这里示例返回一个username键值对,你可以扩展成更多数据 cursor.addRow(new Object[]{"username", sp.getString("username", "")}); return cursor; } // 其他方法如果不需要可以返回默认值 @Override public String getType(Uri uri) { return "vnd.android.cursor.dir/vnd.com.example.dataprovider.mydata"; } @Override public Uri insert(Uri uri, ContentValues values) { return null; } @Override public int delete(Uri uri, String selection, String[] selectionArgs) { return 0; } @Override public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { return 0; } }
提供方Manifest注册Provider与自定义权限
<!-- 自定义权限,设置保护级别,比如signature只允许同签名应用访问 --> <permission android:name="com.example.READ_APP_DATA" android:protectionLevel="normal" /> <provider android:name=".MyDataProvider" android:authorities="com.example.dataprovider" android:exported="true" android:permission="com.example.READ_APP_DATA" />
访问方申请权限并查询数据
<!-- 在Manifest中申请权限 --> <uses-permission android:name="com.example.READ_APP_DATA" />
// 通过ContentResolver查询数据 Cursor cursor = getContentResolver().query( MyDataProvider.CONTENT_URI, null, null, null, null ); if (cursor != null && cursor.moveToFirst()) { String username = cursor.getString(cursor.getColumnIndex("value")); // 处理获取到的数据 cursor.close(); }
优缺点:
- ✅ 优点:完全符合Android规范,权限控制精细,支持复杂数据结构,安全性高。
- ❌ 缺点:需要编写的代码量稍多,适合中大型数据共享场景。
2. SharedPreferences + 签名权限(同签名应用专属)
如果两个应用都是你自己开发的,并且使用同一个签名证书,这个方案会比ContentProvider简单很多,适合快速实现键值对数据共享。
实现步骤:
- 提供方的SharedPreferences依然用
MODE_PRIVATE(安全),然后在Manifest中声明一个protectionLevel="signature"的自定义权限。 - 提供方可以手动设置SharedPreferences文件的可读权限(确保其他应用能访问到文件)。
- 访问方申请该自定义权限,通过创建提供方的
Context来直接读取SharedPreferences。
代码示例:
提供方Manifest声明签名权限
<permission android:name="com.example.ACCESS_SHARED_PREFS" android:protectionLevel="signature" />
提供方设置SharedPreferences文件权限
SharedPreferences sp = getSharedPreferences("app_shared_data", Context.MODE_PRIVATE); File spFile = new File(getApplicationInfo().dataDir + "/shared_prefs/app_shared_data.xml"); if (spFile.exists()) { // 设置文件为其他应用可读,结合签名权限只有同签名应用能访问 spFile.setReadable(true, false); }
访问方读取数据
<uses-permission android:name="com.example.ACCESS_SHARED_PREFS" />
try { // 创建提供方的Context,忽略安全检查(因为有签名权限) Context providerContext = createPackageContext("com.example.providerapp", Context.CONTEXT_IGNORE_SECURITY); SharedPreferences sp = providerContext.getSharedPreferences("app_shared_data", Context.MODE_PRIVATE); String username = sp.getString("username", ""); } catch (PackageManager.NameNotFoundException e) { e.printStackTrace(); }
优缺点:
- ✅ 优点:代码量少,实现快速,适合同签名的兄弟应用。
- ❌ 缺点:仅支持同签名应用,权限控制依赖签名,灵活性不如ContentProvider。
3. FileProvider(文件类数据共享)
如果你的共享数据是以文件形式存储的(比如文本、JSON、二进制文件),FileProvider是Android推荐的安全文件共享方式,避免直接暴露文件路径带来的安全风险。
实现步骤:
- 提供方注册FileProvider,配置共享文件的路径。
- 提供方生成文件的授权Uri,通过Intent或权限授权给访问方。
- 访问方通过Uri读取文件内容。
代码示例:
提供方Manifest注册FileProvider
<provider android:name="androidx.core.content.FileProvider" android:authorities="com.example.fileprovider" android:exported="false" android:grantUriPermissions="true"> <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/file_paths" /> </provider>
res/xml/file_paths.xml(配置共享路径)
<paths xmlns:android="http://schemas.android.com/apk/res/android"> <!-- 共享files目录下的shared文件夹 --> <files-path name="shared_files" path="shared/" /> </paths>
提供方生成授权Uri并授权
// 创建共享文件并写入数据 File sharedFile = new File(getFilesDir(), "shared/data.json"); // 写入数据到文件... // 生成授权Uri Uri fileUri = FileProvider.getUriForFile(this, "com.example.fileprovider", sharedFile); // 给访问方授权读取权限 grantUriPermission("com.example.clientapp", fileUri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
访问方读取文件
try { InputStream inputStream = getContentResolver().openInputStream(fileUri); // 读取文件内容,比如用BufferedReader解析JSON BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream)); StringBuilder content = new StringBuilder(); String line; while ((line = reader.readLine()) != null) { content.append(line); } inputStream.close(); // 处理文件内容 } catch (IOException e) { e.printStackTrace(); }
优缺点:
- ✅ 优点:安全,避免暴露文件路径,适合文件类型的数据共享。
- ❌ 缺点:需要自己处理文件解析,不适合直接共享键值对数据。
4. AIDL/Binder(实时交互与复杂数据)
如果需要跨应用实时通信,或者共享复杂的对象数据,AIDL(Android Interface Definition Language)是不错的选择,本质是基于Binder实现跨进程通信。
实现步骤:
- 定义AIDL接口,声明要暴露的方法。
- 提供方实现AIDL接口的服务,在服务中返回要共享的数据。
- 访问方绑定服务,通过AIDL接口调用方法获取数据。
代码示例:
定义AIDL文件(IMyDataService.aidl)
package com.example; interface IMyDataService { // 声明获取用户名的方法,你可以扩展更多方法 String getUsername(); }
提供方实现服务
public class MyDataService extends Service { // 实现AIDL接口的Stub类 private final IMyDataService.Stub mBinder = new IMyDataService.Stub() { @Override public String getUsername() throws RemoteException { SharedPreferences sp = getSharedPreferences("app_shared_data", Context.MODE_PRIVATE); return sp.getString("username", ""); } }; @Nullable @Override public IBinder onBind(Intent intent) { return mBinder; } }
提供方Manifest注册服务
<service android:name=".MyDataService" android:exported="true" android:permission="com.example.ACCESS_DATA_SERVICE" />
访问方绑定服务并调用方法
private IMyDataService mDataService; private ServiceConnection mConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { // 将IBinder转换为AIDL接口实例 mDataService = IMyDataService.Stub.asInterface(service); try { String username = mDataService.getUsername(); // 处理获取到的数据 } catch (RemoteException e) { e.printStackTrace(); } } @Override public void onServiceDisconnected(ComponentName name) { mDataService = null; } }; // 绑定服务 Intent intent = new Intent(); intent.setComponent(new ComponentName("com.example.providerapp", "com.example.MyDataService")); bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
优缺点:
- ✅ 优点:支持实时交互,可共享复杂对象,适合频繁通信的场景。
- ❌ 缺点:代码复杂度较高,需要处理跨进程异常,适合有一定Android开发经验的开发者。
方案选择建议:
- 常规键值对/简单数据共享:优先选ContentProvider,最规范安全。
- 同签名应用:选SharedPreferences+签名权限,快速省事。
- 文件类数据:选FileProvider。
- 实时交互/复杂数据:选AIDL/Binder。
内容的提问来源于stack exchange,提问作者Anto

