众所周知,ANR一共有四种类型,如下:
1.输入事件类型ANR
2.广播类型ANR
3.ContentProvider类型ANR
4.Service类型ANR
四种类型的超时时间如下所示:
所以ANR系列文章也会分为5篇文章来进行讲解,本篇是该系列的第三篇文章,主要讲解Provider类型的ANR问题是如何触发的。
本文主要会讲解以下内容:
1.provider类型的ANR在系统侧的处理流程;
2.provider类型的ANR在应用侧如何触发;
3.ContentProviderClient的使用;
4.什么场景下,可以触发provider类型的ANR。
另外,本文和以前讲ANR流程有所不一样,之前的流程,都是正向的,从调用方开始一步一步的往后讲。而本文,则反过来,从ANR触发时刻开始,往前一步一步找寻调用方。
PS:阅读本文前,建议阅读下面的文章,做好知识储备,方便本文的理解。
android源码-ContentProvider实现原理分析_失落夏天的博客-CSDN博客
首先,其实ANR本身是不区分类型,并没有文章开头所说的4种类型。我们之所以人为的去区分ANR类型,是因为有四种场景可以触发ANR并且触发条件和内容都不相同。
所以我们也找一下,Provider类型的ANR是如何触发的,搜遍了整个AOSP的代码,我们发现只有一个地方是ContentProvider类型的ANR,在ContentProviderHelper的appNotRespondingViaProvider方法中,代码如下:
void appNotRespondingViaProvider(IBinder connection) {
mService.enforceCallingPermission(android.Manifest.permission.REMOVE_TASKS,
"appNotRespondingViaProvider()");
...
try {
final ProcessRecord host = conn.provider.proc;
if (host == null) {
Slog.w(TAG, "Failed to find hosting ProcessRecord");
return;
}
mService.mAnrHelper.appNotResponding(host, "ContentProvider not responding");
} finally {
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
}
}
我们可以看到,这里会提示ContentProvider not responding,这就是Provider类型ANR的来源。
为了方面读者理解整个ANR的流程,我画了一张流程图,方便理解,整个流程还是比较简单的。
首先,ContentProviderClient类中,存在一个内部类对象NotRespondingRunnable,其实现了接口Runnable,我们可以把其当作一个任务。
private class NotRespondingRunnable implements Runnable {
@Override
public void run() {
Log.w(TAG, "Detected provider not responding: " + mContentProvider);
mContentResolver.appNotRespondingViaProvider(mContentProvider);
}
}
等到这个任务执行的时候,就会调用ContentResolver的appNotRespondingViaProvider方法,这里的mContentResolver的实现类是ContentImpl中的ApplicationContentResolver。
所以,我们看一下其中的appNotRespondingViaProvider方法,如下:
@Override
public void appNotRespondingViaProvider(IContentProvider icp) {
mMainThread.appNotRespondingViaProvider(icp.asBinder());
}
也就是通知到了ActivityThread中的appNotRespondingViaProvider方法,这个方法中的功能就是通知到系统侧的进程,代码如下:
final void appNotRespondingViaProvider(IBinder provider) {
synchronized (mProviderMap) {
ProviderRefCount prc = mProviderRefCountMap.get(provider);
if (prc != null) {
try {
ActivityManager.getService()
.appNotRespondingViaProvider(prc.holder.connection);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
}
}
在APP侧的信号发出后,AMS的appNotRespondingViaProvider方法会收到这个通知,然后交给ContentProviderHelper来处理,我们来看一下其中的处理方法appNotRespondingViaProvider。
void appNotRespondingViaProvider(IBinder connection) {
mService.enforceCallingPermission(android.Manifest.permission.REMOVE_TASKS,
"appNotRespondingViaProvider()");
final ContentProviderConnection conn = (ContentProviderConnection) connection;
if (conn == null) {
Slog.w(TAG, "ContentProviderConnection is null");
return;
}
ActivityManagerService.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
"appNotRespondingViaProvider: ",
(conn.provider != null && conn.provider.info != null
? conn.provider.info.authority : ""));
try {
final ProcessRecord host = conn.provider.proc;
if (host == null) {
Slog.w(TAG, "Failed to find hosting ProcessRecord");
return;
}
mService.mAnrHelper.appNotResponding(host, "ContentProvider not responding");
} finally {
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
}
}
首先,会进行一个权限检查,只有拥有REMOVE_TASKS的应用才允许发送。普通应用,是不可能拥有这个权限的,所以,发送ANR超时的,一定是系统应用。
然后,查看binder链接是否还存在,如果不存在,也没有必要进行后续的流程了。
最后,委托给ANRHelper进行后续的ANR流程,这里,也打印出那个象征个Provider类型的日志:ContentProvider not responding。
通过如上的介绍,我们可以知道,Provider类型的ANR区别于点击事件以及广播,它是应用主动发出的ANR通知,而不是系统侧进行的监控。那么,我们接下来就看看,APP是如何触发这种类型的ANR的。
讲触发原因之前,我们首先要讲一讲ContentProviderClient的使用。
上一篇文章中,我们有介绍了ContentProviderClient的使用,为了方便直接阅读本文的读者,我们这里就稍稍赘述一下,内容和上一篇文章是一样的。
使用方式如下:
ContentValues values = new ContentValues();
values.put("key_main", "value_main");
String AUTHORITY = "com.xt.client.android_startup.multiple";
Uri uri = Uri.parse("content://" + AUTHORITY);
ContentProviderClient client = getContentResolver().acquireContentProviderClient(uri);
try {
int query = client.update(uri, values, null, null);
Log.i("lxltest", "query:" + query);
} catch (RemoteException e) {
e.printStackTrace();
}
ContentProviderClient简单来说,就是不用每次都去查询那个binder的引用,而直接使用同一个对象进行处理。
正常的provider的使用方法是没有ANR监控的,有监控的只存在于使用ContentProviderClient的类型中。
我们仍然以update为例,我们看一下ContentProviderClient的update方法:
@Override
public int update(@NonNull Uri url, @Nullable ContentValues values, @Nullable Bundle extras)
throws RemoteException {
Objects.requireNonNull(url, "url");
beforeRemote();
try {
return mContentProvider.update(mAttributionSource, url, values, extras);
} catch (DeadObjectException e) {
if (!mStable) {
mContentResolver.unstableProviderDied(mContentProvider);
}
throw e;
} finally {
afterRemote();
}
}
这里的核心就是beforeRemote,我们看一下这个方法:
private void beforeRemote() {
if (mAnrRunnable != null) {
sAnrHandler.postDelayed(mAnrRunnable, mAnrTimeout);
}
}
如果mAnrRunnable不为空,则就是通过一个延时任务去执行。而这个mAnrRunnable对象就是我们上面介绍的NotRespondingRunnable。所以我们接下来,就看下mAnrRunnable对象何时设置的,这个方法是setDetectNotResponding。
@SystemApi
@RequiresPermission(android.Manifest.permission.REMOVE_TASKS)
public void setDetectNotResponding(@DurationMillisLong long timeoutMillis) {
synchronized (ContentProviderClient.class) {
mAnrTimeout = timeoutMillis;
if (timeoutMillis > 0) {
if (mAnrRunnable == null) {
mAnrRunnable = new NotRespondingRunnable();
}
if (sAnrHandler == null) {
sAnrHandler = new Handler(Looper.getMainLooper(), null, true /* async */);
}
// If the remote process hangs, we're going to kill it, so we're
// technically okay doing blocking calls.
Binder.allowBlocking(mContentProvider.asBinder());
} else {
mAnrRunnable = null;
// If we're no longer watching for hangs, revert back to default
// blocking behavior.
Binder.defaultBlocking(mContentProvider.asBinder());
}
}
}
所以,就是Provider类型的ANR的秘密,只要通过ContentProviderClient的setDetectNotResponding方法进行配置,就可以让ContentProviderClient具体有ANR的功能。每次增删改查的时候,都记录一个延时任务,如果按时完成则取消任务,否则执行任务。而这个任务一旦执行,就会触发ANR的流程。
但是实际上,好像我们很少遇到provider类型的ANR,这又是为何呢?
其答案就在于这个功能,并非提供给所有应用的,而只是提供给系统级应用。
首先,2.2中的代码中我们也可以看到,这是一个SytemApi,普通应用是无法调用的。
其次,就算我们通过反射调用了setDetectNotResponding方法,仍然是无法生效的。因为上面1.1.2中讲到,只有具有REMOV_TASK权限的APP才能发送这样的通知,否则会给APP侧抛出异常。