先講點(diǎn)題外話
簡(jiǎn)述Activity的幾種啟動(dòng)模式
- standard標(biāo)準(zhǔn)啟動(dòng)模式,也是Activity的啟動(dòng)模式,以這種模式啟動(dòng)的Activity會(huì)新new一個(gè)Activity對(duì)象并放入Activity堆棧,在這種模式下允許一個(gè)Activity類有多個(gè)實(shí)例,并且可互相疊加
- singleTop模式,在一個(gè)Activity堆棧中允許存在多個(gè)實(shí)例,比如啟動(dòng)一個(gè)Activity,如果該Activity不存在,那么就類似standard模式;如果當(dāng)前堆棧中已經(jīng)存在一個(gè)Activity實(shí)例,但是不在棧頂,那也會(huì)新new一個(gè)實(shí)例,然后put到棧頂;如果當(dāng)前已經(jīng)有Activity在棧頂,那就不會(huì)再new一個(gè)新的Activity,而是直接回調(diào)這個(gè)Activity的onNewIntent
- singleTask模式,在一個(gè)Activity堆棧中只允許存在一個(gè)Activity的實(shí)例,比如啟動(dòng)一個(gè)Activity,如果這個(gè)Activity不存在,則跟standard模式一樣,生成新的實(shí)例,然后put到堆頂;如果這個(gè)Activity已經(jīng)存在于棧中,那么會(huì)把該Activity之上的Activity都destroy掉,然后把該Activity顯示出來(lái),并回調(diào)onNewIntent方法
- singleInstance模式是只允許有一個(gè)實(shí)例,而且是運(yùn)行在自己?jiǎn)为?dú)的一個(gè)Activity堆棧中的,并且這個(gè)堆棧只允許有這個(gè)Activity,不能有其他的Activity
如何計(jì)算Activity啟動(dòng)時(shí)間
- 如果你的手機(jī)有root過(guò),那么就可以切換到system_process進(jìn)程查看ActivityManager打印的系統(tǒng)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是指當(dāng)前Activity的啟動(dòng)時(shí)間,正常情況下,如果從桌面啟動(dòng)一個(gè)Activity,那么thisTime==totalTime,但是通常app會(huì)有一個(gè)不加載布局文件的閃屏頁(yè)面,然后再跳轉(zhuǎn)到相應(yīng)的Activity,這時(shí)候thisTime僅僅是代表最后一個(gè)Activity的啟動(dòng)時(shí)間,而totalTime還包括而totalTime是指APP進(jìn)程啟動(dòng)時(shí)長(zhǎng),閃屏頁(yè)面的啟動(dòng)時(shí)長(zhǎng)以及閃屏頁(yè)面的消失,新Activity的啟動(dòng)時(shí)長(zhǎng)之和,所以關(guān)注APP的啟動(dòng)時(shí)間,我們通常關(guān)注的是totalTime
- 通過(guò)程序打印log來(lái)計(jì)算啟動(dòng)時(shí)長(zhǎng),在Application的onCreate方法的第一句開(kāi)始計(jì)算,然后到進(jìn)入指定Activity的onWindowFocus里面停止計(jì)算,這之間的時(shí)間差就是啟動(dòng)耗時(shí)
TraceView識(shí)別耗時(shí)方法
-
對(duì)于APP啟動(dòng)來(lái)說(shuō),啟動(dòng)耗時(shí)包括Android系統(tǒng)啟動(dòng)APP進(jìn)程加上APP啟動(dòng)界面的耗時(shí)時(shí)長(zhǎng),我們可做的優(yōu)化是APP啟動(dòng)界面的耗時(shí),也就是說(shuō)從Application的onCreate到主界面的onWindowFocusChanged的這一段時(shí)間,所以我們可以用Debug.startMethodTracing()和Debug.stopMethodTracing()來(lái)抓取這段時(shí)間內(nèi)的方法耗時(shí),在手機(jī)sd卡根目錄會(huì)聲場(chǎng)一個(gè).trace的文件,用monitor的TraceView打開(kāi)就能看到以下分析圖解:
traceview.jpeg
如上圖,縱軸是各個(gè)線程,橫軸是時(shí)間,下半部分是函數(shù)執(zhí)行耗時(shí)以及函數(shù)堆棧信息,通常分析的時(shí)候,我們可以先看下縱軸,跟APP相關(guān)的運(yùn)行線程多不多,如果多的話可能會(huì)有線程競(jìng)爭(zhēng)問(wèn)題導(dǎo)致主線程卡頓(普通線程的優(yōu)先級(jí)和主線程的優(yōu)先級(jí)是一樣的,如果線程開(kāi)太多的話,cpu可能會(huì)掛起主線程而先去執(zhí)行其他線程),具體再去看函數(shù)的調(diào)用情況,有幾個(gè)指標(biāo)需要注意下:
- Incl Cpu Time指的是函數(shù)執(zhí)行占用cpu的總時(shí)間的百分比和總時(shí)間,這里指的總時(shí)間是包括函數(shù)里面所有調(diào)用子函數(shù)的耗時(shí)之和
- Excl Cpu Time指的是單個(gè)函數(shù)執(zhí)行占用cpu的時(shí)間,例如函數(shù)a()里面又調(diào)用了函數(shù)b() ,c(),d(),這里的時(shí)間僅僅是a的執(zhí)行時(shí)間和不包括b,c,d的耗時(shí)
- Incl Real Time是函數(shù)實(shí)際運(yùn)行的總時(shí)間
- Excl Real Time是函數(shù)自己實(shí)際運(yùn)行的時(shí)間,不包括該函數(shù)體內(nèi)調(diào)用的其他子函數(shù)的運(yùn)行時(shí)間
- Call是函數(shù)被調(diào)用的次數(shù),如果一個(gè)函數(shù)被調(diào)用的非常多次,那說(shuō)明這里耶可能存在異常
- 函數(shù)調(diào)用次數(shù)和cpu time以及real time的一些比例信息,這些指標(biāo)可以看出一個(gè)函數(shù)的平均每次執(zhí)行的耗時(shí)信息
以上的這些指標(biāo)都可以排序,而我們就可以很方便的查看到底哪些方法耗時(shí)最長(zhǎng),哪些方法被調(diào)用次數(shù)最多,哪些方法平均耗時(shí)最大,函數(shù)堆棧信息還可以查看函數(shù)的Parant和Children,Parent代表這個(gè)函數(shù)的父函數(shù),也就是說(shuō)這個(gè)函數(shù)被包括在哪一個(gè)方法里面,children是指這個(gè)函數(shù)體里面又調(diào)用了哪些方法,可以一層一層的跟蹤下去
Systreace識(shí)別主線程卡頓問(wèn)題,View的加載情況
- 用Trace.beginSection和Trace.endSection來(lái)抓取這之間的一些信息,具體是用Monitor去抓取一段時(shí)間內(nèi)的trace信息,然后會(huì)在指定的目錄生成一個(gè)html文件,用谷歌瀏覽器輸入chrome://tracing,然后load這個(gè)html文件就可以把信息可視化,就像下面一樣:
systrace.jpeg
通常我們先看Alert信息,這里面就是一些警告信息,給你一些提示,在systrace的分析文件中,可以看到有問(wèn)題的Frame(紅色代表嚴(yán)重,黃色代表警告,綠色代表正常),點(diǎn)擊有問(wèn)題的Frame,可以具體放大查看這個(gè)Frame都做了一些什么事情:
frame.jpeg
從圖中可以看到,一個(gè)View的measure,layout的耗時(shí)情況,inflat xml文件耗時(shí)等等,這樣很容易就找到具體哪一個(gè)View加載耗時(shí)最長(zhǎng),而且這個(gè)View的加載耗時(shí)都是消耗的哪一個(gè)過(guò)程中(inflat,measure,layout,draw),還可以看到加載圖片的耗時(shí),問(wèn)題點(diǎn)找到了,我們就能針對(duì)性的去做優(yōu)化了,例如xml布局扁平化,ViewStub的使用,降低圖片的分辨率,做圖片緩存,圖片縮放,設(shè)置工作線程的優(yōu)先級(jí)為后臺(tái)的優(yōu)先級(jí),避免和主線程產(chǎn)生大量的線程進(jìn)程等等這些問(wèn)題,總之明確一點(diǎn):只要問(wèn)題找到了,那么改起來(lái)就簡(jiǎn)單了,關(guān)鍵是問(wèn)題的分析過(guò)程;Systrace還可以查看UI Thread的執(zhí)行情況,在哪一個(gè)時(shí)段是處于Running狀態(tài),在哪一個(gè)時(shí)段是出于Sleeping狀態(tài),如果UI Thread出于Sleeping狀態(tài),那么在這個(gè)時(shí)間段內(nèi)cpu是在執(zhí)行什么線程,我們就可以考慮是不是可以把這個(gè)工作線程延遲執(zhí)行,這樣就能盡可能保證UI Thread大多是Running狀態(tài),而不是斷斷續(xù)續(xù)的,因?yàn)閒rame的刷新頻率一旦低于16ms,那么我們?nèi)庋劬湍芨杏X(jué)到界面卡頓,這是一個(gè)很不好的體驗(yàn),降低卡頓就應(yīng)該盡量保證frame的刷新頻率控制在16ms以內(nèi),所以這就要求在準(zhǔn)備frame的工作執(zhí)行不能超過(guò)16ms
造成啟動(dòng)速度慢的常見(jiàn)原因
- 在Application的onCreate里面做了太多的初始化操作,例如第三方庫(kù)的初始化,其實(shí)很多第三方庫(kù)并不是APP啟動(dòng)了就馬上需要初始化,我們完全可以用懶加載的方式,等用到了再去初始化也不遲
- 過(guò)于復(fù)雜的功能邏輯初始化操作,例如賬戶登陸需要去進(jìn)行網(wǎng)絡(luò)請(qǐng)求驗(yàn)證密碼,驗(yàn)證通過(guò)后再去服務(wù)器拉去一大串的賬戶數(shù)據(jù),然后再通過(guò)json解析,保存數(shù)據(jù)庫(kù),再刷新到界面,在APP啟動(dòng)的時(shí)候,我們可以先從數(shù)據(jù)庫(kù)去搜索數(shù)據(jù),把界面線顯示出來(lái),然后再去請(qǐng)求網(wǎng)絡(luò)更新數(shù)據(jù)
- Activity布局層次嵌套過(guò)深,xml布局嵌套過(guò)深灰導(dǎo)致加載這個(gè)布局的時(shí)長(zhǎng)加大,因?yàn)閤ml布局的繪制是不斷的遞歸遍歷到各個(gè)View的根結(jié)點(diǎn),保證扁平化的布局可以有效的縮短布局加載時(shí)間;使用ViewStub,因?yàn)閂iewStub只要你不調(diào)用inflat,它是不會(huì)去加載View的,在Activity啟動(dòng)后,并不是每一個(gè)View都需要馬上加載,有一些View根本是GONE,這些View完全可以用ViewStub來(lái)實(shí)現(xiàn),等用到的時(shí)候再去inflat即可;
- UI線程執(zhí)行太多耗時(shí)操作,數(shù)據(jù)庫(kù)操作,文件操作,開(kāi)過(guò)多的線程執(zhí)行(用RxJava很容易導(dǎo)致這個(gè)問(wèn)題),JSON解析,Bitmap的加載
- Measure/Layout took a significant time, contributing to jank. Avoid triggering layout during animations(避免在View執(zhí)行動(dòng)畫(huà)過(guò)程中出發(fā)View的layout,否則可能會(huì)造成卡頓)
- UI Thread的優(yōu)先級(jí)是默認(rèn)優(yōu)先級(jí),而new Thread的優(yōu)先級(jí)也是默認(rèn)的,所以要是有過(guò)多的工作線程可能會(huì)造成線程競(jìng)爭(zhēng),cpu可能掛起UI Thread而去執(zhí)行其他的work thread,所以work thread應(yīng)該設(shè)置為background的級(jí)別,降低線程競(jìng)爭(zhēng)的概率
- 在加載View的過(guò)程中不要同時(shí)去請(qǐng)求數(shù)據(jù)并更新到View上,在同一時(shí)刻做太多的事情也會(huì)導(dǎo)致cpu處理不過(guò)來(lái)而造成卡頓,我們可以等View加載完成之后采取請(qǐng)求數(shù)據(jù)更新,或者在Activity初始化好了之后再去做其他的數(shù)據(jù)更新操作(onWindowFocusChanged)
- 誤以為在Activity的onResume里面去做一些耗時(shí)操作可以優(yōu)化Activity的啟動(dòng),事實(shí)上Activity在執(zhí)行到onResume的時(shí)候它的初始化操作還沒(méi)有執(zhí)行完成呢,如果在這里面執(zhí)行耗時(shí)操作,不會(huì)有任何優(yōu)化效果,應(yīng)該在Activity第一次被focus的時(shí)候(onWindowFocusChanged),這時(shí)候Activity已經(jīng)是完全初始化好了,你可以試下在onWindowFocusChanged去獲取View的高度是可以獲取到的,但是在onResumen里面去獲取View的高度依然還是0
APP閃屏頁(yè)面實(shí)現(xiàn)
- 為了實(shí)現(xiàn)點(diǎn)擊秒開(kāi)的效果,我們往往會(huì)實(shí)現(xiàn)APP閃屏頁(yè)面,所謂的閃屏頁(yè)面就是一個(gè)不加載布局文件的Activity,但是可以設(shè)置它的theme里面的window background成啟動(dòng)歡迎頁(yè)面(圖片分辨率不要太大,否則加載時(shí)間會(huì)比較長(zhǎng)),這樣就能達(dá)到點(diǎn)擊app,馬上就能看到啟動(dòng)頁(yè)面,由于Activity不用setContentView,所以啟動(dòng)閃屏頁(yè)面的速度也很快,然后再由閃屏頁(yè)面跳轉(zhuǎn)到歡迎頁(yè)面,然后再進(jìn)入主界面,其實(shí)這樣綜合下來(lái),啟動(dòng)時(shí)間是變長(zhǎng)了,因?yàn)樵贏ctivity之間切換的時(shí)候要先pause上一個(gè)activity然后再create下一個(gè)Activity,這樣會(huì)增加一些耗時(shí),不過(guò)閃屏頁(yè)面給用戶的是點(diǎn)擊了立馬就啟動(dòng)APP的感覺(jué),所以即時(shí)啟動(dòng)總時(shí)長(zhǎng)多個(gè)兩三秒也是可以接受的
Activity的啟動(dòng)流程(具體要開(kāi)源碼)
- 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到這里一個(gè)Activity的啟動(dòng)流程就基本結(jié)束,太特么復(fù)雜了。。。
- 從Activity的啟動(dòng)流程來(lái)分析我們可以得知啟動(dòng)一個(gè)Activity需要去匹配到你要啟動(dòng)的Activity(匹配ResolveInfo),這里涉及到顯示啟動(dòng)和隱式啟動(dòng),顯示啟動(dòng)的話比較快,不用再去匹配Intent里面的IntentFilter;然后再監(jiān)測(cè)Activity所在的進(jìn)程是否有啟動(dòng),沒(méi)有啟動(dòng)的話就fock一個(gè)進(jìn)程出來(lái)接下去再做初始化Application并調(diào)用相關(guān)方法,例如onCreate,然后通過(guò)反射的方式創(chuàng)建Activity對(duì)象,再調(diào)用Activity的各個(gè)生命周期;所以APP的啟動(dòng)時(shí)間是包括APP進(jìn)程啟動(dòng)時(shí)長(zhǎng)(無(wú)法優(yōu)化),Application的執(zhí)行時(shí)間和Activity的執(zhí)行時(shí)間(這兩部分是可以優(yōu)化的),另外在啟動(dòng)Activity之前會(huì)設(shè)置Theme,這里可能也會(huì)造成耗時(shí),例如theme里面設(shè)置了一張分辨率較高的background會(huì)導(dǎo)致decode這張圖片的時(shí)間變長(zhǎng)


