APP,Activity的启动速度优化

先讲点题外话

简述Activity的几种启动模式

  • standard标准启动模式,也是Activity的启动模式,以这种模式启动的Activity会新new一个Activity对象并放入Activity堆栈,在这种模式下允许一个Activity类有多个实例,并且可互相叠加
  • singleTop模式,在一个Activity堆栈中允许存在多个实例,比如启动一个Activity,如果该Activity不存在,那么就类似standard模式;如果当前堆栈中已经存在一个Activity实例,但是不在栈顶,那也会新new一个实例,然后put到栈顶;如果当前已经有Activity在栈顶,那就不会再new一个新的Activity,而是直接回调这个Activity的onNewIntent
  • singleTask模式,在一个Activity堆栈中只允许存在一个Activity的实例,比如启动一个Activity,如果这个Activity不存在,则跟standard模式一样,生成新的实例,然后put到堆顶;如果这个Activity已经存在于栈中,那么会把该Activity之上的Activity都destroy掉,然后把该Activity显示出来,并回调onNewIntent方法
  • singleInstance模式是只允许有一个实例,而且是运行在自己单独的一个Activity堆栈中的,并且这个堆栈只允许有这个Activity,不能有其他的Activity

如何计算Activity启动时间

  • 如果你的手机有root过,那么就可以切换到system_process进程查看ActivityManager打印的系统log:
09-15 22:58:51.624 1193-1266/system_process I/ActivityManager: Displayed com.xtc.watch/.view.account.login.activity.WelcomeActivity: +1s150ms (total +4s743ms)

以上打印出了所谓的thisTime和totalTime,thisTime是指当前Activity的启动时间,正常情况下,如果从桌面启动一个Activity,那么thisTime==totalTime,但是通常app会有一个不加载布局文件的闪屏页面,然后再跳转到相应的Activity,这时候thisTime仅仅是代表最后一个Activity的启动时间,而totalTime还包括而totalTime是指APP进程启动时长,闪屏页面的启动时长以及闪屏页面的消失,新Activity的启动时长之和,所以关注APP的启动时间,我们通常关注的是totalTime

  • 通过程序打印log来计算启动时长,在Application的onCreate方法的第一句开始计算,然后到进入指定Activity的onWindowFocus里面停止计算,这之间的时间差就是启动耗时

TraceView识别耗时方法

  • 对于APP启动来说,启动耗时包括Android系统启动APP进程加上APP启动界面的耗时时长,我们可做的优化是APP启动界面的耗时,也就是说从Application的onCreate到主界面的onWindowFocusChanged的这一段时间,所以我们可以用Debug.startMethodTracing()和Debug.stopMethodTracing()来抓取这段时间内的方法耗时,在手机sd卡根目录会声场一个.trace的文件,用monitor的TraceView打开就能看到以下分析图解:


    traceview.jpeg

    如上图,纵轴是各个线程,横轴是时间,下半部分是函数执行耗时以及函数堆栈信息,通常分析的时候,我们可以先看下纵轴,跟APP相关的运行线程多不多,如果多的话可能会有线程竞争问题导致主线程卡顿(普通线程的优先级和主线程的优先级是一样的,如果线程开太多的话,cpu可能会挂起主线程而先去执行其他线程),具体再去看函数的调用情况,有几个指标需要注意下:

  1. Incl Cpu Time指的是函数执行占用cpu的总时间的百分比和总时间,这里指的总时间是包括函数里面所有调用子函数的耗时之和
  2. Excl Cpu Time指的是单个函数执行占用cpu的时间,例如函数a()里面又调用了函数b() ,c(),d(),这里的时间仅仅是a的执行时间和不包括b,c,d的耗时
  3. Incl Real Time是函数实际运行的总时间
  4. Excl Real Time是函数自己实际运行的时间,不包括该函数体内调用的其他子函数的运行时间
  5. Call是函数被调用的次数,如果一个函数被调用的非常多次,那说明这里耶可能存在异常
  6. 函数调用次数和cpu time以及real time的一些比例信息,这些指标可以看出一个函数的平均每次执行的耗时信息
    以上的这些指标都可以排序,而我们就可以很方便的查看到底哪些方法耗时最长,哪些方法被调用次数最多,哪些方法平均耗时最大,函数堆栈信息还可以查看函数的Parant和Children,Parent代表这个函数的父函数,也就是说这个函数被包括在哪一个方法里面,children是指这个函数体里面又调用了哪些方法,可以一层一层的跟踪下去

Systreace识别主线程卡顿问题,View的加载情况

  • 用Trace.beginSection和Trace.endSection来抓取这之间的一些信息,具体是用Monitor去抓取一段时间内的trace信息,然后会在指定的目录生成一个html文件,用谷歌浏览器输入chrome://tracing,然后load这个html文件就可以把信息可视化,就像下面一样:
    systrace.jpeg

    通常我们先看Alert信息,这里面就是一些警告信息,给你一些提示,在systrace的分析文件中,可以看到有问题的Frame(红色代表严重,黄色代表警告,绿色代表正常),点击有问题的Frame,可以具体放大查看这个Frame都做了一些什么事情:
    frame.jpeg

    从图中可以看到,一个View的measure,layout的耗时情况,inflat xml文件耗时等等,这样很容易就找到具体哪一个View加载耗时最长,而且这个View的加载耗时都是消耗的哪一个过程中(inflat,measure,layout,draw),还可以看到加载图片的耗时,问题点找到了,我们就能针对性的去做优化了,例如xml布局扁平化,ViewStub的使用,降低图片的分辨率,做图片缓存,图片缩放,设置工作线程的优先级为后台的优先级,避免和主线程产生大量的线程进程等等这些问题,总之明确一点:只要问题找到了,那么改起来就简单了,关键是问题的分析过程;Systrace还可以查看UI Thread的执行情况,在哪一个时段是处于Running状态,在哪一个时段是出于Sleeping状态,如果UI Thread出于Sleeping状态,那么在这个时间段内cpu是在执行什么线程,我们就可以考虑是不是可以把这个工作线程延迟执行,这样就能尽可能保证UI Thread大多是Running状态,而不是断断续续的,因为frame的刷新频率一旦低于16ms,那么我们肉眼就能感觉到界面卡顿,这是一个很不好的体验,降低卡顿就应该尽量保证frame的刷新频率控制在16ms以内,所以这就要求在准备frame的工作执行不能超过16ms

造成启动速度慢的常见原因

  • 在Application的onCreate里面做了太多的初始化操作,例如第三方库的初始化,其实很多第三方库并不是APP启动了就马上需要初始化,我们完全可以用懒加载的方式,等用到了再去初始化也不迟
  • 过于复杂的功能逻辑初始化操作,例如账户登陆需要去进行网络请求验证密码,验证通过后再去服务器拉去一大串的账户数据,然后再通过json解析,保存数据库,再刷新到界面,在APP启动的时候,我们可以先从数据库去搜索数据,把界面线显示出来,然后再去请求网络更新数据
  • Activity布局层次嵌套过深,xml布局嵌套过深灰导致加载这个布局的时长加大,因为xml布局的绘制是不断的递归遍历到各个View的根结点,保证扁平化的布局可以有效的缩短布局加载时间;使用ViewStub,因为ViewStub只要你不调用inflat,它是不会去加载View的,在Activity启动后,并不是每一个View都需要马上加载,有一些View根本是GONE,这些View完全可以用ViewStub来实现,等用到的时候再去inflat即可;
  • UI线程执行太多耗时操作,数据库操作,文件操作,开过多的线程执行(用RxJava很容易导致这个问题),JSON解析,Bitmap的加载
  • Measure/Layout took a significant time, contributing to jank. Avoid triggering layout during animations(避免在View执行动画过程中出发View的layout,否则可能会造成卡顿)
  • UI Thread的优先级是默认优先级,而new Thread的优先级也是默认的,所以要是有过多的工作线程可能会造成线程竞争,cpu可能挂起UI Thread而去执行其他的work thread,所以work thread应该设置为background的级别,降低线程竞争的概率
  • 在加载View的过程中不要同时去请求数据并更新到View上,在同一时刻做太多的事情也会导致cpu处理不过来而造成卡顿,我们可以等View加载完成之后采取请求数据更新,或者在Activity初始化好了之后再去做其他的数据更新操作(onWindowFocusChanged)
  • 误以为在Activity的onResume里面去做一些耗时操作可以优化Activity的启动,事实上Activity在执行到onResume的时候它的初始化操作还没有执行完成呢,如果在这里面执行耗时操作,不会有任何优化效果,应该在Activity第一次被focus的时候(onWindowFocusChanged),这时候Activity已经是完全初始化好了,你可以试下在onWindowFocusChanged去获取View的高度是可以获取到的,但是在onResumen里面去获取View的高度依然还是0

APP闪屏页面实现

  • 为了实现点击秒开的效果,我们往往会实现APP闪屏页面,所谓的闪屏页面就是一个不加载布局文件的Activity,但是可以设置它的theme里面的window background成启动欢迎页面(图片分辨率不要太大,否则加载时间会比较长),这样就能达到点击app,马上就能看到启动页面,由于Activity不用setContentView,所以启动闪屏页面的速度也很快,然后再由闪屏页面跳转到欢迎页面,然后再进入主界面,其实这样综合下来,启动时间是变长了,因为在Activity之间切换的时候要先pause上一个activity然后再create下一个Activity,这样会增加一些耗时,不过闪屏页面给用户的是点击了立马就启动APP的感觉,所以即时启动总时长多个两三秒也是可以接受的

Activity的启动流程(具体要开源码)

  • Activity或者ContextImpl的startActivity
  • Instrumentation的execStartActivity
  • ActivityManagerService的startActivity->startActivityAsUser
  • ActivityStarter的startActivityMayWait->startActivityLocked->startActivityUnchecked
  • ActivityStackSupervisor的resumeFocusedStackTopActivityLocked->resumeFocusedStackTopActivityLocked
  • ActivityStack的resumeTopActivityUncheckedLocked->resumeTopActivityInnerLocked
  • ActivityStackSupervisor 的startSpecificActivityLocked->realStartActivityLocked
  • ActivityManager的scheduleLaunchActivity->handleLaunchActivity->performLaunchActivity->handleResumeActivity到这里一个Activity的启动流程就基本结束,太特么复杂了。。。
  • 从Activity的启动流程来分析我们可以得知启动一个Activity需要去匹配到你要启动的Activity(匹配ResolveInfo),这里涉及到显示启动和隐式启动,显示启动的话比较快,不用再去匹配Intent里面的IntentFilter;然后再监测Activity所在的进程是否有启动,没有启动的话就fock一个进程出来接下去再做初始化Application并调用相关方法,例如onCreate,然后通过反射的方式创建Activity对象,再调用Activity的各个生命周期;所以APP的启动时间是包括APP进程启动时长(无法优化),Application的执行时间和Activity的执行时间(这两部分是可以优化的),另外在启动Activity之前会设置Theme,这里可能也会造成耗时,例如theme里面设置了一张分辨率较高的background会导致decode这张图片的时间变长
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,332评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,508评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,812评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,607评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,728评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,919评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,071评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,802评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,256评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,576评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,712评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,389评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,032评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,026评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,473评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,606评论 2 350

推荐阅读更多精彩内容