Android 常見性能優化總結

前言

北京時間2022年8月16日凌晨 Android 13正式版全球也已發布,android 每次發新版本都會圍繞“優化”這個詞展開,android 13也不例外 個人認為他最大的亮點是UI界面優化,其實經過這么多年更新迭代 Android系統性能也已經非常流暢,可以在體驗上完全媲美iOS。但是,到了各大廠商手里,改源碼、自定義系統,使得Android原生系統變得魚龍混雜,同時開發人員技術水平的參差不齊,即使很多手機在跑分軟件性能非常高,打開應用依然存在卡頓現象。另外,隨著產品內容迭代,功能越來越復雜,UI頁面也越來越豐富,也成為流暢運行的一種阻礙。綜上所述,對APP進行性能優化已成為開發者該有的一種綜合素質,也是開發者能夠完成高質量應用程序作品的保證。

優化點

Android的性能優化,主要是從以下幾個方面進行優化的:


分類

整體優化方向:


優化總結

優化方案

一:流暢性

1. 響應速度:

多線程的方式 包括:AsyncTask、繼承 Thread類、實現 Runnable接口、Handler消息機制、HandlerThread等
注:實際開發中,當一個進程發生了ANR后,系統會在 /data/anr目錄下創建一個文件 traces.txt,通過分析該文件可定位出ANR的原因

2. 啟動速度:app啟動(Application )、頁面啟動(activity)、h5(wabView)

app啟動(Application ):app啟動分為冷啟動(Cold start)、熱啟動(Hot start)和溫啟動(Warm start)三種(概念不再贅述,類似博客挺多挺詳細的)。
無論何種啟動,我們的優化點都是: Application、Activity創建以及回調等過程
谷歌官方給的建議是:
1、利用提前展示出來的Window,快速展示出來一個界面,給用戶快速反饋的體驗;
2、避免在啟動時做密集沉重的初始化(Heavy app initialization);
3、避免I/O操作、反序列化、網絡操作、布局嵌套等。

方案1: 治標不治本的迷惑性做法:主題設置圖片資源

<layer-list xmlns:android="http://schemas.android.com/apk/res/android" android:opacity="opaque">
  <!-- The background color, preferably the same as your normal theme -->
  <item android:drawable="@android:color/white"/>
  <!-- Your product logo - 144dp color version of your app icon -->
  <item>
    <bitmap
      android:src="@drawable/product_logo_144dp"
      android:gravity="center"/>
  </item>
</layer-list>

方案2: 避免在啟動時做密集沉重的初始化

application 中init三方SDK耗時優化:
1、業務非必要的可以的異步加載(線程池)。
2、非第一時間需要的可以在主線程做延時啟動。當程序已經啟動起來之后,在進行初始化。
3、對于圖片,網絡請求框架必須在主線程里初始化了。
4、啟動圖+開屏廣告(以時間換時間 利用廣告加載的時間初始化sdk)
ps:以上操作也僅僅是在應用層能做總有一些能到極限的操作
5、 通過利用多進程、修改類加載過程、dex壓縮等方向去實現

3. 頁面顯示速度

方案1: 頁面布局優化

如果父控件有顏色,也是自己需要的顏色,那么就不必在子控件加背景顏色
如果每個自控件的顏色不太一樣,而且可以完全覆蓋父控件,那么就不需要再父控件上加背景顏色
盡量減少不必要的嵌套
能用LinearLayout和FrameLayout,就不要用RelativeLayout,因為RelativeLayout控件相對比較復雜,測繪也想要耗時。
使用include和merge增加復用,減少層級
ViewStub按需加載,更加輕便
復雜界面可選擇ConstraintLayout,可有效減少層級
ps:開發者模式,查看是否存在過度繪制

方案2:繪制優化

onDraw中不要創建新的局部對象、也不要做耗時的任務
ps:我們平時感覺的卡頓問題最主要的原因之一是因為渲染性能,因為越來越復雜的界面交互,其中可能添加了動畫,或者圖片等等。我們希望創造出越來越炫的交互界面,同時也希望他可以流暢顯示,但是往往卡頓就發生在這里。
這個是Android的渲染機制造成的,Android系統每隔16ms發出VSYNC信號,觸發對UI進行渲染,但是渲染未必成功,如果成功了那么代表一切順利,但是失敗了可能就要延誤時間,或者直接跳過去,給人視覺上的表現,就是要么卡了一會,要么跳幀。
View的繪制頻率保證60fps是最佳的,這就要求每幀繪制時間不超過16ms(16ms = 1000/60),雖然程序很難保證16ms這個時間,但是盡量降低onDraw方法中的復雜度總是切實有效的。

方案3:如果頁面就是比較復雜,層級再怎么縮減也達不到預取效果該怎么辦:

代碼動態創建View 通過addview的形式添加

方案4:主線程耗時操作排查

開啟strictmode,這樣一來,主線程的耗時操作都將以告警的形式呈現到logcat當中
StrictMode,嚴苛模式,是Android提供的一種運行時檢測機制,用于檢測代碼運行時的一些不規范的操作,最常見的場景是用于發現主線程的IO操作和網絡讀寫等耗時的操作。

方案5:viewStub&merge&ViewStub的使用
方案6:頁面刷新

item 刷新局部刷新
復雜Activity、fragment:利用LiveData等數據共享

二:穩定性

1. ANR:

以下四個條件都可以造成ANR發生:
InputDispatching Timeout:5秒內無法響應屏幕觸摸事件或鍵盤輸入事件
BroadcastQueue Timeout :在執行前臺廣播(BroadcastReceiver)的onReceive()函數時10秒沒有處理完成,后臺為60秒。
Service Timeout :前臺服務20秒內,后臺服務在200秒內沒有執行完畢。
ContentProvider Timeout :ContentProvider的publish在10s內沒進行完。
實際開發中,當一個進程發生了ANR后,系統會在 /data/anr目錄下創建一個文件 traces.txt,通過分析該文件可定位出ANR的原因
ps:總之記住這一點:盡量避免在主線程(UI線程)中作耗時操作,耗時操作都放到子線程
多注意一些常用庫出現ANR的情況:如:SharedPreferences等

1. Crash:

1:頻繁GC

內存抖動
1短時間內創建了大量對象同時又被快速釋放。比如在一個大循環里去不斷創建對象,會導致頻繁gc;
內存泄漏
2內存泄漏會導致可用內存逐漸變少,而且內存碎片加多,這也會增多gc次數,甚至可能發生OOM
3一次申請太大內存空間
由于內存碎片的存在,就算內存本身足夠,但由于碎片導致無法找到一塊大空間,這也會觸發gc;

2:在Android平臺上常見的OOM有如下幾種:

1、使用static修飾Context變量,Context被Hold住了導致Activity無法銷毀。比如 Thread
2、Bitmap沒有及時回收,調用recycle()函數并不能立即釋放Bitmap,讀取Bitmap到內存的時候沒有做采樣率的設置;
3、線程數超限,proc/pid/status中記錄的線程數超過proc/sys/kernel/threads-max中規定的最大線程數,場景如隨意創建線程,沒有使用線程池來管理;
4、文件描述符(fd)超限,proc/pid/fd下文件數目超過proc/pid/limits中的限制,場景如大量的Socket請求或者文件打開操作。
5、Cursor使用問題,使用完之后沒有及時關閉。
6、Java堆內存超限,申請的堆內存超過Runtime.getRuntime().maxMemory()。
7、Adapter沒使用緩存的convertView;
8、廣播注冊之后沒有反注冊;
9、WebView沒有銷毀,應該調用destroy()函數去銷毀;
10、數組內對象過多,沒有及時清理。
11、EventBus 等觀察者模式的框架忘記手動解除注冊
ps:分析工具:LeakCanary、Android Studio自帶工具Profilerd等

3:異常日志搜集:三方的奔潰搜集工具很多,不在贅述,android 崩潰日志收集以及上傳服務器 -自定義我自己實現的感興趣的可以看看

三:apk體積

1. 安裝包結構:
該圖來自網絡截圖
2. 優化方案:

一:最省(資源)

1. 內存:

部分上面OOM分析中已經提到了,下面做個簡單補充
1:SparseArray代替HashMap
2:使用Link優化項目
3:Bitmap壓縮:一個比較好的壓縮庫,Curzibn/Luban: Luban(魯班)—Image compression with efficiency very close to WeChat Moments/可能是最接近微信朋友圈的圖片壓縮算法 (github.com)
4:推薦使用match_parent,或者固定尺寸
5:線程優化(線程池)

2. 網絡:
該圖來自網絡

Android 性能優化之網絡優化 這篇文章寫的挺詳細不在贅述
ps:網上有很多寫的比較好的網絡請求框架的封裝可以借鑒一下思路
Retrofit+OkHttp+Kotlin協程 網絡請求架構等等

1. 耗電量:

Battery Historian
是由Google提供的Android系統電量分析工具,從手機中導出bugreport文件上傳至頁面,在網頁中生成詳細的圖表數據來展示手機上各模塊電量消耗過程,最后通過App數據的分析制定出相關的電量優化的方法。
谷歌推薦使用JobScheduler,來調整任務優先級等策略來達到降低損耗的目的。JobScheduler可以避免頻繁的喚醒硬件模塊,造成不必要的電量消耗。避免在不合適的時間(例如低電量情況下、弱網絡或者移動網絡情況下的)執行過多的任務消耗電量。
具體功能:
1、可以推遲的非面向用戶的任務(如定期數據庫數據更新);
2、當充電時才希望執行的工作(如備份數據);
3、需要訪問網絡或 Wi-Fi 連接的任務(如向服務器拉取配置數據);
4、零散任務合并到一個批次去定期運行;
5、當設備空閑時啟動某些任務;
6、只有當條件得到滿足, 系統才會啟動計劃中的任務(充電、WIFI...);

實現一個通過JobScheduler的簡單demo

public class JobSchedulerService extends JobService{
    private String TAG = JobSchedulerService.class.getSimpleName();

    @Override
    public boolean onStartJob(JobParameters jobParameters) {
        Log.d(TAG, "onStartJob:" + jobParameters.getJobId());

        if(true) {
            // JobService在主線程運行,如果我們這里需要處理比較耗時的業務邏輯需單獨開啟一條子線程來處理并返回true,
            // 當給定的任務完成時通過調用jobFinished(JobParameters params, boolean needsRescheduled)告知系統。

            //假設開啟一個線程去下載文件
            new DownloadTask().execute(jobParameters);

            return true;

        }else {
            //如果只是在本方法內執行一些簡單的邏輯話返回false就可以了
            return false;
        }
    }

    /**
     * 比如我們的服務設定的約束條件為在WIFI狀態下運行,結果在任務運行的過程中WIFI斷開了系統
     * 就會通過回掉onStopJob()來通知我們停止運行,正常的情況下不會回掉此方法
     *
     * @param jobParameters
     * @return
     */
    @Override
    public boolean onStopJob(JobParameters jobParameters) {
        Log.d(TAG, "onStopJob:" + jobParameters.getJobId());

        //如果需要服務在設定的約定條件再次滿足時再次執行服務請返回true,反之false
        return true;
    }

    class DownloadTask extends AsyncTask<JobParameters, Object, Object> {
        JobParameters mJobParameters;

        @Override
        protected Object doInBackground(JobParameters... jobParameterses) {
            mJobParameters = jobParameterses[0];

            //比如說我們這里處理一個下載任務
            //或是處理一些比較復雜的運算邏輯
            //...

            try {
                Thread.sleep(30*1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            return null;
        }

        @Override
        protected void onPostExecute(Object o) {
            super.onPostExecute(o);
            //如果在onStartJob()中返回true的話,處理完成邏輯后一定要執行jobFinished()告知系統已完成,
            //如果需要重新安排服務請true,反之false
            jobFinished(mJobParameters, false);
        }
    }
}

public class MainActivity extends Activity{
    private JobScheduler mJobScheduler;
    private final int JOB_ID = 1;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.mai_layout);

        mJobScheduler = (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE );

        //通過JobInfo.Builder來設定觸發服務的約束條件,最少設定一個條件
        JobInfo.Builder jobBuilder = new JobInfo.Builder(JOB_ID, new ComponentName(this, JobSchedulerService.class));

        //循環觸發,設置任務每三秒定期運行一次
        jobBuilder.setPeriodic(3000);

        //單次定時觸發,設置為三秒以后去觸發。這是與setPeriodic(long time)不兼容的,
        // 并且如果同時使用這兩個函數將會導致拋出異常。
        jobBuilder.setMinimumLatency(3000);

        //在約定的時間內設置的條件都沒有被觸發時三秒以后開始觸發。類似于setMinimumLatency(long time),
        // 這個函數是與 setPeriodic(long time) 互相排斥的,并且如果同時使用這兩個函數,將會導致拋出異常。
        jobBuilder.setOverrideDeadline(3000);

        //在設備重新啟動后設置的觸發條件是否還有效
        jobBuilder.setPersisted(false);

        // 只有在設備處于一種特定的網絡狀態時,它才觸發。
        // JobInfo.NETWORK_TYPE_NONE,無論是否有網絡均可觸發,這個是默認值;
        // JobInfo.NETWORK_TYPE_ANY,有網絡連接時就觸發;
        // JobInfo.NETWORK_TYPE_UNMETERED,非蜂窩網絡中觸發;
        // JobInfo.NETWORK_TYPE_NOT_ROAMING,非漫游網絡時才可觸發;
        jobBuilder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED);

        //設置手機充電狀態下觸發
        jobBuilder.setRequiresCharging(true);

        //設置手機處于空閑狀態時觸發
        jobBuilder.setRequiresDeviceIdle(true);
        
        //得到JobInfo對象
        JobInfo jobInfo = jobBuilder.build();

        //設置開始安排任務,它將返回一個狀態碼
        //JobScheduler.RESULT_SUCCESS,成功
        //JobScheduler.RESULT_FAILURE,失敗
        if (mJobScheduler.schedule(jobInfo) == JobScheduler.RESULT_FAILURE) {
            //安排任務失敗
        }

        //停止指定JobId的工作服務
        mJobScheduler.cancel(JOB_ID);
        //停止全部的工作服務
        mJobScheduler.cancelAll();
    }

最主要的一點

記得在Manifest文件內配置Service <service android:name=".JobSchedulerService" android:permission="android.permission.BIND_JOB_SERVICE"/>

總結

作為一個程序員,性能優化是常有的事情,不管是桌面應用還是web應用,不管是前端還是后端,不管是單點應用還是分布式系統,所以我們應該更加去注重性能優化的一個使用和技術上提升,綜上所述,對APP進行性能優化已成為開發者該有的一種綜合素質,也是開發者能夠完成高質量應用程序作品的保證。
以下文檔有關于單向具體優化的操作,感興趣的童鞋可以看一下
Android 性能優化 | Android 開源項目 | Android Open Source Project (google.cn)
Android深度性能優化--啟動優化(一篇就夠) - 知乎 (zhihu.com)
(28條消息) Android性能優化常見問題,與詳細解決思路方法!_chuhe1989的博客-CSDN博客

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,646評論 6 533
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,595評論 3 418
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,560評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,035評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,814評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,224評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,301評論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,444評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,988評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,804評論 3 355
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,998評論 1 370
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,544評論 5 360
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,237評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,665評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,927評論 1 287
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,706評論 3 393
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,993評論 2 374

推薦閱讀更多精彩內容