专栏首页王小二的Android站[035] onStop提前投放问题

[035] onStop提前投放问题

前言

最近遇到一个奇葩的问题,应用X的Activity1中点击一个Button跳转到Activity2,手机A比手机B上快500ms左右,虽然手机A比手机B的配置高,但是我不信差距会那么大。

一、Trace先分析一下

我抓了两台手机的Trace简化后如下图,在UI线程发现两者一个重要的差异点,在手机B中,Activity1 onStop竟然跑在了ViewRootImpl doTraversal的前面,一个Activity是否显示完成就要看什么时候完成第一帧的绘制,也就是什么时候调用完ViewRootImpl doTraversal

初步分析:

手机B中Activity1 onStopViewRootImpl doTraversal的时序问题,导致了ViewRootImpl doTraversal推迟执行,导致了Activity2界面显示推迟。

手机A UI线程

手机B UI线程

二、onStop为什么提前了?

2.1 两台手机的不同之处

我加了好多log,想了各种可能性,死活找不到onStop跑在doTraversal的前面原因,这问题搁置了很久一直没有解决。然后突然被我发现应用X在两台手机上不同之处。

adb  shell
pm dump 包名 | grep status
手机A
arm64: [status=speed-profile] [reason=bg-dexopt]

手机B
arm64: [status= quicken] [reason=prebuilt]

对于这个speed-profile和quicken不了解的可以看下面这个网址 https://source.android.com/devices/tech/dalvik/configure

简单来说,你只要记住应用X在同一台手机上speed-profile运行的比quicken快。

2.2 改成speed-profile

用下面的指令改成speed-profile之后,果然启动速度快,而且从Trace来看onStop没有运行到doTraversal前面,整个时序也和手机A一样了,虽然手机B上还是慢了那么一丢丢,那也可以接受,毕竟手机A的配置比手机B上好。

adb  shell
pm compile -m  speed-profile 包名

2.3 修改方案

我啥代码都不用提交,因为手机A刚安装应用的时候也是quicken,只不过手机A用这个应用次数频繁,后台自动dex优化了。因此只要手机B多用用,启动速度就会快起来了。

三、speed-profile如何影响onStop

虽然问题已经解决了,但是我还是好奇speed-profile如何影响onStop和doTraversal的执行顺序,按道理来说speed-profile只能缩短Activity2.onResume,Activity1.ononStop,doTraversal的执行时间,怎么能影响到执行的顺序呢?

3.1 我的诊断

onResume的执行时间过长影响到了Activity1.onStop和doTraversal的时序

3.2 诊断理由

onResume是由AMS通过Binder通信通知应用往主线程中投放的onResume任务 doTraversal是由onResume完成之后,在下一个Vsync信号来了之后往主线程中投放的doTraversal任务 onStop是由AMS通过Binder通信通知应用往主线程中投放的onStop的任务 有一个关键点:投放onResume任务和投放onStop的任务的时间差由AMS的逻辑问题决定

3.2.1如果onResume执行时间比较短,doTraversal就赶在onStop前被投放,这样子执行的流程就是onResume-doTraversal-onStop
3.2.2如果onResume执行时间比较长,onStop就赶在doTraversal前被投放,这样子执行的流程就是onResume-onStop-doTraversal

四、写个Demo

我赶紧写个程序验证一下我的猜想,程序也很简单,我们从MainActivity跳转到Main2Activity,在Main2Activity的onResume中休眠500ms,在MainActivity的onStop中休眠1000ms。

4.1 代码

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    private TextView mTxtItem;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mTxtItem = findViewById(R.id.txt_item);
        mTxtItem.setOnClickListener(this);
    }

    @Override
    protected void onResume() {
        super.onResume();
    }

    @Override
    public void onClick(View v) {
        Intent intent = new Intent(this, Main2Activity.class);
        this.startActivity(intent);
    }

    @Override
    protected void onStop() {
        Log.d("KobeWang", "MainActivity : onStop1");
        super.onStop();
        try {
            Thread.sleep(1000);
        } catch (Exception e) {

        }
        Log.d("KobeWang", "MainActivity : onStop2");
    }

    @Override
    public void finish() {
        super.finish();
    }
}

public class Main2Activity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main2);
    }

    @Override
    protected void onResume() {
        Log.d("KobeWang", "Main2Activity : onResume1");
        super.onResume();
        try {
            Thread.sleep(500);
        } catch (Exception e) {

        }
        Log.d("KobeWang", "Main2Activity : onResume2");
    }
}
//MyTextView会在Main2Activity中使用,判断什么时候调用doTraversal
public class MyTextView extends TextView {
    public MyTextView(Context context) {
        super(context);
    }

    public MyTextView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    public MyTextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }


    static boolean show;

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if(!show) {
            Log.v("KobeWang", "onDraw");
            show = true;
        }
    }
}

4.2 运行的Log

2020-03-10 20:08:22.598 11011-11011/com.kobe.jankblock D/KobeWang: Main2Activity : onResume1
2020-03-10 20:08:23.099 11011-11011/com.kobe.jankblock D/KobeWang: Main2Activity : onResume2
2020-03-10 20:08:23.140 11011-11011/com.kobe.jankblock V/KobeWang: onDraw
2020-03-10 20:08:23.194 11011-11011/com.kobe.jankblock D/KobeWang: MainActivity : onStop1
2020-03-10 20:08:24.198 11011-11011/com.kobe.jankblock D/KobeWang: MainActivity : onStop2

4.3 Log分析

从Log中可以看出,我增加Main2Activity.onResume的时长,MainActivity.onStop还是必须在Main2Activity.onResume这个任务完成之后被投放然,并不能在Main2Activity.onResume的运行时候被投放。

onStop流程不熟悉的,可以参考下面这个朋友的文章 https://www.jianshu.com/p/5cb4baa4cf5e

常规的onStop是由ActivityIdle触发的,我写的demo,onStop肯定是由onResume完成以后被投放的,所以永远无法达到onStop提前的效果

五、重大发现

正当我一筹莫展的时候,我打了很多系统的log,发现下面这处log的异常

final ArrayList<ActivityRecord> stops = processStoppingActivitiesLocked(r,
        true /* remove */, processPausingActivities);
NS = stops != null ? stops.size() : 0;

Slog.v("KobeWang","Activity idle: "+ token + " NS:"+ NS);//我添加的重要LOG
for (int i = 0; i < NS; i++) {
    r = stops.get(i);
    final ActivityStack stack = r.getActivityStack();
    if (stack != null) {
        if (r.finishing) {
            stack.finishCurrentActivityLocked(r, ActivityStack.FINISH_IMMEDIATELY, false,
                    "activityIdleInternalLocked");
        } else {
            stack.stopActivityLocked(r);//如果NS>0,注意这个可以让stopActivityLocked提前运行
        }
    }
}

5.1 Demo

1085  2273 V KobeWang: Activity idle: null NS:0

我们正常写的代码Activity1跳转Activity2,NS为0,所以这个stopActivityLocked就不会提前执行,只能等到ActivityIdle之后触发stopActivityLocked。

5.2 应用X

1085  2273 V KobeWang: Activity idle: null NS:1

应用X的代码竟然能让Activity1跳转Activity2,NS为1,这样子Activity1的stopActivityLocked就会被提前执行,也就是导致了所以一但Activity2.onResume运行过长,onStop可能会在Activity2.onResume运行的时候被投放的,就会导致下图的情形。

六、总结

一般来说分析到这里就足够了,我们无法控制应用X怎么写代码,这个启动慢的问题完全是应用做了三个错误的事情,而且缺一不可。 错误1:Activity1跳转Activity2代码特殊,造成NS为1,导致了onStop的提前,不是由正常的ActivityIdle的触发。 错误2:Activity2.onResume在手机B上在Quicken模式下运行速度太慢,导致了doTraversal在Activity1.onStop之后 错误3:Activity1.onStop中做了太多事情,导致了主线程无法提前处理doTraversal。

请记住onStop在某种特殊界面切换逻辑下,有可能被提前投放到主线程,只是目前这个某种特殊界面切换逻辑,我无法写出Demo来复现,应用X的代码可以触发这个特殊界面切换逻辑。

思考

有时间我还是要继续研究以下,如何让Activity1启动到Activity2的时候,NS为1,我看了一下应用X的Activity1和Activity2在不同的Task,我也改了一下我的Demo,变成两个Task,但那是NS还是0,肯定我还是少了一些关键信息。我还发现从Launcher点击启动一个应用,也会让NS为1,而且启动一个应用也是新建一个Task,所以我强烈怀疑这个逻辑应该是新建Task和其他启动的参数结合效果。

有兴趣的朋友也可以试一下。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • [037]Choreographer Skipped含义再探

    在[036]Choreographer Skipped真正含义中,我介绍了一种可以产生Choreographer Skipped的情况。就是在onVsync被调...

    王小二
  • [005]frameworks/ml引发的思考

    Android P上介绍了那么多有关AI的功能,但是真正看起来,Android上AI还处于初级阶段,Android 8.0之后的源码中有一个新增目录:frame...

    王小二
  • [042]f2fs存储结构初探

    将f2fs_device和loop设备绑定,生成一个虚拟块设备,如果提示设备忙,13换成其他数字

    王小二
  • 吉尔德定律

    摩尔定律(Moore’s Law),吉尔德定律(Gilder’s Law),麦特卡尔夫定律(Metcalfe’s Law)是制约同时也是引导信息产业发展的内在规...

    瓜大三哥
  • appium+python自动化57-chromedriver与chrome版本

    由于app的webview自动化是依赖于chromedriver的,并且每个app的webview版本号都不太一样,这就导致了,每次都需要重新去下载对应的chr...

    上海-悠悠
  • 视觉格式化模型-控制框

    我们经常用到块元素、行内元素的概念,那么,到底什么是块元素,什么是行内元素,它们有什么特点,怎么形成的,有什么作用呢?什么是块框,什么又是行内框呢? 一、块级元...

    练小习
  • LINUX下查看CPU使用率的命令

    今天就来好好学习下Linux下如何查看CUP的使用率: 监控CPU的性能一般包括以下3点:运行队列、CPU使用率和上下文切换。 对于每一个CPU来说运行队列最好...

    软测小生
  • 基于Django的双因子认证实现

    双因子简介 对于网络信息系统来说,能否识别使用者的身份,是能否确保安全的基础和关键。在实际应用中,许多网络信息系统都会要求使用者在使用系统之前,提供一些相关信息...

    FB客服
  • 智加科技刘万千:技术与生态的成熟将推动自动驾驶的落地应用丨镁客请讲

    刚过去的二月,自动驾驶领域接连传来了多家企业完成融资的好消息,合计超过107亿元的融资金额足以证明资本对这一领域看好。然而,比起资本更早预见这一领域大有可为的是...

    镁客网
  • VMWARE里安装时Ubuntu16.04 出现'SMBus Host Controller not enabled'

    转载自:http://forum.ubuntu.org.cn/viewtopic.php?t=481315

    二狗不要跑

扫码关注云+社区

领取腾讯云代金券