[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流程不熟悉的,可以参考下面这个朋友的文章
//www.greatytc.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和其他启动的参数结合效果。

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 225,151评论 6 523
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 96,465评论 3 405
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 172,429评论 0 368
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 61,147评论 1 301
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 70,149评论 6 400
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 53,614评论 1 315
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 41,965评论 3 429
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 40,950评论 0 279
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 47,486评论 1 324
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 39,524评论 3 347
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 41,640评论 1 355
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 37,228评论 5 351
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 42,976评论 3 340
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 33,407评论 0 25
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 34,552评论 1 277
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 50,215评论 3 381
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 46,714评论 2 366

推荐阅读更多精彩内容