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

ContentResolver查询返回null,跨应用访问ContentProvider求助

跨应用访问ContentProvider返回Cursor为null的问题排查与解决

问题背景

我有两个Android应用:一个负责向自定义ContentProvider写入字符串数据,另一个负责读取该数据。

第一个应用的ContentProvider实现

package myPackage;

import android.content.ContentProvider;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.MatrixCursor;
import android.net.Uri;

public class IBoxContentProvider extends ContentProvider {

    // 访问ContentProvider的权限标识
    private static final String AUTHORITY = "myPackage.provider";
    // 数据访问URI
    private static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/data");
    // UriMatcher匹配码
    private static final int DATA = 1;
    // URI匹配器
    private static final UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);

    // 存储数据的变量
    private String data;

    static {
        uriMatcher.addURI(AUTHORITY, "data", DATA);
    }

    @Override
    public boolean onCreate() {
        data = "";
        return true;
    }

    @Override
    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
        if (uri.equals(CONTENT_URI)) {
            String[] columnNames = {"data"};
            MatrixCursor cursor = new MatrixCursor(columnNames);
            cursor.addRow(new Object[]{data});
            return cursor;
        } else {
            return null;
        }
    }

    @Override
    public Uri insert(Uri uri, ContentValues values) {
        if (uri.equals(CONTENT_URI)) {
            String newData = values.getAsString("data");
            data = newData;
            getContext().getContentResolver().notifyChange(CONTENT_URI, null);
            return ContentUris.withAppendedId(CONTENT_URI, 1);
        } else {
            return null;
        }
    }

    @Override
    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
        if (uriMatcher.match(uri) == DATA) {
            String newData = values.getAsString("data");
            data = newData;
            getContext().getContentResolver().notifyChange(CONTENT_URI, null);
            return 1;
        } else {
            return 0;
        }
    }

    @Override
    public int delete(Uri uri, String selection, String[] selectionArgs) {
        return 0;
    }

    @Override
    public String getType(Uri uri) {
        return "vnd.android.cursor.item/data";
    }
}

第一个应用写入数据代码

values.put("data", myString);
Uri uri = Uri.parse("content://myPackage.provider/data");
ContentResolver contentResolver = getContentResolver();
Uri insertedUri = contentResolver.insert(uri, values);

第一个应用Manifest配置

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="myPackage">

    <application
        android:allowBackup="true"
        android:icon="@drawable/ico_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">

        <provider
            android:name="myPackage.IBoxContentProvider"
            android:authorities="myPackage.provider"
            android:exported="true"
            android:enabled="true"
            android:process=":myPackage_content_provider_process">
        </provider>

        <activity.../>
            
    </application>

</manifest>

第二个应用读取数据代码

Uri uri = Uri.parse("content://myPackage.provider/data");
ContentResolver contentResolver = getContentResolver();
Cursor cursor = contentResolver.query(uri, projection, null, null, null);

问题描述

第二个应用执行读取代码后,返回的Cursor始终为null。但当两个应用配置相同的android:sharedUserId="myPackage.sharedUserID"时,功能可正常工作,不过该方案无法在项目中使用。需要排查Cursor返回null的原因,并找到不依赖sharedUserId的跨应用访问方案。


原因分析

Cursor返回null的核心原因是跨应用场景下的URI匹配逻辑错误:

  • 当前query和insert方法使用uri.equals(CONTENT_URI)做匹配,但跨应用调用时,系统会对传入的URI进行权限校验和包装,导致实际传递到ContentProvider的URI对象与你定义的CONTENT_URI并非同一个实例,equals判断失败,最终返回null。
  • 使用sharedUserId时,两个应用处于同一权限进程组,URI的处理逻辑无额外包装,equals判断能够成立,因此可以正常返回Cursor。

另外,你的update方法采用了UriMatcher做匹配,这是正确的跨应用兼容方式,但query和insert方法未遵循同一标准,导致了兼容性问题。


解决方案

1. 统一使用UriMatcher做URI匹配

将query和insert方法中的URI判断逻辑改为使用UriMatcher,与update方法保持一致,避免对象实例不相等的问题:

修改后的query方法:

@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
    if (uriMatcher.match(uri) == DATA) {
        String[] columnNames = {"data"};
        MatrixCursor cursor = new MatrixCursor(columnNames);
        cursor.addRow(new Object[]{data});
        return cursor;
    } else {
        return null;
    }
}

修改后的insert方法:

@Override
public Uri insert(Uri uri, ContentValues values) {
    if (uriMatcher.match(uri) == DATA) {
        String newData = values.getAsString("data");
        data = newData;
        getContext().getContentResolver().notifyChange(CONTENT_URI, null);
        return ContentUris.withAppendedId(CONTENT_URI, 1);
    } else {
        return null;
    }
}

2. 适配Android 11+的包可见性要求

如果你的应用运行在Android 11(API 30)及以上版本,第二个应用需要在其Manifest中添加<queries>声明,明确指定要访问的ContentProvider的authority,否则系统会限制其访问:

在第二个应用的Manifest中添加:

<queries>
    <provider android:authorities="myPackage.provider" />
</queries>

3. 可选:添加自定义权限增强安全性

如果需要限制只有授权应用才能访问你的ContentProvider,可以自定义权限:

  • 在第一个应用的Manifest中定义权限:
<permission
    android:name="myPackage.provider.READ_DATA"
    android:protectionLevel="normal" />
<permission
    android:name="myPackage.provider.WRITE_DATA"
    android:protectionLevel="normal" />
  • 在provider标签中绑定权限:
<provider
    android:name="myPackage.IBoxContentProvider"
    android:authorities="myPackage.provider"
    android:exported="true"
    android:enabled="true"
    android:process=":myPackage_content_provider_process"
    android:readPermission="myPackage.provider.READ_DATA"
    android:writePermission="myPackage.provider.WRITE_DATA">
</provider>
  • 在第二个应用的Manifest中申请读取权限:
<uses-permission android:name="myPackage.provider.READ_DATA" />

内容的提问来源于stack exchange,提问作者Zaitsev Ilya

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.15 03:54:57