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

1: ---------課程導(dǎo)學與學習指南【提供免費簡歷指導(dǎo)機會&內(nèi)推大公司機會】 ---------

2: --------App性能概覽與平臺化實踐---------

3: --------App啟動優(yōu)化---------

4: --------App內(nèi)存優(yōu)化---------

1: 學習問題自助手冊

http://coding.imooc.com/lesson/308.html#mid=22071

2: 內(nèi)存優(yōu)化介紹及工具選擇

1 : 內(nèi)存問題

  • 內(nèi)存抖動 : 鋸齒狀 , GC導(dǎo)致卡頓
  • 內(nèi)存泄漏 : 可用內(nèi)存減少 , 頻繁GC
  • 內(nèi)存溢出 : OOM , 程序異常

內(nèi)存一旦出現(xiàn)問題杈湾,最終暴露出來的位置并非是真正的原因,可能由于長時間內(nèi)存問題集中暴露出來的結(jié)果攘须。
三大原因:
1漆撞、內(nèi)存抖動:頻繁的GC導(dǎo)致
2、內(nèi)存泄露:不再使用的內(nèi)存GC未能成功回收
3于宙、內(nèi)存溢出:長時間的內(nèi)存泄露浮驳、申請內(nèi)存過大最終可導(dǎo)致內(nèi)存溢出,程序崩潰

2 : 工具選擇

  • 1捞魁、MemoryProfiler(AndroidStudio集成的檢測工具 , 內(nèi)存查詢工具:Android Studio -> Memory Profiler)
    • 1 : 實時圖表展示內(nèi)存使用量
    • 2 : 識別內(nèi)存泄漏 , 抖動等
    • 3 : 提供捕獲堆轉(zhuǎn)儲 , 強制GC以及跟蹤內(nèi)存分配的能力
  • 2至会、MemoryAnalyzer (MAT)
    • 1 : 強大的Java Heap分析工具 , 查找內(nèi)存泄漏及內(nèi)存占用
    • 2 : 生成整體報告 , 分析問題等
    • 3 : 線下深入使用
  • 3、LeakCanary
    • 1 : 自動內(nèi)存泄露監(jiān)測谱俭,不適合線上使用

3 : Android內(nèi)存管理機制

1 : Java的內(nèi)存管理機制

  • Java內(nèi)存分配
    • 方法區(qū): 類信息 常量 靜態(tài)變量等 比如:public , static , final,多有線程都共享
    • 虛擬機棧: 局部變量表等奉件,棧引用
    • 本地方法棧: native方法
    • 堆:
    • 程序計數(shù)器: 比如當前線程執(zhí)行的方法執(zhí)行到第幾行
  • Java回收算法
  • 1、標記-清除算法:
    標記出所有需要回收的對象昆著,統(tǒng)一回收所有被標記的對象
    優(yōu)缺點:標記清除效率不高县貌,產(chǎn)生大量不連續(xù)的內(nèi)存碎片


    20211212004014.jpg

白色 : 空閑內(nèi)存
藍色 : 正在使用的內(nèi)存
灰色 : 可回收內(nèi)存

  • 2、復(fù)制算法:
    將內(nèi)存劃分為大小相等的兩塊 , 一塊內(nèi)存用完之后復(fù)制存活對象到另一塊空閑內(nèi)存 , 清理另一塊內(nèi)存宣吱。
    騰出一半空間窃这,將對象分配到另一半空間;回收時征候,將存活對象全部拷貝到空閑的那一半空間(內(nèi)存空間連續(xù))杭攻,然后清理另一半空間。
    優(yōu)缺點:運行比標記清理算法高效疤坝,浪費一半空間兆解。


    20211212005454.jpg
  • 3、標記-整理算法:
    標記過程與“標記-清除”算法一樣跑揉,存活對象往一端移動锅睛,清理其余內(nèi)存埠巨。
    優(yōu)缺點:避免標記-清除導(dǎo)致的內(nèi)存碎片(也就是內(nèi)存不連續(xù))和避免復(fù)制算法的空間浪費。


    20211212005915.jpg
  • 4现拒、分代收集算法:
    結(jié)合多種收集算法優(yōu)勢辣垒,新生代對象由于存活率低 用復(fù)制算法(用于復(fù)制的空閑內(nèi)存自定義,如30%)印蔬,老年代對象存活率高 用標記-整理算法勋桶。

2 : Android的內(nèi)存管理機制

  • 內(nèi)存彈性分配 , 分配值與最大值受具體設(shè)備影響
  • OOM場景 : 內(nèi)存真正不足 , 可用內(nèi)存不足
  • Dalvik和Art(android5.0以后)針對垃圾回收的區(qū)別:
    • 1.Dalvik僅固定一種回收算法
    • 2.Art回收算法可運行期選擇(前臺用標記-清除,后臺用標記-整理)
    • 3.Art具備內(nèi)存整理能力侥猬,減少內(nèi)存空洞
  • Low Memory Killer 機制例驹,針對所有進程
    • 進程分類:前臺進程,可見進程退唠,服務(wù)進程鹃锈,后臺進程和空進程等;優(yōu)先級從前到后瞧预;
      優(yōu)先級低的會被先回收屎债。
    • 回收收益: 回收的時候,也會考慮回收內(nèi)存的大小松蒜,是30m扔茅,還是300m的效果更好。

4 : 內(nèi)存抖動解決實戰(zhàn)

定義 : 內(nèi)存頻繁分配和回收導(dǎo)致內(nèi)存不穩(wěn)定

表現(xiàn) : 頻繁GC , 內(nèi)存曲線呈鋸齒狀

危害 : 導(dǎo)致卡頓 , OOM

原因1 : 頻繁創(chuàng)建對象, 導(dǎo)致內(nèi)存不足及碎片(不連續(xù))

原因2 : 不連續(xù)的內(nèi)存片無法被分配 , 導(dǎo)致OOM

實戰(zhàn) :

使用Memory Profiler進行初步排查
使用Memory Profiler或CPU Profiler結(jié)合代碼排查

解決技巧 : 找循環(huán)或者頻繁調(diào)用的地方

5 : 內(nèi)存泄漏解決實戰(zhàn)

定義 : 內(nèi)存中存在已經(jīng)沒有用的對象

表現(xiàn) : 內(nèi)存抖動 , 可用內(nèi)存逐漸變少

危害 : 內(nèi)存不足, GC頻繁, OOM

MAT ( Memory Analyzer )

https://www.eclipse.org/mat/downloads.php
androidstudio 生成的 .hprof 文件在這里需要進行轉(zhuǎn)換一下格式秸苗。
轉(zhuǎn)換 : hprof-conv 1.hprof(原文件路徑) 2.hprof(轉(zhuǎn)換后文件路徑)

6 : 全面理解MAT

with outgoing references:引用了哪些類
with incoming references:被哪些類引用
Shallow Size是對象本身占據(jù)的內(nèi)存的大小,不包含其引用的對象运褪。
Retained Size=當前對象大小+當前對象可直接或間接引用到的對象的大小總和惊楼。

dominator_tree
percentage : 占內(nèi)存百分比

Histogram:基于類的角度分析
dominator_tree:基于實例的角度分析

7 : ARTHook優(yōu)雅檢測不合理圖片

Bitmap內(nèi)存模型

  • API10之前Bitmap自身在Dalvik Heap中,像素在Native
  • API10之后像素也被放在Dalvik Heap中
  • API26之后像素又放回native,但是增加了及時通知回收的機制

https://mp.weixin.qq.com/s/iMVLxsrQ2Um4ToFJGyqkpQ

獲取Bitmap占用內(nèi)存

  • 獲取Bitmap占用內(nèi)存
    • getByteCount
    • 一像素占用內(nèi)存

常規(guī)方式

  • 背景: 圖片對內(nèi)存優(yōu)化至關(guān)重要, 圖片寬高大于控件寬高, 其實就造成了內(nèi)存浪費秸讹,控件用不了這么大
  • 實現(xiàn): 繼承ImageView,覆寫實現(xiàn)計算大小檀咙,在onDraw時判斷控件寬高及對應(yīng)的Bitmap寬高,如果比例超過了一定的限度璃诀,則給一個警告(這種方式侵入性比較強弧可,通用性不強)

ARTHook介紹 : 運行時插樁 + 性能分析

掛鉤,將額外的代碼鉤住原有方法,修改執(zhí)行邏輯

  • java層hook庫:epic
20211212125847.jpg

20211212130008.jpg

20211212131909.jpg

8 : 線上內(nèi)存監(jiān)控方案

20211212140229.jpg

20211212140356.jpg

20211212140528.jpg

20211212140622.jpg

20211212140745.jpg

20211212141104.jpg

20211212141323.jpg

20211212141506.jpg

9 : 內(nèi)存優(yōu)化技巧總結(jié)

  • 優(yōu)化大方向

    • 內(nèi)存泄漏
    • 內(nèi)存抖動
    • Bitmap
  • 優(yōu)化細節(jié)

    • LargeHeap屬性
    • onTrimMemory
    • 使用優(yōu)化過的集合: SparseArray
    • 謹慎使用SharedPreference
    • 謹慎使用外部庫
    • 業(yè)務(wù)架構(gòu)設(shè)計合理
  • 1.largeHeap可以增大系統(tǒng)分配的內(nèi)存

  • 2.onTrimMemory和onLowMemory可以監(jiān)聽程序進入低內(nèi)存

  • 3.使用優(yōu)化后的集合:SparseArray

  • 4.謹慎使用SharedPreference:因為SharedPreference第一次加載所有數(shù)據(jù)到內(nèi)存中

  • 5.謹慎使用使用量不大 沒有經(jīng)過大量驗證的外部庫,防止其有內(nèi)存性能缺陷

  • 6.數(shù)據(jù)量很大時劣欢,使用局部加載數(shù)據(jù)(比如城市棕诵,可以按省加載城市,而不是一次加載全國甚至全球的城市)

10 : 內(nèi)存優(yōu)化模擬面試

11 : LeakCanary

20211212155638.jpg

https://zhuanlan.zhihu.com/p/433541771

5: --------App布局優(yōu)化--------

6: -------App卡頓優(yōu)化---------

7: -------App線程優(yōu)化---------

1: 學習問題自助手冊

http://coding.imooc.com/lesson/308.html#mid=22074

2: Android線程調(diào)度原理剖析

  • 線程的調(diào)度原理

    • 任意時刻 , 只有一個線程占用CPU , 處于運行狀態(tài)
    • 多線程并發(fā) : 輪流獲取CPU使用權(quán)
    • JVM負責線程調(diào)度 : 按照特定機制分配CPU使用權(quán)
  • 線程調(diào)度模型

    • 分時調(diào)度模型 : 輪流獲取 , 均分CPU時間
    • 搶占式調(diào)度模型 : 優(yōu)先級高的獲取 , JVM采用
  • Android線程調(diào)度

    • nice值 : Process中定義 -> 值越小 , 優(yōu)先級越高 -> 默認是THREAD_PRIORITY_DEFAULT , 0
    • cgroup
      • 更嚴格的群組調(diào)度策略
      • 保證前臺線程可以獲取到更多的CPU
  • 注意點

    • 線程過多會導(dǎo)致CPU頻繁切換 , 降低線程運行效率
    • 正確認識任務(wù)重要性決定那種優(yōu)先級
    • 優(yōu)先級具有繼承性

3: Android異步方式匯總

  • Thread

    • 不易復(fù)用 , 頻繁創(chuàng)建及銷毀開銷大
    • 復(fù)雜場景不易使用
  • HandlerThread

    • 自帶消息循環(huán)的線程
      • 串行執(zhí)行
      • 適合長時間運行 , 不斷從隊列中獲取任務(wù)
  • IntentService

    • 繼承自Service在內(nèi)部創(chuàng)建HandlerThread
      • 異步 , 不占用主線程
      • 優(yōu)先級較高 , 不易被系統(tǒng)Kill
  • AsyncTask

    • Android提供的工具類
      • 無需自己處理線程切換
      • 需注意版本不一致問題
  • 線程池

    • Java提供的線程池
      • 易復(fù)用 , 減少頻繁創(chuàng)建 , 銷毀的時間
      • 功能強大 : 定時 , 任務(wù)隊列 , 并發(fā)數(shù)控制等
  • RXJava

    • 由強大的Scheduler集合提供
      • 不同類型的區(qū)分 : IO , Computation

4: Android線程優(yōu)化實戰(zhàn)

線程使用準則:
  • 1.嚴禁使用new Thread
  • 2.提供基礎(chǔ)線程池供各個業(yè)務(wù)線使用凿将,避免各個業(yè)務(wù)線各自維護一套線程池導(dǎo)致線程過多
  • 3.根據(jù)任務(wù)類型選擇合適的異步方式
    ①優(yōu)先級低長時間執(zhí)行校套,HandlerThread
    ②有個任務(wù)定時執(zhí)行,使用線程池
  • 4.創(chuàng)建線程必須命名牧抵;方便定位線程歸屬笛匙;運行期Thread.currentThread().setName修改名字
  • 5.關(guān)鍵異步任務(wù)監(jiān)控侨把;異步不等于不耗時;可以通過AOP方式監(jiān)控
  • 6.重視優(yōu)先級的設(shè)置妹孙;Process.setThreadPriority();可以設(shè)置多次

5: 如何鎖定線程創(chuàng)建者

        DexposedBridge.hookAllConstructors(Thread.class, new XC_MethodHook() {
            @Override
            protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                super.afterHookedMethod(param);
                Thread thread = (Thread) param.thisObject;
                LogUtils.i(thread.getName()+" stack "+Log.getStackTraceString(new Throwable()));
            }
        });

6: 線程收斂優(yōu)雅實踐初步

  • 基礎(chǔ)庫優(yōu)雅使用線程

    • 基礎(chǔ)庫內(nèi)部暴露API :setExecutor
    • 初始化的時候注入統(tǒng)一的線程庫
  • 統(tǒng)一線程庫

    • 區(qū)分任務(wù)類型 : IO , CPU密集型
    • IO密集型任務(wù)不消耗CPU , 核心池可以很大
    • CPU密集型任務(wù) : 核心池大小和CPU核心數(shù)相關(guān)
private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
public static final int CORE_POOL_SIZE = Math.max(2, Math.min(CPU_COUNT - 1, 5));
private static final int MAXIMUM_POOL_SIZE = CORE_POOL_SIZE;
private static final int KEEP_ALIVE_SECONDS = 5;

7: 線程優(yōu)化模擬面試

8: 如何設(shè)定線程池參數(shù)

描述:
面試官問:如何設(shè)定線程池參數(shù)秋柄,你如何回答缀旁?
線程池我們在開發(fā)中經(jīng)常會用到汇跨,課程中我們也說到了濫用線程的危害蚜厉,那請問大家如何設(shè)定線程池的參數(shù)夕土?

思路點撥:

濫用線程會出現(xiàn)哪些問題呐馆?
不同的問題就決定了我們一定要采用多個線程池臼隔,是哪些線程池呢遂唧?
對于不同類型的線程池如输,我們?nèi)绾卧O(shè)定其參數(shù)呢有鹿,需要考慮哪些方面的問題旭旭?

8: -------App網(wǎng)絡(luò)優(yōu)化-------

1: 網(wǎng)絡(luò)優(yōu)化有哪些維度展開?

2: 網(wǎng)絡(luò)優(yōu)化工具選擇

  • a: NetWork Profiler

    • 顯示實時網(wǎng)絡(luò)活動 : 發(fā)送 , 接收數(shù)據(jù)及連接數(shù)
    • 顯示啟動高級分析
    • 只支持HttpURLConnection和OkHttp網(wǎng)絡(luò)庫
  • b: 抓包工具

    • Charles

      • 斷點功能
      • Map Local
      • 弱網(wǎng)環(huán)境模擬
    • Fiddler

    • Wireshark

    • TcpDump

  • c: Stetho

    • 概念

      • 強大的應(yīng)用調(diào)試橋 , 連接Android和Chrome
      • 網(wǎng)絡(luò)監(jiān)控 , 視圖查看 , 數(shù)據(jù)庫查看 , 命令行擴展等
    • Stetho使用

      • com.facebook.stetho:stetho-okhttp3:1.5.0
      • Stetho.initializeWithDefaults(this);
      • addNetworkInterceptor
      • Chrome瀏覽器 : chrome://inspect
  • d: 總結(jié)

    • Network Profiler , 抓包工具 , Stetho介紹及使用實戰(zhàn)
    • 針對網(wǎng)絡(luò) : 最廣泛使用的是抓包工具

3: 精準獲取流量消耗實戰(zhàn)

20211218113819.jpg
20211218114108.jpg
20211218114305.jpg
20211218114447.jpg
20211218114550.jpg
/**
 * 獲取某個時間段流量的使用
 */
public class NetUtils {

    private static volatile String sSubscriberId = null;
    private static volatile int sUId = -1;

    @TargetApi(Build.VERSION_CODES.M)
    public static long getNetStats(@NonNull Context context, long startTime, long endTime) {
        if (Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP_MR1) {
            long netUsedByWifi = getNetStats(context, startTime, endTime, NetworkCapabilities.TRANSPORT_WIFI);
            long netUsedByCellular = getNetStats(context, startTime, endTime, NetworkCapabilities.TRANSPORT_CELLULAR);
            return netUsedByWifi + netUsedByCellular;
        } else {
            return 0;
        }
    }

    /**
     * Given the start time and end time, then you can get the traffic usage during this time.
     *
     * @param startTime Start of period. Defined in terms of "Unix time", see
     *                  {@link System#currentTimeMillis}.
     * @param endTime   End of period. Defined in terms of "Unix time", see
     *                  {@link System#currentTimeMillis}.
     * @param netType   the netWorkType you want to query
     * @return Number of bytes.
     */
    @RequiresApi(api = Build.VERSION_CODES.M)
    public static long getNetStats(@NonNull Context context, long startTime, long endTime, int netType) {
        long netDataReceive = 0;
        long netDataSend = 0;
        TelephonyManager telephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
        String subId = telephonyManager.getSubscriberId();
        NetworkStatsManager manager = (NetworkStatsManager) context.getApplicationContext().
                getSystemService(Context.NETWORK_STATS_SERVICE);

        if (manager == null) {
            return 0;
        }
        NetworkStats networkStats = null;
        NetworkStats.Bucket bucket = new NetworkStats.Bucket();
        try {
            networkStats = manager.querySummary(netType, subId, startTime, endTime);
        } catch (Exception e) {
            e.printStackTrace();
        }

        while (networkStats != null && networkStats.hasNextBucket()) {
            networkStats.getNextBucket(bucket);
            int uid = bucket.getUid();
            if (getAppUid(context) == uid) {
                netDataReceive += bucket.getRxBytes();
                netDataSend += bucket.getTxBytes();
            }
        }
        return (netDataReceive + netDataSend);
    }

    private static int getAppUid(@NonNull Context context) {
        if (sUId == -1) {
            PackageManager packageManager = context.getApplicationContext().getPackageManager();
            try {
                PackageInfo packageInfo = packageManager.getPackageInfo(context.getApplicationContext().getPackageName(), PackageManager.GET_META_DATA);
                if (packageInfo != null) {
                    sUId = packageInfo.applicationInfo.uid;
                }
            } catch (PackageManager.NameNotFoundException e) {
                e.printStackTrace();
            }
        }
        return sUId;
    }
}
20211218120345.jpg
20211218120540.jpg
20211218121208.jpg
20211218121306.jpg

4: 網(wǎng)絡(luò)請求流量優(yōu)化實戰(zhàn)

  • 使用網(wǎng)絡(luò)的場景概述

    • 數(shù)據(jù): Api , 資源包( 升級包 , H5 , RN ) , 配置信息
    • 圖片: 下載 , 上傳
    • 監(jiān)控: APM相關(guān) , 單點問題相關(guān)
  • 數(shù)據(jù)緩存

    • 服務(wù)端返回加上過期時間 , 避免每次重新獲取
    • 節(jié)約流量且大幅提高數(shù)據(jù)訪問速度 , 更好的用戶體驗
    • OkHttp , Volley都有較好實踐
  • 增量數(shù)據(jù)更新

    • 加上版本的概念 , 只傳輸有變化的數(shù)據(jù)
    • 配置信息 , 省市區(qū)縣等更新
  • 數(shù)據(jù)壓縮

    • Post請求Body使用GZip壓縮
    • 請求頭壓縮
    • 圖片上傳之前必須壓縮
  • 優(yōu)化發(fā)送頻率和時機

    • 合并網(wǎng)絡(luò)請求 , 減少請求次數(shù)
    • 性能日志上報 : 批量+特定場景上報
  • 圖片相關(guān)

    • 圖片使用策略細化 : 優(yōu)化縮略圖
    • 使用WebP格式圖片
  • 總結(jié)

    • 數(shù)據(jù)緩存 , 增量更新 , 壓縮 , 圖片相關(guān)等
    • 需要結(jié)合實際情況進行選擇

5: 網(wǎng)絡(luò)請求質(zhì)量優(yōu)化

質(zhì)量指標:

  • 網(wǎng)絡(luò)請求成功率
  • 網(wǎng)絡(luò)請求速度

Http請求過程:

  • 1:請求到達運營商的DNS服務(wù)器并解析成對應(yīng)的IP地址
  • 2:創(chuàng)建連接,走TCP的三次握手,然后根據(jù)IP地址找到相應(yīng)的服務(wù)器,發(fā)送一個請求
  • 3:服務(wù)器找到對應(yīng)的資源原路返回訪問的用戶

DNS相關(guān):

  • 問題:DNS(域名到IP地址這個過程)被劫持,DNS解析慢
  • 方案:使用HttpDNS,繞開運營商域名解析過程

HttpDNS不是使用傳統(tǒng)的DNS協(xié)議向DNS服務(wù)器的53端口發(fā)送請求,而是使用Http協(xié)議向DNS服務(wù)器的80端口發(fā)送請求

  • 優(yōu)勢:防止DNS被劫持,繞開運營商域名解析過程,降低平均訪問時長,提高連接成功率

阿里云Http的DNS解析服務(wù):

compile ('com.aliyun.ams:alicloud-android-httpdns:1.1.7@aar') {
transitive true
}

協(xié)議版本升級:

  • 1.0 : 版本TCP連接不復(fù)用
  • 1.1 : 版本引入持久連接,但數(shù)據(jù)通信按次序進行
  • 2 : 版本多工,客戶端,服務(wù)器雙向?qū)崟r通信

網(wǎng)絡(luò)請求質(zhì)量監(jiān)控:

  • 1:接口請求耗時,成功率,錯誤碼
  • 2:圖片加載的每一步耗時

網(wǎng)絡(luò)容災(zāi)機制:

  • 1:備用服務(wù)器分流
  • 2:多次失敗后一定時間內(nèi)不進行請求,避免雪崩效應(yīng)

其他優(yōu)化:

  • 1:CDN加速,提高帶寬,動靜資源分離(更新后清理緩存)
  • 2:減少傳輸量,注意請求時機及頻率
  • 3:okHttp的請求池

6: 網(wǎng)絡(luò)體系化方案建設(shè)

  • 1:線下測試相關(guān)

    1:只抓單獨APP
    2:側(cè)重點:請求有誤,多余,網(wǎng)絡(luò)切換,弱網(wǎng),無網(wǎng)測試
  • 2:線上監(jiān)控相關(guān)

    1:服務(wù)端監(jiān)控
    • 請求耗時(區(qū)分地域,時間段,版本,機型)
    • 失敗率(業(yè)務(wù)失敗與請求失敗)
    • Top失敗接口,異常接口
    2:客戶端監(jiān)控
    • 接口的每一步詳細信息(DNS,連接,請求等)
    • 請求次數(shù),網(wǎng)絡(luò)包大小,失敗原因
    • 圖片監(jiān)控
    3:異常監(jiān)控體系
    • 服務(wù)器防刷 : 超限拒絕訪問
    • 客戶端 : 大文件預(yù)警,異常兜底策略
    • 單點問題追查

7: 網(wǎng)絡(luò)優(yōu)化模擬面試

  • 1:在網(wǎng)絡(luò)方面你們做了哪些監(jiān)控,建立了哪些指標?

    演進過程,優(yōu)化背景
    質(zhì)量 : 請求成功率,每步耗時,狀態(tài)碼
    流量 : 精確統(tǒng)計,前后臺
  • 2:如何有效的降低用戶流量消耗?

    數(shù)據(jù) : 緩存,增量更新
    上傳 : 壓縮(body,圖片)
    圖片 : 縮略圖,webp
  • 3:用戶反饋消耗流量多這種問題怎么查?

    精準流量獲取能力
    所有請求大小及次數(shù)的監(jiān)控
    主動預(yù)警能力

9: -------App電量優(yōu)化-------

電量優(yōu)化介紹及方案選擇

方案一:設(shè)置--電量排行

直觀,但沒有詳細數(shù)據(jù)葱跋,對解決問題沒有太多幫助
找特定場景專項測試

方案二:發(fā)送電量相關(guān)的廣播:獲取電池電量,充電狀態(tài),電池狀態(tài)等信息

缺點:
價值不大:針對手機整體的耗電量,而非特定APP
實時性差,精度較低,被動通知

 IntentFilter filter = new IntentFilter();
        filter.addAction(Intent.ACTION_BATTERY_CHANGED);
        Intent intent = registerReceiver(null, filter);
        LogUtils.i("battery " + intent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1));

方案三:Battery Historian

Google推出的一款A(yù)ndroid系統(tǒng)電量分析工具持寄,支持5.0(API 21)及以上系統(tǒng)的電量分析
優(yōu)缺點: 1:功能強大, 推薦使用。2: 可視化展示指標:耗電比例娱俺,執(zhí)行時間稍味,次數(shù)。3:適合線下使用
測試點相關(guān):1:耗電場景測試:復(fù)雜運算荠卷,視頻播放模庐。2:傳感器相關(guān):使用時長,耗電量油宜,發(fā)熱掂碱。3: 后臺靜默測試。

Battery Historian實戰(zhàn)分析

安裝一:https://github.com/google/battery-historian
安裝二:安裝Docker
docker -- run -p <port>:9999 gcr.io/android-battery-historian/stable:3.0 --port 9999
導(dǎo)出電量信息:
adb shell dumpsys batterystats --reset (重置手機電量)
adb shell dumpsys batterystats --enable full-wake-history (獲取電量)
adb bugreport bugreport.zip (生成文件)
上傳分析:
http://localhost:9999
上傳bugreport文件即可
備用: https://bathist.ef.lc/

電量輔助監(jiān)控實戰(zhàn)

運行時能耗獲壬髟:
adb pull /system/framework/framework-res.apk
在反編譯 xml ---> power_profile

運行時獲取使用時長:
AOP輔助統(tǒng)計: 次數(shù)疼燥,時間,以WakeLock為例

電量優(yōu)化套路總結(jié)

CPU時間片: 1: 獲取運行過程線程CPU消耗,定位CPU占有率異常方法蚁堤。2: 減少后臺應(yīng)用的主動運行醉者。
網(wǎng)絡(luò)相關(guān): 1: 請求時機及次數(shù)限制。2: 數(shù)據(jù)壓縮, 減少時間披诗。3: 禁止使用輪詢功能撬即。
定位相關(guān):1: 根據(jù)場景謹慎選擇定位模式。2: 考慮網(wǎng)絡(luò)定位代替GPS藤巢。3: 使用后務(wù)必及時關(guān)閉搞莺,減少更新頻率。
界面相關(guān):1:離開界面后停止相關(guān)活動掂咒。2: 耗電操作判斷前后臺才沧。
WakeLock相關(guān):1: 注意成對出現(xiàn):acquire與release迈喉。2: 使用帶參數(shù)的acquire。3: finally確保一定會被釋放温圆。4: 常亮場景使用KeepScreenOn即可挨摸。
JobScheduler: 1: 在符合某些條件時創(chuàng)建執(zhí)行在后臺的任務(wù)。2: 把不緊急的任務(wù)放在更合適的時機批量處理岁歉。

@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public class JobSchedulerService extends JobService {

    @Override
    public boolean onStartJob(JobParameters params) {
        // 此處執(zhí)行在主線程
        // 模擬一些處理:批量網(wǎng)絡(luò)請求得运,APM日志上報
        return false;
    }

    @Override
    public boolean onStopJob(JobParameters params) {
        return false;
    }
}

清單文件:

<service android:name=".net.JobSchedulerService"
            android:permission="android.permission.BIND_JOB_SERVICE"/>
    /**
     * 演示JobScheduler的使用
     */
    private void startJobScheduler() {
        if (Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP) {
            JobScheduler jobScheduler = (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE);
            JobInfo.Builder builder = new JobInfo.Builder(1, new ComponentName(getPackageName(), JobSchedulerService.class.getName()));
            builder.setRequiresCharging(true)//需要連接電源
                    .setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED);//網(wǎng)絡(luò)狀態(tài) wife下
            jobScheduler.schedule(builder.build());
        }
    }

10: -------App瘦身優(yōu)化--------

  • 1: 瘦身優(yōu)化及Apk分析方案介紹

Apk組成

  • 代碼相關(guān): classes.dex
  • 資源相關(guān): res , asserts , resources.arsc
  • So相關(guān): lib

Apk分析1:

APKTool,反編譯工具
網(wǎng)址: https://ibotpeaches.github.io/Apktool/
使用: apktool d xx.apk

20211215122048.jpg

Apk分析2:

20211215122808.jpg

20211215123009.jpg
  • 2:代碼瘦身實戰(zhàn)

代碼混淆( Proguard )

  • 配置minifyEnabled為true,debug下不要配置

  • proguard-rules中配置相應(yīng)規(guī)則

  • 3:資源瘦身實戰(zhàn)

  • 4:So瘦身實戰(zhàn)

    • So是Android上的動態(tài)鏈接庫

    • 七種不同類型的CPU框架

    • 更優(yōu)方案

      • 完美支持所有類型設(shè)備代價太大
      • 都放在armeabi目錄,根據(jù)CPU類型加載對應(yīng)架構(gòu)So
    • 其他方案

      • So動態(tài)下載
      • 插件化

so動態(tài)下載,預(yù)加載并使用
插件化手段锅移,每個功能為一個插件熔掺,并且可以從網(wǎng)上下發(fā)下來使用
關(guān)于插件化可以去看阿里的atelas以及360的replugn

  • 5:APK瘦身問題長效治理

    • 發(fā)版之前與上個版本包體積對比,超過閥值則必須優(yōu)化
    • 推進插件化架構(gòu)改造

11: -------App穩(wěn)定性優(yōu)化-------

1640664832617.jpg
1640665182404.jpg
1640665315828.jpg
1640665412705.jpg
圖片.png
圖片.png
圖片.png
圖片.png
圖片.png
圖片.png
圖片.png
圖片.png
圖片.png
圖片.png
圖片.png
圖片.png

12: -------App專項技術(shù)優(yōu)化-----

  • 1 : 列表卡頓優(yōu)化

    常規(guī)方案

    a : convertView復(fù)用,使用ViewHolder
    b : 耗時任務(wù)異步處理

    其他方案

    a : 布局相關(guān)(布局文件的加載是一個io的過程,同時伴隨著反射)

    • 減少布局層級,避免過度繪制
    • 異步inflate或者X2C

    b : 圖片相關(guān)

    • 避免過大尺寸 : GC頻繁,內(nèi)存抖動
    • 滑動時取消加載

    c : 線程相關(guān)

    • 使用線程池收斂線程,降低線程優(yōu)先級
    • 避免UI線程時間片被搶占

    d : TextView優(yōu)化
    原因 : 面對復(fù)雜文本性能不佳
    繪制原理 : BoringLayout單行,StaticLayout多行,DynamicLayout可編輯.
    方案 : 展示類StaticLayout即可,性能優(yōu)于DynamicLayout,異步創(chuàng)建StaticLayout,然后在繪制,效率更高.

Executors.newSingleThreadExecutor().execute(new Runnable() {
            @Override
            public void run() {
                mStaticLayout = new StaticLayout(mText, mTextPaint, (int) width, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0, false);
                postInvalidate();
            }
        });

建議使用:facebook/TextLayoutBuilder
e : SysTrace跟蹤
f : 注意字符串拼接

  • 2 : 存儲優(yōu)化

常規(guī)方案

a : 確保IO操作發(fā)生在非主線程
b : Hook或者AOP輔助
c : SharePreferences(xml)相關(guān)(缺點)
  • 加載慢 : 初始化加載整個文件(雖然SharePreferences的加載在源碼里是在子線程做的,但是獲取的方法會等待這個異步線程執(zhí)行完成,這也就出現(xiàn)了UI線程等待異步線程的情況,所以在attachBaseContext方法中提前異步加載SP文件)
  • 全量寫入 : 單次改動都會導(dǎo)致整體寫入
  • 卡頓 : 補償策略導(dǎo)致
d : SharePreferences替代者MMKV
  • mmap和文件鎖保證數(shù)據(jù)完整
  • 增量寫入 , Protocol Buffer
  • 支持從SharePreferences遷移
e : 日志存儲的優(yōu)化
  • 大量服務(wù)需要日志庫支持
  • 對于性能的要求 : 不影響性能 , 日志不丟失 , 安全
  • 常規(guī)方案
    • 每產(chǎn)生一個日志,寫一遍到磁盤中 : 不丟失 , 性能損耗
    • 開辟一個內(nèi)存buffer , 先存buffer , 再存文件 : 丟日志
  • 優(yōu)化方案使用mmap(實現(xiàn)文件磁盤地址 , 和進程虛擬地址 , 空間中一段虛擬地址的一一映射關(guān)系,實現(xiàn)了這個映射關(guān)系之后,進程就可以采用指針的方式,來讀寫操作一段內(nèi)存,而系統(tǒng)會自動的回寫到對應(yīng)的磁盤中,也就是完成了對文件的操作,而不需要自己調(diào)用read,write這些系統(tǒng)函數(shù))
    • 內(nèi)存映射文件
    • 優(yōu)點 : 高性能 , 不丟失
    • 業(yè)界實現(xiàn) : 微信(XLog) , 美團(Logan)
  • 其他優(yōu)化
    • 常用數(shù)據(jù)緩存 , 避免多次讀寫
    • 合理選擇緩沖區(qū)Buffer大小 : 4 - 8kb
  • 3 : WebView異常監(jiān)控

問題 : 性能與適配

騰訊開源框架 : VasSonic

思路

監(jiān)控屏幕是否白屏 , 白屏則WebView有問題
確認白屏 : 所有像素一樣則認為白屏
/**
 * WebView白屏檢測
 */
public class BlankDetect {

    /**
     * 判斷Bitmap是否都是一個顏色
     * @param bitmap
     * @return
     */
    public static boolean isBlank(View view) {
        Bitmap bitmap = getBitmapFromView(view);
        if (bitmap == null) {
            return true;
        }
        int width = bitmap.getWidth();
        int height = bitmap.getHeight();
        if (width > 0 && height > 0) {
            int originPix = bitmap.getPixel(0, 0);
            int[] target = new int[width];
            Arrays.fill(target, originPix);
            int[] source = new int[width];
            boolean isWhiteScreen = true;
            for (int col = 0; col < height; col++) {
                bitmap.getPixels(source, 0, width, 0, col, width, 1);
                if (!Arrays.equals(target, source)) {
                    isWhiteScreen = false;
                    break;
                }
            }
            return isWhiteScreen;
        }
        return false;
    }

    /**
     * 從View獲取轉(zhuǎn)換到的Bitmap
     * @param view
     * @return
     */
    private static Bitmap getBitmapFromView(View view){
        Bitmap bitmap = Bitmap.createBitmap(view.getWidth(), view.getHeight(), Bitmap.Config.ARGB_8888);
        Canvas canvas = new Canvas(bitmap);
        if (Build.VERSION.SDK_INT >= 11) {
            view.measure(View.MeasureSpec.makeMeasureSpec(view.getWidth(), View.MeasureSpec.EXACTLY), View.MeasureSpec.makeMeasureSpec(view.getHeight(), View.MeasureSpec.EXACTLY));
            view.layout((int) view.getX(), (int) view.getY(), (int) view.getX() + view.getMeasuredWidth(), (int) view.getY() + view.getMeasuredHeight());
        } else {
            view.measure(View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED), View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));
            view.layout(0, 0, view.getMeasuredWidth(), view.getMeasuredHeight());
        }
        view.draw(canvas);
        return bitmap;
    }
}

13: ------課程總結(jié) ---------

Android 基礎(chǔ)架構(gòu)組面試以及面試題
https://zhuanlan.zhihu.com/p/439065104

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末非剃,一起剝皮案震驚了整個濱河市置逻,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌备绽,老刑警劉巖券坞,帶你破解...
    沈念sama閱讀 217,907評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異肺素,居然都是意外死亡恨锚,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,987評論 3 395
  • 文/潘曉璐 我一進店門倍靡,熙熙樓的掌柜王于貴愁眉苦臉地迎上來猴伶,“玉大人,你說我怎么就攤上這事菌瘫∥贤纾” “怎么了?”我有些...
    開封第一講書人閱讀 164,298評論 0 354
  • 文/不壞的土叔 我叫張陵雨让,是天一觀的道長。 經(jīng)常有香客問我忿等,道長栖忠,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,586評論 1 293
  • 正文 為了忘掉前任贸街,我火速辦了婚禮庵寞,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘薛匪。我一直安慰自己捐川,他們只是感情好,可當我...
    茶點故事閱讀 67,633評論 6 392
  • 文/花漫 我一把揭開白布逸尖。 她就那樣靜靜地躺著古沥,像睡著了一般瘸右。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上岩齿,一...
    開封第一講書人閱讀 51,488評論 1 302
  • 那天太颤,我揣著相機與錄音,去河邊找鬼盹沈。 笑死龄章,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的乞封。 我是一名探鬼主播做裙,決...
    沈念sama閱讀 40,275評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼肃晚!你這毒婦竟也來了锚贱?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,176評論 0 276
  • 序言:老撾萬榮一對情侶失蹤陷揪,失蹤者是張志新(化名)和其女友劉穎惋鸥,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體悍缠,經(jīng)...
    沈念sama閱讀 45,619評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡卦绣,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,819評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了飞蚓。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片滤港。...
    茶點故事閱讀 39,932評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖趴拧,靈堂內(nèi)的尸體忽然破棺而出溅漾,到底是詐尸還是另有隱情,我是刑警寧澤著榴,帶...
    沈念sama閱讀 35,655評論 5 346
  • 正文 年R本政府宣布添履,位于F島的核電站,受9級特大地震影響脑又,放射性物質(zhì)發(fā)生泄漏暮胧。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,265評論 3 329
  • 文/蒙蒙 一问麸、第九天 我趴在偏房一處隱蔽的房頂上張望往衷。 院中可真熱鬧,春花似錦严卖、人聲如沸席舍。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,871評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽来颤。三九已至汰扭,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間脚曾,已是汗流浹背东且。 一陣腳步聲響...
    開封第一講書人閱讀 32,994評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留本讥,地道東北人珊泳。 一個月前我還...
    沈念sama閱讀 48,095評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像拷沸,于是被迫代替她去往敵國和親色查。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,884評論 2 354

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