androidx中ViewPager导致的死循环绘制问题

简介

最近项目升级到了androidx,突然出现了一些问题,Activity的onStop和onDestroy变得很慢,基本都在十秒了,导致一些页面表现不正常,于是来着手来解决这个问题。

查找原因

onStop和onDestroy回调延时的原因

先看下onStop和onDestroy回调延时的原因,找到一篇文章写得很好:深入分析Android中Activity的onStop和onDestroy()回调延时及延时10s的问题,总结下就是:Looper中的消息队列中一直有Message要处理,没有处理空闲消息的机会。

在处理什么消息?

使用Looper的setMessageLogging方法来打印出是什么消息在处理,打开APP后等待一会儿,看一下输出日志
Printer
满眼都是Choreographer$FrameHandler,原来是绘制消息一直在执行,可是使用android support时没有这个问题!找找资料,找到一篇Choreographer源码理解,发现所有的绘制都是触发了ViewRootImpl的scheduleTraversals方法,
接下来就只能调试源码了,打开模拟器,debug这个scheduleTraversals方法,看看到底是哪里触发的。看下调用栈如下
ViewCompat
我们看到触发绘制的ViewCompat中的一句代码:requestApplyInsets,而这句代码是在setOnApplyWindowInsetsListener方法中调用的,如下:

static void setOnApplyWindowInsetsListener(final @NonNull View v,
                final @Nullable OnApplyWindowInsetsListener listener) {
            ……

            ……

            v.setOnApplyWindowInsetsListener(new View.OnApplyWindowInsetsListener() {
                WindowInsetsCompat mLastInsets = null;

                @Override
                public WindowInsets onApplyWindowInsets(final View view,
                        final WindowInsets insets) {
                    WindowInsetsCompat compatInsets = WindowInsetsCompat.toWindowInsetsCompat(
                            insets, view);
                    if (Build.VERSION.SDK_INT < 30) {
                        callCompatInsetAnimationCallback(insets, v);

                        if (compatInsets.equals(mLastInsets)) {
                            // We got the same insets we just return the previously computed insets.
                            return listener.onApplyWindowInsets(view, compatInsets)
                                    .toWindowInsets();
                        }
                    }
                    mLastInsets = compatInsets;
                    compatInsets = listener.onApplyWindowInsets(view, compatInsets);

                    if (Build.VERSION.SDK_INT >= 30) {
                        return compatInsets.toWindowInsets();
                    }

                    // On API < 30, the visibleInsets, used to built WindowInsetsCompat, are
                    // updated after the insets dispatch so we don't have the updated visible
                    // insets at that point. As a workaround, we re-apply the insets so we know
                    // that we'll have the right value the next time it's called.
                    requestApplyInsets(view);
                    // Keep a copy in case the insets haven't changed on the next call so we don't
                    // need to call the listener again.

                    return compatInsets.toWindowInsets();
                }
            });
        }

requestApplyInsets(view)触发了view的requestApplyInsets,

	// View
	
    public void requestFitSystemWindows() {
        if (mParent != null) {
            mParent.requestFitSystemWindows();
        }
    }

    public void requestApplyInsets() {
        requestFitSystemWindows();
    }

而此方法又默认触发了mParent的requestFitSystemWindows,最后就触发了DecorView的mParent(也就是ViewRootImpl)的requestFitSystemWindows方法,执行了scheduleTraversals进行了重绘

	// ViewRootImpl
    @Override
    public void requestFitSystemWindows() {
        checkThread();
        mApplyInsetsRequested = true;
        scheduleTraversals();
    }

requestApplyInsets(view)触发的时机是:API 30以下,且上次保存的WindowInsetsCompat对象与本次不同。哪些布局设置了OnApplyWindowInsetsListener呢?直接查找引用,自定义的布局和项目代码中没有使用过,就是下面这些布局中用到了
OnApplyWindowInsetsListener

WindowInsets

结合那么WindowInsets是怎么分发的呢?继续查找资料,找到一篇写得比较清晰的文章:WindowInsets的分发
文章中写到CollapsingToolbarLayout是消费的,最后写到ViewPager返回的Insets是未消费的,可能是这个问题。
结合自己的项目,写个demo来测试,很简单的demo,Activity中有ViewPager,ViewPager中的页面有CoordinatorLayout,AppBarLayout和CollapsingToolbarLayout。默认情况下是会死循环调用绘制的,我们自定义一个ViewPager,重新设置setOnApplyWindowInsetsListener,然后在最后的返回中添加consumeSystemWindowInsets()

public class CustomViewPager extends ViewPager {
 ……
 return applied
 		.replaceSystemWindowInsets(res.left, res.top, res.right, res.bottom)
 		.consumeSystemWindowInsets();
 ……
}

重新运行,果然不再循环绘制了。

总结

先看触发条件

  • 升级到androidx
  • 使用了全屏(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN)
  • 页面中有ViewPager
  • ViewPager中有CollapsingToolbarLayout
  • 根布局没有使用fitsSystemWindows
    这些条件同时满足时才会出现这个问题。
    当未使用全屏时,Insets会被状态栏消费掉(绘制了状态栏);当根布局使用了fitsSystemWindows会被根布局消费掉;不会再传入到ViewPager中了。
    当首页出现这个问题时,会导致所有页面的onStop和onDestroy回调延时,如果在onStop或onDestroy中有业务逻辑的话会有影响。而且因为消息队列一直很满,会很容易导致ANR的发生。
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值