主流開源框架源碼深刻了解第5篇——BlockCanary源碼分析。(源碼以1.5.0版爲準)java
問:爲何16ms沒完成繪製就會卡頓?android
咱們先來了解幾個概念:git
答:Android系統每隔16ms就會發出VSYNC信號,觸發對UI進行渲染,VSYNC是Vertical Synchronization(垂直同步)的縮寫,能夠簡單的把它認爲是一種定時中斷。在Android 4.1中開始引入VSYNC機制。爲何是16ms?由於Android設定的刷新率是60FPS(Frame Per Second),也就是每秒60幀的刷新率,約16ms刷新一次。這就意味着,咱們須要在16ms內完成下一次要刷新的界面的相關運算,以便界面刷新更新。舉個例子,當運算須要24ms完成時,16ms時就沒法正常刷新了,而須要等到32ms時刷新,這就是丟幀了。丟幀越多,給用戶的感受就越卡頓。github
正常流暢刷新圖示: 算法
哎呀!丟幀啦。卡頓圖示: 數組
在說原理以前,咱們先來了解幾個概念:bash
主線程ActivityThread:嚴格來講,UI主線程不是ActivityThread。ActivityThread類是Android APP進程的初始類,它的main函數是這個APP進程的入口。APP進程中UI事件的執行代碼段都是由ActivityThread提供的。也就是說,Main Thread實例是存在的,只是建立它的代碼咱們不可見。ActivityThread的main函數就是在這個Main Thread裏被執行的。這個主線程會建立一個Looper(Looper.prepare),而Looper又會關聯一個MessageQueue,主線程Looper會在應用的生命週期內不斷輪詢(Looper.loop),從MessageQueue取出Message 更新UI。網絡
// ActivityThread類:
public static void main(String[] args) {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");
...
Looper.prepareMainLooper();
...
ActivityThread thread = new ActivityThread();
thread.attach(false, startSeq);
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
...
// End of event ActivityThreadMain.
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
// Looper開始輪詢
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
複製代碼
Vsync信號:屏幕的刷新過程是每一行從左到右(行刷新,水平刷新,Horizontal Scanning),從上到下(屏幕刷新,垂直刷新,Vertical Scanning)。當整個屏幕刷新完畢,即一個垂直刷新週期完成,會有短暫的空白期,此時發出 VSync 信號。因此,VSync 中的 V指的是垂直刷新中的垂直-Vertical。Android系統每隔16ms發出VSYNC信號,觸發對UI進行渲染,VSync是Vertical Synchronization(垂直同步)的縮寫,是一種在PC上很早就普遍使用的技術,能夠簡單的把它認爲是一種定時中斷。而在Android 4.1(JB)中已經開始引入VSync機制,用來同步渲染,讓App的UI和SurfaceFlinger能夠按硬件產生的VSync節奏進行工做。併發
界面刷新:界面上任何一個 View 的刷新請求最終都會走到 ViewRootImpl 中的 scheduleTraversals() 裏來安排一次遍歷繪製 View 樹的任務;而且經過源碼均可以知道全部的界面刷新(包括Vsync信號觸發的),都會經過Choreographer 的 postCallback() 方法,將界面刷新這個 Runnable 任務以當前事件放進一個待執行的隊列裏,最後經過主線程的Looper的loop方法取出消息並執行。app
// Looper類:
public static void loop() {
final Looper me = myLooper();
...
// 獲取當前Looper的消息隊列
final MessageQueue queue = me.mQueue;
...
for (; ; ) {
// 取出一個消息
Message msg = queue.next(); // might block
...
// "此mLogging可經過Looper.getMainLooper().setMessageLogging方法設置自定義"
final Printer logging = me.mLogging;
if (logging != null) {// 消息處理前
// "若mLogging不爲null,則此處可回調到該類的println方法"
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方法"
logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
}
...
}
}
複製代碼
卡頓發生點:從第3條中,咱們能夠看出,全部消息最終都通過dispatchMessage方法。所以界面的卡頓最終都應該是發生在Handler的dispatchMessage裏。
// Handler類:
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
複製代碼
屏幕刷新機制,具體可參考:Android 屏幕刷新機制
Handler消息機制和View的繪製機制,具體可參考:Handler機制和View繪製流程源碼分析
原理: 上面幾個概念中,其實裏面已包含卡頓監控的原理啦。咱們在界面刷新中Looper的loop方法註釋中聲明:"若mLogging不爲null,則此處可回調到該類的println方法",所以咱們能夠經過自定義的mLogging(實際爲Printer接口的子類),實現Printer接口的println方法,而後在println方法中監控是否有卡頓發生。從loop方法中,能夠看出logging.println調用是成對出現的,會在消息處理先後分別調用,所以能夠在自定義的println方法中經過標識來分辨是消息處理前/後,經過計算時間差與咱們本身設置的閥值(咱們認爲消息處理的最長時間,即卡頓的臨界值)比對,來監控咱們的程序是否發生卡頓。
官方原理介紹示例圖:
// Application中
// 卡頓優化
// 指定的卡頓閥值爲500毫秒——provideBlockThreshold()方法;可在onBlock方法處收集堆棧信息
BlockCanary.install(this, new AppBlockCanaryContext()).start();
/**
* BlockCanary配置的各類信息(部分)
*/
public class AppBlockCanaryContext extends BlockCanaryContext {
// 實現各類上下文,包括應用標識符,用戶uid,網絡類型,卡頓判斷閥值,Log保存位置等
/**
* 指定的卡頓閥值 500毫秒
*/
public int provideBlockThreshold() {
return 500;
}
/**
* 保存日誌的路徑
*/
public String providePath() {
return "/blockcanary/";
}
/**
* 是否須要在通知欄通知卡頓
*/
public boolean displayNotification() {
return true;
}
/**
* 此處可收集堆棧信息,以備上傳分析
* Block interceptor, developer may provide their own actions.
*/
public void onBlock(Context context, BlockInfo blockInfo) {
Log.i("lz","blockInfo "+blockInfo.toString());
// 獲取當前執行方法的調用棧信息
// String trace = Log.getStackTraceString(new Throwable());
}
複製代碼
AppBlockCanaryContext具體配置可參考:AppBlockCanaryContext.java
public static BlockCanary install(Context context, BlockCanaryContext blockCanaryContext) {
// 將上下文和咱們自定義的blockCanaryContext傳入
BlockCanaryContext.init(context, blockCanaryContext);
// 根據displayNotification()設置是否啓用或者禁用DisplayActivity組件
setEnabled(context, DisplayActivity.class, BlockCanaryContext.get().displayNotification());
// 返回單例BlockCanary
return get();
}
複製代碼
咱們能夠看到install方法中幹了3件事情,咱們分別來分析一下。
// BlockCanaryContext類:
private static Context sApplicationContext;
private static BlockCanaryContext sInstance = null;
static void init(Context context, BlockCanaryContext blockCanaryContext) {
sApplicationContext = context;
// 將咱們自定義的blockCanaryContext類,保存在BlockCanaryContext類的成員變量sInstance中
sInstance = blockCanaryContext;
}
複製代碼
第一步,實際上就是在咱們使用BlockCanary時,將咱們自定義的AppBlockCanaryContext保存在BlockCanaryContext類的成員變量sInstance中,以供BlockCanary能夠經過sInstance,來使用咱們自已配置的各類信息(包括應用標識符,用戶uid,網絡類型,卡頓判斷閥值,Log保存位置等)。
// BlockCanaryContext類:
public static BlockCanaryContext get() {
if (sInstance == null) {
throw new RuntimeException("BlockCanaryContext null");
} else {
return sInstance;
}
}
// BlockCanary類:
// 調用newSingleThreadExecutor初始化文件IO線程池
private static final Executor fileIoExecutor = newSingleThreadExecutor("File-IO");
private static void setEnabledBlocking(Context appContext,
Class<?> componentClass,
boolean enabled) {
// 初始化組件對象
ComponentName component = new ComponentName(appContext, componentClass);
// 獲取包管理者
PackageManager packageManager = appContext.getPackageManager();
int newState = enabled ? COMPONENT_ENABLED_STATE_ENABLED : COMPONENT_ENABLED_STATE_DISABLED;
// 動態不殺死應用啓用或者禁用組件,若enabled爲true則啓用,不然禁用
packageManager.setComponentEnabledSetting(component, newState, DONT_KILL_APP);
}
private static void executeOnFileIoThread(Runnable runnable) {
fileIoExecutor.execute(runnable);
}
private static Executor newSingleThreadExecutor(String threadName) {
return Executors.newSingleThreadExecutor(new SingleThreadFactory(threadName));
}
private static void setEnabled(Context context,
final Class<?> componentClass,
final boolean enabled) {
final Context appContext = context.getApplicationContext();
executeOnFileIoThread(new Runnable() {
@Override
public void run() {
setEnabledBlocking(appContext, componentClass, enabled);
}
});
}
複製代碼
從上述代碼中,能夠看出來setEnabled方法,經過參數:BlockCanaryContext.get().displayNotification(),來設置DisplayActivity組件(用於顯示記錄的異常信息給開發者)是否啓用。
public static BlockCanary get() {
if (sInstance == null) {
synchronized (BlockCanary.class) {
if (sInstance == null) {
sInstance = new BlockCanary();
}
}
}
return sInstance;
}
private BlockCanary() {
// 將BlockCanaryContext.get(),即sInstance(咱們自定義的AppBlockCanaryContext)
// 設置到BlockCanary核心類BlockCanaryInternals中,用來獲取咱們自定義配置的信息
BlockCanaryInternals.setContext(BlockCanaryContext.get());
// 初始化BlockCanaryInternals
mBlockCanaryCore = BlockCanaryInternals.getInstance();
// 添加攔截器(將自定義的AppBlockCanaryContext添加到攔截器中,可回調其onBlock方法)
mBlockCanaryCore.addBlockInterceptor(BlockCanaryContext.get());
// 根據咱們自定義的AppBlockCanaryContext獲取是否展現通知,默認爲true
if (!BlockCanaryContext.get().displayNotification()) {
return;
}
// 若容許展現通知,則將DisplayService繼續添加到攔截器中
mBlockCanaryCore.addBlockInterceptor(new DisplayService());
}
複製代碼
咱們從這部分源碼中看到,BlockCanary的構造方法中完成了其核心類:BlockCanaryInternals的初始化與設置(包括sInstance傳入和添加攔截器),那麼咱們再來看一看BlockCanaryInternals的初始化都有些什麼操做:
// BlockCanaryInternals類:
static BlockCanaryInternals getInstance() {
if (sInstance == null) {
synchronized (BlockCanaryInternals.class) {
if (sInstance == null) {
sInstance = new BlockCanaryInternals();
}
}
}
return sInstance;
}
public BlockCanaryInternals() {
// 初始化堆棧採樣器
stackSampler = new StackSampler(
Looper.getMainLooper().getThread(),
sContext.provideDumpInterval());
// 初始化cpu採樣器
cpuSampler = new CpuSampler(sContext.provideDumpInterval());
// 設置監視器,傳入LooperMonitor looper監控器
// LooperMonitor 實際上就是咱們上面【BlockCanary原理】中講到的Printer接口的子類
setMonitor(new LooperMonitor(new LooperMonitor.BlockListener() {
@Override
public void onBlockEvent(long realTimeStart, long realTimeEnd,
long threadTimeStart, long threadTimeEnd) {
// Get recent thread-stack entries and cpu usage
ArrayList<String> threadStackEntries = stackSampler
.getThreadStackEntries(realTimeStart, realTimeEnd);
if (!threadStackEntries.isEmpty()) {
BlockInfo blockInfo = BlockInfo.newInstance()
.setMainThreadTimeCost(realTimeStart, realTimeEnd, threadTimeStart,
threadTimeEnd)
.setCpuBusyFlag(cpuSampler.isCpuBusy(realTimeStart, realTimeEnd))
.setRecentCpuRate(cpuSampler.getCpuRateInfo())
.setThreadStackEntries(threadStackEntries)
.flushString();
// 卡頓日誌記錄
LogWriter.save(blockInfo.toString());
if (mInterceptorChain.size() != 0) {
// 遍歷全部攔截器成員,調用每一個成員的onBlock,並將卡頓信息傳入
for (BlockInterceptor interceptor : mInterceptorChain) {
interceptor.onBlock(getContext().provideContext(), blockInfo);
}
}
}
}
}, getContext().provideBlockThreshold(), getContext().stopWhenDebugging()));
LogWriter.cleanObsolete();
}
private void setMonitor(LooperMonitor looperPrinter) {
// setMonitor把建立的LooperMonitor賦值給BlockCanaryInternals的成員變量monitor。
monitor = looperPrinter;
}
複製代碼
BlockCanaryInternals的構造方法中,初始化了幾個變量,包括:堆棧採樣器、cpu採樣器、looper監控器,以及looper監控器的回調方法onBlockEvent。
public void start() {
if (!mMonitorStarted) {
mMonitorStarted = true;
// 設置Looper中的mLogging,每次消息處理先後,
// 均可回調自定義的實現Printer接口LooperMonitor類的println方法
Looper.getMainLooper().setMessageLogging(mBlockCanaryCore.monitor);
}
}
複製代碼
將在BlockCanaryInternals中建立的LooperMonitor給主線程Looper的mLogging變量賦值。這樣主線程Looper就能夠消息分發先後使用LooperMonitor#println輸出日誌。此時BlockCanary已經開始監控卡頓狀況,因此咱們如今須要關注的就是LooperMonitor的println方法。
再回顧一下Looper的loop方法:
//Looper
for (;;) {
Message msg = queue.next();
// This must be in a local variable, in case a UI event sets the logger
Printer logging = me.mLogging;
if (logging != null) {
logging.println(">>>>> Dispatching to " + msg.target + " " +
msg.callback + ": " + msg.what);
}
msg.target.dispatchMessage(msg);
if (logging != null) {
logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
}
...
}
複製代碼
Lopper的loop方法中logging如今就是BlockCanary中實現了Printer接口的LooperMonitor類。
// LooperMonitor類:
private boolean mPrintingStarted = false;
@Override
public void println(String x) {
if (mStopWhenDebugging && Debug.isDebuggerConnected()) {
return;
}
if (!mPrintingStarted) {
// 獲取消息處理前系統當前時間
mStartTimestamp = System.currentTimeMillis();
// 獲取當前線程運行時間
mStartThreadTimestamp = SystemClock.currentThreadTimeMillis();
// 將此標識置爲true,下此進入就是消息處理以後
mPrintingStarted = true;
// 開始獲取堆棧信息
startDump();
} else {
// 獲取消息處理後系統當前時間
final long endTime = System.currentTimeMillis();
// 將此標識置爲true,下此進入就是下一條消息處理以前
mPrintingStarted = false;
// 判斷是否發生卡頓
if (isBlock(endTime)) {
// 發生卡頓,通知卡頓事件發生
notifyBlockEvent(endTime);
}
// 中止獲取堆棧信息
stopDump();
}
}
複製代碼
對於每個Message消息而言,println方法都是按順序成對出現的,所以根據mPrintingStarted是不是消息開始前的標識,來判斷此消息當前的處理先後兩種狀態。下面咱們來看一下卡頓發生的狀況:
// LooperMonitor類:
private boolean isBlock(long endTime) {
// 判斷消息執行時間是否超過閾值
return endTime - mStartTimestamp > mBlockThresholdMillis;
}
// 若超過閥值,則通知卡頓事件
private void notifyBlockEvent(final long endTime) {
final long startTime = mStartTimestamp;
final long startThreadTime = mStartThreadTimestamp;
// 獲取消息處理結束後線程運行時間
final long endThreadTime = SystemClock.currentThreadTimeMillis();
// HandlerThreadFactory異步線程Looper的Handler
HandlerThreadFactory.getWriteLogThreadHandler().post(new Runnable() {
@Override
public void run() {
// 異步線程中執行onBlockEvent回調
mBlockListener.onBlockEvent(startTime, endTime, startThreadTime, endThreadTime);
}
});
}
複製代碼
經過消息執行的先後時間差 - 咱們自定義AppBlockCanaryContext中設置的卡頓閥值,來肯定是否發生卡頓,卡頓後的回調消息是在設置爲異步線程Looper的Handler中執行。
// BlockCanaryInternals類構造方法中:
setMonitor(new LooperMonitor(new LooperMonitor.BlockListener() {
@Override
public void onBlockEvent(long realTimeStart, long realTimeEnd,
long threadTimeStart, long threadTimeEnd) {
// 根據開始及結束時間,從堆棧採集器的map當中獲取記錄信息
ArrayList<String> threadStackEntries = stackSampler
.getThreadStackEntries(realTimeStart, realTimeEnd);
if (!threadStackEntries.isEmpty()) {
// 構建 BlockInfo對象,設置相關的信息
BlockInfo blockInfo = BlockInfo.newInstance()
.setMainThreadTimeCost(realTimeStart, realTimeEnd, threadTimeStart, threadTimeEnd)
.setCpuBusyFlag(cpuSampler.isCpuBusy(realTimeStart, realTimeEnd))
.setRecentCpuRate(cpuSampler.getCpuRateInfo())
.setThreadStackEntries(threadStackEntries)
.flushString();
// 記錄信息
LogWriter.save(blockInfo.toString());
// 遍歷攔截器,通知
if (mInterceptorChain.size() != 0) {
for (BlockInterceptor interceptor : mInterceptorChain) {
interceptor.onBlock(getContext().provideContext(), blockInfo);
}
}
}
}
}, getContext().provideBlockThreshold(), getContext().stopWhenDebugging()));
複製代碼
最後若攔截器成員中存在DisplayService,則會發送前臺的通知,代碼以下:
// DisplayService類:
@Override
public void onBlock(Context context, BlockInfo blockInfo) {
Intent intent = new Intent(context, DisplayActivity.class);
intent.putExtra("show_latest", blockInfo.timeStart);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
PendingIntent pendingIntent = PendingIntent.getActivity(context, 1, intent, FLAG_UPDATE_CURRENT);
String contentTitle = context.getString(R.string.block_canary_class_has_blocked, blockInfo.timeStart);
String contentText = context.getString(R.string.block_canary_notification_message);
// 根據不一樣的sdk兼容全部版本的通知欄顯示
show(context, contentTitle, contentText, pendingIntent);
}
複製代碼
// LooperMonitor類:
private void startDump() {
if (null != BlockCanaryInternals.getInstance().stackSampler) {
// 開始記錄堆棧信息
BlockCanaryInternals.getInstance().stackSampler.start();
}
if (null != BlockCanaryInternals.getInstance().cpuSampler) {
// 開始記錄cpu信息
BlockCanaryInternals.getInstance().cpuSampler.start();
}
}
private void stopDump() {
if (null != BlockCanaryInternals.getInstance().stackSampler) {
// 中止記錄堆棧信息
BlockCanaryInternals.getInstance().stackSampler.stop();
}
if (null != BlockCanaryInternals.getInstance().cpuSampler) {
// 中止記錄cpu信息
BlockCanaryInternals.getInstance().cpuSampler.stop();
}
}
public void start() {
// mShouldSample其實是AtomicBoolean原子布爾值。
if (mShouldSample.get()) {
return;
}
// 原子布爾值,可以保證在高併發的狀況下只有一個線程可以訪問這個屬性值。
// 原子布爾值具體詳情,參考:https://www.jianshu.com/p/8a44d4a819bc
mShouldSample.set(true);
// 移除上一次任務
HandlerThreadFactory.getTimerThreadHandler().removeCallbacks(mRunnable);
// 延遲 卡頓閥值*0.8 的時間執行相應信息的收集
HandlerThreadFactory.getTimerThreadHandler().postDelayed(mRunnable,
BlockCanaryInternals.getInstance().getSampleDelay());
}
public void stop() {
if (!mShouldSample.get()) {
return;
}
mShouldSample.set(false);
// 移除任務
HandlerThreadFactory.getTimerThreadHandler().removeCallbacks(mRunnable);
}
private Runnable mRunnable = new Runnable() {
@Override
public void run() {
// 調用doSample方法,執行相應操做
doSample();
// 若此原子布爾值爲true,即此時爲開始記錄堆棧信息
if (mShouldSample.get()) {
// 延遲 卡頓閥值 時間執行任務
HandlerThreadFactory.getTimerThreadHandler()
.postDelayed(mRunnable, mSampleInterval);
}
}
};
// BlockCanaryInternals類:
long getSampleDelay() {
// 卡頓閥值的0.8
return (long) (BlockCanaryInternals.getContext().provideBlockThreshold() * 0.8f);
}
複製代碼
卡頓信息的記錄,其實是經過CpuSampler和StackSampler二者相同父類AbstractSampler類,提供的方法start和stop記錄,而start方法中經過HandlerThreadFactory獲取異步的TimerThreadHandler發送延時消息,最後分別調用CpuSampler類和StackSampler類中,繼承自AbstractSampler抽象方法doSample()完成的卡頓信息的記錄。下面分別看一下CpuSampler類和StackSampler類的doSample()方法的實現。
StackSampler類的doSample()方法
private static final LinkedHashMap<Long, String> sStackMap = new LinkedHashMap<>();
@Override
protected void doSample() {
StringBuilder stringBuilder = new StringBuilder();
// mCurrentThread.getStackTrace():返回一個表示該線程堆棧轉儲的堆棧跟蹤元素數組。
// 經過mCurrentThread.getStackTrace()獲取StackTraceElement,加入到StringBuilder
for (StackTraceElement stackTraceElement : mCurrentThread.getStackTrace()) {
stringBuilder
.append(stackTraceElement.toString())
.append(BlockInfo.SEPARATOR);
}
synchronized (sStackMap) {
// Lru算法,控制LinkHashMap的長度,移除最先添加進來的數據
if (sStackMap.size() == mMaxEntryCount && mMaxEntryCount > 0) {
sStackMap.remove(sStackMap.keySet().iterator().next());
}
// 以當前系統時間爲key,存儲此處的堆棧信息
sStackMap.put(System.currentTimeMillis(), stringBuilder.toString());
}
}
複製代碼
CpuSampler類的doSample()方法
// 主要經過獲取/proc/stat文件 去獲取cpu的信息
@Override
protected void doSample() {
BufferedReader cpuReader = null;
BufferedReader pidReader = null;
try {
// 經過bufferReader讀取 /proc 下的cpu文件
cpuReader = new BufferedReader(new InputStreamReader(
new FileInputStream("/proc/stat")), BUFFER_SIZE);
String cpuRate = cpuReader.readLine();
if (cpuRate == null) {
cpuRate = "";
}
if (mPid == 0) {
mPid = android.os.Process.myPid();
}
// 經過bufferReader讀取 /proc 下的內存文件
pidReader = new BufferedReader(new InputStreamReader(
new FileInputStream("/proc/" + mPid + "/stat")), BUFFER_SIZE);
String pidCpuRate = pidReader.readLine();
if (pidCpuRate == null) {
pidCpuRate = "";
}
parse(cpuRate, pidCpuRate);
} catch (Throwable throwable) {
Log.e(TAG, "doSample: ", throwable);
} finally {
try {
if (cpuReader != null) {
cpuReader.close();
}
if (pidReader != null) {
pidReader.close();
}
} catch (IOException exception) {
Log.e(TAG, "doSample: ", exception);
}
}
}
private void parse(String cpuRate, String pidCpuRate) {
String[] cpuInfoArray = cpuRate.split(" ");
if (cpuInfoArray.length < 9) {
return;
}
long user = Long.parseLong(cpuInfoArray[2]);
long nice = Long.parseLong(cpuInfoArray[3]);
long system = Long.parseLong(cpuInfoArray[4]);
long idle = Long.parseLong(cpuInfoArray[5]);
long ioWait = Long.parseLong(cpuInfoArray[6]);
long total = user + nice + system + idle + ioWait
+ Long.parseLong(cpuInfoArray[7])
+ Long.parseLong(cpuInfoArray[8]);
String[] pidCpuInfoList = pidCpuRate.split(" ");
if (pidCpuInfoList.length < 17) {
return;
}
long appCpuTime = Long.parseLong(pidCpuInfoList[13])
+ Long.parseLong(pidCpuInfoList[14])
+ Long.parseLong(pidCpuInfoList[15])
+ Long.parseLong(pidCpuInfoList[16]);
if (mTotalLast != 0) {
StringBuilder stringBuilder = new StringBuilder();
long idleTime = idle - mIdleLast;
long totalTime = total - mTotalLast;
stringBuilder
.append("cpu:")
.append((totalTime - idleTime) * 100L / totalTime)
.append("% ")
.append("app:")
.append((appCpuTime - mAppCpuTimeLast) * 100L / totalTime)
.append("% ")
.append("[")
.append("user:").append((user - mUserLast) * 100L / totalTime)
.append("% ")
.append("system:").append((system - mSystemLast) * 100L / totalTime)
.append("% ")
.append("ioWait:").append((ioWait - mIoWaitLast) * 100L / totalTime)
.append("% ]");
synchronized (mCpuInfoEntries) {
mCpuInfoEntries.put(System.currentTimeMillis(), stringBuilder.toString());
if (mCpuInfoEntries.size() > MAX_ENTRY_COUNT) {
for (Map.Entry<Long, String> entry : mCpuInfoEntries.entrySet()) {
Long key = entry.getKey();
mCpuInfoEntries.remove(key);
break;
}
}
}
}
mUserLast = user;
mSystemLast = system;
mIdleLast = idle;
mIoWaitLast = ioWait;
mTotalLast = total;
mAppCpuTimeLast = appCpuTime;
}
複製代碼
Android平臺CPU的一些常識:
> cat /proc/stat
1. cpu 2255 34 2290 22625563 6290 127 456
2. cpu0 1132 34 1441 11311718 3675 127 438
3. cpu1 1123 0 849 11313845 2614 0 18
4. intr 114930548 113199788 3 0 5 263 0 4 [... lots more numbers ...]
5. ctxt 1990473
6. btime 1062191376
7. processes 2915
8. procs_running 1
9. procs_blocked 0
複製代碼
這些數字指明瞭CPU執行不一樣的任務所消耗的時間(從系統啓動開始累計到當前時刻)。時間單位是USER_HZ或jiffies(一般是百分之一秒)。參數 解析 (如下數值都是從系統啓動累計到當前時刻)
user (38082) 處於用戶態的運行時間,不包含 nice值爲負進程
nice (627) nice值爲負的進程所佔用的CPU時間
system (27594) 處於核心態的運行時間
idle (893908) 除IO等待時間之外的其它等待時間iowait (12256) 從系統啓動開始累計到當前時刻,IO等待時間
irq (581) 硬中斷時間
irq (581) 軟中斷時間
stealstolen(0) 一個其餘的操做系統運行在虛擬環境下所花費的時間
guest(0) 這是在Linux內核控制下爲客戶操做系統運行虛擬CPU所花費的時間
複製代碼
總結:總的CPU時間totalCpuTime = user + nice + system + idle + iowait + irq + softirq + stealstolen + guestcat /proc/6873/stat
6873 (a.out) R 6723 6873 6723 34819 6873 8388608 77 0 0 0 41958 31 0 0 25 0 3 0 5882654 1409024 56 4294967295 134512640 134513720 3215579040 0 2097798 0 0 0 0 0 0 0 17 0 0 0
複製代碼
計算CPU使用率有用相關參數:參數 解析
pid=6873 進程號
utime=1587 該任務在用戶態運行的時間,單位爲jiffies
stime=41958 該任務在覈心態運行的時間,單位爲jiffies
cutime=0 全部已死線程在用戶態運行的時間,單位爲jiffies
cstime=0 全部已死在覈心態運行的時間,單位爲jiffies
複製代碼
結論:進程的總CPU時間processCpuTime = utime + stime + cutime + cstime,該值包括其全部線程的CPU時間。卡頓發生時,會回調LooperMonitor的onBlockEvent方法,而此方法中,會將卡頓信息寫入本地日誌文件,日誌的路徑在自定義的AppBlockCanaryContext中定義。
// BlockCanaryInternals類構造方法中:
setMonitor(new LooperMonitor(new LooperMonitor.BlockListener() {
@Override
public void onBlockEvent(long realTimeStart, long realTimeEnd,
long threadTimeStart, long threadTimeEnd) {
ArrayList<String> threadStackEntries = stackSampler
.getThreadStackEntries(realTimeStart, realTimeEnd);
if (!threadStackEntries.isEmpty()) {
BlockInfo blockInfo = BlockInfo.newInstance()
.setMainThreadTimeCost(realTimeStart, realTimeEnd, threadTimeStart, threadTimeEnd)
.setCpuBusyFlag(cpuSampler.isCpuBusy(realTimeStart, realTimeEnd))
.setRecentCpuRate(cpuSampler.getCpuRateInfo())
.setThreadStackEntries(threadStackEntries)
.flushString();
// 日誌保存
LogWriter.save(blockInfo.toString());
if (mInterceptorChain.size() != 0) {
for (BlockInterceptor interceptor : mInterceptorChain) {
interceptor.onBlock(getContext().provideContext(), blockInfo);
}
}
}
}
}, getContext().provideBlockThreshold(), getContext().stopWhenDebugging()));
// LogWriter類:
public static String save(String str) {
String path;
synchronized (SAVE_DELETE_LOCK) {
path = save("looper", str);
}
return path;
}
private static String save(String logFileName, String str) {
String path = "";
BufferedWriter writer = null;
try {
// 根據開發者本身配置的日誌存儲路徑,生成文件
File file = BlockCanaryInternals.detectedBlockDirectory();
long time = System.currentTimeMillis();
path = file.getAbsolutePath() + "/"
+ logFileName + "-"
+ FILE_NAME_FORMATTER.format(time) + ".log";
// 寫入卡頓信息
OutputStreamWriter out =
new OutputStreamWriter(new FileOutputStream(path, true), "UTF-8");
writer = new BufferedWriter(out);
writer.write(BlockInfo.SEPARATOR);
writer.write("**********************");
writer.write(BlockInfo.SEPARATOR);
writer.write(TIME_FORMATTER.format(time) + "(write log time)");
writer.write(BlockInfo.SEPARATOR);
writer.write(BlockInfo.SEPARATOR);
writer.write(str);
writer.write(BlockInfo.SEPARATOR);
writer.flush();
writer.close();
writer = null;
} catch (Throwable t) {
Log.e(TAG, "save: ", t);
} finally {
try {
if (writer != null) {
writer.close();
}
} catch (Exception e) {
Log.e(TAG, "save: ", e);
}
}
return path;
}
複製代碼
至此,BlockCanary的總體已分析完成,收工咯。
...
注:如有什麼地方闡述有誤,敬請指正。期待您的點贊哦!!!