写在前面
近期开始 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
- 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