什么是启动耗时

分为两个角度:

  • 冷启动:就是点击应用图标到打开应用的冷启动响应时间,且前提是应用从未被创建过进程,
  • 热启动:测量点击应用图标到打开应用的热启动响应时间,被测应用之前已经被打开过,无关闭应用行为,测试时被重新切换到前台

    启动耗时影响什么

    第一想到的肯定是用户体验,如果你的应用半分钟没有启动起来,那谁还想用呢?所以很多大厂App,虽然一个App承载的业务多的数不胜数,但肯定都有一个特点,一点就开,即开即用。

    启动耗时的标准是什么

    各类应用的冷启动时间应≤2000毫秒、游戏类应用和影音娱乐类应用冷启动时间≤3000毫秒,各类应用的热启动时间应≤500毫秒、游戏类应用和影音娱乐类应用冷启动时间≤1000毫秒。同样来源于软件绿色联盟应用体验标准3.0——性能标准,请点击链接查看详情。

    如何查看启动耗时呢

    其实查看启动耗时,官方已经给我提供了很多工具,如TraceView,我们就可以通过它来查看图形执行时间,调用栈等,但是它的缺点也很明显,运行时开销严重,得出的结果并不真实,同样的我们还可以借助android studio terminal,来一个简单的测试,如下:
    命令:

    1. adb shell am start -W [packageName]/[AppstartActivity全路径]

    image.png
    可以看到,我用同一个命令,测试了两次,第一次是应用进程存活时,LaunchState是HOT,TotalTime就是启动耗时,WaitTime是AMS启动Activity的总耗时,包括创建进程 + Application初始化 + Activity初始化到界面显示的过程。第二次的冷启动低于500ms,还算是比较合理的,热启动在115ms,是不是很优秀,其实这个应用不具有代表性,因为是测试Demo,代码比较简单,所以启动很快,对于一些大型引用肯定就不是这样了,虽然这种方式可以很快的拿到指标数据,但有个缺点你会发现,即使我知道了耗时,如果出现耗时不正常的操作,就不知道哪里出现的问题。所以我就想,是不是Matrix能解决这些问题呢,待我们去验证。我们还是不着急去看Matrix的源码,我们先来看下如何通过代码实现一个启动耗时统计,除了以上方法,google还给我们提供了Systrace 命令行工具,可以结合代码插桩一起完成耗时分析,插桩就是在需要监听的方法前后,插入一行代码。 最新消息google 在Android 10 中引入的全新平台级跟踪工具 Perfetto,具体请看https://developer.android.com/topic/performance/tracing?hl=zh-cn,具体可以理解为Systrace的升级版本,我们现在不研究这些工具,先来看看,我们如何通过代码插桩的方式来监控应用的启动耗时。插桩也是Matrix实现的核心,所以我们仔细聊聊。

    需要监控的函数

    既然我们决定使用代码的插桩来实现,那么就需要知道对哪些函数做操作,具体什么函数,这要看App整个启动过程的函数调用顺序,我整理了几个流程图,请看:
    大致流程就是这样,并没有特别详细,基本原理给大家搞清楚,然后知道函数的调用顺序就ok,从图中分析出的知识点:

  • 包括SystemServer在内,我们的app也都是zygote进程fork出来的

  • 当别人通过startActivity启动我们的app时,其实是ActivityManagerService通过startProcessLocked告知zygote进程
  • 当app进程被创建后,进程中会创建出ActivityThread,通过源码我们发现ActivityThread中有个java的main函数,main函数调用attach函数,如图

image.png

  • attach函数通过binder又返回到ActivityManagerService中,再由ActivityManagerService调用attachApplicationLocked,然后再通过binder调到ApplicationThread.bindApplication,ApplicationThread是ActivityThread中的私有类,如图

image.png

  • ApplicationThread 通过handler message通信,最终调用ActivityThread的handleBindApplication函数,然后在该方法中根据拿到的appInfo信息,创建appContext,最后创建Application,调用application的onCreate函数。
  • Activity的创建通过ActivityStackSupervisor.realStartActivityLocked,最终通过binder,在ActivityThread中执行handleLaunchActivity,紧接着attach到对应的上下文中。

从这张图中,我们了解了App的启动过程,其实在Android不同的SDK版本中都有升级,会导致部分代码找不到,但大同小异。我们的目的其实是为了找到插桩的地方,且有一点我们用到的插桩是java字节码,所以有binder通信的地方,我们只能改动java层的代码,所以基本可以敲定,插桩代码就是在我们的App进程中。
简单定义一个计算公式: App的启动耗时 = 第一个Activity创建好的时间 - Application onCreate 时间
当然有的app是启动页+Home主页才算是app启动完成,这里先不纠结这个,我们现在已经可以明确的点,Application onCreate方法和 Activity的相关方法(后面再分析哪个方法更合适)都是我们要插桩的点。那么接下来我们简单说下插桩的几个框架,来看看哪个更加合适。

插桩方案选择哪个?AspectJ、ASM、ReDex

AspectJ 和 ASM 框架是我们最常用的 Java 字节码处理框架。AspectJ是 Java 中流行的 AOP(aspect-oriented programming)编程扩展框架,从使用上来看,作为字节码处理元老,AspectJ 的框架的确有自己的一些优势,但官方建议切换到 ObjectWeb 的 ASM 框架,而ReDex是 Facebook 开源的工具,通过对字节码进行优化,以减小 Android Apk 大小,同时提高 App 启动速度。ReDex 里甚至提供了 Method Tracing 和 Block Tracing 工具,可以在所有方法或者指定方法前面插入一段跟踪代码。我们为什么不用它呢,因为Matrix用的ASM,并且ASM可以实现 100% 场景的 Java 字节码操作,已经满足了我们的需求。那么接下来,我们用ASM来实现一个代码插桩用例。

ASM实现插桩用例

我们的目标是给Android的某个类,做函数插桩,下面我们做一个demo作为本次的用例,带你有序的了解,该如何通过ASM做函数插桩。

1.Demo项目创建

这一步不用多说,直接在Android Studio中,new project 就行,等待项目第一次编译完成

2.gradle插件创建

在项目的根目录中,创建buildSrc文件夹,然后构建一下项目,然后在buildSrc文件夹中创建build.gradle配置文件,如下:

  1. plugins{
  2. //使用 java groovy 插件
  3. id 'java'
  4. id 'groovy'
  5. }
  6. group 'com.julive.sam'
  7. version '0.0.1'
  8. sourceCompatibility = 1.8
  9. repositories{
  10. //使用阿里云的maven代理
  11. maven { url 'https://maven.aliyun.com/repository/google' }
  12. maven { url 'https://maven.aliyun.com/repository/public' }
  13. maven {
  14. url 'http://maven.aliyun.com/nexus/content/groups/public/'
  15. }
  16. maven {
  17. url 'http://maven.aliyun.com/nexus/content/repositories/jcenter'
  18. }
  19. }
  20. def asmVersion = '8.0.1'
  21. dependencies {
  22. //引入gradle api
  23. implementation gradleApi()
  24. implementation localGroovy()
  25. //引入android studio扩展gradle的相关api
  26. implementation "com.android.tools.build:gradle:4.1.0"
  27. //引入apache io
  28. implementation 'org.apache.directory.studio:org.apache.commons.io:2.4'
  29. //引入ASM相关api,这是我们插桩的关键,要靠他实现方法插桩
  30. implementation "org.ow2.asm:asm:$asmVersion"
  31. implementation "org.ow2.asm:asm-util:$asmVersion"
  32. implementation "org.ow2.asm:asm-commons:$asmVersion"
  33. }

接下来创建插件代码目录,由于我们使用java写的插件,所以需要选中buildSrc,然后鼠标右键选择new,再选择directory,最后出现的对话框中选择 src/main/java,下图中是因为我的项目已经创建完了,所以只有groovy目录,如果你需要写groovy的实现就创建下图中文件夹路径,创建完这个下一步就是创建插件。
image.png
在java目录中,创建包名com.julive.sam,在该包路径下创建Plugins插件,代码如下:

  1. public class Plugins implements Plugin<Project> {
  2. @Override
  3. public void apply(Project target) {
  4. }
  5. }

然后创建插件的配置resources文件夹,和java文件夹同级,在resources下创建文件夹META-INF/gradle-plugins/,最终在gradle-plugins中创建com.julive.sam.properties,意思是你的包名.properties ,一定要对应好包名,然后在该文件中加入代码

  1. implementation-class=com.julive.sam.Plugins

com.julive.sam.Plugins 你点击后,看能否跳转至 上面创建的Plugins插件中,如果可以直接跳转那就ok了。

3.下一步在App的build.gradle中配置插件

image.png

4.创建gradle的Transform实现

Transform是在.class -> .dex转换期间,用来修改.class文件的一套标准API,所以你现在应该知道了,在transform中我们肯定要调用ASM的实现,来实现.class文件的修改,最终转换为.dex文件。创建Transform的实现如下:

  1. public class TransformTest extends Transform {
  2. @Override
  3. public String getName() {
  4. // 随便起个名字
  5. return "TransformSam";
  6. }
  7. @Override
  8. public Set<QualifiedContent.ContentType> getInputTypes() {
  9. //代表处理的 java 的 class 文件
  10. return TransformManager.CONTENT_CLASS;
  11. }
  12. @Override
  13. public Set<? super QualifiedContent.Scope> getScopes() {
  14. //要处理所有的class字节码
  15. return TransformManager.SCOPE_FULL_PROJECT;
  16. }
  17. @Override
  18. public boolean isIncremental() {
  19. // 是否增量编译,我们先不考虑,返回false
  20. return false;
  21. }
  22. @Override
  23. public void transform(TransformInvocation transformInvocation) throws TransformException, InterruptedException, IOException {
  24. super.transform(transformInvocation);
  25. try {
  26. //待实现
  27. doTransform(transformInvocation); // hack
  28. } catch (Exception e) {
  29. e.printStackTrace();
  30. }
  31. }
  32. }

看上面注释是不是就对Transform有了一定的了解呢,那么如何处理.class文件呢?我们来实现doTransform函数,来看如何处理

  1. private void doTransform(TransformInvocation transformInvocation) throws IOException {
  2. System.out.println("doTransform =======================================================");
  3. //inputs中是传过来的输入流,其中有两种格式,一种是jar包格式一种是目录格式。
  4. Collection<TransformInput> inputs = transformInvocation.getInputs();
  5. //获取到输出目录,最后将修改的文件复制到输出目录,这一步必须做不然编译会报错
  6. TransformOutputProvider outputProvider = transformInvocation.getOutputProvider();
  7. //删除之前的输出
  8. if (outputProvider != null)
  9. outputProvider.deleteAll();
  10. inputs.forEach(transformInput -> {
  11. //遍历directoryInputs
  12. transformInput.getDirectoryInputs().forEach(directoryInput -> {
  13. });
  14. //jarInputs
  15. transformInput.getJarInputs().forEach(jarInput -> {
  16. });
  17. });
  18. }

从transformInvocation的api中,我们获取了两个东西,一个是inputs,一个是outputProvider,我们遍历inputs后发现,他有两个api getDirectoryInputs和getJarInputs 这俩是什么东西呢?我描述不太好,我加了日志,来看下日志输出:
image.png
image.png
这下是不是看明白了,其实我对getDirectoryInputs做了一层文件筛选处理

  1. transformInput.getDirectoryInputs().forEach(directoryInput -> {
  2. ArrayList<File> list = new ArrayList<>();
  3. getFileList(directoryInput.getFile(), list);
  4. });
  5. //递归查找该文件夹下所有文件,因为我们修改的是.class 文件,而不关系文件夹
  6. void getFileList(File file, ArrayList<File> fileList) {
  7. if (file.isFile()) {
  8. fileList.add(file);
  9. } else {
  10. File[] list = file.listFiles();
  11. for (File value : list) {
  12. getFileList(value, fileList);
  13. }
  14. }
  15. }

好,从上面我们看出,已经找到了MainActivity的class文件,那么接下来给MainActivity.class的onCreate函数,插入两行代码,

5.现在开始操作ASM的api

首先要实现ASM的 ClassVisitor 类来操作我们想要操作的类,它可以访问class文件的各个部分,比如方法变量注解
基本的实现如下:

  1. public class TestClassVisitor extends ClassVisitor{
  2. private String className;
  3. private String superName;
  4. TestClassVisitor(final ClassVisitor classVisitor) {
  5. super(Opcodes.ASM6, classVisitor);
  6. }
  7. /**
  8. * 这里可以拿到关于.class的所有信息,比如当前类所实现的接口类表等
  9. *
  10. * @param version 表示jdk的版本
  11. * @param access 当前类的修饰符 (这个和ASM 和 java有些差异,比如public 在这里就是ACC_PUBLIC)
  12. * @param name 当前类名
  13. * @param signature 泛型信息
  14. * @param superName 当前类的父类
  15. * @param interfaces 当前类实现的接口列表
  16. */
  17. @Override
  18. public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
  19. super.visit(version, access, name, signature, superName, interfaces);
  20. this.className = name;
  21. this.superName = superName;
  22. }
  23. @Override
  24. public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
  25. //委托函数
  26. MethodVisitor methodVisitor = cv.visitMethod(access, name, descriptor, signature, exceptions);
  27. //找到我们需要修改的类,注意这里是/ 斜杠来表示文件的路径,并不是java代码中的.
  28. if (className.equals("com/julive/samtest/MainActivity")) {
  29. // 判断方法name是onCreate
  30. if (name.startsWith("onCreate")) {
  31. //插桩函数的实现,同样用到ASM提供的对象,下面看具体实现代码
  32. return new TestMethodVisitor(Opcodes.ASM6, methodVisitor, access, name, descriptor, className, superName);
  33. }
  34. }
  35. return methodVisitor;
  36. }
  37. }

这里集成AdviceAdapter,其实AdviceAdapter是继承自MethodVisitor,这是不是就跟ClassVisitor一一呼应呢,使用它是因为它比较方便的实现,提供了onMethodEnter,onMethodExit,正好是我们的需求。在onCreate的函数的前后各插入一行代码。但仔细看onMethodEnter的函数实现,你会发现一脸懵逼,不知道是啥玩意。往下看

  1. public class TestMethodVisitor extends AdviceAdapter {
  2. private String className;
  3. private String superName;
  4. protected TestMethodVisitor(int i, MethodVisitor methodVisitor, int i1, String s, String s1,String className,String superName) {
  5. super(i, methodVisitor, i1, s, s1);
  6. this.className = className;
  7. this.superName = superName;
  8. }
  9. @Override
  10. protected void onMethodEnter() {
  11. super.onMethodEnter();
  12. mv.visitLdcInsn("TAG");
  13. mv.visitLdcInsn(className + "---->" + superName);
  14. mv.visitMethodInsn(Opcodes.INVOKESTATIC, "android/util/Log", "i", "(Ljava/lang/String;Ljava/lang/String;)I", false);
  15. mv.visitInsn(Opcodes.POP);
  16. }
  17. @Override
  18. protected void onMethodExit(int opcode) {
  19. mv.visitLdcInsn("TAG");
  20. mv.visitLdcInsn("this is end");
  21. mv.visitMethodInsn(Opcodes.INVOKESTATIC, "android/util/Log", "i", "(Ljava/lang/String;Ljava/lang/String;)I", false);
  22. mv.visitInsn(Opcodes.POP);
  23. super.onMethodExit(opcode);
  24. }
  25. }

在这里推荐一个插件,https://plugins.jetbrains.com/plugin/14860-asm-bytecode-viewer-support-kotlin,用插件测试代码如下:

  1. public class Test {
  2. void aa() {
  3. Log.i("TAG", "this is end");
  4. }
  5. }

转换ASM代码如下:

  1. public static byte[] dump() throws Exception {
  2. ClassWriter classWriter = new ClassWriter(0);
  3. FieldVisitor fieldVisitor;
  4. MethodVisitor methodVisitor;
  5. AnnotationVisitor annotationVisitor0;
  6. classWriter.visit(V1_8, ACC_PUBLIC | ACC_SUPER, "com/julive/samtest/Test", null, "java/lang/Object", null);
  7. classWriter.visitSource("Test.java", null);
  8. {
  9. methodVisitor = classWriter.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
  10. methodVisitor.visitCode();
  11. Label label0 = new Label();
  12. methodVisitor.visitLabel(label0);
  13. methodVisitor.visitLineNumber(5, label0);
  14. methodVisitor.visitVarInsn(ALOAD, 0);
  15. methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
  16. methodVisitor.visitInsn(RETURN);
  17. Label label1 = new Label();
  18. methodVisitor.visitLabel(label1);
  19. methodVisitor.visitLocalVariable("this", "Lcom/julive/samtest/Test;", null, label0, label1, 0);
  20. methodVisitor.visitMaxs(1, 1);
  21. methodVisitor.visitEnd();
  22. }
  23. {
  24. methodVisitor = classWriter.visitMethod(0, "aa", "()V", null, null);
  25. methodVisitor.visitCode();
  26. Label label0 = new Label();
  27. methodVisitor.visitLabel(label0);
  28. methodVisitor.visitLineNumber(8, label0);
  29. methodVisitor.visitLdcInsn("TAG");
  30. methodVisitor.visitLdcInsn("this is end");
  31. methodVisitor.visitMethodInsn(INVOKESTATIC, "android/util/Log", "i", "(Ljava/lang/String;Ljava/lang/String;)I", false);
  32. methodVisitor.visitInsn(POP);
  33. Label label1 = new Label();
  34. methodVisitor.visitLabel(label1);
  35. methodVisitor.visitLineNumber(9, label1);
  36. methodVisitor.visitInsn(RETURN);
  37. Label label2 = new Label();
  38. methodVisitor.visitLabel(label2);
  39. methodVisitor.visitLocalVariable("this", "Lcom/julive/samtest/Test;", null, label0, label2, 0);
  40. methodVisitor.visitMaxs(2, 1);
  41. methodVisitor.visitEnd();
  42. }
  43. classWriter.visitEnd();
  44. return classWriter.toByteArray();
  45. }

是不是很长,哈哈,这段代码其实是将整个Test类的东西,都通过ASM的方式生成,我们只需要找到对应的日志如下:

  1. methodVisitor.visitLdcInsn("TAG");
  2. methodVisitor.visitLdcInsn("this is end");
  3. methodVisitor.visitMethodInsn(INVOKESTATIC, "android/util/Log", "i", "(Ljava/lang/String;Ljava/lang/String;)I", false);
  4. methodVisitor.visitInsn(POP);

然后将其放入到onMethodExit函数中,就可以了。

6.Tranfrom结合ASM实现

现在万事具备只欠东风,就是将Tranform拿到的class文件通过ASM做修改,具体如何关联,请看,回到刚才的doTransform中,改成如下代码:

  1. private void doTransform(TransformInvocation transformInvocation) throws IOException {
  2. System.out.println("doTransform =======================================================");
  3. //inputs中是传过来的输入流,其中有两种格式,一种是jar包格式一种是目录格式。
  4. Collection<TransformInput> inputs = transformInvocation.getInputs();
  5. //获取到输出目录,最后将修改的文件复制到输出目录,这一步必须做不然编译会报错
  6. TransformOutputProvider outputProvider = transformInvocation.getOutputProvider();
  7. //删除之前的输出
  8. if (outputProvider != null)
  9. outputProvider.deleteAll();
  10. inputs.forEach(transformInput -> {
  11. //遍历directoryInputs
  12. transformInput.getDirectoryInputs().forEach(directoryInput -> {
  13. ArrayList<File> list = new ArrayList<>();
  14. getFileList(directoryInput.getFile(), list);
  15. list.forEach(file -> {
  16. System.out.println("getDirectoryInputs =======================================================" + file.getName());
  17. // 判断是.class文件
  18. if (file.isFile() && file.getName().endsWith(".class")) {
  19. try {
  20. //ASM提供的读取类信息的对象
  21. ClassReader classReader = new ClassReader(new FileInputStream(file));
  22. //ASM提供的类修改对象,并将读到的信息交给classWriter
  23. ClassWriter classWriter = new ClassWriter(classReader, ClassWriter.COMPUTE_MAXS);
  24. //创建修改规则,TestClassVisitor
  25. ClassVisitor visitor = new TestClassVisitor(classWriter);
  26. //将修改规则给classReader
  27. classReader.accept(visitor, ClassReader.EXPAND_FRAMES);
  28. //通过toByteArray方法,将变更后信息转成byte数组
  29. byte[] bytes = classWriter.toByteArray();
  30. //放入输出流中往原文件中写入
  31. FileOutputStream fileOutputStream = new FileOutputStream(file.getAbsolutePath());
  32. fileOutputStream.write(bytes);
  33. fileOutputStream.close();
  34. } catch (IOException e) {
  35. e.printStackTrace();
  36. }
  37. }
  38. });
  39. if (outputProvider != null) {
  40. File dest = outputProvider.getContentLocation(directoryInput.getName(), directoryInput.getContentTypes(), directoryInput.getScopes(), Format.DIRECTORY);
  41. try {
  42. //将该文件放入到目标目录中,这步骤必须实现,否则会导致dex文件找不到该文件
  43. FileUtils.copyDirectory(directoryInput.getFile(), dest);
  44. } catch (IOException e) {
  45. e.printStackTrace();
  46. }
  47. }
  48. });
  49. //jarInputs
  50. transformInput.getJarInputs().forEach(jarInput -> {
  51. ArrayList<File> list = new ArrayList<>();
  52. getFileList(jarInput.getFile(), list);
  53. list.forEach(file -> {
  54. System.out.println("getJarInputs =======================================================" + file.getName());
  55. });
  56. if (outputProvider != null) {
  57. File dest = outputProvider.getContentLocation(
  58. jarInput.getName(),
  59. jarInput.getContentTypes(),
  60. jarInput.getScopes(),
  61. Format.JAR);
  62. //将该文件放入到目标目录中,这步骤必须实现,否则会导致dex文件找不到该文件
  63. try {
  64. FileUtils.copyFile(jarInput.getFile(), dest);
  65. } catch (IOException e) {
  66. e.printStackTrace();
  67. }
  68. }
  69. });
  70. });
  71. }

7.反编译检查代码

好了,通过ASM的一顿操作,已经将代码插入到了MainActivity的onCreate函数中,我们如何验证?可以通过反编译来看,也可以通过日志,日志不太合理,因为一般我们不会插入很多日志来验证我们插入的正确性,太多了,照顾不过来,下面我们就反编译来看:这里推荐使用https://github.com/skylot/jadx
它提供了可视化操作,首先做如下操作:

  1. git clone https://github.com/skylot/jadx.git
  2. cd jadx
  3. ./gradlew dist

执行成功后,可以执行如下:

  1. jadx-gui

然后就会打来工具,如下:
image.png
然后将 app的debug apk包拖到这个窗口就行,如下:
image.png
我们找到MainActivity如下:
image.png
而我们原代码是这样,跟我们预想的效果一致。
image.png
好了整体下来,你已经掌握的基本的ASM操作,如果需要更加深入的学习,请到官网学习。接下来,就回到我们的主题,研究Matrix的启动耗时,都插入哪些代码呢?

Matrix 启动耗时统计插桩代码

顺着上面的思路,我们按照如下流程分析它的代码
先找到Plugins,如下:

  1. class MatrixPlugin implements Plugin<Project> {
  2. private static final String TAG = "Matrix.MatrixPlugin"
  3. @Override
  4. void apply(Project project) {
  5. //创建新的配置项,就是你在build.gradle中用的配置
  6. project.extensions.create("matrix", MatrixExtension)
  7. project.matrix.extensions.create("trace", MatrixTraceExtension)
  8. project.matrix.extensions.create("removeUnusedResources", MatrixDelUnusedResConfiguration)
  9. //仅支持application,如果在library中配置就会导致gradle项目编译失败
  10. if (!project.plugins.hasPlugin('com.android.application')) {
  11. throw new GradleException('Matrix Plugin, Android Application plugin required')
  12. }
  13. //较常见的一个配置参数的回调方式,只要 project 配置成功均会调用
  14. project.afterEvaluate {
  15. //拿到项目的android配置
  16. def android = project.extensions.android
  17. //拿到matrix配置
  18. def configuration = project.matrix
  19. //ApplicationVariant对象
  20. android.applicationVariants.all { variant ->
  21. //matrix 配置中 trace 下 enable属性如果为true,开启MatrixTraceTransform插桩
  22. if (configuration.trace.enable) {
  23. com.tencent.matrix.trace.transform.MatrixTraceTransform.inject(project, configuration.trace, variant.getVariantData().getScope())
  24. }
  25. //如果删除无用资源开工是true,则在project的tasks中创建相关任务。
  26. if (configuration.removeUnusedResources.enable) {
  27. if (Util.isNullOrNil(configuration.removeUnusedResources.variant) || variant.name.equalsIgnoreCase(configuration.removeUnusedResources.variant)) {
  28. Log.i(TAG, "removeUnusedResources %s", configuration.removeUnusedResources)
  29. RemoveUnusedResourcesTask removeUnusedResourcesTask = project.tasks.create("remove" + variant.name.capitalize() + "UnusedResources", RemoveUnusedResourcesTask)
  30. removeUnusedResourcesTask.inputs.property(RemoveUnusedResourcesTask.BUILD_VARIANT, variant.name)
  31. project.tasks.add(removeUnusedResourcesTask)
  32. removeUnusedResourcesTask.dependsOn variant.packageApplication
  33. variant.assemble.dependsOn removeUnusedResourcesTask
  34. }
  35. }
  36. }
  37. }
  38. }
  39. }

我们找到了MatrixTraceTransform,这就是插桩的第二步,来看代码,直接上重点

  1. @Override
  2. public void transform(TransformInvocation transformInvocation) throws TransformException, InterruptedException, IOException {
  3. super.transform(transformInvocation);
  4. // 记录开始时间
  5. long start = System.currentTimeMillis();
  6. try {
  7. //开始插桩
  8. doTransform(transformInvocation); // hack
  9. } catch (ExecutionException e) {
  10. e.printStackTrace();
  11. }
  12. long cost = System.currentTimeMillis() - start;
  13. long begin = System.currentTimeMillis();
  14. origTransform.transform(transformInvocation);
  15. long origTransformCost = System.currentTimeMillis() - begin;
  16. Log.i("Matrix." + getName(), "[transform] cost time: %dms %s:%sms MatrixTraceTransform:%sms", System.currentTimeMillis() - start, origTransform.getClass().getSimpleName(), origTransformCost, cost);
  17. }
  18. private void doTransform(TransformInvocation transformInvocation) throws ExecutionException, InterruptedException {
  19. //判断是否为增量编译
  20. final boolean isIncremental = transformInvocation.isIncremental() && this.isIncremental();
  21. /**
  22. * step 1
  23. */
  24. long start = System.currentTimeMillis();
  25. //Future 表示异步计算的结果。它提供了检查计算是否完成的方法,以等待计算的完成,并获取计算的结果。
  26. //计算完成后只能使用 get 方法来获取结果,如有必要,计算完成前可以阻塞此方法,
  27. //可以异步处理,同步返回
  28. List<Future> futures = new LinkedList<>();
  29. //存储 混淆前方法、混淆后方法的映射关系
  30. final MappingCollector mappingCollector = new MappingCollector();
  31. final AtomicInteger methodId = new AtomicInteger(0);
  32. //存储 需要插桩的 方法名 和 方法的封装对象TraceMethod
  33. final ConcurrentHashMap<String, TraceMethod> collectedMethodMap = new ConcurrentHashMap<>();
  34. futures.add(executor.submit(new ParseMappingTask(mappingCollector, collectedMethodMap, methodId)));
  35. //存放原始 源文件 和 输出 源文件的 对应关系
  36. Map<File, File> dirInputOutMap = new ConcurrentHashMap<>();
  37. //存放原始jar文件和 输出jar文件 对应关系
  38. Map<File, File> jarInputOutMap = new ConcurrentHashMap<>();
  39. Collection<TransformInput> inputs = transformInvocation.getInputs();
  40. //上面都是属于的预处理,我们先不管,直接看下面的ASM项目实现代码
  41. for (TransformInput input : inputs) {
  42. for (DirectoryInput directoryInput : input.getDirectoryInputs()) {
  43. //找到了插桩ASM实现的地方,看了下CollectDirectoryInputTask源码,它最终输出增量的dirInputOutMap
  44. futures.add(executor.submit(new CollectDirectoryInputTask(dirInputOutMap, directoryInput, isIncremental)));
  45. }
  46. for (JarInput inputJar : input.getJarInputs()) {
  47. //跟CollectDirectoryInputTask几乎一样
  48. futures.add(executor.submit(new CollectJarInputTask(inputJar, isIncremental, jarInputOutMap, dirInputOutMap)));
  49. }
  50. }
  51. //future任务在 executor线程池中,并发执行。
  52. for (Future future : futures) {
  53. future.get();
  54. }
  55. futures.clear();
  56. //执行完成
  57. Log.i(TAG, "[doTransform] Step(1)[Parse]... cost:%sms", System.currentTimeMillis() - start);
  58. /**
  59. * step 2
  60. */
  61. start = System.currentTimeMillis();
  62. //计算出需要处理的dirInputOutMap文件,开始插桩
  63. MethodCollector methodCollector = new MethodCollector(executor, mappingCollector, methodId, config, collectedMethodMap);
  64. methodCollector.collect(dirInputOutMap.keySet(), jarInputOutMap.keySet());
  65. Log.i(TAG, "[doTransform] Step(2)[Collection]... cost:%sms", System.currentTimeMillis() - start);
  66. /**
  67. * step 3
  68. */
  69. start = System.currentTimeMillis();
  70. //这里看名字应该就是Trace相关的插桩逻辑,我们的启动耗时应该就在这里,根据我们的猜想接着往下看
  71. MethodTracer methodTracer = new MethodTracer(executor, mappingCollector, config, methodCollector.getCollectedMethodMap(), methodCollector.getCollectedClassExtendMap());
  72. methodTracer.trace(dirInputOutMap, jarInputOutMap);
  73. Log.i(TAG, "[doTransform] Step(3)[Trace]... cost:%sms", System.currentTimeMillis() - start);
  74. }
  1. //MethodTracer 的trace方法
  2. public void trace(Map<File, File> srcFolderList, Map<File, File> dependencyJarList) throws ExecutionException, InterruptedException {
  3. List<Future> futures = new LinkedList<>();
  4. traceMethodFromSrc(srcFolderList, futures);
  5. traceMethodFromJar(dependencyJarList, futures);
  6. for (Future future : futures) {
  7. future.get();
  8. }
  9. futures.clear();
  10. }
  11. private void traceMethodFromSrc(Map<File, File> srcMap, List<Future> futures) {
  12. if (null != srcMap) {
  13. for (Map.Entry<File, File> entry : srcMap.entrySet()) {
  14. futures.add(executor.submit(new Runnable() {
  15. @Override
  16. public void run() {
  17. //对非jar包文件插入trace相关方法,看下方函数实现
  18. innerTraceMethodFromSrc(entry.getKey(), entry.getValue());
  19. }
  20. }));
  21. }
  22. }
  23. }
  24. private void traceMethodFromJar(Map<File, File> dependencyMap, List<Future> futures) {
  25. if (null != dependencyMap) {
  26. for (Map.Entry<File, File> entry : dependencyMap.entrySet()) {
  27. futures.add(executor.submit(new Runnable() {
  28. @Override
  29. public void run() {
  30. //对jar包插入trace相关方法
  31. innerTraceMethodFromJar(entry.getKey(), entry.getValue());
  32. }
  33. }));
  34. }
  35. }
  36. }
  37. //开始插入代码
  38. private void innerTraceMethodFromSrc(File input, File output) {
  39. //找到所有文件,过滤到文件夹
  40. ArrayList<File> classFileList = new ArrayList<>();
  41. if (input.isDirectory()) {
  42. listClassFiles(classFileList, input);
  43. } else {
  44. classFileList.add(input);
  45. }
  46. //遍历所有文件,进行插桩
  47. for (File classFile : classFileList) {
  48. InputStream is = null;
  49. FileOutputStream os = null;
  50. try {
  51. final String changedFileInputFullPath = classFile.getAbsolutePath();
  52. final File changedFileOutput = new File(changedFileInputFullPath.replace(input.getAbsolutePath(), output.getAbsolutePath()));
  53. if (!changedFileOutput.exists()) {
  54. changedFileOutput.getParentFile().mkdirs();
  55. }
  56. changedFileOutput.createNewFile();
  57. //根据类名判断方法需不需要插桩,检查是否是.class文件
  58. if (MethodCollector.isNeedTraceFile(classFile.getName())) {
  59. is = new FileInputStream(classFile);
  60. ClassReader classReader = new ClassReader(is);
  61. ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS);
  62. //按照TraceClassAdapter的规则进行修改class文件,接下来看下TraceClassAdapter
  63. ClassVisitor classVisitor = new TraceClassAdapter(Opcodes.ASM5, classWriter);
  64. classReader.accept(classVisitor, ClassReader.EXPAND_FRAMES);
  65. is.close();
  66. if (output.isDirectory()) {
  67. os = new FileOutputStream(changedFileOutput);
  68. } else {
  69. os = new FileOutputStream(output);
  70. }
  71. os.write(classWriter.toByteArray());
  72. os.close();
  73. } else {
  74. FileUtil.copyFileUsingStream(classFile, changedFileOutput);
  75. }
  76. } catch (Exception e) {
  77. Log.e(TAG, "[innerTraceMethodFromSrc] input:%s e:%s", input.getName(), e);
  78. try {
  79. Files.copy(input.toPath(), output.toPath(), StandardCopyOption.REPLACE_EXISTING);
  80. } catch (Exception e1) {
  81. e1.printStackTrace();
  82. }
  83. } finally {
  84. try {
  85. is.close();
  86. os.close();
  87. } catch (Exception e) {
  88. // ignore
  89. }
  90. }
  91. }
  92. }
  1. private class TraceClassAdapter extends ClassVisitor {
  2. private String className;
  3. private boolean isABSClass = false;
  4. private boolean hasWindowFocusMethod = false;
  5. private boolean isActivityOrSubClass;
  6. private boolean isNeedTrace;
  7. TraceClassAdapter(int i, ClassVisitor classVisitor) {
  8. super(i, classVisitor);
  9. }
  10. @Override
  11. public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
  12. super.visit(version, access, name, signature, superName, interfaces);
  13. this.className = name;
  14. this.isActivityOrSubClass = isActivityOrSubClass(className, collectedClassExtendMap);
  15. this.isNeedTrace = MethodCollector.isNeedTrace(configuration, className, mappingCollector);
  16. if ((access & Opcodes.ACC_ABSTRACT) > 0 || (access & Opcodes.ACC_INTERFACE) > 0) {
  17. this.isABSClass = true;
  18. }
  19. }
  20. @Override
  21. public MethodVisitor visitMethod(int access, String name, String desc,
  22. String signature, String[] exceptions) {
  23. if (isABSClass) {
  24. return super.visitMethod(access, name, desc, signature, exceptions);
  25. } else {
  26. if (!hasWindowFocusMethod) {
  27. //判断方法名是否是onWindowFocusChanged
  28. hasWindowFocusMethod = MethodCollector.isWindowFocusChangeMethod(name, desc);
  29. }
  30. MethodVisitor methodVisitor = cv.visitMethod(access, name, desc, signature, exceptions);
  31. //方法插入规则
  32. return new TraceMethodAdapter(api, methodVisitor, access, name, desc, this.className,
  33. hasWindowFocusMethod, isActivityOrSubClass, isNeedTrace);
  34. }
  35. }
  36. @Override
  37. public void visitEnd() {
  38. if (!hasWindowFocusMethod && isActivityOrSubClass && isNeedTrace) {
  39. insertWindowFocusChangeMethod(cv, className);
  40. }
  41. super.visitEnd();
  42. }
  43. }
  44. //方法的插入规则
  45. private class TraceMethodAdapter extends AdviceAdapter {
  46. private final String methodName;
  47. private final String name;
  48. private final String className;
  49. private final boolean hasWindowFocusMethod;
  50. private final boolean isNeedTrace;
  51. private final boolean isActivityOrSubClass;
  52. protected TraceMethodAdapter(int api, MethodVisitor mv, int access, String name, String desc, String className,
  53. boolean hasWindowFocusMethod, boolean isActivityOrSubClass, boolean isNeedTrace) {
  54. super(api, mv, access, name, desc);
  55. TraceMethod traceMethod = TraceMethod.create(0, access, className, name, desc);
  56. this.methodName = traceMethod.getMethodName();
  57. this.hasWindowFocusMethod = hasWindowFocusMethod;
  58. this.className = className;
  59. this.name = name;
  60. this.isActivityOrSubClass = isActivityOrSubClass;
  61. this.isNeedTrace = isNeedTrace;
  62. }
  63. @Override
  64. protected void onMethodEnter() {
  65. TraceMethod traceMethod = collectedMethodMap.get(methodName);
  66. //方法开始位置插入com/tencent/matrix/trace/core/AppMethodBeat类的i方法
  67. if (traceMethod != null) {
  68. traceMethodCount.incrementAndGet();
  69. mv.visitLdcInsn(traceMethod.id);
  70. mv.visitMethodInsn(INVOKESTATIC, TraceBuildConstants.MATRIX_TRACE_CLASS, "i", "(I)V", false);
  71. }
  72. }
  73. @Override
  74. protected void onMethodExit(int opcode) {
  75. TraceMethod traceMethod = collectedMethodMap.get(methodName);
  76. if (traceMethod != null) {
  77. //如果方法是onWindowFocusChanged 并且是Activity或者其子类,并且开启Trace
  78. if (hasWindowFocusMethod && isActivityOrSubClass && isNeedTrace) {
  79. TraceMethod windowFocusChangeMethod = TraceMethod.create(-1, Opcodes.ACC_PUBLIC, className,
  80. TraceBuildConstants.MATRIX_TRACE_ON_WINDOW_FOCUS_METHOD, TraceBuildConstants.MATRIX_TRACE_ON_WINDOW_FOCUS_METHOD_ARGS);
  81. if (windowFocusChangeMethod.equals(traceMethod)) {
  82. //往onWindowFocusChanged函数中插入代码
  83. traceWindowFocusChangeMethod(mv, className);
  84. }
  85. }
  86. traceMethodCount.incrementAndGet();
  87. //方法结束位置插入com/tencent/matrix/trace/core/AppMethodBeat类的o方法
  88. mv.visitLdcInsn(traceMethod.id);
  89. mv.visitMethodInsn(INVOKESTATIC, TraceBuildConstants.MATRIX_TRACE_CLASS, "o", "(I)V", false);
  90. }
  91. }
  92. //插入的代码是 com/tencent/matrix/trace/core/AppMethodBeat 的at函数
  93. private void traceWindowFocusChangeMethod(MethodVisitor mv, String classname) {
  94. mv.visitVarInsn(Opcodes.ALOAD, 0);
  95. mv.visitVarInsn(Opcodes.ILOAD, 1);
  96. mv.visitMethodInsn(Opcodes.INVOKESTATIC, TraceBuildConstants.MATRIX_TRACE_CLASS, "at", "(Landroid/app/Activity;Z)V", false);
  97. }
  98. }

找到了插桩的函数,但不知道到底做了什么,回到com/tencent/matrix/trace/core/AppMethodBeat类中的三个函数中来瞅一眼

  1. public static void i(int methodId) {
  2. if (status <= STATUS_STOPPED) {
  3. return;
  4. }
  5. if (methodId >= METHOD_ID_MAX) {
  6. return;
  7. }
  8. if (status == STATUS_DEFAULT) {
  9. synchronized (statusLock) {
  10. if (status == STATUS_DEFAULT) {
  11. //这个函数做了时间的计算,请看下面
  12. realExecute();
  13. status = STATUS_READY;
  14. }
  15. }
  16. }
  17. long threadId = Thread.currentThread().getId();
  18. if (sMethodEnterListener != null) {
  19. sMethodEnterListener.enter(methodId, threadId);
  20. }
  21. if (threadId == sMainThreadId) {
  22. if (assertIn) {
  23. android.util.Log.e(TAG, "ERROR!!! AppMethodBeat.i Recursive calls!!!");
  24. return;
  25. }
  26. assertIn = true;
  27. if (sIndex < Constants.BUFFER_SIZE) {
  28. mergeData(methodId, sIndex, true);
  29. } else {
  30. sIndex = 0;
  31. mergeData(methodId, sIndex, true);
  32. }
  33. ++sIndex;
  34. assertIn = false;
  35. }
  36. }
  37. private static void realExecute() {
  38. MatrixLog.i(TAG, "[realExecute] timestamp:%s", System.currentTimeMillis());
  39. sCurrentDiffTime = SystemClock.uptimeMillis() - sDiffTime;
  40. sHandler.removeCallbacksAndMessages(null);
  41. sHandler.postDelayed(sUpdateDiffTimeRunnable, Constants.TIME_UPDATE_CYCLE_MS);
  42. sHandler.postDelayed(checkStartExpiredRunnable = new Runnable() {
  43. @Override
  44. public void run() {
  45. synchronized (statusLock) {
  46. MatrixLog.i(TAG, "[startExpired] timestamp:%s status:%s", System.currentTimeMillis(), status);
  47. if (status == STATUS_DEFAULT || status == STATUS_READY) {
  48. status = STATUS_EXPIRED_START;
  49. }
  50. }
  51. }
  52. }, Constants.DEFAULT_RELEASE_BUFFER_DELAY);
  53. //hook android.app.ActivityThread 中Handler对象mH的mCallBack,将其赋值为HackCallback
  54. ActivityThreadHacker.hackSysHandlerCallback();
  55. //添加Looper监控
  56. LooperMonitor.register(looperMonitorListener);
  57. }
  58. //hook ActivityThread 中 Handler对象 mh 的mCallBack属性
  59. public static void hackSysHandlerCallback() {
  60. try {
  61. sApplicationCreateBeginTime = SystemClock.uptimeMillis();
  62. sApplicationCreateBeginMethodIndex = AppMethodBeat.getInstance().maskIndex("ApplicationCreateBeginMethodIndex");
  63. Class<?> forName = Class.forName("android.app.ActivityThread");
  64. Field field = forName.getDeclaredField("sCurrentActivityThread");
  65. field.setAccessible(true);
  66. Object activityThreadValue = field.get(forName);
  67. Field mH = forName.getDeclaredField("mH");
  68. mH.setAccessible(true);
  69. Object handler = mH.get(activityThreadValue);
  70. Class<?> handlerClass = handler.getClass().getSuperclass();
  71. if (null != handlerClass) {
  72. //将HackCallback赋值给mCallback
  73. Field callbackField = handlerClass.getDeclaredField("mCallback");
  74. callbackField.setAccessible(true);
  75. Handler.Callback originalCallback = (Handler.Callback) callbackField.get(handler);
  76. HackCallback callback = new HackCallback(originalCallback);
  77. callbackField.set(handler, callback);
  78. }
  79. MatrixLog.i(TAG, "hook system handler completed. start:%s SDK_INT:%s", sApplicationCreateBeginTime, Build.VERSION.SDK_INT);
  80. } catch (Exception e) {
  81. MatrixLog.e(TAG, "hook system handler err! %s", e.getCause().toString());
  82. }
  83. }
  84. //为什么要hook mH呢,回顾下前面的App启动流程中,ActivityManagerService其实是通过binder启动ApplicationThread,然后通过message消息,
  85. //最终在ActivityThread中启动luanchActivity,hook它就可以监听message消息,发现是luanchActivity的消息后,就可以做相应的信息记录,如app启动完成的标志。
  86. private final static class HackCallback implements Handler.Callback {
  87. private static final int LAUNCH_ACTIVITY = 100;
  88. private static final int CREATE_SERVICE = 114;
  89. private static final int RECEIVER = 113;
  90. private static final int EXECUTE_TRANSACTION = 159; // for Android 9.0
  91. private static boolean isCreated = false;
  92. private static int hasPrint = 10;
  93. private final Handler.Callback mOriginalCallback;
  94. HackCallback(Handler.Callback callback) {
  95. this.mOriginalCallback = callback;
  96. }
  97. @Override
  98. public boolean handleMessage(Message msg) {
  99. if (!AppMethodBeat.isRealTrace()) {
  100. return null != mOriginalCallback && mOriginalCallback.handleMessage(msg);
  101. }
  102. //判断是否是launchActivity的消息
  103. boolean isLaunchActivity = isLaunchActivity(msg);
  104. if (hasPrint > 0) {
  105. MatrixLog.i(TAG, "[handleMessage] msg.what:%s begin:%s isLaunchActivity:%s", msg.what, SystemClock.uptimeMillis(), isLaunchActivity);
  106. hasPrint--;
  107. }
  108. if (isLaunchActivity) {
  109. ActivityThreadHacker.sLastLaunchActivityTime = SystemClock.uptimeMillis();
  110. ActivityThreadHacker.sLastLaunchActivityMethodIndex = AppMethodBeat.getInstance().maskIndex("LastLaunchActivityMethodIndex");
  111. }
  112. if (!isCreated) {
  113. if (isLaunchActivity || msg.what == CREATE_SERVICE || msg.what == RECEIVER) { // todo for provider
  114. //赋值app启动结束时间 sApplicationCreateEndTime - sApplicationCreateBeginTime 就是我们的app启动时间
  115. ActivityThreadHacker.sApplicationCreateEndTime = SystemClock.uptimeMillis();
  116. ActivityThreadHacker.sApplicationCreateScene = msg.what;
  117. isCreated = true;
  118. sIsCreatedByLaunchActivity = isLaunchActivity;
  119. MatrixLog.i(TAG, "application create end, sApplicationCreateScene:%d, isLaunchActivity:%s", msg.what, isLaunchActivity);
  120. synchronized (listeners) {
  121. for (IApplicationCreateListener listener : listeners) {
  122. //app启动完成回调
  123. listener.onApplicationCreateEnd();
  124. }
  125. }
  126. }
  127. }
  128. return null != mOriginalCallback && mOriginalCallback.handleMessage(msg);
  129. }
  130. private Method method = null;
  131. //判断消息是否是LaunchActivity
  132. private boolean isLaunchActivity(Message msg) {
  133. if (Build.VERSION.SDK_INT > Build.VERSION_CODES.O_MR1) {
  134. if (msg.what == EXECUTE_TRANSACTION && msg.obj != null) {
  135. try {
  136. if (null == method) {
  137. Class clazz = Class.forName("android.app.servertransaction.ClientTransaction");
  138. method = clazz.getDeclaredMethod("getCallbacks");
  139. method.setAccessible(true);
  140. }
  141. List list = (List) method.invoke(msg.obj);
  142. if (!list.isEmpty()) {
  143. return list.get(0).getClass().getName().endsWith(".LaunchActivityItem");
  144. }
  145. } catch (Exception e) {
  146. MatrixLog.e(TAG, "[isLaunchActivity] %s", e);
  147. }
  148. }
  149. return msg.what == LAUNCH_ACTIVITY;
  150. } else {
  151. return msg.what == LAUNCH_ACTIVITY;
  152. }
  153. }
  154. }
  155. /**
  156. * hook method when it's called out.
  157. *
  158. * @param methodId
  159. */
  160. public static void o(int methodId) {
  161. if (status <= STATUS_STOPPED) {
  162. return;
  163. }
  164. if (methodId >= METHOD_ID_MAX) {
  165. return;
  166. }
  167. if (Thread.currentThread().getId() == sMainThreadId) {
  168. if (sIndex < Constants.BUFFER_SIZE) {
  169. mergeData(methodId, sIndex, false);
  170. } else {
  171. sIndex = 0;
  172. mergeData(methodId, sIndex, false);
  173. }
  174. ++sIndex;
  175. }
  176. }
  177. /**
  178. * when the special method calls,it's will be called.
  179. *
  180. * @param activity now at which activity
  181. * @param isFocus this window if has focus
  182. */
  183. public static void at(Activity activity, boolean isFocus) {
  184. String activityName = activity.getClass().getName();
  185. if (isFocus) {
  186. if (sFocusActivitySet.add(activityName)) {
  187. synchronized (listeners) {
  188. for (IAppMethodBeatListener listener : listeners) {
  189. listener.onActivityFocused(activity);
  190. }
  191. }
  192. MatrixLog.i(TAG, "[at] visibleScene[%s] has %s focus!", getVisibleScene(), "attach");
  193. }
  194. } else {
  195. if (sFocusActivitySet.remove(activityName)) {
  196. MatrixLog.i(TAG, "[at] visibleScene[%s] has %s focus!", getVisibleScene(), "detach");
  197. }
  198. }
  199. }

分析到这里发现App启动的开始时间是在插桩的函数中,第一次被执行i函数时记录的,结束时间是hook了Handler的消息,发现是LaunchActivity时记录的,整个应用的启动时间已经出现了,但我们配置那么多splashActivities,怎么没有相关逻辑呢?再来看一段代码

  1. //StartupTracer类中我们发现这个
  2. @Override
  3. protected void onAlive() {
  4. super.onAlive();
  5. MatrixLog.i(TAG, "[onAlive] isStartupEnable:%s", isStartupEnable);
  6. if (isStartupEnable) {
  7. AppMethodBeat.getInstance().addListener(this);
  8. //通过application注册了所有activity的生命回调
  9. Matrix.with().getApplication().registerActivityLifecycleCallbacks(this);
  10. }
  11. }
  12. //生命周期,
  13. public interface ActivityLifecycleCallbacks {
  14. void onActivityCreated(Activity activity, Bundle savedInstanceState);
  15. void onActivityStarted(Activity activity);
  16. void onActivityResumed(Activity activity);
  17. void onActivityPaused(Activity activity);
  18. void onActivityStopped(Activity activity);
  19. void onActivitySaveInstanceState(Activity activity, Bundle outState);
  20. void onActivityDestroyed(Activity activity);
  21. }
  22. //同样的在StartupTracer中,发现这个方法并不在ActivityLifecycleCallback中,其实这个生命周期就是插桩函数at中的回调
  23. //插桩函数at给每个onActivityFocused函数都插入了相关代码,所以会回调到这里
  24. @Override
  25. public void onActivityFocused(Activity activity) {
  26. if (ActivityThreadHacker.sApplicationCreateScene == Integer.MIN_VALUE) {
  27. Log.w(TAG, "start up from unknown scene");
  28. return;
  29. }
  30. String activityName = activity.getClass().getName();
  31. //冷启动
  32. if (isColdStartup()) {
  33. //判断是否有启动页面
  34. boolean isCreatedByLaunchActivity = ActivityThreadHacker.isCreatedByLaunchActivity();
  35. MatrixLog.i(TAG, "#ColdStartup# activity:%s, splashActivities:%s, empty:%b, "
  36. + "isCreatedByLaunchActivity:%b, hasShowSplashActivity:%b, "
  37. + "firstScreenCost:%d, now:%d, application_create_begin_time:%d, app_cost:%d",
  38. activityName, splashActivities, splashActivities.isEmpty(), isCreatedByLaunchActivity,
  39. hasShowSplashActivity, firstScreenCost, uptimeMillis(),
  40. ActivityThreadHacker.getEggBrokenTime(), ActivityThreadHacker.getApplicationCost());
  41. //用activity的名字和hash作为key,从createdTimeMap中获取createdTime时间,createdTime是在onActivityCreated中记录的
  42. String key = activityName + "@" + activity.hashCode();
  43. Long createdTime = createdTimeMap.get(key);
  44. if (createdTime == null) {
  45. createdTime = 0L;
  46. }
  47. //记录当前Activity启动耗时
  48. createdTimeMap.put(key, uptimeMillis() - createdTime);
  49. if (firstScreenCost == 0) {
  50. //第一屏启动耗时,减去app启动开始时间
  51. this.firstScreenCost = uptimeMillis() - ActivityThreadHacker.getEggBrokenTime();
  52. }
  53. if (hasShowSplashActivity) {
  54. //冷启动总耗时,在splash页启动完成时间减去应用启动时间,这个跟我们之前分析的不太一样,其实逻辑就是这样
  55. //冷启动时间,在没有splash页面就是lauchActivity消息发出的时间差,如果配置了splash页,
  56. //就是在splash页面启动完成的时间差
  57. coldCost = uptimeMillis() - ActivityThreadHacker.getEggBrokenTime();
  58. } else {
  59. if (splashActivities.contains(activityName)) {
  60. hasShowSplashActivity = true;
  61. } else if (splashActivities.isEmpty()) { //process which is has activity but not main UI process
  62. if (isCreatedByLaunchActivity) {
  63. coldCost = firstScreenCost;
  64. } else {
  65. firstScreenCost = 0;
  66. coldCost = ActivityThreadHacker.getApplicationCost();
  67. }
  68. } else {
  69. if (isCreatedByLaunchActivity) {
  70. // MatrixLog.e(TAG, "pass this activity[%s] at duration of start up! splashActivities=%s", activity, splashActivities);
  71. coldCost = firstScreenCost;
  72. } else {
  73. firstScreenCost = 0;
  74. coldCost = ActivityThreadHacker.getApplicationCost();
  75. }
  76. }
  77. }
  78. if (coldCost > 0) {
  79. Long betweenCost = createdTimeMap.get(key);
  80. if (null != betweenCost && betweenCost >= 30 * 1000) {
  81. MatrixLog.e(TAG, "%s cost too much time[%s] between activity create and onActivityFocused, "
  82. + "just throw it.(createTime:%s) ", key, uptimeMillis() - createdTime, createdTime);
  83. return;
  84. }
  85. //更新时间,发出报告
  86. analyse(ActivityThreadHacker.getApplicationCost(), firstScreenCost, coldCost, false);
  87. }
  88. } else if (isWarmStartUp()) {
  89. //热启动,就只需要记录最后一个activity创建的时间
  90. isWarmStartUp = false;
  91. long warmCost = uptimeMillis() - lastCreateActivity;
  92. MatrixLog.i(TAG, "#WarmStartup# activity:%s, warmCost:%d, now:%d, lastCreateActivity:%d", activityName, warmCost, uptimeMillis(), lastCreateActivity);
  93. if (warmCost > 0) {
  94. analyse(0, 0, warmCost, true);
  95. }
  96. }
  97. }

现在来总结下启动耗时中trace canary都做了啥:

  • 插桩i、o、at函数,在i函数中记录app启动开始时间,并hook ActivityThread Handler对象,通过callBack拿到launchActivity的消息,来记录application启动结束时间
  • at函数中回调Activity的onActivityFocused生命周期函数,用来记录activity启动结束时间,开始时间在onActivityCreated中记录

插桩、Hook、注册Activity的生命周期监听等,把复杂的流程简单化,解放双手。
你是不是还有疑问:为什么它不直接在ActivityThread中插桩呢?这样不就不用hook了吗,抱歉不行的,我没搜到相关支持的信息。hook思想也是一个不错的东西,值得我们深入学习下。希望这次分析对你有帮助。