[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 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和其他启动的参数结合效果。

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

你可能感兴趣的:([035] onStop提前投放问题)