Android 腾讯 Matrix 原理分析(一):Matrix 概览

写在前面

近期开始 Android Framework 层的学习,然而较为庞大的 Framework 让人感觉无从下手。碰巧看到一篇文章说到腾讯的 性能监控框架 Matrix 用到了大量 Framework 相关的知识,所以试着分析下该框架的源码实现。

在学习大佬们代码的同时主要关注该框架用到了哪些、是怎么使用的 Framework 的内容。

一、Matrix 简介

官方说明

Matrix 是一款微信研发并日常使用的应用性能接入框架,支持iOS, macOS和Android。 Matrix 通过接入各种性能监控方案,对性能监控项的异常数据进行采集和分析,输出相应的问题分析、定位与优化建议,从而帮助开发者开发出更高质量的应用。

大公司就是大气,直接双端都给你整一套。

Matrix 地址

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

  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 的?... 后面会发文继续分析,敬请期待。

系列文章

Android 腾讯 Matrix 原理分析(二):TracePlugin 卡顿分析之主线程监听
Android 腾讯 Matrix 原理分析(三):TracePlugin 卡顿分析之帧率监听
Android 腾讯 Matrix 原理分析(四):TracePlugin 卡顿分析之丢帧展现

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,839评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,543评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,116评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,371评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,384评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,111评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,416评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,053评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,558评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,007评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,117评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,756评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,324评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,315评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,539评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,578评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,877评论 2 345

推荐阅读更多精彩内容