Android 騰訊 Matrix 原理分析(一):Matrix 概覽

語言: CN / TW / HK

寫在前面

近期開始 Android Framework 層的學習,然而較為龐大的 Framework 讓人感覺無從下手。碰巧看到一篇文章說到騰訊的 效能監控框架 Matrix 用到了大量 Framework 相關的知識,所以試著分析下該框架的原始碼實現。

在學習大佬們程式碼的同時主要關注該框架用到了哪些、是怎麼使用的 Framework 的內容。

一、Matrix 簡介

官方說明

Matrix 是一款微信研發並日常使用的應用效能接入框架,支援iOS, macOS和Android。Matrix 通過接入各種效能監控方案,對效能監控項的異常資料進行採集和分析,輸出相應的問題分析、定位與優化建議,從而幫助開發者開發出更高質量的應用。

大公司就是大氣,直接雙端都給你整一套。

Matrix 地址

http://links.jianshu.com/go?to=https%3A%2F%2Fgithub.com%2FTencent%2Fmatrix

Matrix for Android

Matrix-android 當前監控範圍包括:應用安裝包大小,幀率變化,啟動耗時,卡頓,慢方法,SQLite 操作優化,檔案讀寫,記憶體洩漏等等。

  • APK Checker: 針對 APK 安裝包的分析檢測工具,根據一系列設定好的規則,檢測 APK 是否存在特定的問題,並輸出較為詳細的檢測結果報告,用於分析排查問題以及版本追蹤

  • Resource Canary: 基於 WeakReference 的特性和 Square Haha 庫開發的 Activity 洩漏和 Bitmap 重複建立檢測工具

  • Trace Canary: 監控介面流暢性、啟動耗時、頁面切換耗時、慢函式及卡頓等問題

  • SQLite Lint: 按官方最佳實踐自動化檢測 SQLite 語句的使用質量

  • IO Canary: 檢測檔案 IO 問題,包括:檔案 IO 監控和 Closeable Leak 監控

好傢伙,功能還真不少。看樣子是個大工程,排個計劃吧:

  1. 首先是對框架的大致瞭解,對框架中用到的需要用到的類、函式進行預習;

  2. 從某一模組入手,分析功能實現的同時注重 Framework 的內容;

  3. 最後進行總結,思考為什麼這樣做,有沒有更好的做法。

那麼本文先大概瞭解一下框架,遇到 Framework 中的知識進行簡單的瞭解和預習。

二、使用 Matrix

有關 Matrix 的接入和使用官方文件已經寫得很清楚了,本文簡單總結下:

  1. 引入 Matrix 庫,新增相關依賴;

  2. 建立外掛監聽,可以接收到外掛的啟動和工作通知。

    Matrix 的功能基本都是由這些 外掛 Plugin 實現的,這樣做的好處一方面是解耦,另一方面是使用者可以根據需要選擇使用的功能。

  3. 在 Application 中初始化 Matrix,新增外掛並開啟外掛功能。

三、Matrix 結構

接下來根據 Matrix 的建立和使用來確定它的結構。

初始化

Matrix 需要在 Applicaton 中初始化,物件的構建方式是熟悉的建造者模式:

public class MatrixApplication extends Application {


@Override
public void onCreate() {
super.onCreate();
// 建立 Matrix,傳入 application
Matrix.Builder builder = new Matrix.Builder(this);
// 設定外掛監聽
builder.patchListener(new TestPluginListener(this));
// 建立外掛
TracePlugin tracePlugin = new TracePlugin(new TraceConfig.Builder()
.build());
// 新增外掛
builder.plugin(tracePlugin);
// 初始化
Matrix.init(builder.build());
// 外掛開始工作
tracePlugin.start();
}
}

維護的變數也比較簡單:

public static class Builder {
// 持有 Application
private final Application application;
// 外掛工作回撥
private PluginListener pluginListener;
// 維護外掛列表
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:是一個介面,定義了外掛的生命週期。官方提供了預設實現 DefaultPluginListener,我們只需要繼承該類並設定給 Matrix 就可以接收到外掛的生命週期。

public interface PluginListener {
void onInit(Plugin plugin);// 初始化


void onStart(Plugin plugin);// 開始


void onStop(Plugin plugin);// 結束


void onDestroy(Plugin plugin);// 銷燬


void onReportIssue(Issue issue);// 提交報告
}
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 維護,保證外掛不會重複新增。

外掛 Plugin

外掛是 Matrix 的重要組成結構,通過繼承抽象類 Plugin 來建立一個外掛,Plugin 是介面 IPlugin 的實現。IPlugin 介面定義了外掛所實現的主要功能:

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 的外掛實現,在工作的過程中也會呼叫這些方法。

Matrix 構造器

Matrix.Builder builder = new Matrix.Builder(this);
Matrix.init(builder.build());

呼叫 builder.build() 之後會建立一個 Matrix 物件,然後建立一個用於監聽 App 生命週期的 AppActiveMatrixDelegate。之後遍歷所有的外掛列表,並呼叫它們的 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

這個類是個列舉單例,並且監聽應用 Activity 生命週期以及記憶體狀態。

public enum AppActiveMatrixDelegate {
// 1. 利用列舉建立單例
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. 註冊應用記憶體狀態回撥
application.registerComponentCallbacks(controller);
// 4. 註冊監聽 Activity 生命週期
application.registerActivityLifecycleCallbacks(controller);
}
}
  1. 列舉實現單例的原理是利用列舉的特點來實現的:列舉型別是執行緒安全的,並且只會裝載一次。

  2. HandlerTherad 繼承了 Thread,可以看作是一個執行緒。而其內部維護了一個 Handler,在呼叫 start() 方法後初始化 Looper,可以很方便地執行非同步任務。其它類可以通過 getThreadHandler 方法獲取 HandlerTherad 的 Handler,然後 post 任務由 HandlerTherad 內部的 Looper 取出並執行。

  3. registerComponentCallbacks:Application 的方法,作用是監聽應用的記憶體狀態。

    在系統記憶體不足,所有後臺程式(優先順序為background的程序,不是指後臺執行的程序)都被殺死時,系統會呼叫 onLowMemory。

    OnTrimMemory 是 Android 4.0 之後提供的 API。比起 onLowMemory,這個回撥新增返回了一個 int 值表示當前記憶體狀態,開發者可以根據返回的狀態來適當回收資源避免 app 被殺死的風險。

    想要監聽記憶體狀態回撥需要實現 ComponentCallbacks2 介面,該介面是 ComponentCallbacks 的升級版。

應用記憶體優化之OnLowMemory&OnTrimMemory             https://www.cnblogs.com/xiajf/p/3993599.html

  1. registerActivityLifecycleCallbacks:註冊監聽 Activity 狀態回撥, 實現 Application.ActivityLifecycleCallbacks 介面以監聽 Activity 生命週期回撥。

Controller

Controller 實現 ComponentCallbacks2 監聽記憶體狀態、ActivityLifecycleCallbacks 監聽 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 表示當前 app UI 不再可見
if (level == TRIM_MEMORY_UI_HIDDEN && isAppForeground) { // fallback
onDispatchBackground(visibleScene);
}
}
}

Controller 的邏輯主要為了區分 App 進入前臺或後臺。怎麼區分呢?

  • 有 Activity 回調了 onStart,說明 App 進入了前臺,記錄並返回給監聽就行;

  • 有 Activity 回調了 onStop,且棧頂沒有 Resume 狀態的 Activity,說明 App 進入了後臺;

    使用者點選了 Home 或者 Back 鍵,系統會通過 onTrimMemory 回撥一個 TRIM_MEMORY_UI_HIDDEN 狀態,告知這是 App 進入後臺,是回收資源的大好時機。

回撥 onStart 之後用一個字串記錄當前 Activity

private void updateScene(Activity activity) {
visibleScene = activity.getClass().getName();
}


public String getVisibleScene() {
return visibleScene;
}

我們主要關注 onActivityStopped() 回撥中的 getTopActivityName() 方法,該方法用於獲取棧頂活動狀態的 Activity。

getTopActivityName()
public static String getTopActivityName() {
long start = System.currentTimeMillis();
try {
// 獲取 ActivityThread Class 物件
Class activityThreadClass = Class.forName("android.app.ActivityThread");
// 呼叫這個類的 currentActivityThread 方法,返回一個靜態的 ActivityThread 例項 sCurrentActivityThread
// 這個靜態例項是在 main 函式中賦值的
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 的引數和函式,獲取棧頂的非 paused 狀態的 Activity。這段程式碼需要看著 ActivityThread 類慢慢消化,在你的 IDE 檢視或者線上檢視。

綜上,AppActiveMatrixDelegate 是利用內部的 Controller 監聽 App 發來的訊號,用來確定應用程式的前後臺狀態。

外部可以設定監聽,等應用程式前後臺轉換的時候再遍歷監聽者回調告知。

Issue

當外掛監控到 App 執行出現問題時,會把問題資訊封裝為一個 Issue 類進行報告。

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";
}

可以看到該類詳細記錄了問題的型別、資訊、外掛資訊等,發現問題是怎麼報告呢?我們拿效能監控外掛 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 {
// 根據外掛名稱遍歷查詢
TracePlugin plugin = Matrix.with().getPluginByClass(TracePlugin.class);
if (null == plugin) {
return;
}
// ... 省略部分程式碼

JSONObject resultObject = new JSONObject();
resultObject = DeviceUtil.getDeviceInfo(resultObject, plugin.getApplication());
// 組裝內容
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);
// 呼叫外掛方法
plugin.onDetectIssue(issue);


} catch (JSONException e) {
MatrixLog.e(TAG, "json error", e);
} finally {
sumFrame = 0;
sumDroppedFrames = 0;
sumFrameCost = 0;
}
}

最後會呼叫 Plugin 的 onDetectIssue (Detect:發現、偵查出)方法傳遞 Issue 資訊。

Plugin # onDetectIssue
@Override
public void onDetectIssue(Issue issue) {
if (issue.getTag() == null) {
// 設定預設 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);
}


// 報告 Issue
pluginListener.onReportIssue(issue);
}

這個 pluginListener 物件其實就是在 初始化 的時候建立並設定的 TestPluginListener,現在發現問題了就通過這個 Listener 報告問題。

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,做後續工作
}
}

畫個簡單的流程圖:

流程

到這裡,Matrix 大致的工作流程已經搞清楚了。但是到現在基本沒有接觸核心功能,Matrix 是怎麼分析卡頓的?怎麼分析 ANR 的?... 後面會發文繼續分析,敬請期待。

作者:Marker_Sky

連結:https://www.jianshu.com/p/fc77b4807636

關注我獲取更多知識或者投稿