Android性能優(yōu)化總結(jié)

最近大半個(gè)月都在做app的優(yōu)化,最主要是從apk包大小、界面過(guò)度繪制、掉幀、內(nèi)存抖動(dòng)、主線程IO這幾個(gè)方面來(lái)入手的。相比開(kāi)發(fā)新功能,做優(yōu)化真的是更費(fèi)腦力和心神,因?yàn)橐苍S你做了大量的修改和優(yōu)化操作,能看見(jiàn)的效果卻微乎其微,但又不得不做。我想對(duì)于大多數(shù)Android開(kāi)發(fā)者來(lái)說(shuō),開(kāi)發(fā)出一款app并不難,但是開(kāi)發(fā)出一款高性能體驗(yàn)棒的app卻并不是每個(gè)開(kāi)發(fā)者都能做到的。在開(kāi)發(fā)過(guò)程中,可能會(huì)因?yàn)楦鞣N各樣的原因會(huì)使你開(kāi)發(fā)出來(lái)的app性能不佳,體驗(yàn)很差,開(kāi)發(fā)者寫(xiě)代碼的水平肯定是最重要的因素,另外還有一些第三方SDK也可以能會(huì)引起你的app出現(xiàn)問(wèn)題。

今天抽空把這大半個(gè)月以來(lái)優(yōu)化的過(guò)程和心得記錄一下。

apk包大小

無(wú)論是個(gè)人開(kāi)發(fā)者自己開(kāi)發(fā)的產(chǎn)品還是公司開(kāi)發(fā)的產(chǎn)品,盡可能減小apk包的大小可以大大提高app的下載轉(zhuǎn)化率,所以優(yōu)化過(guò)程中apk體積是開(kāi)發(fā)者必須要注意的一點(diǎn)。我們先看看apk包是由哪些部分組成的。我們可以使用Android Studio自帶的功能Build->Analyze APK,下圖是微信的apk組成:

wechat_apk
  • lib 存放app所需的native庫(kù)文件
  • classes.dex 開(kāi)發(fā)者編寫(xiě)的java文件最后都會(huì)轉(zhuǎn)化成dex文件運(yùn)行在Android虛擬機(jī)上
  • assets 存放需要保持原始文件的資源文件
  • res 存放所有的資源文件
  • resource.arsc 所有資源文件的id映射
  • META-INF 簽名校驗(yàn)文件
  • AndroldManifest.xml Android應(yīng)用全局配置文件
  • 其它一些配置文件和第三方庫(kù)生成的文件

刪除無(wú)用資源

首先我從資源文件入手。我們可以使用Android Studio中的工具搜索項(xiàng)目中沒(méi)有使用的的資源文件:

run-inspection-by-name

然后通過(guò)unused resources這個(gè)功能來(lái)查找:

unused-resources

查找完成后會(huì)把整個(gè)項(xiàng)目中未被使用的資源列出來(lái),但是這個(gè)清理的時(shí)候我們需要注意下,有些資源文件可能是在你的項(xiàng)目中有用到的,但是資源id未出現(xiàn)在項(xiàng)目中的,比如你在代碼中是通過(guò)資源名來(lái)獲取這個(gè)資源而不是通過(guò)資源id,這種情況下的資源就不能刪了,否則就會(huì)出錯(cuò)。另外一些第三方庫(kù)的資源文件也要小心誤刪。

關(guān)于保留幾種屏幕分辨率的資源文件

隨著目前手機(jī)市場(chǎng)的發(fā)展,屏幕越來(lái)越大,分辨率越來(lái)越高,但是基于成本的考慮,仍然還有一些相對(duì)較低的分辨率的機(jī)型,而我們的app是適配所有分辨率的機(jī)型,還是針對(duì)一些主流的機(jī)型做適配,這取決于我們app所適用的人群范圍。我們UI設(shè)計(jì)師一般是設(shè)計(jì)xxhdpi(480dpi)和xhdpi(320dpi)兩套,基本上可以滿足絕大部分的手機(jī)屏幕了,如果你的app對(duì)apk體積有極高的要求,那么你也可以只選擇一套分辨率的素材。

資源壓縮

圖片壓縮:一般UI給我們的圖片都可以壓縮,我一般采用tinypng在線壓縮,支持png/jpg格式,為了避免失真,不要對(duì)同一張圖片壓縮多次。如果是Mac系統(tǒng)下,還可以選擇ImageOptim。

使用SVG圖片:SVG圖片即矢量圖,簡(jiǎn)單的說(shuō),就是縮放不失真的圖像格式。使用矢量圖的好處有很多,可被非常多的工具讀取和修改(比如記事本)。SVG與JPEG和GIF圖像比起來(lái),尺寸更小,且可壓縮性更強(qiáng)。SVG是可伸縮的,SVG圖像可在任何的分辨率下被高質(zhì)量地打印,SVG可在圖像質(zhì)量不下降的情況下被放大,SVG圖像中的文本是可選的,同時(shí)也是可搜索的(很適合制作地圖),SVG可以與Java技術(shù)一起運(yùn)行,SVG文件是純粹的XML。使用Android Studio也可以將下載的svg格式的矢量圖轉(zhuǎn)化為xml格式。比如我一般在阿里的Iconfont上下載一些簡(jiǎn)單的圖標(biāo),它可以選擇svg格式下載,然后在Android Studio的drawable文件夾右鍵NEW->Vector Asset:

然后會(huì)進(jìn)到下面這個(gè)界面:

其中Asset Type類型中Material Icon是通過(guò)系統(tǒng)自帶的一些符合Material Design設(shè)計(jì)的icon來(lái)制作,Local file就是通過(guò)我們自己下載的svg文件來(lái)制作,完成后svg格式的文件就轉(zhuǎn)化成xml格式保存在我們的目標(biāo)文件夾里了。當(dāng)然普通的ImageView是無(wú)法使用矢量圖的,必須使用v7包下的AppCompatImageView,而且不能使用src屬性,必須是app:srcCompat屬性才可以。

用jpg代替png:因?yàn)閖pg沒(méi)有alpha通道,所以文件更小,比較適合于不需要透明度的圖片。

用shape、color來(lái)代替圖片:如果是漸變背景或者純顏色的控件背景,都可以使用shape或者color做背景,一個(gè)xml文件相比一張位圖要小得多。

使用混淆

混淆除了代碼壓縮代碼混淆的功能還有資源壓縮的作用,在app module的build.gradle文件中配置shrinkResources true和minifyEnabled true這兩個(gè)參數(shù),就可以達(dá)到混淆壓縮的目的。關(guān)于混淆,參看這篇文章 Android代碼混淆與進(jìn)階

native庫(kù)文件

native庫(kù)都是為了支持不同架構(gòu)的CPU,雖然native庫(kù)文件是占整個(gè)apk體積最大的部分,減少對(duì)其中一些CPU架構(gòu)的支持可以很直觀的看到變化。關(guān)于so文件的知識(shí)和它的適配,可以看看下面幾篇文章,會(huì)有很大幫助。

過(guò)度繪制

首先我們得知道,過(guò)度繪制這個(gè)概念,到底什么是過(guò)渡繪制?UI過(guò)度繪制簡(jiǎn)單的來(lái)說(shuō)是指在一個(gè)界面中有很多元素,但是我們只需要更新某一小塊的元素,app卻把所有的元素都刷新一遍,這就造成過(guò)度繪制。過(guò)度繪制會(huì)造成GPU資源浪費(fèi),引起我們的app頁(yè)面卡頓,掉幀現(xiàn)象。

overdraw_options_view

上圖是UI界面的過(guò)度繪制的情況,不同的顏色代表被過(guò)度繪制的次數(shù),開(kāi)發(fā)者可以在手機(jī)設(shè)置的開(kāi)發(fā)者選項(xiàng)中將“調(diào)式過(guò)度繪制”選項(xiàng)開(kāi)啟,然后就可以看到界面上顯示不同的顏色了。我們優(yōu)化的目的就是盡可能讓界面上只看到藍(lán)色或綠色,盡量不要出現(xiàn)大量的紅色。

怎么做才能減少紅色部分?盡量將我們的layout布局層次簡(jiǎn)單化,移除Window默認(rèn)的background,移除layout中非必須的background等,UI布局層次不應(yīng)該過(guò)多,否則系統(tǒng)繪制UI時(shí)會(huì)占用較多時(shí)間,引起掉幀。

更詳細(xì)的優(yōu)化方法請(qǐng)看

內(nèi)存抖動(dòng)及優(yōu)化

內(nèi)存抖動(dòng)是因?yàn)樵诙虝r(shí)間內(nèi)大量的對(duì)象被創(chuàng)建又馬上被釋放。瞬間產(chǎn)生大量的對(duì)象會(huì)嚴(yán)重占用Young Generation的內(nèi)存區(qū)域,當(dāng)達(dá)到閥值,剩余空間不夠的時(shí)候,會(huì)觸發(fā)GC從而導(dǎo)致剛產(chǎn)生的對(duì)象又很快被回收。即使每次分配的對(duì)象占用了很少的內(nèi)存,但是他們疊加在一起會(huì)增加Heap的壓力,從而觸發(fā)更多其他類型的GC。這個(gè)操作有可能會(huì)影響到幀率,并使得用戶感知到性能問(wèn)題。如果你在Memory Monitor里面查看到短時(shí)間發(fā)生了多次內(nèi)存的漲跌,這意味著很有可能發(fā)生了內(nèi)存抖動(dòng)。同時(shí)我們還可以通過(guò)Allocation Tracker來(lái)查看在短時(shí)間內(nèi),同一個(gè)棧中不斷進(jìn)出的相同對(duì)象。這是內(nèi)存抖動(dòng)的典型信號(hào)之一。當(dāng)你大致定位問(wèn)題之后,接下去的問(wèn)題修復(fù)也就顯得相對(duì)直接簡(jiǎn)單了。例如,你需要避免在for循環(huán)里面分配對(duì)象占用內(nèi)存,需要嘗試把對(duì)象的創(chuàng)建移到循環(huán)體之外,自定義View中的onDraw方法也需要引起注意,每次屏幕發(fā)生繪制以及動(dòng)畫(huà)執(zhí)行過(guò)程中,onDraw方法都會(huì)被調(diào)用到,避免在onDraw方法里面執(zhí)行復(fù)雜的操作,避免創(chuàng)建對(duì)象。對(duì)于那些無(wú)法避免需要?jiǎng)?chuàng)建對(duì)象的情況,我們可以考慮對(duì)象池模型,通過(guò)對(duì)象池來(lái)解決頻繁創(chuàng)建與銷毀的問(wèn)題,但是這里需要注意結(jié)束使用之后,需要手動(dòng)釋放對(duì)象池中的對(duì)象。

啟動(dòng)時(shí)間(冷啟動(dòng),暖啟動(dòng),熱啟動(dòng))

app的啟動(dòng)分為三種狀態(tài),冷啟動(dòng)即應(yīng)用從零開(kāi)始加載運(yùn)行,而其它狀態(tài)則是應(yīng)用從后臺(tái)運(yùn)行回到前臺(tái)運(yùn)行。

冷啟動(dòng)狀態(tài):系統(tǒng)不存在該應(yīng)用的進(jìn)程,啟動(dòng)應(yīng)用才創(chuàng)建出應(yīng)用的進(jìn)程。冷啟動(dòng)一般指的就是應(yīng)用在開(kāi)機(jī)后或者被系統(tǒng)停止后的第一次啟動(dòng)過(guò)程。因?yàn)橄到y(tǒng)和應(yīng)用在冷啟動(dòng)時(shí)需要做更多的工作,所以減少它的啟動(dòng)時(shí)間的難度是最大的。

冷啟動(dòng)初始時(shí),系統(tǒng)完成三個(gè)任務(wù):

  • 啟動(dòng)和加載應(yīng)用(這里泛指的是應(yīng)用本身)
  • 創(chuàng)建應(yīng)用的專屬進(jìn)程
  • 啟動(dòng)后立刻顯示啟動(dòng)視圖(通常是個(gè)空白屏)

一旦系統(tǒng)創(chuàng)建了應(yīng)用的專屬進(jìn)程,該進(jìn)程開(kāi)始創(chuàng)建應(yīng)用:

  • 創(chuàng)建應(yīng)用對(duì)象
  • 啟動(dòng)主線程 (MainThread)
  • 創(chuàng)建 Launcher Activity
  • 加載視圖 (Inflating views)
  • 渲染布局 (Laying out)
  • 執(zhí)行初始繪制

當(dāng)應(yīng)用完成了第一次繪制,系統(tǒng)進(jìn)程就把當(dāng)前顯示的啟動(dòng)視圖切換為應(yīng)用界面,用戶就可以使用應(yīng)用了。

應(yīng)用進(jìn)程的啟動(dòng)流程可以分為:Application的創(chuàng)建和Launcher Activity的創(chuàng)建,Application的創(chuàng)建是從Application.onCreate()開(kāi)始的,重寫(xiě)onCreate()方法通常我們會(huì)在這里完成一些通用組件和第三方SDK的初始化操作。接著應(yīng)用程序生成主線程,并開(kāi)始創(chuàng)建Launcher Activity。創(chuàng)建Activity的過(guò)程分為初始化、調(diào)用構(gòu)造方法、調(diào)用當(dāng)前生命周期的回調(diào)方法。通常onCreate()方法對(duì)加載時(shí)間的影響最大,因?yàn)樗獔?zhí)行加載、渲染和初始化Activity所需要的對(duì)象等開(kāi)銷最大的任務(wù),如果Activity的布局過(guò)于復(fù)雜,那么就很可能會(huì)導(dǎo)致啟動(dòng)性能問(wèn)題。

暖啟動(dòng)狀態(tài):應(yīng)用程序的暖啟動(dòng)與冷啟動(dòng)類似,但比冷啟動(dòng)開(kāi)銷低。在暖啟動(dòng)中,系統(tǒng)只需要把 Activity 切換到前臺(tái)運(yùn)行。如果應(yīng)用的該 Activity 之前駐留在內(nèi)存中,那么應(yīng)用程序就不用重新初始化對(duì)象和渲染布局。但是,如果由于響應(yīng)了低內(nèi)存事件,例如在 onTrimMemory() 方法中清除了資源對(duì)象,那么這些對(duì)象就需要在熱啟動(dòng)時(shí)重新創(chuàng)建。

熱啟動(dòng)狀態(tài):熱啟動(dòng)為冷啟動(dòng)的過(guò)程操作的子集,而且開(kāi)銷比暖啟動(dòng)稍小。以下這些情況可以認(rèn)為是熱啟動(dòng):

  • 用戶退出應(yīng)用,但隨后重新啟動(dòng)它。應(yīng)用的進(jìn)程還在運(yùn)行,但應(yīng)用必須重新從 onCreate() 開(kāi)始創(chuàng)建 Activity。
  • 系統(tǒng)從內(nèi)存中清除了應(yīng)用(非用戶主動(dòng)),然后用戶重新啟動(dòng)它。進(jìn)程和 Activity 需要重新啟動(dòng),但 onCreate() 將接收到保存狀態(tài)的 Bundle。事實(shí)上,savedInstanceState 在用戶未主動(dòng)銷毀 Activity 時(shí)系統(tǒng)就會(huì)調(diào)用。

啟動(dòng)時(shí)間的優(yōu)化:

一般我們優(yōu)化啟動(dòng)時(shí)間都是基于冷啟動(dòng)時(shí)間的,所以主要是創(chuàng)建應(yīng)用和創(chuàng)建Activity這兩部分所花時(shí)間的優(yōu)化,在Application.onCreate()方法中,要盡量避免創(chuàng)建太多臨時(shí)變量、執(zhí)行IO操作、以及反序列化操作、多重for循環(huán)等。而在創(chuàng)建Launcher Activity過(guò)程中,布局視圖要盡量層次簡(jiǎn)單,讓繪制過(guò)程盡量短,加載大量資源時(shí)可以選擇懶加載方式。

關(guān)于啟動(dòng)視圖:

之前說(shuō)過(guò)當(dāng)應(yīng)用啟動(dòng)后而且Launcher Activity的布局還沒(méi)有被渲染完成時(shí),會(huì)出現(xiàn)一個(gè)空白屏,如果Launcher Activity的布局非常復(fù)雜,渲染消耗的時(shí)間很長(zhǎng),那用戶可能會(huì)感知到這個(gè)空白屏的存在,體驗(yàn)上來(lái)說(shuō),是很不友好的,那么怎么解決這個(gè)問(wèn)題呢?我們可以使用windowBackground這個(gè)屬性,一般我們會(huì)設(shè)置成一張圖片,然后在AndroidManifest.xml中的application節(jié)點(diǎn)的Theme中加上這個(gè)屬性。這樣做之后,當(dāng)應(yīng)用被點(diǎn)擊后立馬就可以看到這張圖片,而不是一個(gè)空白屏了。

主線程IO

如果在主線程中有讀寫(xiě)文件等這些耗時(shí)的操作,就會(huì)阻塞主線程,影響app的性能。所以在主線程中,不要進(jìn)行IO操作,也不要進(jìn)行for循環(huán)創(chuàng)建大量的對(duì)象。如果必須要操作IO,也要放在子線程中或者Service中進(jìn)行,以保證主線程不會(huì)被阻塞。

64K 引用限制

關(guān)于64k引用限制,官方的說(shuō)明文檔:配置方法數(shù)超過(guò) 64K 的應(yīng)用,規(guī)避64k限制的辦法就是采用Dalvik可執(zhí)行文件分包。具體的配置方法請(qǐng)看文檔說(shuō)明。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,578評(píng)論 6 544
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,701評(píng)論 3 429
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事?!?“怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 178,691評(píng)論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 63,974評(píng)論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,694評(píng)論 6 413
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 56,026評(píng)論 1 329
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,015評(píng)論 3 450
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 43,193評(píng)論 0 290
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,719評(píng)論 1 336
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,442評(píng)論 3 360
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,668評(píng)論 1 374
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,151評(píng)論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,846評(píng)論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 35,255評(píng)論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 36,592評(píng)論 1 295
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 52,394評(píng)論 3 400
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,635評(píng)論 2 380

推薦閱讀更多精彩內(nèi)容