ANR系列之四:ContentProvider类型ANR产生原理讲解

前言:

众所周知,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博客

一.Provider类型ANR如何触发?

1.1 Provider类型ANR的触发点

首先,其实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的来源。

1.1 Provider类型ANR的触发流程

为了方面读者理解整个ANR的流程,我画了一张流程图,方便理解,整个流程还是比较简单的。

1.1.1应用侧发出信号

首先,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();
                }
            }
        }
    }

1.1.2系统侧接收

在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的。

二.APP侧如何触发Provider

讲触发原因之前,我们首先要讲一讲ContentProviderClient的使用。

2.1 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的引用,而直接使用同一个对象进行处理。

2.2 provider类型ANR监控原理

正常的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的流程。

2.3 普通的APP可以用吗?

但是实际上,好像我们很少遇到provider类型的ANR,这又是为何呢?

其答案就在于这个功能,并非提供给所有应用的,而只是提供给系统级应用。

首先,2.2中的代码中我们也可以看到,这是一个SytemApi,普通应用是无法调用的。

其次,就算我们通过反射调用了setDetectNotResponding方法,仍然是无法生效的。因为上面1.1.2中讲到,只有具有REMOV_TASK权限的APP才能发送这样的通知,否则会给APP侧抛出异常。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

失落夏天

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值