性能優(yōu)化的目的
在不斷地迭代開發(fā)過程中,我們的應用功能會越來越復雜,代碼量也會不斷增加。再加上偶爾的重構、人員的變更等等原因,我們曾經(jīng)那個如絲順滑的項目也會漸漸變得卡頓。
那么這個時候,就不得不提性能優(yōu)化這個話題了。正好這段時間有空,就整理了一下常見的性能優(yōu)化的幾個方面以及各個方面的注意事項。一來是給自己腦中的知識做個梳理,加深下記憶,二來也能給一些萌新提供點思路。
內(nèi)存優(yōu)化
內(nèi)存優(yōu)化,可以說是性能優(yōu)化中最重要的一部分內(nèi)容了。如果應用占用內(nèi)存過大,輕則應用卡頓降低用戶體驗,重則內(nèi)存溢出(OOM),程序崩潰。所以內(nèi)存優(yōu)化很重要,接下來我們從一下幾個方面來進行講解
android的內(nèi)存管理
-
垃圾回收
android的垃圾回收和java的垃圾回收一直,一旦確定程序不再使用內(nèi)存,便將其釋放,而無需人為干預。垃圾回收有兩個步驟:在程序中產(chǎn)兆將來無法訪問的數(shù)據(jù)對象;將那些對象的資源釋放并回收。
-
內(nèi)存管理
android作為一個多任務的操作系統(tǒng),為了維持系統(tǒng)功能正常,會對每個應用程序的內(nèi)存大小做出硬性限制。具體的數(shù)值和機型以及內(nèi)存有關。如果應用內(nèi)存打到限制后還要申請內(nèi)存,就會引發(fā)OutOfMemoryError。如果在開發(fā)中有獲取剩余內(nèi)存的需求,可以調(diào)用
getMemoryClass()方法。用來在內(nèi)存快滿時及時回收一些不必要的東西。
android內(nèi)存監(jiān)測
-
LeakCanary 詳情
一個可以檢測程序在運行過程中發(fā)生的內(nèi)存泄漏問題,通過簡單的代碼配置,可以方便的找出我們應用中的內(nèi)存問題
-
Memory Monitor
android studio自帶的實時內(nèi)存分析工具,我們可以通過實時的內(nèi)存、CPU等的波動來分析問題,如果某個頁面反復進入后內(nèi)存持續(xù)增長,我們就要注意了。[圖片上傳失敗...(image-211493-1570784432908)]
-
Heap Viewer 詳情
也是
android studio中可以直接使用的內(nèi)存分析工具,需要android系統(tǒng)在5.0以上并保持開發(fā)者選項可用。具體使用情況請點擊詳情。 -
Allocation Tracker 詳情
可以追蹤內(nèi)存分配信息,按順序排列,這樣我們就可以清晰看出某一個操作的內(nèi)存是如何一步步分配出來的,從而進一步找出發(fā)生問題的代碼。
更多性能測試工具看這里
內(nèi)存優(yōu)化方案
-
界面不可見時及時回收部分內(nèi)存
當用戶打開了另外一個程序,我們的程序界面已經(jīng)不再可見的時候,我們應當將所有和界面相關的資源進行釋放。在這種場景下釋放資源可以讓系統(tǒng)緩存后臺進程的能力顯著增加,因此也會讓用戶體驗變得更好。
檢測界面是否可見我們可以重寫如下方法:
@Override public void onTrimMemory(int level) { super.onTrimMemory(level); switch (level) { case TRIM_MEMORY_UI_HIDDEN: // 進行資源釋放操作 break; } }onTrimMemory方法只有當一個Activity完全不可見時候才會調(diào)用,這和onStop()方法還是有很大區(qū)別的,因為onStop()方法只是當一個Activity完全不可見的時候就會調(diào)用,比如說用戶打開了我們程序中的另一個Activity。因此,我們可以在
onStop()方法中去釋放一些Activity相關的資源,比如說取消網(wǎng)絡連接或者注銷廣播接收器等,但是像UI相關的資源應該一直要等到onTrimMemory(TRIM_MEMORY_UI_HIDDEN)這個回調(diào)之后才去釋放,這樣可以保證如果用戶只是從我們程序的一個Activity回到了另外一個Activity,界面相關的資源都不需要重新加載,從而提升響應速度。 -
使用Handler時盡量弱引用
我們經(jīng)常會在
handler中進行一些延時任務,這些延時任務會導致Activity被引用,從而發(fā)生內(nèi)存泄漏,為了避免這類事情發(fā)生,我們可以對handler使用弱引用。public class MainActivity extends AppCompatActivity { public static final String TAG = "carson:"; private Handler showhandler; // 主線程創(chuàng)建時便自動創(chuàng)建Looper & 對應的MessageQueue // 之后執(zhí)行Loop()進入消息循環(huán) @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); //1. 實例化自定義的Handler類對象->>分析1 //注: // a. 此處并無指定Looper,故自動綁定當前線程(主線程)的Looper、MessageQueue; // b. 定義時需傳入持有的Activity實例(弱引用) showhandler = new FHandler(this); // 2. 啟動子線程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;// 消息標識 msg.obj = "AA";// 消息存放 // b. 傳入主線程的Handler & 向其MessageQueue發(fā)送消息 showhandler.sendMessage(msg); } }.start(); // 3. 啟動子線程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;// 消息標識 msg.obj = "BB";// 消息存放 // b. 傳入主線程的Handler & 向其MessageQueue發(fā)送消息 showhandler.sendMessage(msg); } }.start(); } // 分析1:自定義Handler子類 // 設置為:靜態(tài)內(nèi)部類 private static class FHandler extends Handler{ // 定義 弱引用實例 private WeakReference<Activity> reference; // 在構造方法中傳入需持有的Activity實例 public FHandler(Activity activity) { // 使用WeakReference弱引用持有Activity實例 reference = new WeakReference<Activity>(activity); } // 通過復寫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ā)送延時消息便不會發(fā)生內(nèi)存泄漏了。當然避免
handler內(nèi)存泄漏還可以采取當Activity結束使用時候,清空消息隊列的操作,如下:@Override protected void onDestroy() { super.onDestroy(); mHandler.removeCallbacksAndMessages(null); // 外部類Activity生命周期結束時,同時清空消息隊列 & 結束Handler生命周期 } -
加載圖片的注意事項 詳情
- 不在小控件上顯示大圖
- 列表類圖片,僅加載當前頁面可見的圖片
- 有顯示原圖需求時,要判斷可用內(nèi)存,內(nèi)存不夠時要壓縮圖片
-
避免內(nèi)存抖動 詳情
盡量避免在循環(huán)體內(nèi)創(chuàng)建對象,應該把對象創(chuàng)建移到循環(huán)體外。
注意自定義View的onDraw()方法會被頻繁調(diào)用,所以在這里面不應該頻繁的創(chuàng)建對象。
當需要大量使用Bitmap的時候,試著把它們緩存在數(shù)組中實現(xiàn)復用。
對于能夠復用的對象,同理可以使用對象池將它們緩存起來。
布局優(yōu)化 詳情
避免過度繪制
-
什么是過度繪制
過度繪制就是在同一個位置,有多次的顏色繪制過程。常見的情況就是在同一個位置堆疊了許多控件,這會造成一些性能問題,嚴重的情況會造成卡頓。
-
如何檢測過度繪制
開發(fā)者選項->調(diào)試GPU過度繪制->顯示過度繪制區(qū)域
-
過度繪制優(yōu)化
- 移除控件中不需要的背景
- 將layout層級扁平化
- 減少透明度的使用
- 自定義
View中減少重復繪制區(qū)域
布局優(yōu)化技巧
- 簡單布局優(yōu)先使用
LinearLayout或FragmentLayout - 復雜布局優(yōu)先使用
constrainLayout - 使用
include標簽提高復用性 - 使用
ViewStub標簽延遲加載 -
onDraw()中不要創(chuàng)建新的局部變量以及不要做耗時操作
網(wǎng)絡優(yōu)化 詳情
為什么要網(wǎng)絡優(yōu)化
- 過多的無用網(wǎng)絡請求,會消耗用戶的網(wǎng)絡流量。流量消耗過大會流失用戶
- 頻繁的網(wǎng)絡操作會導致設備用電量提升
- 網(wǎng)絡彈框的頻繁出現(xiàn)會降低用戶體驗
- 應用更新、大文件下載等場景,更優(yōu)的網(wǎng)絡傳輸速度可提升用戶體驗
網(wǎng)絡優(yōu)化的方式
- 使用
GZip壓縮,數(shù)據(jù)壓縮后可以減少流量的消耗,減少傳輸?shù)臅r間 - 使用IP直連,DNS域名解析是一個較為耗時操作,可以直連IP減少解析時間
- 圖片加載
- 使用
WebP格式可以大幅節(jié)省流量 - 圖片按需加載,列表中圖片只加載縮略圖
- 大圖上傳時,采用分片傳輸,失敗只傳對應片段
- 用戶體驗影響不大時,手機原圖壓縮后再傳輸
- 使用
- 減少接口數(shù)量,同一個頁面盡量只使用一個接口,數(shù)據(jù)可以放到后臺去拼湊
- 利用緩存,對數(shù)據(jù)設定有效期,有效期內(nèi)數(shù)據(jù)不重復請求
- 檢測網(wǎng)絡狀態(tài),不同網(wǎng)絡轉(zhuǎn)態(tài)執(zhí)行不同策略,例如移動網(wǎng)絡不加載圖片,2G網(wǎng)絡只加載標題等。
- 文件上傳、下載采用斷點續(xù)傳,不浪費已傳輸完成部分流量
- 利用抓包工具模擬多種情況,在實踐中調(diào)整不斷優(yōu)化用戶體驗
啟動優(yōu)化 詳情
閃屏頁優(yōu)化
主流APP是在應用啟動時候會加載一個默認的主題,用來去掉應用啟動時候的黑/白屏的情況
<style name="AppThemeWelcome" parent="Theme.AppCompat.NoActionBar">
...
<item name="android:windowBackground">@drawable/logo</item> <!-- 默認背景-->
</style>
應用主題到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>
其實就是個障眼法而已,提前讓你看到了假的界面。也算是一種不錯的方法,但是治標不治本。
第三方庫懶加載
在開發(fā)中會用到很多的三方庫,如友盟、百度、bugly、圖片庫、網(wǎng)絡庫等。
這些都是必須的,不能去掉,那么辦法就是異步加載了,可以有以下幾種思路
像友盟,bugly這樣的業(yè)務非必要的可以的異步加載。
比如地圖,推送等,非第一時間需要的可以在主線程做延時啟動。當程序已經(jīng)啟動起來之后,在進行初始化。
對于圖片,網(wǎng)絡請求框架必須在主線程里初始化了。
我們一般會有閃屏頁面,也可以把延時啟動的地圖、推送的啟動放到這個頁面
按照以上方式處理后,還可以進一步降低應用啟動時間。
WebView啟動優(yōu)化
- WebView第一次創(chuàng)建比較耗時,可以預先創(chuàng)建WebView,提前將其內(nèi)核初始化。
- 使用WebView緩存池,用到WebView的地方都從緩存池取,緩存池中沒有緩存再創(chuàng)建,注意內(nèi)存泄漏問題。
- 本地預置html和css,WebView創(chuàng)建的時候先預加載本地html,之后通過js腳本填充內(nèi)容部分。
數(shù)據(jù)項預加載
主頁數(shù)據(jù)變化不大時候,可以再第一次啟動后,緩存主頁數(shù)據(jù)到本地,下次啟動先讀取本地數(shù)據(jù),頁面完全顯示后再去請求新數(shù)據(jù)進行增量更新。
安裝包體積優(yōu)化
體積優(yōu)化的必要性
安裝包體積是用戶搜索應用后能第一眼看到的數(shù)據(jù),雖然現(xiàn)在的應用體積越來越大,但小體積的App依舊是很多存儲空間緊張用戶的痛點。所以減少安裝包體積是性能優(yōu)化方面必不可少的一步。
減少應用體積的N種辦法
使用
lint工具刪除無用的資源簡單的切圖盡量替換為
shape類型的xml文件形狀一致的圖片只使用一個切圖,比如方向不同的箭頭、圖像相同著色不同的切圖等
對圖片進行壓縮,優(yōu)先使用
WebP格式圖像使用矢量圖(.9)圖來實現(xiàn)大小可變的背景圖
代碼混淆,使用
proGuard代碼混淆器工具,它包括壓縮、優(yōu)化、混淆等功能。插件化,不需要的部分可以存在服務器,當用到時候動態(tài)下載。
電量優(yōu)化
電量優(yōu)化我放到最后說,是因為這個優(yōu)先級比較低,因為一般APP在使用過程中,很難造成電量的明顯下降,除非是游戲、相機或者視頻類APP
電量優(yōu)化相對來說比較簡單,在開發(fā)中注意一下幾點就可以了:
- 數(shù)據(jù)備份、日志報告等后臺活動,可以放到電量充足或者正在充電時候執(zhí)行
- 除視屏播放外,一直避免一直亮屏。
- 錄音、GPS、相機等耗電操作,在執(zhí)行完成后及時釋放對應資源
- 后臺不必要的
service記得及時關閉
總結
Android的性能優(yōu)化是一個長期且漫長的過程。一般企業(yè)在開發(fā)中都是先實現(xiàn)功能再去管性能,這樣做會導致后期優(yōu)化起來麻煩且耗時。建議有可能的話盡量保持一個好的開發(fā)習慣,在項目初期就注意性能方面的事情,不要引入無用的內(nèi)容、保持代碼整潔、及時刪除已廢棄模塊等,這樣開發(fā)的項目才回高效且易維護。
這篇文章也是根據(jù)我開發(fā)的經(jīng)驗以及網(wǎng)絡中的好多精品文章整理而來,其中一些精彩的深入分析文章大家可以點擊詳情去查看。