• ANR系列之ContentProvider类型原理讲解


    前言:

    众所周知,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方法中,代码如下:

    1. void appNotRespondingViaProvider(IBinder connection) {
    2. mService.enforceCallingPermission(android.Manifest.permission.REMOVE_TASKS,
    3. "appNotRespondingViaProvider()");
    4. ...
    5. try {
    6. final ProcessRecord host = conn.provider.proc;
    7. if (host == null) {
    8. Slog.w(TAG, "Failed to find hosting ProcessRecord");
    9. return;
    10. }
    11. mService.mAnrHelper.appNotResponding(host, "ContentProvider not responding");
    12. } finally {
    13. Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
    14. }
    15. }

    我们可以看到,这里会提示ContentProvider not responding,这就是Provider类型ANR的来源。

    1.1 Provider类型ANR的触发流程

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

    1.1.1应用侧发出信号

    首先,ContentProviderClient类中,存在一个内部类对象NotRespondingRunnable,其实现了接口Runnable,我们可以把其当作一个任务。

    1. private class NotRespondingRunnable implements Runnable {
    2. @Override
    3. public void run() {
    4. Log.w(TAG, "Detected provider not responding: " + mContentProvider);
    5. mContentResolver.appNotRespondingViaProvider(mContentProvider);
    6. }
    7. }

    等到这个任务执行的时候,就会调用ContentResolver的appNotRespondingViaProvider方法,这里的mContentResolver的实现类是ContentImpl中的ApplicationContentResolver。

    所以,我们看一下其中的appNotRespondingViaProvider方法,如下:

    1. @Override
    2. public void appNotRespondingViaProvider(IContentProvider icp) {
    3. mMainThread.appNotRespondingViaProvider(icp.asBinder());
    4. }

    也就是通知到了ActivityThread中的appNotRespondingViaProvider方法,这个方法中的功能就是通知到系统侧的进程,代码如下:

    1. final void appNotRespondingViaProvider(IBinder provider) {
    2. synchronized (mProviderMap) {
    3. ProviderRefCount prc = mProviderRefCountMap.get(provider);
    4. if (prc != null) {
    5. try {
    6. ActivityManager.getService()
    7. .appNotRespondingViaProvider(prc.holder.connection);
    8. } catch (RemoteException e) {
    9. throw e.rethrowFromSystemServer();
    10. }
    11. }
    12. }
    13. }

    1.1.2系统侧接收

    在APP侧的信号发出后,AMS的appNotRespondingViaProvider方法会收到这个通知,然后交给ContentProviderHelper来处理,我们来看一下其中的处理方法appNotRespondingViaProvider。

    1. void appNotRespondingViaProvider(IBinder connection) {
    2. mService.enforceCallingPermission(android.Manifest.permission.REMOVE_TASKS,
    3. "appNotRespondingViaProvider()");
    4. final ContentProviderConnection conn = (ContentProviderConnection) connection;
    5. if (conn == null) {
    6. Slog.w(TAG, "ContentProviderConnection is null");
    7. return;
    8. }
    9. ActivityManagerService.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
    10. "appNotRespondingViaProvider: ",
    11. (conn.provider != null && conn.provider.info != null
    12. ? conn.provider.info.authority : ""));
    13. try {
    14. final ProcessRecord host = conn.provider.proc;
    15. if (host == null) {
    16. Slog.w(TAG, "Failed to find hosting ProcessRecord");
    17. return;
    18. }
    19. mService.mAnrHelper.appNotResponding(host, "ContentProvider not responding");
    20. } finally {
    21. Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
    22. }
    23. }

    首先,会进行一个权限检查,只有拥有REMOVE_TASKS的应用才允许发送。普通应用,是不可能拥有这个权限的,所以,发送ANR超时的,一定是系统应用。

    然后,查看binder链接是否还存在,如果不存在,也没有必要进行后续的流程了。

    最后,委托给ANRHelper进行后续的ANR流程,这里,也打印出那个象征个Provider类型的日志:ContentProvider not responding。

    通过如上的介绍,我们可以知道,Provider类型的ANR区别于点击事件以及广播,它是应用主动发出的ANR通知,而不是系统侧进行的监控。那么,我们接下来就看看,APP是如何触发这种类型的ANR的。

    二.APP侧如何触发Provider

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

    2.1 ContentProviderClient的使用

    上一篇文章中,我们有介绍了ContentProviderClient的使用,为了方便直接阅读本文的读者,我们这里就稍稍赘述一下,内容和上一篇文章是一样的。

    使用方式如下:

    1. ContentValues values = new ContentValues();
    2. values.put("key_main", "value_main");
    3. String AUTHORITY = "com.xt.client.android_startup.multiple";
    4. Uri uri = Uri.parse("content://" + AUTHORITY);
    5. ContentProviderClient client = getContentResolver().acquireContentProviderClient(uri);
    6. try {
    7. int query = client.update(uri, values, null, null);
    8. Log.i("lxltest", "query:" + query);
    9. } catch (RemoteException e) {
    10. e.printStackTrace();
    11. }

    ContentProviderClient简单来说,就是不用每次都去查询那个binder的引用,而直接使用同一个对象进行处理。

    2.2 provider类型ANR监控原理

    正常的provider的使用方法是没有ANR监控的,有监控的只存在于使用ContentProviderClient的类型中。

    我们仍然以update为例,我们看一下ContentProviderClient的update方法:
     

    1. @Override
    2. public int update(@NonNull Uri url, @Nullable ContentValues values, @Nullable Bundle extras)
    3. throws RemoteException {
    4. Objects.requireNonNull(url, "url");
    5. beforeRemote();
    6. try {
    7. return mContentProvider.update(mAttributionSource, url, values, extras);
    8. } catch (DeadObjectException e) {
    9. if (!mStable) {
    10. mContentResolver.unstableProviderDied(mContentProvider);
    11. }
    12. throw e;
    13. } finally {
    14. afterRemote();
    15. }
    16. }

    这里的核心就是beforeRemote,我们看一下这个方法:

    1. private void beforeRemote() {
    2. if (mAnrRunnable != null) {
    3. sAnrHandler.postDelayed(mAnrRunnable, mAnrTimeout);
    4. }
    5. }

    如果mAnrRunnable不为空,则就是通过一个延时任务去执行。而这个mAnrRunnable对象就是我们上面介绍的NotRespondingRunnable。所以我们接下来,就看下mAnrRunnable对象何时设置的,这个方法是setDetectNotResponding。

    1. @SystemApi
    2. @RequiresPermission(android.Manifest.permission.REMOVE_TASKS)
    3. public void setDetectNotResponding(@DurationMillisLong long timeoutMillis) {
    4. synchronized (ContentProviderClient.class) {
    5. mAnrTimeout = timeoutMillis;
    6. if (timeoutMillis > 0) {
    7. if (mAnrRunnable == null) {
    8. mAnrRunnable = new NotRespondingRunnable();
    9. }
    10. if (sAnrHandler == null) {
    11. sAnrHandler = new Handler(Looper.getMainLooper(), null, true /* async */);
    12. }
    13. // If the remote process hangs, we're going to kill it, so we're
    14. // technically okay doing blocking calls.
    15. Binder.allowBlocking(mContentProvider.asBinder());
    16. } else {
    17. mAnrRunnable = null;
    18. // If we're no longer watching for hangs, revert back to default
    19. // blocking behavior.
    20. Binder.defaultBlocking(mContentProvider.asBinder());
    21. }
    22. }
    23. }

    所以,就是Provider类型的ANR的秘密,只要通过ContentProviderClient的setDetectNotResponding方法进行配置,就可以让ContentProviderClient具体有ANR的功能。每次增删改查的时候,都记录一个延时任务,如果按时完成则取消任务,否则执行任务。而这个任务一旦执行,就会触发ANR的流程。

    2.3 普通的APP可以用吗?

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

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

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

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

  • 相关阅读:
    华为数通方向HCIP-DataCom H12-821题库(单选题:181-200)
    一文彻底理解什么是同步和异步!
    python的多线程介绍之thread
    想要精通算法和SQL的成长之路 - 最长等差数列
    GoLand 2023.2.3(go语言开发)
    驱动开发:通过PIPE管道与内核层通信
    ES7 Nested Sort Search
    微信推出自研NLP大规模语言模型WeLM,现已开放API推动应用落地
    kubebuilder operator的运行逻辑
    Win10怎么设置待机时间
  • 原文地址:https://blog.csdn.net/AA5279AA/article/details/128039319