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
相关产品推荐
相关产品推荐

