Android 11+下提供方App未运行时自定义Content Provider失效求助
问题描述
我开发了两个应用App A和App B,希望通过自定义Content Provider让App A向App B提供数据。代码在Android 11环境下运行正常,但在Android 11+的真机设备上,当App A未运行时,App B会出现“Failed to find provider info for xxx”错误,打开App A后则恢复正常。
我的疑问:
- Android 11+之后,Content Provider是否要求提供方App必须处于运行状态?
- 有没有办法无需运行App,就能让沙箱内的文件对其他App可见?
我已添加<queries>节点、尝试各类权限配置,但App A未启动时仍无法正常工作(Android 11下无此要求)。
补充信息:
- compileSdk版本:34
- targetSdk版本:33
- gradle插件版本:8.1.2
App A的AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools"> <queries> <package android:name="com.test.projectb" /> </queries> <application android:allowBackup="true" android:dataExtractionRules="@xml/data_extraction_rules" android:fullBackupContent="@xml/backup_rules" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:theme="@style/Theme.ProjectA" tools:targetApi="31"> <provider android:name=".CustomDataProvider" android:authorities="com.test.projecta.provider" android:exported="true" android:enabled="true" android:grantUriPermissions="true" > </provider> <activity android:name=".MainActivity" android:exported="true" android:label="@string/app_name" android:theme="@style/Theme.ProjectA"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest>
App A的CustomDataProvider代码
public class CustomDataProvider extends ContentProvider { @Override public boolean onCreate() { Log.i("projectA","onCreate"); return true; } @Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { Log.i("projectA","query"); String fileName = "data"; String fileContent = FileUtil.readFromFile(getContext(), fileName); MatrixCursor cursor = new MatrixCursor(new String[]{"content"}); cursor.addRow(new Object[]{fileContent}); return cursor; } @Nullable @Override public String getType(@NonNull Uri uri) { Log.i("projectA","getType"); return "vnd.android.cursor.item/vnd.com.test.projecta.provider.data"; } @Nullable @Override public Uri insert(@NonNull Uri uri, @Nullable ContentValues contentValues) { Log.i("projectA","insert"); return null; } @Override public int delete(@NonNull Uri uri, @Nullable String s, @Nullable String[] strings) { Log.i("projectA","delete"); return 0; } @Override public int update(@NonNull Uri uri, @Nullable ContentValues contentValues, @Nullable String s, @Nullable String[] strings) { Log.i("projectA","update"); return 0; } }
App B的AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" > <queries> <package android:name="com.test.projecta" /> <provider android:authorities="com.test.projecta.provider"/> </queries> <application android:allowBackup="true" android:dataExtractionRules="@xml/data_extraction_rules" android:fullBackupContent="@xml/backup_rules" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/Theme.ProjectB" tools:targetApi="31"> <activity android:name=".MainActivity" android:exported="true" android:label="@string/app_name" android:theme="@style/Theme.ProjectB"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest>
App B获取数据的代码
public String readDataFromProvider(Context context, Uri contentUri) { Cursor cursor = null; String data = ""; try { ContentResolver resolver = getContentResolver(); cursor = resolver.query(contentUri, null, null, null, null); if (cursor != null && cursor.moveToFirst()) { int columnIndex = cursor.getColumnIndexOrThrow("content"); data = cursor.getString(columnIndex); } } catch (Exception e) { e.printStackTrace(); } finally { if (cursor != null) { cursor.close(); } } return data; } @Override public void onClick(View view) { Uri dataUri = Uri.parse("content://com.test.projecta.provider/data"); String content = readDataFromProvider(context,dataUri); }
解决方案与分析
一、关于Content Provider的运行要求
Android 11(API 30)之后,尤其是Android 12(API 31)及以上版本,系统的后台启动限制和App Standby模式收紧了未运行App的组件唤醒规则:
- 当App A完全未启动(进程不存在)时,系统不会为了响应App B的Content Provider请求而主动启动App A的进程,这就是你看到"Failed to find provider info"错误的核心原因。
- Android 11及以前版本没有这么严格的限制,系统会自动启动Provider所在进程处理请求。
注意:这并不要求App A必须一直处于前台运行,只要App A的进程还在后台驻留,Provider就能正常响应请求。
二、无需启动App共享沙箱文件的可行方案
如果要实现App A未启动时沙箱文件也能被App B访问,可尝试以下方案:
1. 配置android:directBootAware="true"
如果你的应用支持直接启动(Direct Boot),给Content Provider添加该属性后,在设备解锁前,Provider就能被系统唤醒并响应请求,无需用户手动启动App A。
修改App A的Provider配置:
<provider android:name=".CustomDataProvider" android:authorities="com.test.projecta.provider" android:exported="true" android:enabled="true" android:grantUriPermissions="true" android:directBootAware="true"> </provider>
注意:该方案仅适用于存储在设备保护存储中的文件,你需要通过Context.createDeviceProtectedStorageContext()获取对应上下文来读取文件。
2. 外部私有目录+FileProvider
将需要共享的文件放到App A的外部私有目录(/Android/data/com.test.projecta/files/),通过FileProvider向App B授权访问:
- 在App A的Manifest中配置FileProvider:
<provider android:name="androidx.core.content.FileProvider" android:authorities="com.test.projecta.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:
<?xml version="1.0" encoding="utf-8"?> <paths> <files-path name="shared_files" path="shared/" /> </paths>
说明:如果是静态文件,可在App A首次启动时从assets目录复制到外部私有目录;动态生成的文件无法绕过App A启动的要求。
3. MediaStore(仅适用于媒体文件)
如果共享的是图片、音频、视频等媒体文件,可将文件插入到MediaStore的公共目录,App B无需依赖App A进程,直接通过MediaStore访问。但该方案不适合私有数据,因为公共目录文件对所有应用可见。
三、临时优化方案(需唤醒App A)
如果无法绕过App A启动的要求,可在App B请求Provider前,先通过隐式Intent唤醒App A进程:
// 在App B的readDataFromProvider方法前添加 try { Intent intent = getPackageManager().getLaunchIntentForPackage("com.test.projecta"); if (intent != null) { intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP); startActivity(intent); Thread.sleep(1000); // 等待App A启动完成 } } catch (Exception e) { e.printStackTrace(); }
该方案会将App A唤醒到前台,用户体验较差,但能解决Provider找不到的问题。
内容的提问来源于stack exchange,提问作者Eric Sun

