寫在前面
近期開始 Android Framework 層的學(xué)習(xí)烈涮,然而較為龐大的 Framework 讓人感覺無從下手揉阎。碰巧看到一篇文章說到騰訊的 性能監(jiān)控框架 Matrix 用到了大量 Framework 相關(guān)的知識,所以試著分析下該框架的源碼實(shí)現(xiàn)。
在學(xué)習(xí)大佬們代碼的同時(shí)主要關(guān)注該框架用到了哪些、是怎么使用的 Framework 的內(nèi)容床玻。
一、Matrix 簡介
官方說明
Matrix 是一款微信研發(fā)并日常使用的應(yīng)用性能接入框架后添,支持iOS, macOS和Android笨枯。 Matrix 通過接入各種性能監(jiān)控方案薪丁,對性能監(jiān)控項(xiàng)的異常數(shù)據(jù)進(jìn)行采集和分析,輸出相應(yīng)的問題分析馅精、定位與優(yōu)化建議严嗜,從而幫助開發(fā)者開發(fā)出更高質(zhì)量的應(yīng)用。
大公司就是大氣洲敢,直接雙端都給你整一套漫玄。
Matrix for Android
Matrix-android 當(dāng)前監(jiān)控范圍包括:應(yīng)用安裝包大小,幀率變化压彭,啟動耗時(shí)睦优,卡頓,慢方法壮不,SQLite 操作優(yōu)化汗盘,文件讀寫,內(nèi)存泄漏等等询一。
- APK Checker: 針對 APK 安裝包的分析檢測工具隐孽,根據(jù)一系列設(shè)定好的規(guī)則,檢測 APK 是否存在特定的問題健蕊,并輸出較為詳細(xì)的檢測結(jié)果報(bào)告菱阵,用于分析排查問題以及版本追蹤
- Resource Canary: 基于 WeakReference 的特性和 Square Haha 庫開發(fā)的 Activity 泄漏和 Bitmap 重復(fù)創(chuàng)建檢測工具
- Trace Canary: 監(jiān)控界面流暢性、啟動耗時(shí)缩功、頁面切換耗時(shí)晴及、慢函數(shù)及卡頓等問題
- SQLite Lint: 按官方最佳實(shí)踐自動化檢測 SQLite 語句的使用質(zhì)量
- IO Canary: 檢測文件 IO 問題,包括:文件 IO 監(jiān)控和 Closeable Leak 監(jiān)控
好家伙嫡锌,功能還真不少虑稼。看樣子是個大工程世舰,排個計(jì)劃吧:
- 首先是對框架的大致了解动雹,對框架中用到的需要用到的類槽卫、函數(shù)進(jìn)行預(yù)習(xí)跟压;
- 從某一模塊入手,分析功能實(shí)現(xiàn)的同時(shí)注重 Framework 的內(nèi)容歼培;
- 最后進(jìn)行總結(jié)震蒋,思考為什么這樣做,有沒有更好的做法躲庄。
那么本文先大概了解一下框架查剖,遇到 Framework 中的知識進(jìn)行簡單的了解和預(yù)習(xí)。
二噪窘、使用 Matrix
有關(guān) Matrix 的接入和使用官方文檔已經(jīng)寫得很清楚了笋庄,本文簡單總結(jié)下:
- 引入 Matrix 庫,添加相關(guān)依賴;
- 創(chuàng)建插件監(jiān)聽直砂,可以接收到插件的啟動和工作通知菌仁。
Matrix 的功能基本都是由這些 插件 Plugin 實(shí)現(xiàn)的,這樣做的好處一方面是解耦静暂,另一方面是用戶可以根據(jù)需要選擇使用的功能济丘。 - 在 Application 中初始化 Matrix,添加插件并開啟插件功能洽蛀。
三摹迷、Matrix 結(jié)構(gòu)
接下來根據(jù) Matrix 的創(chuàng)建和使用來確定它的結(jié)構(gòu)。
初始化
Matrix 需要在 Applicaton 中初始化郊供,對象的構(gòu)建方式是熟悉的建造者模式:
public class MatrixApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
// 創(chuàng)建 Matrix峡碉,傳入 application
Matrix.Builder builder = new Matrix.Builder(this);
// 設(shè)置插件監(jiān)聽
builder.patchListener(new TestPluginListener(this));
// 創(chuàng)建插件
TracePlugin tracePlugin = new TracePlugin(new TraceConfig.Builder()
.build());
// 添加插件
builder.plugin(tracePlugin);
// 初始化
Matrix.init(builder.build());
// 插件開始工作
tracePlugin.start();
}
}
維護(hù)的變量也比較簡單:
public static class Builder {
// 持有 Application
private final Application application;
// 插件工作回調(diào)
private PluginListener pluginListener;
// 維護(hù)插件列表
private HashSet<Plugin> plugins = new HashSet<>();
public Builder(Application app) {
if (app == null) {
throw new RuntimeException("matrix init, application is null");
}
this.application = app;
}
}
- PluginListener:是一個接口,定義了插件的生命周期驮审。官方提供了默認(rèn)實(shí)現(xiàn) DefaultPluginListener异赫,我們只需要繼承該類并設(shè)置給 Matrix 就可以接收到插件的生命周期。
public interface PluginListener {
void onInit(Plugin plugin);// 初始化
void onStart(Plugin plugin);// 開始
void onStop(Plugin plugin);// 結(jié)束
void onDestroy(Plugin plugin);// 銷毀
void onReportIssue(Issue issue);// 提交報(bào)告
}
public class TestPluginListener extends DefaultPluginListener {
public static final String TAG = "Matrix.TestPluginListener";
public TestPluginListener(Context context) {
super(context);
}
@Override
public void onReportIssue(Issue issue) {
super.onReportIssue(issue);
MatrixLog.e(TAG, issue.toString());
//add your code to process data
}
}
- plugins:插件列表头岔,使用 HashSet 維護(hù)塔拳,保證插件不會重復(fù)添加。
插件 Plugin
插件是 Matrix 的重要組成結(jié)構(gòu)峡竣,通過繼承抽象類 Plugin 來創(chuàng)建一個插件靠抑,Plugin 是接口 IPlugin 的實(shí)現(xiàn)。IPlugin 接口定義了插件所實(shí)現(xiàn)的主要功能:
public interface IPlugin {
Application getApplication();
void init(Application application, PluginListener pluginListener);
void start();
void stop();
void destroy();
String getTag();
void onForeground(boolean isForeground);
}
比如分析卡頓的 TracePlugin适掰,它就是一個繼承了 Plugin 的插件實(shí)現(xiàn)颂碧,在工作的過程中也會調(diào)用這些方法。
Matrix 構(gòu)造器
Matrix.Builder builder = new Matrix.Builder(this);
Matrix.init(builder.build());
調(diào)用 builder.build()
之后會創(chuàng)建一個 Matrix 對象类浪,然后創(chuàng)建一個用于監(jiān)聽 App 生命周期的 AppActiveMatrixDelegate载城。之后遍歷所有的插件列表,并調(diào)用它們的 init()
方法初始化插件费就。
private Matrix(Application app, PluginListener listener, HashSet<Plugin> plugins) {
this.application = app;
this.pluginListener = listener;
this.plugins = plugins;
// 初始化
AppActiveMatrixDelegate.INSTANCE.init(application);
for (Plugin plugin : plugins) {
plugin.init(application, pluginListener);
pluginListener.onInit(plugin);
}
}
AppActiveMatrixDelegate
這個類是個枚舉單例诉瓦,并且監(jiān)聽?wèi)?yīng)用 Activity 生命周期以及內(nèi)存狀態(tài)。
public enum AppActiveMatrixDelegate {
// 1. 利用枚舉創(chuàng)建單例
INSTANCE;
private static final String TAG = "Matrix.AppActiveDelegate";
private final Set<IAppForeground> listeners = new HashSet();
private boolean isAppForeground = false;
private String visibleScene = "default";
private Controller controller = new Controller();
private boolean isInit = false;
private String currentFragmentName;
private Handler handler;
public void init(Application application) {
if (isInit) {
MatrixLog.e(TAG, "has inited!");
return;
}
this.isInit = true;
// 2. HandlerTherad:一個封裝了 Handler 的線程
if (null != MatrixHandlerThread.getDefaultHandlerThread()) {
this.handler = new Handler(MatrixHandlerThread.getDefaultHandlerThread().getLooper());
}
// 3. 注冊應(yīng)用內(nèi)存狀態(tài)回調(diào)
application.registerComponentCallbacks(controller);
// 4. 注冊監(jiān)聽 Activity 生命周期
application.registerActivityLifecycleCallbacks(controller);
}
}
- 枚舉實(shí)現(xiàn)單例的原理是利用枚舉的特點(diǎn)來實(shí)現(xiàn)的:枚舉類型是線程安全的力细,并且只會裝載一次睬澡。
- HandlerTherad 繼承了 Thread,可以看作是一個線程眠蚂。而其內(nèi)部維護(hù)了一個 Handler煞聪,在調(diào)用 start() 方法后初始化 Looper,可以很方便地執(zhí)行異步任務(wù)逝慧。其它類可以通過
getThreadHandler
方法獲取 HandlerTherad 的 Handler昔脯,然后 post 任務(wù)由 HandlerTherad 內(nèi)部的 Looper 取出并執(zhí)行啄糙。 - registerComponentCallbacks:Application 的方法,作用是監(jiān)聽?wèi)?yīng)用的內(nèi)存狀態(tài)云稚。
在系統(tǒng)內(nèi)存不足迈套,所有后臺程序(優(yōu)先級為background的進(jìn)程,不是指后臺運(yùn)行的進(jìn)程)都被殺死時(shí)碱鳞,系統(tǒng)會調(diào)用 onLowMemory桑李。
OnTrimMemory 是 Android 4.0 之后提供的 API。比起 onLowMemory窿给,這個回調(diào)新增返回了一個 int 值表示當(dāng)前內(nèi)存狀態(tài)贵白,開發(fā)者可以根據(jù)返回的狀態(tài)來適當(dāng)回收資源避免 app 被殺死的風(fēng)險(xiǎn)。
想要監(jiān)聽內(nèi)存狀態(tài)回調(diào)需要實(shí)現(xiàn) ComponentCallbacks2 接口崩泡,該接口是 ComponentCallbacks 的升級版禁荒。
- registerActivityLifecycleCallbacks:注冊監(jiān)聽 Activity 狀態(tài)回調(diào),實(shí)現(xiàn) Application.ActivityLifecycleCallbacks 接口以監(jiān)聽 Activity 生命周期回調(diào)角撞。
Controller
Controller 實(shí)現(xiàn) ComponentCallbacks2 監(jiān)聽內(nèi)存狀態(tài)呛伴、ActivityLifecycleCallbacks 監(jiān)聽 Activity 生命周期。
private final class Controller implements Application.ActivityLifecycleCallbacks, ComponentCallbacks2 {
@Override
public void onActivityStarted(Activity activity) {
// 1. 記錄啟動的 Activity
updateScene(activity);
// 1.1 告知 listeners Activity 在前臺了
onDispatchForeground(getVisibleScene());
}
@Override
public void onActivityStopped(Activity activity) {
// 1.2 獲取棧頂活動的 Activity
if (getTopActivityName() == null) {
// 1.3 告知 listeners Activity 在后臺了
onDispatchBackground(getVisibleScene());
}
}
...
@Override
public void onTrimMemory(int level) {
MatrixLog.i(TAG, "[onTrimMemory] level:%s", level);
// 2. TRIM_MEMORY_UI_HIDDEN 表示當(dāng)前 app UI 不再可見
if (level == TRIM_MEMORY_UI_HIDDEN && isAppForeground) { // fallback
onDispatchBackground(visibleScene);
}
}
}
Controller 的邏輯主要為了區(qū)分 App 進(jìn)入前臺或后臺谒所。怎么區(qū)分呢热康?
- 有 Activity 回調(diào)了 onStart,說明 App 進(jìn)入了前臺劣领,記錄并返回給監(jiān)聽就行姐军;
- 有 Activity 回調(diào)了 onStop,且棧頂沒有 Resume 狀態(tài)的 Activity尖淘,說明 App 進(jìn)入了后臺奕锌;
用戶點(diǎn)擊了 Home 或者 Back 鍵,系統(tǒng)會通過 onTrimMemory 回調(diào)一個 TRIM_MEMORY_UI_HIDDEN 狀態(tài)村生,告知這是 App 進(jìn)入后臺惊暴,是回收資源的大好時(shí)機(jī)。
回調(diào) onStart 之后用一個字符串記錄當(dāng)前 Activity
private void updateScene(Activity activity) {
visibleScene = activity.getClass().getName();
}
public String getVisibleScene() {
return visibleScene;
}
我們主要關(guān)注 onActivityStopped()
回調(diào)中的 getTopActivityName()
方法趁桃,該方法用于獲取棧頂活動狀態(tài)的 Activity辽话。
getTopActivityName()
public static String getTopActivityName() {
long start = System.currentTimeMillis();
try {
// 獲取 ActivityThread Class 對象
Class activityThreadClass = Class.forName("android.app.ActivityThread");
// 調(diào)用這個類的 currentActivityThread 方法,返回一個靜態(tài)的 ActivityThread 實(shí)例 sCurrentActivityThread
// 這個靜態(tài)實(shí)例是在 main 函數(shù)中賦值的
Object activityThread = activityThreadClass.getMethod("currentActivityThread").invoke(null);
// 獲取 ActivityThread 的 mActivities 列表
// 在 Activity onCreate 之后镇辉,往列表添加 Activity 記錄
Field activitiesField = activityThreadClass.getDeclaredField("mActivities");
activitiesField.setAccessible(true);
Map<Object, Object> activities; // 獲取 activityThread 類的 mActivities 對象
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
activities = (HashMap<Object, Object>) activitiesField.get(activityThread);
} else {
activities = (ArrayMap<Object, Object>) activitiesField.get(activityThread);
}
if (activities.size() < 1) {
return null;
}
for (Object activityRecord : activities.values()) {
Class activityRecordClass = activityRecord.getClass();
Field pausedField = activityRecordClass.getDeclaredField("paused");
pausedField.setAccessible(true);
if (!pausedField.getBoolean(activityRecord)) {// onResume 的 Activity paused 為 false
Field activityField = activityRecordClass.getDeclaredField("activity");
activityField.setAccessible(true);
Activity activity = (Activity) activityField.get(activityRecord);
return activity.getClass().getName();
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
long cost = System.currentTimeMillis() - start;
MatrixLog.d(TAG, "[getTopActivityName] Cost:%s", cost);
}
return null;
}
整個過程就是利用反射操作 Framework ActivityThread 的參數(shù)和函數(shù)屡穗,獲取棧頂?shù)姆?paused 狀態(tài)的 Activity。這段代碼需要看著 ActivityThread 類慢慢消化忽肛,在你的 IDE 查看或者在線查看。
綜上烂斋,AppActiveMatrixDelegate 是利用內(nèi)部的 Controller 監(jiān)聽 App 發(fā)來的信號屹逛,用來確定應(yīng)用程序的前后臺狀態(tài)础废。
外部可以設(shè)置監(jiān)聽,等應(yīng)用程序前后臺轉(zhuǎn)換的時(shí)候再遍歷監(jiān)聽者回調(diào)告知罕模。
Issue
當(dāng)插件監(jiān)控到 App 運(yùn)行出現(xiàn)問題時(shí)评腺,會把問題信息封裝為一個 Issue 類進(jìn)行報(bào)告。
public class Issue {
private int type;
private String tag;
private String key;
private JSONObject content;
private Plugin plugin;
public static final String ISSUE_REPORT_TYPE = "type";
public static final String ISSUE_REPORT_TAG = "tag";
public static final String ISSUE_REPORT_PROCESS = "process";
public static final String ISSUE_REPORT_TIME = "time";
}
可以看到該類詳細(xì)記錄了問題的類型淑掌、信息蒿讥、插件信息等,發(fā)現(xiàn)問題是怎么報(bào)告呢抛腕?我們拿性能監(jiān)控插件 TracePlugin 中的 FrameTracer 舉例:
FrameTracer
void report() {
float fps = Math.min(60.f, 1000.f * sumFrame / sumFrameCost);
MatrixLog.i(TAG, "[report] FPS:%s %s", fps, toString());
try {
// 根據(jù)插件名稱遍歷查找
TracePlugin plugin = Matrix.with().getPluginByClass(TracePlugin.class);
if (null == plugin) {
return;
}
// ... 省略部分代碼
JSONObject resultObject = new JSONObject();
resultObject = DeviceUtil.getDeviceInfo(resultObject, plugin.getApplication());
// 組裝內(nèi)容
resultObject.put(SharePluginInfo.ISSUE_SCENE, visibleScene);
resultObject.put(SharePluginInfo.ISSUE_DROP_LEVEL, dropLevelObject);
resultObject.put(SharePluginInfo.ISSUE_DROP_SUM, dropSumObject);
resultObject.put(SharePluginInfo.ISSUE_FPS, fps);
Issue issue = new Issue();
issue.setTag(SharePluginInfo.TAG_PLUGIN_FPS);
issue.setContent(resultObject);
// 調(diào)用插件方法
plugin.onDetectIssue(issue);
} catch (JSONException e) {
MatrixLog.e(TAG, "json error", e);
} finally {
sumFrame = 0;
sumDroppedFrames = 0;
sumFrameCost = 0;
}
}
最后會調(diào)用 Plugin 的 onDetectIssue (Detect:發(fā)現(xiàn)芋绸、偵查出)方法傳遞 Issue 信息。
Plugin # onDetectIssue
@Override
public void onDetectIssue(Issue issue) {
if (issue.getTag() == null) {
// 設(shè)置默認(rèn) tag
issue.setTag(getTag());
}
issue.setPlugin(this);
JSONObject content = issue.getContent();
// add tag and type for default
try {
if (issue.getTag() != null) {
content.put(Issue.ISSUE_REPORT_TAG, issue.getTag());
}
if (issue.getType() != 0) {
content.put(Issue.ISSUE_REPORT_TYPE, issue.getType());
}
content.put(Issue.ISSUE_REPORT_PROCESS, MatrixUtil.getProcessName(application));
content.put(Issue.ISSUE_REPORT_TIME, System.currentTimeMillis());
} catch (JSONException e) {
MatrixLog.e(TAG, "json error", e);
}
// 報(bào)告 Issue
pluginListener.onReportIssue(issue);
}
這個 pluginListener 對象其實(shí)就是在 初始化 的時(shí)候創(chuàng)建并設(shè)置的 TestPluginListener担敌,現(xiàn)在發(fā)現(xiàn)問題了就通過這個 Listener 報(bào)告問題摔敛。
public class TestPluginListener extends DefaultPluginListener {
public static final String TAG = "Matrix.TestPluginListener";
public TestPluginListener(Context context) {
super(context);
}
@Override
public void onReportIssue(Issue issue) {
super.onReportIssue(issue);
MatrixLog.e(TAG, issue.toString());
// 收到 Issue,做后續(xù)工作
}
}
總結(jié)
畫個簡單的流程圖:
到這里全封,Matrix 大致的工作流程已經(jīng)搞清楚了马昙。但是到現(xiàn)在基本沒有接觸核心功能,Matrix 是怎么分析卡頓的刹悴?怎么分析 ANR 的行楞?... 后面會發(fā)文繼續(xù)分析,敬請期待土匀。
系列文章
Android 騰訊 Matrix 原理分析(二):TracePlugin 卡頓分析之主線程監(jiān)聽
Android 騰訊 Matrix 原理分析(三):TracePlugin 卡頓分析之幀率監(jiān)聽
Android 騰訊 Matrix 原理分析(四):TracePlugin 卡頓分析之丟幀展現(xiàn)