android studio建立一個類繼承application_Android 騰訊 Matrix 原理分析(一):Matrix 概覽...
技術標籤:android studio建立一個類繼承application
寫在前面
近期開始 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 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 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 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 https://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
@Overridepublic 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
關注我獲取更多知識或者投稿