前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >[035] onStop提前投放问题

[035] onStop提前投放问题

作者头像
王小二
发布2020-06-08 12:06:13
7570
发布2020-06-08 12:06:13
举报

前言

最近遇到一个奇葩的问题,应用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在两台手机上不同之处。

代码语言:javascript
复制
adb  shell
pm dump 包名 | grep status
代码语言:javascript
复制
手机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上好。

代码语言:javascript
复制
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 代码

代码语言:javascript
复制
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

代码语言:javascript
复制
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的异常

代码语言:javascript
复制
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

代码语言:javascript
复制
1085  2273 V KobeWang: Activity idle: null NS:0

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

5.2 应用X

代码语言:javascript
复制
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和其他启动的参数结合效果。

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

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 一、Trace先分析一下
  • 二、onStop为什么提前了?
    • 2.1 两台手机的不同之处
      • 2.2 改成speed-profile
        • 2.3 修改方案
        • 三、speed-profile如何影响onStop
          • 3.1 我的诊断
            • 3.2 诊断理由
            • 四、写个Demo
              • 4.1 代码
                • 4.2 运行的Log
                  • 4.3 Log分析
                  • 五、重大发现
                    • 5.1 Demo
                      • 5.2 应用X
                      • 六、总结
                      • 思考
                      领券
                      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档