卡頓優化
- 2020 年 7 月 28 日
- 筆記
- Android 性能優化
前言
無論是啟動,記憶體,布局等等這些優化,最終的目的就是為了應用不卡頓。應用的體驗性好壞,最直觀的表現就是應用的流暢程度,用戶不知道什麼啟動優化,記憶體不足,等等,應用卡頓,那麼這個應用就不行,被卸載的概率非常大。所以說為了保證用戶留存率,卡頓優化是非常非常的重要。在這篇文章,咱們不討論是什麼原因造成卡頓,其實在前面寫的性能優化文章中,都是造成卡頓的原因,需要需要做好卡頓優化,最好從頭開始一步一步來處理。今天我們主要是介紹一些針對卡頓檢測的一些工具使用。
檢測卡頓常用工具
Systrace
Systrace這個工具在《布局優化》一章節中已經介紹過了,這裡就不在贅述。地址://www.cnblogs.com/huangjialin/p/13353541.html
StrictMode的使用
StrictMode,嚴苛模式。StrictMode是在Android開發過程中不可缺少的性能檢測工具,它能夠檢測出在開發過程中不合理的程式碼塊,非常方便。
策略分類
StrictMode分為執行緒策略(ThreadPolicy)和虛擬機策略(VmPolicy)
使用方式
//開啟Thread策略模式
StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
.detectNetwork()//監測主執行緒使用網路io
// .detectCustomSlowCalls()//監測自定義運行緩慢函數
// .detectDiskReads() // 檢測在UI執行緒讀磁碟操作
// .detectDiskWrites() // 檢測在UI執行緒寫磁碟操作
.detectAll()
.penaltyLog() //寫入日誌
.penaltyDialog()//監測到上述狀況時彈出對話框
.build());
//開啟VM策略模式
StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
// .detectLeakedSqlLiteObjects()//監測sqlite泄露
// .detectLeakedClosableObjects()//監測沒有關閉IO對象
// .setClassInstanceLimit(MainActivity.class, 1) // 設置某個類的同時處於記憶體中的實例上限,可以協助檢查記憶體泄露
// .detectActivityLeaks()
.detectAll()
.penaltyLog()//寫入日誌
.build());
上面基本都注釋好了,這裡就不在一一說明了。如果我們在開發過程中,能夠通過StrictMode這個工具類來規避掉這些問題,那麼將會大大的減少很多性能相關的問題。
BlockCanary使用
我們先看看怎麼使用,然後在看BlockCanary
依賴
debugImplementation 'com.github.bzcoder:blockcanarycompat-android:0.0.4'
在application中
BlockCanary.install(mContext, appBlockCanaryContext).start();
appBlockCanaryContext類
/**
* BlockCanary配置的各種資訊
*/
public class AppBlockCanaryContext extends BlockCanaryContext {
/**
* Implement in your project.
*
* @return Qualifier which can specify this installation, like version + flavor.
*/
public String provideQualifier() {
return "unknown";
}
/**
* Implement in your project.
*
* @return user id
*/
public String provideUid() {
return "uid";
}
/**
* Network type
*
* @return {@link String} like 2G, 3G, 4G, wifi, etc.
*/
public String provideNetworkType() {
return "unknown";
}
/**
* Config monitor duration, after this time BlockCanary will stop, use
* with {@code BlockCanary}'s isMonitorDurationEnd
*
* @return monitor last duration (in hour)
*/
public int provideMonitorDuration() {
return -1;
}
/**
* Config block threshold (in millis), dispatch over this duration is regarded as a BLOCK. You may set it
* from performance of device.
*
* @return threshold in mills
*/
public int provideBlockThreshold() {
return 500;
}
/**
* Thread stack dump interval, use when block happens, BlockCanary will dump on main thread
* stack according to current sample cycle.
* <p>
* Because the implementation mechanism of Looper, real dump interval would be longer than
* the period specified here (especially when cpu is busier).
* </p>
*
* @return dump interval (in millis)
*/
public int provideDumpInterval() {
return provideBlockThreshold();
}
/**
* Path to save log, like "/blockcanary/", will save to sdcard if can.
*
* @return path of log files
*/
public String providePath() {
return "/blockcanary/";
}
/**
* If need notification to notice block.
*
* @return true if need, else if not need.
*/
public boolean displayNotification() {
return true;
}
/**
* Implement in your project, bundle files into a zip file.
*
* @param src files before compress
* @param dest files compressed
* @return true if compression is successful
*/
public boolean zip(File[] src, File dest) {
return false;
}
/**
* Implement in your project, bundled log files.
*
* @param zippedFile zipped file
*/
public void upload(File zippedFile) {
throw new UnsupportedOperationException();
}
/**
* Packages that developer concern, by default it uses process name,
* put high priority one in pre-order.
*
* @return null if simply concern only package with process name.
*/
public List<String> concernPackages() {
return null;
}
/**
* Filter stack without any in concern package, used with @{code concernPackages}.
*
* @return true if filter, false it not.
*/
public boolean filterNonConcernStack() {
return false;
}
/**
* Provide white list, entry in white list will not be shown in ui list.
*
* @return return null if you don't need white-list filter.
*/
public List<String> provideWhiteList() {
LinkedList<String> whiteList = new LinkedList<>();
whiteList.add("org.chromium");
return whiteList;
}
/**
* Whether to delete files whose stack is in white list, used with white-list.
*
* @return true if delete, false it not.
*/
public boolean deleteFilesInWhiteList() {
return true;
}
/**
* Block interceptor, developer may provide their own actions.
*/
public void onBlock(Context context, BlockInfo blockInfo) {
Log.i("lz","blockInfo "+blockInfo.toString());
}
}
就是這麼簡單,一旦發生卡頓,那麼將會以通知的形式在通知欄中顯示出對應的堆棧資訊(和leakcanary類似,但是兩者沒有任何關係)。或者在
SD卡中的blockcanary文件夾中生成對應的log日誌
那麼,問題來了BlockCanary是怎麼檢測卡頓的呢?大家注意AppBlockCanaryContext類中有一個provideBlockThreshold()方法,return了一個500ms。我們知道Android中消息的分發處理都是通過handler來的,handler中有一個looper對象,looper對象中有一個loop方法,看一下源碼
...
for (;;) {
Message msg = queue.next(); // might block
...//省略若干
if (logging != null) {
logging.println(">>>>> Dispatching to " + msg.target + " " +
msg.callback + ": " + msg.what);
}
...//省略若干
try {
msg.target.dispatchMessage(msg);
dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
} finally {
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
...//省略若干
if (logging != null) {
logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
}
...//省略若干
}
...
應用中的所有事件都是通過dispatchMessage這個方法來進行處理,那麼是不是說dispatchMessage執行的時間就是某個事件執行的時間。如果這個時間大於我們在provideBlockThreshold()定義的時間,我們就認為發送了卡頓了,需要優化,就會獲取到對應的堆棧資訊保存起來並提示。
ANR-WatchDog介紹
發生卡頓,嚴重時很容易導致anr,我們知道發生anr的情況基本上就是以下幾種情況
1、觸摸事件在5s沒有得到響應。
2、Bradcast 是10s,後台廣播是60s
3、service:前台service 20s 後台service 200s
用戶在使用應用時,出現ANR,那麼體驗將會是非常非常差,現在很多手機廠商為了用戶體驗,去掉了這個ANR彈框,但是還是會一直卡在這裡。
ANR-WatchDog 使用
添加依賴
implementation 'com.github.anrwatchdog:anrwatchdog:1.3.0'
在application中的onCreate()方法
new ANRWatchDog().setIgnoreDebugger(true).start();
setIgnoreDebugger 是指忽略點斷點的情況
當然如果這樣設置的話,一旦出現ANR,那麼ANR-Watchdog是會將應用殺死的,如果不想殺死應用那麼需要設置一個監聽
new ANRWatchDog().setANRListener(new ANRWatchDog.ANRListener() {
@Override
public void onAppNotResponding(ANRError error) {
// Handle the error. For example, log it to HockeyApp:
}
}).start();
ANR-WatchDog 原理
我用自己的話來總結一下原理:前面我們說了所有的消息都是通過handler中的dispatchMessage方法進行分發的,而ANR-WatchDog 原理就是通過dispatchMessage的時間長度來判斷是否出現anr的,過程大概是這樣的:ANR-WatchDog 開啟一個監控執行緒,並向主執行緒發一個消息,然後自己休眠5s,5s後看主執行緒有沒有處理這個消息,如果處理了,那麼繼續發送,進入一下次檢查。如果沒有處理,說明主執行緒發生了阻塞,收集對應的anr資訊。
Lancet的介紹
前面寫的幾篇性能優化文章基本都介紹有對應的hook框架,這裡再介紹一個AOP框架—Lancet,這幾個框架的比對,後面會再開一篇。這裡先看看怎麼使用。Lancet是一個輕量級Android AOP框架,特點:
1、編譯速度快,並且支援增量編譯
2、簡潔的API,幾行程式碼完成注入需求
3、沒有任何多餘程式碼插入apk
4、支援用於SDK,可以在SDK編寫注入程式碼來修改依賴SDK的APP
使用方式
在根目錄的build.gradle
dependencies{
classpath ‘me.ele:lancet-plugin:1.0.5’
}
在APP目錄的build.gradle
apply plugin: ‘me.ele.lancet’
dependencies {
provided ‘me.ele:lancet-base:1.0.5’
}
開一個類aaaa.java
@Proxy(“i”)
@TargetClass(“android.util.Log”)
public static int anyName(String tag, String msg){
msg = msg + “lancet”;
return (int) Origin.call();
}
這樣,就完成了,當我們通過Log.i(“lancet”,”你好「);來列印日誌的時候,輸出的文本先被Lancet出來後,在輸出。”你好,lancet”
常見卡頓問題解決方案
1、記憶體抖動的問題,GC過於頻繁
2、方法太耗時了(CPU佔用)
3、布局過於複雜,渲染速度慢
….