众所周知,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侧抛出异常。