Android 騰訊 Matrix 原理分析(一):Matrix 概覽
寫在前面
近期開始 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 監控
好傢伙,功能還真不少。看樣子是個大工程,排個計劃吧:
-
首先是對框架的大致瞭解,對框架中用到的需要用到的類、函式進行預習;
-
從某一模組入手,分析功能實現的同時注重 Framework 的內容;
-
最後進行總結,思考為什麼這樣做,有沒有更好的做法。
那麼本文先大概瞭解一下框架,遇到 Framework 中的知識進行簡單的瞭解和預習。
二、使用 Matrix
有關 Matrix 的接入和使用官方文件已經寫得很清楚了,本文簡單總結下:
-
引入 Matrix 庫,新增相關依賴;
-
建立外掛監聽,可以接收到外掛的啟動和工作通知。
Matrix 的功能基本都是由這些 外掛 Plugin 實現的,這樣做的好處一方面是解耦,另一方面是使用者可以根據需要選擇使用的功能。
-
在 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);
}
}
-
列舉實現單例的原理是利用列舉的特點來實現的:列舉型別是執行緒安全的,並且只會裝載一次。
-
HandlerTherad 繼承了 Thread,可以看作是一個執行緒。而其內部維護了一個 Handler,在呼叫 start() 方法後初始化 Looper,可以很方便地執行非同步任務。其它類可以通過
getThreadHandler
方法獲取 HandlerTherad 的 Handler,然後 post 任務由 HandlerTherad 內部的 Looper 取出並執行。 -
registerComponentCallbacks:Application 的方法,作用是監聽應用的記憶體狀態。
在系統記憶體不足,所有後臺程式(優先順序為background的程序,不是指後臺執行的程序)都被殺死時,系統會呼叫 onLowMemory。
OnTrimMemory 是 Android 4.0 之後提供的 API。比起 onLowMemory,這個回撥新增返回了一個 int 值表示當前記憶體狀態,開發者可以根據返回的狀態來適當回收資源避免 app 被殺死的風險。
想要監聽記憶體狀態回撥需要實現 ComponentCallbacks2 介面,該介面是 ComponentCallbacks 的升級版。
應用記憶體優化之OnLowMemory&OnTrimMemory http://www.cnblogs.com/xiajf/p/3993599.html
-
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
連結:http://www.jianshu.com/p/fc77b4807636
關注我獲取更多知識或者投稿
- 【建議收藏】17個XML佈局小技巧
- Kotlin擴充套件方法進化之Context Receiver
- 網路請求元件封裝
- Flutter 繪製探索 | 箭頭端點的設計
- 在網頁上除錯手機上的APP
- Android應用安全解決方案
- Android應用安全開發之元件安全淺談
- Android 元件化架構設計從原理到實戰
- 涉作業系統技術領域,華為公開安卓應用程式遷移相關專利
- 避坑!!webview如何載入pdf ?
- Hook AMS APT實現集中式登入框架
- Retrofit是如何支援協程的
- 自定義雙向繫結框架-只需一個註解,簡單實用
- Android 10、11 儲存完全適配
- Java註解和註解解析器深耕,架構師必會
- 寫個更牛逼的Transform | Plugin 進階教程
- Android 騰訊 Matrix 原理分析(一):Matrix 概覽
- ART視角 | 如何自動回收native記憶體
- Android Gradle 多渠道打包
- Iterator 這些點你GET到了嗎?