You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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后则恢复正常。

我的疑问:

  1. Android 11+之后,Content Provider是否要求提供方App必须处于运行状态?
  2. 有没有办法无需运行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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.05 21:59:55