Android 版本每次的更新除了新功能之外,通常还伴随着各种 API 的更新以及随之而来的异常,8.0 的更新也不例外。
当我们在 8.0 或 8.1 系统上使用 26 或以上的版本的 SDK 时,调用 ContentResolver 的 notifyChange 方法通知数据更新,或者调用 ContentResolver 的 registerContentObserver 方法监听数据变化时,会出现异常:java.lang.SecurityException: Failed to find provider < authority > for user 0,而在 SDK 26 之前的版本上这两个方法都是可以正常运行的。
通过查看 ContentResolver 源码,我们可以发现该类的 notifyChange 和 registerContentObserver 方法都是调用到了 ContentService 类里面的同名方法,而在 ContentService 类里面的这两个方法中,相比 SDK 25 版本的代码,都明显多了一段代码:
if (targetSdkVersion >= Build.VERSION_CODES.O) {
throw new SecurityException(msg);
}
由于两者原理类似,我们就只对 registerContentObserver 方法进行分析,先看下对应源码:
public void registerContentObserver(Uri uri, boolean notifyForDescendants,
IContentObserver observer, int userHandle, int targetSdkVersion) {
if (observer == null || uri == null) {
throw new IllegalArgumentException("You must pass a valid uri and observer");
}
final int uid = Binder.getCallingUid();
final int pid = Binder.getCallingPid();
userHandle = handleIncomingUser(uri, pid, uid,
Intent.FLAG_GRANT_READ_URI_PERMISSION, true, userHandle);
final String msg = LocalServices.getService(ActivityManagerInternal.class)
.checkContentProviderAccess(uri.getAuthority(), userHandle);
if (msg != null) {
if (targetSdkVersion >= Build.VERSION_CODES.O) {
throw new SecurityException(msg);
} else {
if (msg.startsWith("Failed to find provider")) {
// Sigh, we need to quietly let apps targeting older API
// levels notify on non-existent providers.
} else {
Log.w(TAG, "Ignoring content changes for " + uri + " from " + uid + ": " + msg);
return;
}
}
}
synchronized (mRootNode) {
mRootNode.addObserverLocked(uri, observer, notifyForDescendants, mRootNode,
uid, pid, userHandle);
if (false) Log.v(TAG, "Registered observer " + observer + " at " + uri +
" with notifyForDescendants " + notifyForDescendants);
}
}
在 registerContentObserver 方法的第 13 行中调用了以下语句,
LocalServices.getService(ActivityManagerInternal.class)
.checkContentProviderAccess(uri.getAuthority(), userHandle);
实质上是调用到了 ActivityManagerService.java 的 checkContentProviderAccess 方法,如下:
public String checkContentProviderAccess(String authority, int userId) {
if (userId == UserHandle.USER_ALL) {
mContext.enforceCallingOrSelfPermission(
Manifest.permission.INTERACT_ACROSS_USERS_FULL, TAG);
userId = UserHandle.getCallingUserId();
}
ProviderInfo cpi = null;
try {
cpi = AppGlobals.getPackageManager().resolveContentProvider(authority,
STOCK_PM_FLAGS | PackageManager.GET_URI_PERMISSION_PATTERNS
| PackageManager.MATCH_DISABLED_COMPONENTS
| PackageManager.MATCH_DIRECT_BOOT_AWARE
| PackageManager.MATCH_DIRECT_BOOT_UNAWARE,
userId);
} catch (RemoteException ignored) {
}
if (cpi == null) {
return "Failed to find provider " + authority + " for user " + userId
+ "; expected to find a valid ContentProvider for this authority";
}
ProcessRecord r = null;
synchronized (mPidsSelfLocked) {
r = mPidsSelfLocked.get(Binder.getCallingPid());
}
if (r == null) {
return "Failed to find PID " + Binder.getCallingPid();
}
synchronized (this) {
return checkContentProviderPermissionLocked(cpi, r, userId, true);
}
}
在该方法的第 10 行中通过 PackageManager.java 的 resolveContentProvider 查询是否有匹配我们传入的 uri 的 authority 的 ContentProvider。当查询不到时,将会返回一段异常字符串,而这个异常字符串就是我们异常时所接受到的。
接下来我们回到 registerContentObserver 方法,在第 15 行判断到 msg 不为空,并且在第 16 行中判断到 SDK 版本大于等于 26 时,将会抛出一个 SecurityException 异常。
既然我们已经弄懂了异常是如何产生的了,那么,解决这个问题也就不是难事了。从上面的代码可知,应用之所以会出现异常,是因为系统没检测到以我们传入的 uri 的 authority 为 authority 的 ContentProvider,那么,我们只需要在我们的应用中注册一个对应的 ContentProvider,就能解决这个问题了。
Google 为什么要在新版本里要添加这个限制呢?我个人认为这还是为了加强应用的用户隐私安全。纵观历代 Android 版本更新,Google 都在致力于保护用户的隐私以及加大开发者的工作量(开个小玩笑),比如 6.0 的运行时权限,7.0 的 FileProvider 的修改。通过强制要求匹配对应 authority 的 ContentProvider 这一操作,可以防止其他应用在我们不情愿的情况下监听我们应用的数据变化情况,保护用户隐私。
本篇博客到此为止,如有错漏欢迎指出。