Android Content Resolver点击加载崩溃、返回空Cursor的问题排查
崩溃定位与修复方案
直接崩溃原因
触发崩溃的直接原因:getContentResolver().query() 方法执行后返回了null空对象,代码未做非空校验直接调用cursor.moveToFirst(),触发空指针异常。
跨进程访问场景下,只要Provider注册、URI匹配、SQL查询任何一个环节出错,系统都不会把具体异常抛给Resolver端,只会返回空cursor,这是ContentProvider跨应用调用类崩溃的典型表现。
分步修复方案
- 修正Content Provider应用的Manifest配置
ContentProvider必须显式声明在Manifest中,跨应用访问时必须开启导出权限,否则系统无法识别该Provider,所有跨进程调用都会返回空cursor:
注意三个校验点:<provider android:name=".database.SeekersContactsProvider" android:authorities="com.example.connectly" android:exported="true" />- authorities值必须和代码里定义的
AUTHORITY字符串完全一致,不能有拼写错误 - Android 12及以上版本必须显式声明
android:exported="true",否则应用安装时会报错,Provider根本不会注册到系统服务 - provider的类路径要和实际的Provider全类名完全匹配
- authorities值必须和代码里定义的
- 修复Content Provider端的query逻辑缺陷
现有Provider代码存在两个会导致跨进程返回空cursor的隐患:- 硬编码使用内置
ALL_COLUMNS作为查询列,完全忽略Resolver端传入的projection参数,如果Resolver传入的列名不存在,SQL查询会直接抛异常,跨进程场景下异常被系统捕获后就会返回空cursor - 当URI不匹配时直接抛出
IllegalArgumentException,跨进程场景下这个异常同样会被系统捕获,最终返回空cursor给调用方
修复后的核心query逻辑:
@Nullable @Override public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) { int uriMatch = uriMatcher.match(uri); // 不匹配的URI直接返回null,不要抛出跨进程异常 if (uriMatch != SEEKER) { return null; } // 外部未传排序规则时,使用默认的firstname升序 String orderBy = sortOrder != null ? sortOrder : "firstname ASC"; // 外部传入projection就使用外部规则,未传就用默认全列 String[] queryColumns = projection != null ? projection : ALL_COLUMNS; Cursor queryCursor = seekerSQLiteDatabase.query( SEEKER_TABLE_NAME, queryColumns, selection, selectionArgs, null, null, orderBy ); queryCursor.setNotificationUri(getContext().getContentResolver(), uri); return queryCursor; } - 硬编码使用内置
- 给Resolver端的查询逻辑增加防护
不管前期配置是否完善,都要对query返回的cursor做非空校验,避免任意场景下直接崩溃;同时cursor使用完成后必须关闭,避免内存泄漏:
修复后的按钮点击逻辑:@Override public void onClick(View v) { TextView resultView = findViewById(R.id.textView); Cursor cursor = null; try { cursor = getContentResolver().query( Uri.parse("content://com.example.connectly/seekers"), null, null, null, null ); // 必须先校验cursor非空,再执行遍历 if (cursor != null && cursor.moveToFirst()) { StringBuilder strBuild = new StringBuilder(); while (!cursor.isAfterLast()) { strBuild.append("\n") .append(cursor.getString(cursor.getColumnIndex("firstname"))) .append("-") .append(cursor.getString(cursor.getColumnIndex("lastname"))) .append(cursor.getString(cursor.getColumnIndex("email"))) .append(cursor.getString(cursor.getColumnIndex("phone"))) .append(cursor.getString(cursor.getColumnIndex("age"))) .append(cursor.getString(cursor.getColumnIndex("dob"))) .append(cursor.getString(cursor.getColumnIndex("gender"))); cursor.moveToNext(); } resultView.setText(strBuild); } else { resultView.setText("No Records Found"); } } finally { // 关闭cursor释放资源 if (cursor != null) { cursor.close(); } } } - 排查其他隐藏问题
- 安装顺序:Content Provider应用必须先于Resolver应用安装,否则系统找不到对应的Provider服务
- 代码笔误:Resolver端代码第一行包名漏了开头的p,写成了
ackage com.example.App2ContentProvider;,实际开发中要修正这类拼写错误 - 全局变量优化:不要把Cursor定义成Provider的全局成员变量,多并发调用时会出现数据错乱,应该在方法内部声明局部Cursor变量
内容的提问来源于stack exchange,提问作者zmn
相关产品推荐
相关产品推荐

