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的效果更好。
- 進程分類:前臺進程,可見進程退唠,服務(wù)進程鹃锈,后臺進程和空進程等;優(yōu)先級從前到后瞧预;
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
8 : 線上內(nèi)存監(jiān)控方案
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
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ù)
- 自帶消息循環(huán)的線程
-
IntentService
- 繼承自Service在內(nèi)部創(chuàng)建HandlerThread
- 異步 , 不占用主線程
- 優(yōu)先級較高 , 不易被系統(tǒng)Kill
- 繼承自Service在內(nèi)部創(chuàng)建HandlerThread
-
AsyncTask
- Android提供的工具類
- 無需自己處理線程切換
- 需注意版本不一致問題
- Android提供的工具類
-
線程池
- Java提供的線程池
- 易復(fù)用 , 減少頻繁創(chuàng)建 , 銷毀的時間
- 功能強大 : 定時 , 任務(wù)隊列 , 并發(fā)數(shù)控制等
- Java提供的線程池
-
RXJava
- 由強大的Scheduler集合提供
- 不同類型的區(qū)分 : IO , Computation
- 由強大的Scheduler集合提供
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)
/**
* 獲取某個時間段流量的使用
*/
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;
}
}
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
Apk分析2:
-
2:代碼瘦身實戰(zhàn)
代碼混淆( Proguard )
配置minifyEnabled為true,debug下不要配置
proguard-rules中配置相應(yīng)規(guī)則
-
3:資源瘦身實戰(zhàn)
- 清理無用資源: Android studio 右鍵 , Refactor , Remove Unused Resource
- 圖片壓縮
- 快速發(fā)展期的APP沒有相關(guān)規(guī)范
- https://tinypng.com/及TinyPngPlugin
- 圖片格式選擇
- 資源混淆
- https://github.com/shwenzhang/AndResGuard : 將資源路徑混淆變短
-
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)化-------
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