黃鐘銳
摘 ?要:Android操作系統(tǒng)中應(yīng)用的Activity組件有4種啟動模式(Launch Mode)。根據(jù)不同的啟動模式,其Activity組件在Back stack內(nèi)的行為和活動的Activity之間跳轉(zhuǎn)的行為會有所區(qū)別。因此,其啟動模式會影響應(yīng)用的性能(這里的性能是基于電量消耗、CPU占用率和內(nèi)存使用得到的數(shù)據(jù))。在實驗時通過用馬爾可夫鏈來建立一個虛擬的應(yīng)用模型,對其中的啟動模式進行調(diào)整,記錄性能的狀態(tài)并對結(jié)果進行對比,在這之后,得出一個基于性能的應(yīng)用的Activity啟動模式的優(yōu)化建議。
關(guān)鍵詞:Android;啟動模式;性能測試
中圖分類號:TP31 ? ? ? ? ? ? ? ? ? 文獻標(biāo)志碼:A
0 引言
在通常的Android應(yīng)用程序當(dāng)中,許多的行為是依賴于Activity組件才能正常運行的,而這些Activity的加載和存儲會不可避免地占用時間和系統(tǒng)資源。實際上,在每個Activity組件的設(shè)置中都有對其啟動模式的調(diào)整,這些啟動模式的作用會在當(dāng)前Activity發(fā)生變化時體現(xiàn)出來——不同的啟動模式會影響B(tài)ack Stack中的Activity的信息保留情況和自動新建Back Stack的條件。如果啟動模式不做調(diào)整,隨著用戶啟動過的Activity的數(shù)量越來越多,Back stack中會有更多不活躍的Activity存儲,占用和消耗系統(tǒng)資源。因此,合理地使用和調(diào)整啟動模式有利于緩解Back stack的存儲壓力。因此,該文將會討論啟動模式的具體工作方式并基于馬爾可夫鏈設(shè)計的Android應(yīng)用模型來模擬這一過程。
1 啟動模式的介紹
在常見的Android應(yīng)用中,通常采用給Activity設(shè)置啟動模式的方式來控制其生成實例的具體行為以及在Back Stack中的儲存。
1.1 Standard(STD)
這是Android里默認(rèn)的啟動模式,在STD的模式下,新的Intent都會被放到Back Stack的棧頂。并且不會創(chuàng)建新的任務(wù)(Task),即保持在當(dāng)前的Back Stack中操作。這意味著,在該啟動模式下的Activity內(nèi)進行返回操作,系統(tǒng)會將Back stack頂層的Activity移除,也就是退回到生成這個Intent的Activity內(nèi)。
1.2 singleTop(STP)
如果某個Activity的啟動模式設(shè)置為singleTop,那么之后的操作也不會涉及創(chuàng)建新的Task和生成新的Back Stack。不過在將Activity存儲到Back Stack之前,會先檢查棧頂?shù)腁ctivity。如果這個Activity也是同樣的Activity的實例,那么接下來就不會創(chuàng)建新的Activity并放到棧頂,而是直接返回棧頂?shù)腁ctivity。這意味著不管在棧頂創(chuàng)建多少次一樣的singleTop類型的Activity,只要進行一次返回操作,Back stack的狀態(tài)就會倒回到這一系列的操作之前。在設(shè)置Intent的Flag值時,通過將Flag的值設(shè)置為FLAG_ACTIVITY_SINGLE_TOP即可得到和xml里聲明singleTop相同的行為。
1.3 singleTask(STK)
singleTask會使得該Back Stack中只存在一個該Intent的Activity——如果Back Stack中沒有與之相同的Intent,那么新的Activity會壓到到棧頂,反之,singleTask的Activity會使得Back Stack中具有相同Intent標(biāo)識的Activity以上的內(nèi)容都被從中銷毀。用這種機制來保證在一個Task中只有一個相同的實例。而如果在第一種情況下進行返回操作,也就是沒有其他相同Intent的Activity的情況下,那么Back Stack中仍然保留有原先的Activity,可以倒回到進行操作之前的狀態(tài)。而在第二種情況下,因為原先的Activity在Back Stack中已經(jīng)被銷毀,所以此時進行返回操作,會直接退出應(yīng)用,因為Back stack為空,所以在設(shè)置Intent的Flag值時,通過將Flag的值設(shè)置為FLAG_ACTIVITY_NEW_TASK即可得到和xml里聲明singleTask相同的行為。
1.4 singleInstance(SIT)
如果Activity設(shè)置成這種模式,那么新建的Activity會單獨占一個Task——同時新建一個僅僅包含新Activity的Back Stack。而任何新的Back Stack會維持在僅有一個Activity的狀態(tài)。但是,特殊的是,如果在singleinstance中的Activity進行返回操作,會導(dǎo)致Task也隨之切換——從新的Task退回原先的Task的棧頂。在遠(yuǎn)棧頂進行返回操作無法跳轉(zhuǎn)至新的Task上。
2 基于馬爾可夫鏈的多種啟動模式性能分析
2.1 設(shè)計思想
2.1.1 馬爾可夫鏈的引入
馬爾可夫鏈?zhǔn)侵笭顟B(tài)空間中一個狀態(tài)到另一個狀態(tài)的轉(zhuǎn)換的無記憶性的隨機過程。這一過程只與當(dāng)前的狀態(tài)有關(guān),和之前的過程沒有關(guān)聯(lián)。而狀態(tài)的轉(zhuǎn)移概率則是取決于當(dāng)前所處的狀態(tài)轉(zhuǎn)向其他狀態(tài)的概率。由時刻n轉(zhuǎn)向時刻n+1的跳轉(zhuǎn)概率表示方法為Pr(Xn+1=x|Xn=xn)其中Pr表示跳轉(zhuǎn)的概率,n與n+1表示第n時刻和第n+1時刻,X表示某時刻的狀態(tài),x為具體的狀態(tài)類型。 而更直觀的表示方法,如圖1所示,可以直接用有向圖在路徑上標(biāo)明概率的方式來表示馬爾可夫鏈。
類比到這里的模型中,在固定的時間內(nèi),對于每種狀態(tài),我們規(guī)定好用戶進行各種操作的可能性,方便之后進行模擬。而在數(shù)據(jù)結(jié)構(gòu)上,用一個xml文件來存儲跳轉(zhuǎn)的方向和概率,在讀入后轉(zhuǎn)換成用二維數(shù)組表示的鄰接矩陣。此時處理跳轉(zhuǎn)問題就可以像處理其他問題一樣來處理Activity之間的連接關(guān)系了。這里引入馬爾可夫鏈的意義不僅是在于實現(xiàn)一個跳轉(zhuǎn)的控制或者得到隨機的結(jié)論,而是希望能借助這個模型來模擬實際使用時的用戶行為。這里筆者首先選用了一組有向圖中所表示的數(shù)據(jù)來進行測試,接下來會有一組根據(jù)筆者使用社交軟件行為的數(shù)據(jù)模擬。
2.1.2 資源占用的表示和測量
使用的測量方式是使用Google的Android Profiler來進行應(yīng)用性能的測量。在應(yīng)用運行時,通過Android Profiler可以得到我們需要的性能指標(biāo)。而在這個例子中,我們測試的指標(biāo)是CPU使用、內(nèi)存占用和電量消耗。這樣的好處是監(jiān)控和測量消耗的資源是由電腦來提供的而不是手機端進行資源消耗,可以將關(guān)注點都放在應(yīng)用本身。
2.2 模型的構(gòu)建
在準(zhǔn)備好模擬的條件之后,就要做好具體模型環(huán)境的搭建。在每個Activity啟動時,讀取xml配置文件中的數(shù)據(jù),從中提取出數(shù)據(jù)。而在另一方面,每次Activity進行的時候都生成一個隨機數(shù),用來記錄之后要進行的動作。在這之后,根據(jù)xml數(shù)據(jù)進行對應(yīng)的動作。不同的Activity也對應(yīng)著不同的內(nèi)容和啟動模式以及跳轉(zhuǎn)概率。
3 測試與驗證
3.1 資源加載
如果沒有適量的資源放到應(yīng)用中進行加載,那么資源的消耗就會變得不是很清晰。因此在設(shè)計時,可以特意放一些組件和圖片音頻等資源來增加對資源加載的負(fù)荷,以此來模擬Activity在加載時需要進行的工作。
3.2 實驗環(huán)境
該次實驗的所有實驗環(huán)境采用的是Pixel 2XL的虛擬機,虛擬機是通過Android Virtual Device生成的。配置列表見表1。
3.3 基于馬爾可夫鏈的啟動模式性能分析
3.3.1 全部用TSD模式
觀察到的情況是在standard的情況下,如圖2所示。內(nèi)存的使用會出現(xiàn)一個逐漸上升的趨勢。這說明Back Stack中仍然存儲著這些資源,并且在創(chuàng)建和銷毀Activity前CPU使用率和電量的消耗也會出現(xiàn)一個小的極大值。
圖中從上至下依次為CPU使用率,內(nèi)存占用量,和電量消耗速度
3.3.2 混合采用
在這種情況下,內(nèi)容比較復(fù)雜,而且行為稍有混亂,如圖3所示,在cover的Activity啟動時,電量的消耗要比其他的Activity要高一些,而cover的啟動模式為singleTop。另一方面,CPU占用率方面,在nextcover的Activity啟動時CPU的占用率增量要比其余的2種情況明顯??梢哉J(rèn)為是在singleTask的啟動模式下對CPU的使用率更高。
3.4 限制
首先,在設(shè)計測試時有意忽略掉了singleInstance的情況。因為這種設(shè)計模式使用的情況較少,由于每次新建的Activity都會在新的Task中,singleInstance用戶的體驗是比較不符合習(xí)慣。第二,測試時沒有引入TaskAffinity這個變量,這意味著在測試環(huán)境內(nèi)不會出現(xiàn)包名不同的Activity在同一個Back Stack中的情況,所以在測試?yán)餂]有發(fā)揮出singleTask的全部功能其實際情況要遠(yuǎn)遠(yuǎn)復(fù)雜于用馬爾可夫鏈生成的簡單模型,包括動態(tài)加載的資源。該文只是提供了一種解決這類問題的思路。另一方面,后續(xù)還應(yīng)繼續(xù)對得到的數(shù)據(jù)進行更多定量的分析,該文只是對比了其峰值的大小,如果能有更嚴(yán)謹(jǐn)?shù)臄?shù)學(xué)方法分析這些數(shù)據(jù),一定能得到更多的信息。因此后續(xù)的研究應(yīng)當(dāng)著重于對實際情況的擬真和對獲取數(shù)據(jù)進行更詳細(xì)處理。
4 測試結(jié)果的分析和整理
在得到監(jiān)控數(shù)據(jù)之后,接下來的工作就是對數(shù)據(jù)進行進一步的分析和處理。這里通過Android Profiler來獲取了模擬程序運行的100 s內(nèi)其在各層Activity上的狀態(tài)信息。由于CPU的狀態(tài)和電量消耗實時發(fā)生變化,所以在收集數(shù)據(jù)的時候更要關(guān)注每一段的極值點,并將多次的測量數(shù)據(jù)進行處理和統(tǒng)計。為了保持?jǐn)?shù)據(jù)的直觀性和盡可能多的保留更多的信息,在進行統(tǒng)計時采用了箱型圖的方式保存數(shù)據(jù)。
Android Profiler 中沒給出電量消耗的縱軸單位,只是用了Light、Medium、High和Heavy來表示電量消耗的速度快慢,為了便于統(tǒng)計,我們以整個運行過程中電量消耗最快的那一點作為1個單位,這里記錄的為其余各時刻消耗電量和其所占的百分比。因為在電量消耗的最大值已經(jīng)達(dá)到了Heavy的級別,因此可以作為對電量消耗測量的一個“標(biāo)桿”來看待。
根據(jù)圖4與圖5中所呈現(xiàn)出的統(tǒng)計分析,從平均值來看,Standard的啟動模式對CPU的占用要少一些,而singleTop和singleTask的啟動模式在CPU占用上雖然平均值相差很小,但是singleTask對CPU的使用率表現(xiàn)更不穩(wěn)定。而在電量消耗方面,singleTask的表現(xiàn)非常好,平均值和中位數(shù)明顯低于另外2種,并且表現(xiàn)也比較穩(wěn)定。而Standard和singleTop的最低消耗表現(xiàn)相似,singleTop的電量消耗雖然總體表現(xiàn)更穩(wěn)定,但是最差情況非常糟糕。
5 結(jié)論
通過基于馬爾可夫鏈的隨機實驗測試,可以明顯的觀測到,不同的啟動模式對性能表現(xiàn)的影響是存在差異的。這也證實了實驗過程的有效性。在一個Activity從Back Stack被移除前,其內(nèi)存的占用是不會解除的,也就是說如果需要維持較低的內(nèi)存占用率,我們可以通過阻止新Activity產(chǎn)生以及摧毀Activity的方式來對內(nèi)存方面進行限制。而這需要通過活用singleTop和singleTask的方式來達(dá)到這一目的。從另一個角度考量,如果我們需要的是減少CPU的占用率,避免對Back stack進行復(fù)雜操作可以減少CPU的占用。這就應(yīng)當(dāng)在程序設(shè)計時減少singleTask和singleInstance的使用,使得應(yīng)用對棧的操作量減小至最低。
另外,由于啟動模式可以通過在Intent中設(shè)置Flag的值來控制,這就意味著我們可以在寫程序的時候通過設(shè)置參數(shù)的方式來根據(jù)我們的需要選擇對資源分配更有利的啟動模式。不過在設(shè)置啟動模式時還應(yīng)該考慮到用戶的體驗(如使用singleTask的時候可能會銷毀很多處于Back Stack中的Activity)。因此在選用時應(yīng)當(dāng)注意這點。而對于不是很確定的情況,可以通過調(diào)研等方式獲取用戶在使用應(yīng)用時的各種行為及發(fā)生頻率,通過構(gòu)建馬爾可夫鏈并在模型中監(jiān)控資源消耗的方式來確認(rèn)其是否合理。這給了開發(fā)者在選擇啟動模式時一個相對直觀并可行的新思路。
參考文獻
[1]Taolue Chen,Jinlong He,F(xiàn)u Song,ex al.Android Stack Machine[J].LNCS 10982,2018,pp.487–504.
[2]Android documentation[S/OL].https://developer.android.com/guide/components/tasks-and-back-stack.html?hl=zh-CN.
[3]Chuangang Ren,Yulong Zhang,Hui Xue,ex al.Towards Discovering and Understanding Task Hijacking in Android[R].24th USENIX Security Symposium.2015.
[4]Norris,James R.Markov chains[M].UK:Cambridge University Press,1998.