性能優(yōu)化的目的
在不斷地迭代開發(fā)過程中,我們的應(yīng)用功能會越來越復(fù)雜,代碼量也會不斷增加设拟。再加上偶爾的重構(gòu)、人員的變更等等原因久脯,我們曾經(jīng)那個(gè)如絲順滑的項(xiàng)目也會漸漸變得卡頓蒜绽。
那么這個(gè)時(shí)候,就不得不提性能優(yōu)化這個(gè)話題了桶现。正好這段時(shí)間有空躲雅,就整理了一下常見的性能優(yōu)化的幾個(gè)方面以及各個(gè)方面的注意事項(xiàng)。一來是給自己腦中的知識做個(gè)梳理骡和,加深下記憶相赁,二來也能給一些萌新提供點(diǎn)思路。
內(nèi)存優(yōu)化
內(nèi)存優(yōu)化慰于,可以說是性能優(yōu)化中最重要的一部分內(nèi)容了钮科。如果應(yīng)用占用內(nèi)存過大,輕則應(yīng)用卡頓降低用戶體驗(yàn)婆赠,重則內(nèi)存溢出(OOM)绵脯,程序崩潰。所以內(nèi)存優(yōu)化很重要,接下來我們從一下幾個(gè)方面來進(jìn)行講解
android的內(nèi)存管理
-
垃圾回收
android
的垃圾回收和java
的垃圾回收一直蛆挫,一旦確定程序不再使用內(nèi)存赃承,便將其釋放,而無需人為干預(yù)悴侵。垃圾回收有兩個(gè)步驟:在程序中產(chǎn)兆將來無法訪問的數(shù)據(jù)對象瞧剖;將那些對象的資源釋放并回收。
-
內(nèi)存管理
android
作為一個(gè)多任務(wù)的操作系統(tǒng)可免,為了維持系統(tǒng)功能正常抓于,會對每個(gè)應(yīng)用程序的內(nèi)存大小做出硬性限制。具體的數(shù)值和機(jī)型以及內(nèi)存有關(guān)浇借。如果應(yīng)用內(nèi)存打到限制后還要申請內(nèi)存捉撮,就會引發(fā)OutOfMemoryError
。如果在開發(fā)中有獲取剩余內(nèi)存的需求妇垢,可以調(diào)用
getMemoryClass()
方法呕缭。用來在內(nèi)存快滿時(shí)及時(shí)回收一些不必要的東西。
android內(nèi)存監(jiān)測
-
LeakCanary 詳情
一個(gè)可以檢測程序在運(yùn)行過程中發(fā)生的內(nèi)存泄漏問題修己,通過簡單的代碼配置恢总,可以方便的找出我們應(yīng)用中的內(nèi)存問題
-
Memory Monitor
android studio
自帶的實(shí)時(shí)內(nèi)存分析工具,我們可以通過實(shí)時(shí)的內(nèi)存睬愤、CPU等的波動(dòng)來分析問題片仿,如果某個(gè)頁面反復(fù)進(jìn)入后內(nèi)存持續(xù)增長,我們就要注意了尤辱。[圖片上傳失敗...(image-211493-1570784432908)]
-
Heap Viewer 詳情
也是
android studio
中可以直接使用的內(nèi)存分析工具砂豌,需要android
系統(tǒng)在5.0以上并保持開發(fā)者選項(xiàng)可用。具體使用情況請點(diǎn)擊詳情光督。 -
Allocation Tracker 詳情
可以追蹤內(nèi)存分配信息阳距,按順序排列,這樣我們就可以清晰看出某一個(gè)操作的內(nèi)存是如何一步步分配出來的结借,從而進(jìn)一步找出發(fā)生問題的代碼筐摘。
更多性能測試工具看這里
內(nèi)存優(yōu)化方案
-
界面不可見時(shí)及時(shí)回收部分內(nèi)存
當(dāng)用戶打開了另外一個(gè)程序,我們的程序界面已經(jīng)不再可見的時(shí)候船老,我們應(yīng)當(dāng)將所有和界面相關(guān)的資源進(jìn)行釋放咖熟。在這種場景下釋放資源可以讓系統(tǒng)緩存后臺進(jìn)程的能力顯著增加,因此也會讓用戶體驗(yàn)變得更好柳畔。
檢測界面是否可見我們可以重寫如下方法:
@Override public void onTrimMemory(int level) { super.onTrimMemory(level); switch (level) { case TRIM_MEMORY_UI_HIDDEN: // 進(jìn)行資源釋放操作 break; } }
onTrimMemory
方法只有當(dāng)一個(gè)Activity
完全不可見時(shí)候才會調(diào)用馍管,這和onStop()
方法還是有很大區(qū)別的,因?yàn)?code>onStop()方法只是當(dāng)一個(gè)Activity
完全不可見的時(shí)候就會調(diào)用薪韩,比如說用戶打開了我們程序中的另一個(gè)Activity
确沸。因此捌锭,我們可以在
onStop()
方法中去釋放一些Activity相關(guān)的資源,比如說取消網(wǎng)絡(luò)連接或者注銷廣播接收器等罗捎,但是像UI相關(guān)的資源應(yīng)該一直要等到onTrimMemory(TRIM_MEMORY_UI_HIDDEN)
這個(gè)回調(diào)之后才去釋放观谦,這樣可以保證如果用戶只是從我們程序的一個(gè)Activity
回到了另外一個(gè)Activity
,界面相關(guān)的資源都不需要重新加載宛逗,從而提升響應(yīng)速度坎匿。 -
使用Handler時(shí)盡量弱引用
我們經(jīng)常會在
handler
中進(jìn)行一些延時(shí)任務(wù)盾剩,這些延時(shí)任務(wù)會導(dǎo)致Activity
被引用雷激,從而發(fā)生內(nèi)存泄漏,為了避免這類事情發(fā)生告私,我們可以對handler
使用弱引用屎暇。public class MainActivity extends AppCompatActivity { public static final String TAG = "carson:"; private Handler showhandler; // 主線程創(chuàng)建時(shí)便自動(dòng)創(chuàng)建Looper & 對應(yīng)的MessageQueue // 之后執(zhí)行Loop()進(jìn)入消息循環(huán) @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); //1. 實(shí)例化自定義的Handler類對象->>分析1 //注: // a. 此處并無指定Looper,故自動(dòng)綁定當(dāng)前線程(主線程)的Looper驻粟、MessageQueue根悼; // b. 定義時(shí)需傳入持有的Activity實(shí)例(弱引用) showhandler = new FHandler(this); // 2. 啟動(dòng)子線程1 new Thread() { @Override public void run() { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } // a. 定義要發(fā)送的消息 Message msg = Message.obtain(); msg.what = 1;// 消息標(biāo)識 msg.obj = "AA";// 消息存放 // b. 傳入主線程的Handler & 向其MessageQueue發(fā)送消息 showhandler.sendMessage(msg); } }.start(); // 3. 啟動(dòng)子線程2 new Thread() { @Override public void run() { try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } // a. 定義要發(fā)送的消息 Message msg = Message.obtain(); msg.what = 2;// 消息標(biāo)識 msg.obj = "BB";// 消息存放 // b. 傳入主線程的Handler & 向其MessageQueue發(fā)送消息 showhandler.sendMessage(msg); } }.start(); } // 分析1:自定義Handler子類 // 設(shè)置為:靜態(tài)內(nèi)部類 private static class FHandler extends Handler{ // 定義 弱引用實(shí)例 private WeakReference<Activity> reference; // 在構(gòu)造方法中傳入需持有的Activity實(shí)例 public FHandler(Activity activity) { // 使用WeakReference弱引用持有Activity實(shí)例 reference = new WeakReference<Activity>(activity); } // 通過復(fù)寫handlerMessage() 從而確定更新UI的操作 @Override public void handleMessage(Message msg) { switch (msg.what) { case 1: Log.d(TAG, "收到線程1的消息"); break; case 2: Log.d(TAG, " 收到線程2的消息"); break; } } } }
這樣一來,
handler
中發(fā)送延時(shí)消息便不會發(fā)生內(nèi)存泄漏了蜀撑。當(dāng)然避免
handler
內(nèi)存泄漏還可以采取當(dāng)Activity
結(jié)束使用時(shí)候挤巡,清空消息隊(duì)列的操作,如下:@Override protected void onDestroy() { super.onDestroy(); mHandler.removeCallbacksAndMessages(null); // 外部類Activity生命周期結(jié)束時(shí)酷麦,同時(shí)清空消息隊(duì)列 & 結(jié)束Handler生命周期 }
-
加載圖片的注意事項(xiàng) 詳情
- 不在小控件上顯示大圖
- 列表類圖片矿卑,僅加載當(dāng)前頁面可見的圖片
- 有顯示原圖需求時(shí),要判斷可用內(nèi)存沃饶,內(nèi)存不夠時(shí)要壓縮圖片
-
避免內(nèi)存抖動(dòng) 詳情
盡量避免在循環(huán)體內(nèi)創(chuàng)建對象母廷,應(yīng)該把對象創(chuàng)建移到循環(huán)體外。
注意自定義View的onDraw()方法會被頻繁調(diào)用糊肤,所以在這里面不應(yīng)該頻繁的創(chuàng)建對象琴昆。
當(dāng)需要大量使用Bitmap的時(shí)候,試著把它們緩存在數(shù)組中實(shí)現(xiàn)復(fù)用馆揉。
對于能夠復(fù)用的對象业舍,同理可以使用對象池將它們緩存起來。
布局優(yōu)化 詳情
避免過度繪制
-
什么是過度繪制
過度繪制就是在同一個(gè)位置升酣,有多次的顏色繪制過程勤讽。常見的情況就是在同一個(gè)位置堆疊了許多控件,這會造成一些性能問題拗踢,嚴(yán)重的情況會造成卡頓脚牍。
-
如何檢測過度繪制
開發(fā)者選項(xiàng)->調(diào)試GPU過度繪制->顯示過度繪制區(qū)域
-
過度繪制優(yōu)化
- 移除控件中不需要的背景
- 將layout層級扁平化
- 減少透明度的使用
- 自定義
View
中減少重復(fù)繪制區(qū)域
布局優(yōu)化技巧
- 簡單布局優(yōu)先使用
LinearLayout
或FragmentLayout
- 復(fù)雜布局優(yōu)先使用
constrainLayout
- 使用
include
標(biāo)簽提高復(fù)用性 - 使用
ViewStub
標(biāo)簽延遲加載 -
onDraw()
中不要?jiǎng)?chuàng)建新的局部變量以及不要做耗時(shí)操作
網(wǎng)絡(luò)優(yōu)化 詳情
為什么要網(wǎng)絡(luò)優(yōu)化
- 過多的無用網(wǎng)絡(luò)請求,會消耗用戶的網(wǎng)絡(luò)流量巢墅。流量消耗過大會流失用戶
- 頻繁的網(wǎng)絡(luò)操作會導(dǎo)致設(shè)備用電量提升
- 網(wǎng)絡(luò)彈框的頻繁出現(xiàn)會降低用戶體驗(yàn)
- 應(yīng)用更新诸狭、大文件下載等場景券膀,更優(yōu)的網(wǎng)絡(luò)傳輸速度可提升用戶體驗(yàn)
網(wǎng)絡(luò)優(yōu)化的方式
- 使用
GZip
壓縮,數(shù)據(jù)壓縮后可以減少流量的消耗驯遇,減少傳輸?shù)臅r(shí)間 - 使用IP直連芹彬,DNS域名解析是一個(gè)較為耗時(shí)操作,可以直連IP減少解析時(shí)間
- 圖片加載
- 使用
WebP
格式可以大幅節(jié)省流量 - 圖片按需加載叉庐,列表中圖片只加載縮略圖
- 大圖上傳時(shí)舒帮,采用分片傳輸,失敗只傳對應(yīng)片段
- 用戶體驗(yàn)影響不大時(shí)陡叠,手機(jī)原圖壓縮后再傳輸
- 使用
- 減少接口數(shù)量玩郊,同一個(gè)頁面盡量只使用一個(gè)接口办斑,數(shù)據(jù)可以放到后臺去拼湊
- 利用緩存赃绊,對數(shù)據(jù)設(shè)定有效期,有效期內(nèi)數(shù)據(jù)不重復(fù)請求
- 檢測網(wǎng)絡(luò)狀態(tài)铃将,不同網(wǎng)絡(luò)轉(zhuǎn)態(tài)執(zhí)行不同策略兴溜,例如移動(dòng)網(wǎng)絡(luò)不加載圖片侦厚,2G網(wǎng)絡(luò)只加載標(biāo)題等。
- 文件上傳拙徽、下載采用斷點(diǎn)續(xù)傳刨沦,不浪費(fèi)已傳輸完成部分流量
- 利用抓包工具模擬多種情況,在實(shí)踐中調(diào)整不斷優(yōu)化用戶體驗(yàn)
啟動(dòng)優(yōu)化 詳情
閃屏頁優(yōu)化
主流APP
是在應(yīng)用啟動(dòng)時(shí)候會加載一個(gè)默認(rèn)的主題膘怕,用來去掉應(yīng)用啟動(dòng)時(shí)候的黑/白屏的情況
<style name="AppThemeWelcome" parent="Theme.AppCompat.NoActionBar">
...
<item name="android:windowBackground">@drawable/logo</item> <!-- 默認(rèn)背景-->
</style>
應(yīng)用主題到Application
或Activity
<activity android:name=".ui.activity.DemoSplashActivity"
android:configChanges="orientation|screenSize|keyboardHidden"
android:theme="@style/AppThemeWelcome"
android:screenOrientation="portrait">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
其實(shí)就是個(gè)障眼法而已想诅,提前讓你看到了假的界面。也算是一種不錯(cuò)的方法淳蔼,但是治標(biāo)不治本侧蘸。
第三方庫懶加載
在開發(fā)中會用到很多的三方庫,如友盟鹉梨、百度讳癌、bugly
、圖片庫存皂、網(wǎng)絡(luò)庫等晌坤。
這些都是必須的,不能去掉旦袋,那么辦法就是異步加載了骤菠,可以有以下幾種思路
像友盟,bugly這樣的業(yè)務(wù)非必要的可以的異步加載疤孕。
比如地圖商乎,推送等,非第一時(shí)間需要的可以在主線程做延時(shí)啟動(dòng)祭阀。當(dāng)程序已經(jīng)啟動(dòng)起來之后鹉戚,在進(jìn)行初始化鲜戒。
對于圖片,網(wǎng)絡(luò)請求框架必須在主線程里初始化了抹凳。
我們一般會有閃屏頁面遏餐,也可以把延時(shí)啟動(dòng)的地圖、推送的啟動(dòng)放到這個(gè)頁面
按照以上方式處理后赢底,還可以進(jìn)一步降低應(yīng)用啟動(dòng)時(shí)間失都。
WebView
啟動(dòng)優(yōu)化
- WebView第一次創(chuàng)建比較耗時(shí),可以預(yù)先創(chuàng)建WebView幸冻,提前將其內(nèi)核初始化粹庞。
- 使用WebView緩存池,用到WebView的地方都從緩存池取嘁扼,緩存池中沒有緩存再創(chuàng)建信粮,注意內(nèi)存泄漏問題黔攒。
- 本地預(yù)置html和css趁啸,WebView創(chuàng)建的時(shí)候先預(yù)加載本地html,之后通過js腳本填充內(nèi)容部分督惰。
數(shù)據(jù)項(xiàng)預(yù)加載
主頁數(shù)據(jù)變化不大時(shí)候不傅,可以再第一次啟動(dòng)后,緩存主頁數(shù)據(jù)到本地赏胚,下次啟動(dòng)先讀取本地?cái)?shù)據(jù)访娶,頁面完全顯示后再去請求新數(shù)據(jù)進(jìn)行增量更新。
安裝包體積優(yōu)化
體積優(yōu)化的必要性
安裝包體積是用戶搜索應(yīng)用后能第一眼看到的數(shù)據(jù)觉阅,雖然現(xiàn)在的應(yīng)用體積越來越大崖疤,但小體積的App
依舊是很多存儲空間緊張用戶的痛點(diǎn)。所以減少安裝包體積是性能優(yōu)化方面必不可少的一步典勇。
減少應(yīng)用體積的N種辦法
使用
lint
工具刪除無用的資源簡單的切圖盡量替換為
shape
類型的xml
文件形狀一致的圖片只使用一個(gè)切圖劫哼,比如方向不同的箭頭、圖像相同著色不同的切圖等
對圖片進(jìn)行壓縮割笙,優(yōu)先使用
WebP
格式圖像使用矢量圖(.9)圖來實(shí)現(xiàn)大小可變的背景圖
代碼混淆权烧,使用
proGuard
代碼混淆器工具,它包括壓縮伤溉、優(yōu)化般码、混淆等功能。插件化乱顾,不需要的部分可以存在服務(wù)器板祝,當(dāng)用到時(shí)候動(dòng)態(tài)下載。
電量優(yōu)化
電量優(yōu)化我放到最后說走净,是因?yàn)檫@個(gè)優(yōu)先級比較低券时,因?yàn)橐话?code>APP在使用過程中囊嘉,很難造成電量的明顯下降,除非是游戲革为、相機(jī)或者視頻類APP
電量優(yōu)化相對來說比較簡單扭粱,在開發(fā)中注意一下幾點(diǎn)就可以了:
- 數(shù)據(jù)備份、日志報(bào)告等后臺活動(dòng)震檩,可以放到電量充足或者正在充電時(shí)候執(zhí)行
- 除視屏播放外琢蛤,一直避免一直亮屏。
- 錄音抛虏、GPS博其、相機(jī)等耗電操作,在執(zhí)行完成后及時(shí)釋放對應(yīng)資源
- 后臺不必要的
service
記得及時(shí)關(guān)閉
總結(jié)
Android
的性能優(yōu)化是一個(gè)長期且漫長的過程迂猴。一般企業(yè)在開發(fā)中都是先實(shí)現(xiàn)功能再去管性能慕淡,這樣做會導(dǎo)致后期優(yōu)化起來麻煩且耗時(shí)。建議有可能的話盡量保持一個(gè)好的開發(fā)習(xí)慣沸毁,在項(xiàng)目初期就注意性能方面的事情峰髓,不要引入無用的內(nèi)容、保持代碼整潔息尺、及時(shí)刪除已廢棄模塊等携兵,這樣開發(fā)的項(xiàng)目才回高效且易維護(hù)。
這篇文章也是根據(jù)我開發(fā)的經(jīng)驗(yàn)以及網(wǎng)絡(luò)中的好多精品文章整理而來搂誉,其中一些精彩的深入分析文章大家可以點(diǎn)擊詳情去查看徐紧。