Matrix源码分析系列-如何计算启动耗时

什么是启动耗时

分为两个角度:

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

启动耗时影响什么

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

启动耗时的标准是什么

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

如何查看启动耗时呢

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

命令:

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

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

需要监控的函数

既然我们决定使用代码的插桩来实现,那么就需要知道对哪些函数做操作,具体什么函数,这要看App整个启动过程的函数调用顺序,我整理了几个流程图,请看:

大致流程就是这样,并没有特别详细,基本原理给大家搞清楚,然后知道函数的调用顺序就ok,从图中分析出的知识点:

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

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

  • 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配置文件,如下:

plugins{
    //使用 java groovy 插件
    id 'java'
    id 'groovy'
}

group 'com.julive.sam'
version '0.0.1'

sourceCompatibility = 1.8

repositories{
    //使用阿里云的maven代理
    maven { url 'https://maven.aliyun.com/repository/google' }
    maven { url 'https://maven.aliyun.com/repository/public' }
    maven {
        url 'http://maven.aliyun.com/nexus/content/groups/public/'
    }
    maven {
        url 'http://maven.aliyun.com/nexus/content/repositories/jcenter'
    }
}

def asmVersion = '8.0.1'

dependencies {
	//引入gradle api
    implementation gradleApi()
    implementation localGroovy()
    //引入android studio扩展gradle的相关api
    implementation "com.android.tools.build:gradle:4.1.0"
    //引入apache io
    implementation 'org.apache.directory.studio:org.apache.commons.io:2.4'
    //引入ASM相关api,这是我们插桩的关键,要靠他实现方法插桩
    implementation "org.ow2.asm:asm:$asmVersion"
    implementation "org.ow2.asm:asm-util:$asmVersion"
    implementation "org.ow2.asm:asm-commons:$asmVersion"
}

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

在java目录中,创建包名com.julive.sam,在该包路径下创建Plugins插件,代码如下:

public class Plugins implements Plugin<Project> {
    @Override
    public void apply(Project target) {

    }
}

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

implementation-class=com.julive.sam.Plugins

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

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

4.创建gradle的Transform实现

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

public class TransformTest extends Transform {

    @Override
    public String getName() {
        // 随便起个名字
        return "TransformSam";
    }

    @Override
    public Set<QualifiedContent.ContentType> getInputTypes() {
    		//代表处理的 java 的 class 文件
        return TransformManager.CONTENT_CLASS; 
    }

    @Override
    public Set<? super QualifiedContent.Scope> getScopes() {
    		//要处理所有的class字节码
        return TransformManager.SCOPE_FULL_PROJECT;
    }

    @Override
    public boolean isIncremental() {
    		// 是否增量编译,我们先不考虑,返回false
        return false; 
    }

    @Override
    public void transform(TransformInvocation transformInvocation) throws TransformException,InterruptedException,IOException {
        super.transform(transformInvocation);
        try {
        		//待实现
            doTransform(transformInvocation); // hack
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}    

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

private void doTransform(TransformInvocation transformInvocation) throws IOException {
        System.out.println("doTransform   =======================================================");
        //inputs中是传过来的输入流,其中有两种格式,一种是jar包格式一种是目录格式。
        Collection<TransformInput> inputs = transformInvocation.getInputs();
        //获取到输出目录,最后将修改的文件复制到输出目录,这一步必须做不然编译会报错
        TransformOutputProvider outputProvider = transformInvocation.getOutputProvider();

        //删除之前的输出
        if (outputProvider != null)
            outputProvider.deleteAll();

        inputs.forEach(transformInput -> {
            //遍历directoryInputs
            transformInput.getDirectoryInputs().forEach(directoryInput -> {

            });
            //jarInputs
            transformInput.getJarInputs().forEach(jarInput -> {

            });
        });
    }

从transformInvocation的api中,我们获取了两个东西,一个是inputs,一个是outputProvider,我们遍历inputs后发现,他有两个api getDirectoryInputs和getJarInputs 这俩是什么东西呢?我描述不太好,我加了日志,来看下日志输出:

这下是不是看明白了,其实我对getDirectoryInputs做了一层文件筛选处理

transformInput.getDirectoryInputs().forEach(directoryInput -> {
      ArrayList<File> list = new ArrayList<>();
      getFileList(directoryInput.getFile(),list);
});
  //递归查找该文件夹下所有文件,因为我们修改的是.class 文件,而不关系文件夹
   void getFileList(File file,ArrayList<File> fileList) {
        if (file.isFile()) {
            fileList.add(file);
        } else {
            File[] list = file.listFiles();
            for (File value : list) {
                getFileList(value,fileList);
            }
        }
    }

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

5.现在开始操作ASM的api

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

基本的实现如下:

public class TestClassVisitor extends ClassVisitor{

    private String className;
    private String superName;

    TestClassVisitor(final ClassVisitor classVisitor) {
        super(Opcodes.ASM6,classVisitor);
    }

    /**
     * 这里可以拿到关于.class的所有信息,比如当前类所实现的接口类表等
     *
     * @param version    表示jdk的版本
     * @param access     当前类的修饰符 (这个和ASM 和 java有些差异,比如public 在这里就是ACC_PUBLIC)
     * @param name       当前类名
     * @param signature  泛型信息
     * @param superName  当前类的父类
     * @param interfaces 当前类实现的接口列表
     */
    @Override
    public void visit(int version,int access,String name,String signature,String superName,String[] interfaces) {
        super.visit(version,access,name,signature,superName,interfaces);
        this.className = name;
        this.superName = superName;
    }

    @Override
    public MethodVisitor visitMethod(int access,String descriptor,String[] exceptions) {
        //委托函数
        MethodVisitor methodVisitor = cv.visitMethod(access,descriptor,exceptions);
        //找到我们需要修改的类,注意这里是/ 斜杠来表示文件的路径,并不是java代码中的.
        if (className.equals("com/julive/samtest/MainActivity")) {
            // 判断方法name是onCreate
            if (name.startsWith("onCreate")) {
                //插桩函数的实现,同样用到ASM提供的对象,下面看具体实现代码
                return new TestMethodVisitor(Opcodes.ASM6,methodVisitor,className,superName);
            }
        }
        return methodVisitor;
    }
}

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

public class TestMethodVisitor extends AdviceAdapter {

    private String className;
    private String superName;

    protected TestMethodVisitor(int i,MethodVisitor methodVisitor,int i1,String s,String s1,String className,String superName) {
        super(i,i1,s,s1);
        this.className = className;
        this.superName = superName;
    }

    @Override
    protected void onMethodEnter() {
        super.onMethodEnter();
        mv.visitLdcInsn("TAG");
        mv.visitLdcInsn(className + "---->" + superName);
        mv.visitMethodInsn(Opcodes.INVOKESTATIC,"android/util/Log","i","(Ljava/lang/String;Ljava/lang/String;)I",false);
        mv.visitInsn(Opcodes.POP);
    }

    @Override
    protected void onMethodExit(int opcode) {
        mv.visitLdcInsn("TAG");
        mv.visitLdcInsn("this is end");
        mv.visitMethodInsn(Opcodes.INVOKESTATIC,false);
        mv.visitInsn(Opcodes.POP);
        super.onMethodExit(opcode);
    }
}

在这里推荐一个插件,plugins.jetbrains.com/plugin/1486…,用插件测试代码如下:

public class Test {

    void aa() {
        Log.i("TAG","this is end");
    }

}

转换ASM代码如下:

public static byte[] dump() throws Exception {

        ClassWriter classWriter = new ClassWriter(0);
        FieldVisitor fieldVisitor;
        MethodVisitor methodVisitor;
        AnnotationVisitor annotationVisitor0;

        classWriter.visit(V1_8,ACC_PUBLIC | ACC_SUPER,"com/julive/samtest/Test",null,"java/lang/Object",null);

        classWriter.visitSource("Test.java",null);

        {
            methodVisitor = classWriter.visitMethod(ACC_PUBLIC,"<init>","()V",null);
            methodVisitor.visitCode();
            Label label0 = new Label();
            methodVisitor.visitLabel(label0);
            methodVisitor.visitLineNumber(5,label0);
            methodVisitor.visitVarInsn(ALOAD,0);
            methodVisitor.visitMethodInsn(INVOKESPECIAL,false);
            methodVisitor.visitInsn(RETURN);
            Label label1 = new Label();
            methodVisitor.visitLabel(label1);
            methodVisitor.visitLocalVariable("this","Lcom/julive/samtest/Test;",label0,label1,0);
            methodVisitor.visitMaxs(1,1);
            methodVisitor.visitEnd();
        }
        {
            methodVisitor = classWriter.visitMethod(0,"aa",null);
            methodVisitor.visitCode();
            Label label0 = new Label();
            methodVisitor.visitLabel(label0);
            methodVisitor.visitLineNumber(8,label0);
            methodVisitor.visitLdcInsn("TAG");
            methodVisitor.visitLdcInsn("this is end");
            methodVisitor.visitMethodInsn(INVOKESTATIC,false);
            methodVisitor.visitInsn(POP);
            Label label1 = new Label();
            methodVisitor.visitLabel(label1);
            methodVisitor.visitLineNumber(9,label1);
            methodVisitor.visitInsn(RETURN);
            Label label2 = new Label();
            methodVisitor.visitLabel(label2);
            methodVisitor.visitLocalVariable("this",label2,0);
            methodVisitor.visitMaxs(2,1);
            methodVisitor.visitEnd();
        }
        classWriter.visitEnd();

        return classWriter.toByteArray();
    }

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

   methodVisitor.visitLdcInsn("TAG");
   methodVisitor.visitLdcInsn("this is end");
   methodVisitor.visitMethodInsn(INVOKESTATIC,false);
   methodVisitor.visitInsn(POP);

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

6.Tranfrom结合ASM实现

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

private void doTransform(TransformInvocation transformInvocation) throws IOException {
        System.out.println("doTransform   =======================================================");
        //inputs中是传过来的输入流,其中有两种格式,一种是jar包格式一种是目录格式。
        Collection<TransformInput> inputs = transformInvocation.getInputs();
        //获取到输出目录,最后将修改的文件复制到输出目录,这一步必须做不然编译会报错
        TransformOutputProvider outputProvider = transformInvocation.getOutputProvider();

        //删除之前的输出
        if (outputProvider != null)
            outputProvider.deleteAll();

        inputs.forEach(transformInput -> {
            //遍历directoryInputs
            transformInput.getDirectoryInputs().forEach(directoryInput -> {
                ArrayList<File> list = new ArrayList<>();
                getFileList(directoryInput.getFile(),list);
                list.forEach(file -> {
                    System.out.println("getDirectoryInputs   =======================================================" + file.getName());
                    // 判断是.class文件
                    if (file.isFile() && file.getName().endsWith(".class")) {
                        try {
                            //ASM提供的读取类信息的对象
                            ClassReader classReader = new ClassReader(new FileInputStream(file));
                            //ASM提供的类修改对象,并将读到的信息交给classWriter
                            ClassWriter classWriter = new ClassWriter(classReader,ClassWriter.COMPUTE_MAXS);
                            //创建修改规则,TestClassVisitor
                            ClassVisitor visitor = new TestClassVisitor(classWriter);
                            //将修改规则给classReader
                            classReader.accept(visitor,ClassReader.EXPAND_FRAMES);
                            //通过toByteArray方法,将变更后信息转成byte数组
                            byte[] bytes = classWriter.toByteArray();
                            //放入输出流中往原文件中写入
                            FileOutputStream fileOutputStream = new FileOutputStream(file.getAbsolutePath());
                            fileOutputStream.write(bytes);
                            fileOutputStream.close();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                });
                if (outputProvider != null) {
                    File dest = outputProvider.getContentLocation(directoryInput.getName(),directoryInput.getContentTypes(),directoryInput.getScopes(),Format.DIRECTORY);
                    try {
                        //将该文件放入到目标目录中,这步骤必须实现,否则会导致dex文件找不到该文件
                        FileUtils.copyDirectory(directoryInput.getFile(),dest);
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            });
            //jarInputs
            transformInput.getJarInputs().forEach(jarInput -> {
                ArrayList<File> list = new ArrayList<>();
                getFileList(jarInput.getFile(),list);
                list.forEach(file -> {
                    System.out.println("getJarInputs   =======================================================" + file.getName());
                });
                if (outputProvider != null) {
                    File dest = outputProvider.getContentLocation(
                            jarInput.getName(),jarInput.getContentTypes(),jarInput.getScopes(),Format.JAR);
                    //将该文件放入到目标目录中,这步骤必须实现,否则会导致dex文件找不到该文件
                    try {
                        FileUtils.copyFile(jarInput.getFile(),dest);
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            });
        });
    }

7.反编译检查代码

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

它提供了可视化操作,首先做如下操作:

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

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

jadx-gui

然后就会打来工具,如下:

然后将 app的debug apk包拖到这个窗口就行,如下:

我们找到MainActivity如下:

而我们原代码是这样,跟我们预想的效果一致。

好了整体下来,你已经掌握的基本的ASM操作,如果需要更加深入的学习,请到官网学习。接下来,就回到我们的主题,研究Matrix的启动耗时,都插入哪些代码呢?

Matrix 启动耗时统计插桩代码

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

先找到Plugins,如下:

class MatrixPlugin implements Plugin<Project> {
    private static final String TAG = "Matrix.MatrixPlugin"

    @Override
    void apply(Project project) {
        //创建新的配置项,就是你在build.gradle中用的配置
        project.extensions.create("matrix",MatrixExtension)
        project.matrix.extensions.create("trace",MatrixTraceExtension)
        project.matrix.extensions.create("removeUnusedResources",MatrixDelUnusedResConfiguration)
        //仅支持application,如果在library中配置就会导致gradle项目编译失败
        if (!project.plugins.hasPlugin('com.android.application')) {
            throw new GradleException('Matrix Plugin,Android Application plugin required')
        }
		//较常见的一个配置参数的回调方式,只要 project 配置成功均会调用	
        project.afterEvaluate {
            //拿到项目的android配置
            def android = project.extensions.android
            //拿到matrix配置
            def configuration = project.matrix
            //ApplicationVariant对象    
            android.applicationVariants.all { variant ->
				//matrix 配置中 trace 下 enable属性如果为true,开启MatrixTraceTransform插桩
                if (configuration.trace.enable) {
                    com.tencent.matrix.trace.transform.MatrixTraceTransform.inject(project,configuration.trace,variant.getVariantData().getScope())
                }
				//如果删除无用资源开工是true,则在project的tasks中创建相关任务。
                if (configuration.removeUnusedResources.enable) {
                    if (Util.isNullOrNil(configuration.removeUnusedResources.variant) || variant.name.equalsIgnoreCase(configuration.removeUnusedResources.variant)) {
                        Log.i(TAG,"removeUnusedResources %s",configuration.removeUnusedResources)
                        RemoveUnusedResourcesTask removeUnusedResourcesTask = project.tasks.create("remove" + variant.name.capitalize() + "UnusedResources",RemoveUnusedResourcesTask)
                        removeUnusedResourcesTask.inputs.property(RemoveUnusedResourcesTask.BUILD_VARIANT,variant.name)
                        project.tasks.add(removeUnusedResourcesTask)
                        removeUnusedResourcesTask.dependsOn variant.packageApplication
                        variant.assemble.dependsOn removeUnusedResourcesTask
                    }
                }

            }
        }
    }
}

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

	@Override
    public void transform(TransformInvocation transformInvocation) throws TransformException,IOException {
        super.transform(transformInvocation);
        // 记录开始时间
        long start = System.currentTimeMillis();
        try {
            //开始插桩
            doTransform(transformInvocation); // hack
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
        long cost = System.currentTimeMillis() - start;
        long begin = System.currentTimeMillis();
        origTransform.transform(transformInvocation);
        long origTransformCost = System.currentTimeMillis() - begin;
        Log.i("Matrix." + getName(),"[transform] cost time: %dms %s:%sms MatrixTraceTransform:%sms",System.currentTimeMillis() - start,origTransform.getClass().getSimpleName(),origTransformCost,cost);
    }
 private void doTransform(TransformInvocation transformInvocation) throws ExecutionException,InterruptedException {
     	//判断是否为增量编译
        final boolean isIncremental = transformInvocation.isIncremental() && this.isIncremental();

        /**
         * step 1
         */
        long start = System.currentTimeMillis();
		//Future 表示异步计算的结果。它提供了检查计算是否完成的方法,以等待计算的完成,并获取计算的结果。
        //计算完成后只能使用 get 方法来获取结果,如有必要,计算完成前可以阻塞此方法,
     	//可以异步处理,同步返回
        List<Future> futures = new LinkedList<>();
     	//存储 混淆前方法、混淆后方法的映射关系
        final MappingCollector mappingCollector = new MappingCollector();
        final AtomicInteger methodId = new AtomicInteger(0);
     	//存储 需要插桩的 方法名 和 方法的封装对象TraceMethod
        final ConcurrentHashMap<String,TraceMethod> collectedMethodMap = new ConcurrentHashMap<>();

        futures.add(executor.submit(new ParseMappingTask(mappingCollector,collectedMethodMap,methodId)));
		//存放原始 源文件 和 输出 源文件的 对应关系
        Map<File,File> dirInputOutMap = new ConcurrentHashMap<>();
     	//存放原始jar文件和 输出jar文件 对应关系
        Map<File,File> jarInputOutMap = new ConcurrentHashMap<>();
        Collection<TransformInput> inputs = transformInvocation.getInputs();

        //上面都是属于的预处理,我们先不管,直接看下面的ASM项目实现代码
        for (TransformInput input : inputs) {

            for (DirectoryInput directoryInput : input.getDirectoryInputs()) {
                //找到了插桩ASM实现的地方,看了下CollectDirectoryInputTask源码,它最终输出增量的dirInputOutMap
                futures.add(executor.submit(new CollectDirectoryInputTask(dirInputOutMap,directoryInput,isIncremental)));
            }

            for (JarInput inputJar : input.getJarInputs()) {
                //跟CollectDirectoryInputTask几乎一样
                futures.add(executor.submit(new CollectJarInputTask(inputJar,isIncremental,jarInputOutMap,dirInputOutMap)));
            }
        }
		//future任务在 executor线程池中,并发执行。
        for (Future future : futures) {
            future.get();
        }
        futures.clear();
		//执行完成
        Log.i(TAG,"[doTransform] Step(1)[Parse]... cost:%sms",System.currentTimeMillis() - start);

        /**
         * step 2
         */
        start = System.currentTimeMillis();
     	//计算出需要处理的dirInputOutMap文件,开始插桩
        MethodCollector methodCollector = new MethodCollector(executor,mappingCollector,methodId,config,collectedMethodMap);
        methodCollector.collect(dirInputOutMap.keySet(),jarInputOutMap.keySet());
        Log.i(TAG,"[doTransform] Step(2)[Collection]... cost:%sms",System.currentTimeMillis() - start);

        /**
         * step 3
         */
        start = System.currentTimeMillis();
        //这里看名字应该就是Trace相关的插桩逻辑,我们的启动耗时应该就在这里,根据我们的猜想接着往下看
        MethodTracer methodTracer = new MethodTracer(executor,methodCollector.getCollectedMethodMap(),methodCollector.getCollectedClassExtendMap());
        methodTracer.trace(dirInputOutMap,jarInputOutMap);
        Log.i(TAG,"[doTransform] Step(3)[Trace]... cost:%sms",System.currentTimeMillis() - start);

    }

	//MethodTracer 的trace方法
	public void trace(Map<File,File> srcFolderList,Map<File,File> dependencyJarList) throws ExecutionException,InterruptedException {
        List<Future> futures = new LinkedList<>();
        traceMethodFromSrc(srcFolderList,futures);
        traceMethodFromJar(dependencyJarList,futures);
        for (Future future : futures) {
            future.get();
        }
        futures.clear();
    }
	private void traceMethodFromSrc(Map<File,File> srcMap,List<Future> futures) {
        if (null != srcMap) {
            for (Map.Entry<File,File> entry : srcMap.entrySet()) {
                futures.add(executor.submit(new Runnable() {
                    @Override
                    public void run() {
                        //对非jar包文件插入trace相关方法,看下方函数实现
                        innerTraceMethodFromSrc(entry.getKey(),entry.getValue());
                    }
                }));
            }
        }
    }
   private void traceMethodFromJar(Map<File,File> dependencyMap,List<Future> futures) {
        if (null != dependencyMap) {
            for (Map.Entry<File,File> entry : dependencyMap.entrySet()) {
                futures.add(executor.submit(new Runnable() {
                    @Override
                    public void run() {
                        //对jar包插入trace相关方法
                        innerTraceMethodFromJar(entry.getKey(),entry.getValue());
                    }
                }));
            }
        }
    }
//开始插入代码
private void innerTraceMethodFromSrc(File input,File output) {
		//找到所有文件,过滤到文件夹
        ArrayList<File> classFileList = new ArrayList<>();
        if (input.isDirectory()) {
            listClassFiles(classFileList,input);
        } else {
            classFileList.add(input);
        }
		//遍历所有文件,进行插桩
        for (File classFile : classFileList) {
            InputStream is = null;
            FileOutputStream os = null;
            try {
                final String changedFileInputFullPath = classFile.getAbsolutePath();
                final File changedFileOutput = new File(changedFileInputFullPath.replace(input.getAbsolutePath(),output.getAbsolutePath()));
                if (!changedFileOutput.exists()) {
                    changedFileOutput.getParentFile().mkdirs();
                }
                changedFileOutput.createNewFile();
				//根据类名判断方法需不需要插桩,检查是否是.class文件
                if (MethodCollector.isNeedTraceFile(classFile.getName())) {
                    is = new FileInputStream(classFile);
                    ClassReader classReader = new ClassReader(is);
                    ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS);
                    //按照TraceClassAdapter的规则进行修改class文件,接下来看下TraceClassAdapter
                    ClassVisitor classVisitor = new TraceClassAdapter(Opcodes.ASM5,classWriter);
                    classReader.accept(classVisitor,ClassReader.EXPAND_FRAMES);
                    is.close();

                    if (output.isDirectory()) {
                        os = new FileOutputStream(changedFileOutput);
                    } else {
                        os = new FileOutputStream(output);
                    }
                    os.write(classWriter.toByteArray());
                    os.close();
                } else {
                    FileUtil.copyFileUsingStream(classFile,changedFileOutput);
                }
            } catch (Exception e) {
                Log.e(TAG,"[innerTraceMethodFromSrc] input:%s e:%s",input.getName(),e);
                try {
                    Files.copy(input.toPath(),output.toPath(),StandardCopyOption.REPLACE_EXISTING);
                } catch (Exception e1) {
                    e1.printStackTrace();
                }
            } finally {
                try {
                    is.close();
                    os.close();
                } catch (Exception e) {
                    // ignore
                }
            }
        }
    }

private class TraceClassAdapter extends ClassVisitor {

        private String className;
        private boolean isABSClass = false;
        private boolean hasWindowFocusMethod = false;
        private boolean isActivityOrSubClass;
        private boolean isNeedTrace;

        TraceClassAdapter(int i,ClassVisitor classVisitor) {
            super(i,classVisitor);
        }

        @Override
        public void visit(int version,String[] interfaces) {
            super.visit(version,interfaces);
            this.className = name;
            this.isActivityOrSubClass = isActivityOrSubClass(className,collectedClassExtendMap);
            this.isNeedTrace = MethodCollector.isNeedTrace(configuration,mappingCollector);
            if ((access & Opcodes.ACC_ABSTRACT) > 0 || (access & Opcodes.ACC_INTERFACE) > 0) {
                this.isABSClass = true;
            }

        }

        @Override
        public MethodVisitor visitMethod(int access,String desc,String[] exceptions) {
            if (isABSClass) {
                return super.visitMethod(access,desc,exceptions);
            } else {
                if (!hasWindowFocusMethod) {
                    //判断方法名是否是onWindowFocusChanged
                    hasWindowFocusMethod = MethodCollector.isWindowFocusChangeMethod(name,desc);
                }
                MethodVisitor methodVisitor = cv.visitMethod(access,exceptions);
                //方法插入规则
                return new TraceMethodAdapter(api,this.className,hasWindowFocusMethod,isActivityOrSubClass,isNeedTrace);
            }
        }

        @Override
        public void visitEnd() {
            if (!hasWindowFocusMethod && isActivityOrSubClass && isNeedTrace) {
                insertWindowFocusChangeMethod(cv,className);
            }
            super.visitEnd();
        }
    }
//方法的插入规则
private class TraceMethodAdapter extends AdviceAdapter {

        private final String methodName;
        private final String name;
        private final String className;
        private final boolean hasWindowFocusMethod;
        private final boolean isNeedTrace;
        private final boolean isActivityOrSubClass;

        protected TraceMethodAdapter(int api,MethodVisitor mv,boolean hasWindowFocusMethod,boolean isActivityOrSubClass,boolean isNeedTrace) {
            super(api,mv,desc);
            TraceMethod traceMethod = TraceMethod.create(0,desc);
            this.methodName = traceMethod.getMethodName();
            this.hasWindowFocusMethod = hasWindowFocusMethod;
            this.className = className;
            this.name = name;
            this.isActivityOrSubClass = isActivityOrSubClass;
            this.isNeedTrace = isNeedTrace;

        }

        @Override
        protected void onMethodEnter() {
            TraceMethod traceMethod = collectedMethodMap.get(methodName);
            //方法开始位置插入com/tencent/matrix/trace/core/AppMethodBeat类的i方法
            if (traceMethod != null) {
                traceMethodCount.incrementAndGet();
                mv.visitLdcInsn(traceMethod.id);
                mv.visitMethodInsn(INVOKESTATIC,TraceBuildConstants.MATRIX_TRACE_CLASS,"(I)V",false);
            }
        }
        @Override
        protected void onMethodExit(int opcode) {
            TraceMethod traceMethod = collectedMethodMap.get(methodName);
            if (traceMethod != null) {
                //如果方法是onWindowFocusChanged 并且是Activity或者其子类,并且开启Trace
                if (hasWindowFocusMethod && isActivityOrSubClass && isNeedTrace) {
                    TraceMethod windowFocusChangeMethod = TraceMethod.create(-1,Opcodes.ACC_PUBLIC,TraceBuildConstants.MATRIX_TRACE_ON_WINDOW_FOCUS_METHOD,TraceBuildConstants.MATRIX_TRACE_ON_WINDOW_FOCUS_METHOD_ARGS);
                    if (windowFocusChangeMethod.equals(traceMethod)) {
                        //往onWindowFocusChanged函数中插入代码
                        traceWindowFocusChangeMethod(mv,className);
                    }
                }
                traceMethodCount.incrementAndGet();
            	//方法结束位置插入com/tencent/matrix/trace/core/AppMethodBeat类的o方法
                mv.visitLdcInsn(traceMethod.id);
                mv.visitMethodInsn(INVOKESTATIC,"o",false);
            }
        }
    //插入的代码是 com/tencent/matrix/trace/core/AppMethodBeat 的at函数
     private void traceWindowFocusChangeMethod(MethodVisitor mv,String classname) {
        mv.visitVarInsn(Opcodes.ALOAD,0);
        mv.visitVarInsn(Opcodes.ILOAD,1);
        mv.visitMethodInsn(Opcodes.INVOKESTATIC,"at","(Landroid/app/Activity;Z)V",false);
    }
}

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

 public static void i(int methodId) {

        if (status <= STATUS_STOPPED) {
            return;
        }
        if (methodId >= METHOD_ID_MAX) {
            return;
        }

        if (status == STATUS_DEFAULT) {
            synchronized (statusLock) {
                if (status == STATUS_DEFAULT) {
                    //这个函数做了时间的计算,请看下面
                    realExecute();
                    status = STATUS_READY;
                }
            }
        }

        long threadId = Thread.currentThread().getId();
        if (sMethodEnterListener != null) {
            sMethodEnterListener.enter(methodId,threadId);
        }

        if (threadId == sMainThreadId) {
            if (assertIn) {
                android.util.Log.e(TAG,"ERROR!!! AppMethodBeat.i Recursive calls!!!");
                return;
            }
            assertIn = true;
            if (sIndex < Constants.BUFFER_SIZE) {
                mergeData(methodId,sIndex,true);
            } else {
                sIndex = 0;
                mergeData(methodId,true);
            }
            ++sIndex;
            assertIn = false;
        }
    }

 	private static void realExecute() {
        MatrixLog.i(TAG,"[realExecute] timestamp:%s",System.currentTimeMillis());

        sCurrentDiffTime = SystemClock.uptimeMillis() - sDiffTime;

        sHandler.removeCallbacksAndMessages(null);
        sHandler.postDelayed(sUpdateDiffTimeRunnable,Constants.TIME_UPDATE_CYCLE_MS);
        sHandler.postDelayed(checkStartExpiredRunnable = new Runnable() {
            @Override
            public void run() {
                synchronized (statusLock) {
                    MatrixLog.i(TAG,"[startExpired] timestamp:%s status:%s",System.currentTimeMillis(),status);
                    if (status == STATUS_DEFAULT || status == STATUS_READY) {
                        status = STATUS_EXPIRED_START;
                    }
                }
            }
        },Constants.DEFAULT_RELEASE_BUFFER_DELAY);
		//hook android.app.ActivityThread 中Handler对象mH的mCallBack,将其赋值为HackCallback
        ActivityThreadHacker.hackSysHandlerCallback();
        //添加Looper监控
        LooperMonitor.register(looperMonitorListener);
    }
	//hook ActivityThread 中 Handler对象 mh 的mCallBack属性
  	public static void hackSysHandlerCallback() {
        try {
            sApplicationCreateBeginTime = SystemClock.uptimeMillis();
            sApplicationCreateBeginMethodIndex = AppMethodBeat.getInstance().maskIndex("ApplicationCreateBeginMethodIndex");
            Class<?> forName = Class.forName("android.app.ActivityThread");
            Field field = forName.getDeclaredField("sCurrentActivityThread");
            field.setAccessible(true);
            Object activityThreadValue = field.get(forName);
            Field mH = forName.getDeclaredField("mH");
            mH.setAccessible(true);
            Object handler = mH.get(activityThreadValue);
            Class<?> handlerClass = handler.getClass().getSuperclass();
            if (null != handlerClass) {
                //将HackCallback赋值给mCallback
                Field callbackField = handlerClass.getDeclaredField("mCallback");
                callbackField.setAccessible(true);
                Handler.Callback originalCallback = (Handler.Callback) callbackField.get(handler);
                HackCallback callback = new HackCallback(originalCallback);
                callbackField.set(handler,callback);
            }

            MatrixLog.i(TAG,"hook system handler completed. start:%s SDK_INT:%s",sApplicationCreateBeginTime,Build.VERSION.SDK_INT);
        } catch (Exception e) {
            MatrixLog.e(TAG,"hook system handler err! %s",e.getCause().toString());
        }
    }
	//为什么要hook mH呢,回顾下前面的App启动流程中,ActivityManagerService其实是通过binder启动ApplicationThread,然后通过message消息,
    //最终在ActivityThread中启动luanchActivity,hook它就可以监听message消息,发现是luanchActivity的消息后,就可以做相应的信息记录,如app启动完成的标志。

 	private final static class HackCallback implements Handler.Callback {
        private static final int LAUNCH_ACTIVITY = 100;
        private static final int CREATE_SERVICE = 114;
        private static final int RECEIVER = 113;
        private static final int EXECUTE_TRANSACTION = 159; // for Android 9.0
        private static boolean isCreated = false;
        private static int hasPrint = 10;

        private final Handler.Callback mOriginalCallback;

        HackCallback(Handler.Callback callback) {
            this.mOriginalCallback = callback;
        }

        @Override
        public boolean handleMessage(Message msg) {

            if (!AppMethodBeat.isRealTrace()) {
                return null != mOriginalCallback && mOriginalCallback.handleMessage(msg);
            }
			//判断是否是launchActivity的消息
            boolean isLaunchActivity = isLaunchActivity(msg);

            if (hasPrint > 0) {
                MatrixLog.i(TAG,"[handleMessage] msg.what:%s begin:%s isLaunchActivity:%s",msg.what,SystemClock.uptimeMillis(),isLaunchActivity);
                hasPrint--;
            }
            if (isLaunchActivity) {
                ActivityThreadHacker.sLastLaunchActivityTime = SystemClock.uptimeMillis();
                ActivityThreadHacker.sLastLaunchActivityMethodIndex = AppMethodBeat.getInstance().maskIndex("LastLaunchActivityMethodIndex");
            }

            if (!isCreated) {
                if (isLaunchActivity || msg.what == CREATE_SERVICE || msg.what == RECEIVER) { // todo for provider
                    //赋值app启动结束时间 sApplicationCreateEndTime - sApplicationCreateBeginTime 就是我们的app启动时间
                    ActivityThreadHacker.sApplicationCreateEndTime = SystemClock.uptimeMillis();
                    ActivityThreadHacker.sApplicationCreateScene = msg.what;
                    isCreated = true;
                    sIsCreatedByLaunchActivity = isLaunchActivity;
                    MatrixLog.i(TAG,"application create end,sApplicationCreateScene:%d,isLaunchActivity:%s",isLaunchActivity);
                    synchronized (listeners) {
                        for (IApplicationCreateListener listener : listeners) {
                            //app启动完成回调
                            listener.onApplicationCreateEnd();
                        }
                    }
                }
            }

            return null != mOriginalCallback && mOriginalCallback.handleMessage(msg);
        }

        private Method method = null;
		//判断消息是否是LaunchActivity
        private boolean isLaunchActivity(Message msg) {
            if (Build.VERSION.SDK_INT > Build.VERSION_CODES.O_MR1) {
                if (msg.what == EXECUTE_TRANSACTION && msg.obj != null) {
                    try {
                        if (null == method) {
                            Class clazz = Class.forName("android.app.servertransaction.ClientTransaction");
                            method = clazz.getDeclaredMethod("getCallbacks");
                            method.setAccessible(true);
                        }
                        List list = (List) method.invoke(msg.obj);
                        if (!list.isEmpty()) {
                            return list.get(0).getClass().getName().endsWith(".LaunchActivityItem");
                        }
                    } catch (Exception e) {
                        MatrixLog.e(TAG,"[isLaunchActivity] %s",e);
                    }
                }
                return msg.what == LAUNCH_ACTIVITY;
            } else {
                return msg.what == LAUNCH_ACTIVITY;
            }
        }
    }

    /**
     * hook method when it's called out.
     *
     * @param methodId
     */
    public static void o(int methodId) {
        if (status <= STATUS_STOPPED) {
            return;
        }
        if (methodId >= METHOD_ID_MAX) {
            return;
        }
        if (Thread.currentThread().getId() == sMainThreadId) {
            if (sIndex < Constants.BUFFER_SIZE) {
                mergeData(methodId,false);
            } else {
                sIndex = 0;
                mergeData(methodId,false);
            }
            ++sIndex;
        }
    }

    /**
     * when the special method calls,it's will be called.
     *
     * @param activity now at which activity
     * @param isFocus  this window if has focus
     */
    public static void at(Activity activity,boolean isFocus) {
        String activityName = activity.getClass().getName();
        if (isFocus) {
            if (sFocusActivitySet.add(activityName)) {
                synchronized (listeners) {
                    for (IAppMethodBeatListener listener : listeners) {
                        listener.onActivityFocused(activity);
                    }
                }
                MatrixLog.i(TAG,"[at] visibleScene[%s] has %s focus!",getVisibleScene(),"attach");
            }
        } else {
            if (sFocusActivitySet.remove(activityName)) {
                MatrixLog.i(TAG,"detach");
            }
        }
    }

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

   //StartupTracer类中我们发现这个
 	@Override
    protected void onAlive() {
        super.onAlive();
        MatrixLog.i(TAG,"[onAlive] isStartupEnable:%s",isStartupEnable);
        if (isStartupEnable) {
            AppMethodBeat.getInstance().addListener(this);
            //通过application注册了所有activity的生命回调
            Matrix.with().getApplication().registerActivityLifecycleCallbacks(this);
        }
    }
  	//生命周期,
    public interface ActivityLifecycleCallbacks {
        void onActivityCreated(Activity activity,Bundle savedInstanceState);
        void onActivityStarted(Activity activity);
        void onActivityResumed(Activity activity);
        void onActivityPaused(Activity activity);
        void onActivityStopped(Activity activity);
        void onActivitySaveInstanceState(Activity activity,Bundle outState);
        void onActivityDestroyed(Activity activity);
    }
 	//同样的在StartupTracer中,发现这个方法并不在ActivityLifecycleCallback中,其实这个生命周期就是插桩函数at中的回调
    //插桩函数at给每个onActivityFocused函数都插入了相关代码,所以会回调到这里
  	@Override
    public void onActivityFocused(Activity activity) {
        if (ActivityThreadHacker.sApplicationCreateScene == Integer.MIN_VALUE) {
            Log.w(TAG,"start up from unknown scene");
            return;
        }

        String activityName = activity.getClass().getName();
        //冷启动
        if (isColdStartup()) {
            //判断是否有启动页面
            boolean isCreatedByLaunchActivity = ActivityThreadHacker.isCreatedByLaunchActivity();
            MatrixLog.i(TAG,"#ColdStartup# activity:%s,splashActivities:%s,empty:%b,"
                            + "isCreatedByLaunchActivity:%b,hasShowSplashActivity:%b,"
                            + "firstScreenCost:%d,now:%d,application_create_begin_time:%d,app_cost:%d",activityName,splashActivities,splashActivities.isEmpty(),isCreatedByLaunchActivity,hasShowSplashActivity,firstScreenCost,uptimeMillis(),ActivityThreadHacker.getEggBrokenTime(),ActivityThreadHacker.getApplicationCost());
			//用activity的名字和hash作为key,从createdTimeMap中获取createdTime时间,createdTime是在onActivityCreated中记录的
            String key = activityName + "@" + activity.hashCode();
            Long createdTime = createdTimeMap.get(key);
            if (createdTime == null) {
                createdTime = 0L;
            }
            //记录当前Activity启动耗时
            createdTimeMap.put(key,uptimeMillis() - createdTime);

            if (firstScreenCost == 0) {
                //第一屏启动耗时,减去app启动开始时间
                this.firstScreenCost = uptimeMillis() - ActivityThreadHacker.getEggBrokenTime();
            }
            if (hasShowSplashActivity) {
                //冷启动总耗时,在splash页启动完成时间减去应用启动时间,这个跟我们之前分析的不太一样,其实逻辑就是这样
                //冷启动时间,在没有splash页面就是lauchActivity消息发出的时间差,如果配置了splash页,
                //就是在splash页面启动完成的时间差
                coldCost = uptimeMillis() - ActivityThreadHacker.getEggBrokenTime();
            } else {
                if (splashActivities.contains(activityName)) {
                    hasShowSplashActivity = true;
                } else if (splashActivities.isEmpty()) { //process which is has activity but not main UI process
                    if (isCreatedByLaunchActivity) {
                        coldCost = firstScreenCost;
                    } else {
                        firstScreenCost = 0;
                        coldCost = ActivityThreadHacker.getApplicationCost();
                    }
                } else {
                    if (isCreatedByLaunchActivity) {
//                        MatrixLog.e(TAG,"pass this activity[%s] at duration of start up! splashActivities=%s",activity,splashActivities);
                        coldCost = firstScreenCost;
                    } else {
                        firstScreenCost = 0;
                        coldCost = ActivityThreadHacker.getApplicationCost();
                    }
                }
            }
            if (coldCost > 0) {
                Long betweenCost = createdTimeMap.get(key);
                if (null != betweenCost && betweenCost >= 30 * 1000) {
                    MatrixLog.e(TAG,"%s cost too much time[%s] between activity create and onActivityFocused,"
                            + "just throw it.(createTime:%s) ",key,uptimeMillis() - createdTime,createdTime);
                    return;
                }
                //更新时间,发出报告
                analyse(ActivityThreadHacker.getApplicationCost(),coldCost,false);
            }

        } else if (isWarmStartUp()) {
            //热启动,就只需要记录最后一个activity创建的时间
            isWarmStartUp = false;
            long warmCost = uptimeMillis() - lastCreateActivity;
            MatrixLog.i(TAG,"#WarmStartup# activity:%s,warmCost:%d,lastCreateActivity:%d",warmCost,lastCreateActivity);

            if (warmCost > 0) {
                analyse(0,true);
            }
        }

    }

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

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

插桩、Hook、注册Activity的生命周期监听等,把复杂的流程简单化,解放双手。

你是不是还有疑问:为什么它不直接在ActivityThread中插桩呢?这样不就不用hook了吗,抱歉不行的,我没搜到相关支持的信息。hook思想也是一个不错的东西,值得我们深入学习下。希望这次分析对你有帮助。


为了帮助到大家更好的掌握性能优化相关知识点,这准备了 性能优化知识点汇总和Android 性能监控框架 的学习文档,中间记录了 启动优化、内存优化、UI优化……等知识点,可谓是很全面了,有需要的可以 点击这 ↓↓↓传送直达!!!

内功心法不是一天两天就可以修炼出来的,而是需要每天的坚持,技术提升也是如此。所以最好的速成修炼方法就是每天学习一点,日积月累后就会发现自己进步的效果。

原文地址:https://blog.csdn.net/weixin_61845324

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


更新Android SDK到3.0版本时,遇到Failed to rename directory E:\android\tools to E:\android\temp\ToolPackage.old01问题,导致无法更新,出现该问题的原因是由于3.0版本与较早的sdk版本之间文件结构有冲突,解决
Android 如何解决dialog弹出时无法捕捉Activity的back事件 在一些情况下,我们需要捕捉back键事件,然后在捕捉到的事件里写入我们需要进行的处理,通常可以采用下面三种办法捕捉到back事件: 1)重写onKeyDown或者onKeyUp方法 2)重写onBackPressed方
Android实现自定义带文字和图片的Button 在Android开发中经常会需要用到带文字和图片的button,下面来讲解一下常用的实现办法。一.用系统自带的Button实现 最简单的一种办法就是利用系统自带的Button来实现,这种方式代码量最小。在Button的属性中有一个是drawable
Android中的&quot;Unable to start activity ComponentInfo&quot;的错误 最近在做一款音乐播放器的时候,然后在调试的过程中发现一直报这个错误&quot;Unable to start activity ComponentInfo&quot;,从字面
Android 关于长按back键退出应用程序的实现最近在做一个Android上的应用,碰到一个问题就是如何实现长按back键退出应用程序。在网上查找了很多资料,发现几乎没有这样的实现,大部分在处理时是双击back键来退出应用程序。参考了一下双击back键退出应用程序的代码,网上主流的一种方法是下面
android自带的时间选择器只能精确到分,但是对于某些应用要求选择的时间精确到秒级,此时只有自定义去实现这样的时间选择器了。下面介绍一个可以精确到秒级的时间选择器。 先上效果图: 下面是工程目录: 这个控件我也是用的别人的,好像是一个老外写的,com.wheel中的WheelView是滑动控件的主
Android平台下利用zxing实现二维码开发 现在走在大街小巷都能看到二维码,而且最近由于项目需要,所以研究了下二维码开发的东西,开源的二维码扫描库主要有zxing和zbar,zbar在iPos平台上应用比较成熟,而在Android平台上主流还是用zxing库,因此这里主要讲述如何利用zxing
Android ListView的item背景色设置以及item点击无响应等相关问题 在Android开发中,listview控件是非常常用的控件,在大多数情况下,大家都会改掉listview的item默认的外观,下面讲解以下在使用listview时最常见的几个问题。1.如何改变item的背景色和按
如何向Android模拟器中导入含有中文名称的文件在进行Android开发的时候,如果需要向Android模拟器中导入文件进行测试,通过DDMS下手动导入或者在命令行下通过adb push命令是无法导入含有中文文件名的文件的。后来发现借用其他工具可以向模拟器中导入中文名称的文件,这个工具就是Ultr
Windows 下搭建Android开发环境一.下载并安装JDK版本要求JDK1.6+,下载JDK成功后进行安装,安装好后进行环境变量的配置【我的电脑】-——&gt;【属性】——&gt;【高级】 ——&gt;【环境变量】——&gt;【系统变量】中点击【新建】:变量名:CLASSPATH变量值:……
如何利用PopupWindow实现弹出菜单并解决焦点获取以及与软键盘冲突问题 在android中有时候可能要实现一个底部弹出菜单,此时可以考虑用PopupWindow来实现。下面就来介绍一下如何使用PopupWindow实现一个弹出窗。 主Activity代码:public void onCreat
解决Android中的ERROR: the user data image is used by another emulator. aborting的方法 今天调试代码的时候,突然出现这个错误,折腾了很久没有解决。最后在google上找到了大家给出的两种解决方案,下面给出这两种方法的链接博客:ht
AdvserView.java package com.earen.viewflipper; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory;
ImageView的scaleType的属性有好几种,分别是matrix(默认)、center、centerCrop、centerInside、fitCenter、fitEnd、fitStart、fitXY。 |值|说明| |:--:|:--| |center|保持原图的大小,显示在ImageVie
文章浏览阅读8.8k次,点赞9次,收藏20次。本文操作环境:win10/Android studio 3.21.环境配置 在SDK Tools里选择 CMAKE/LLDB/NDK点击OK 安装这些插件. 2.创建CMakeLists.txt文件 在Project 目录下,右键app,点击新建File文件,命名为CMakeLists.txt点击OK,创建完毕! 3.配置文件 在CMa..._link c++ project with gradle
文章浏览阅读1.2w次,点赞15次,收藏69次。实现目的:由mainActivity界面跳转到otherActivity界面1.写好两个layout文件,activity_main.xml和otherxml.xmlactivity_main.xml&lt;?xml version="1.0" encoding="utf-8"?&gt;&lt;RelativeLayout ="http://schemas..._android studio 界面跳转
文章浏览阅读3.8w次。前言:最近在找Android上的全局代理软件来用,然后发现了这两款神作,都是外国的软件,而且都是开源的软件,因此把源码下载了下来,给有需要研究代理这方面的童鞋看看。不得不说,国外的开源精神十分浓,大家相互使用当前基础的开源软件,然后组合成一个更大更强的大开源软件。好吧,废话不多说,下面简单介绍一下这两款开源项目。一、ProxyDroid:ProxyDroid功能比较强大,用到的技术也比较多,源码也_proxydroid
文章浏览阅读2.5w次,点赞17次,收藏6次。创建项目后,运行项目时Gradle Build 窗口却显示错误:程序包R不存在通常情况下是不会出现这个错误的。我是怎么遇到这个错误的呢?第一次创建项目,company Domain我使用的是:aven.com,但是创建过程在卡在了Building 'Calculator' Gradle Project info这个过程中,于是我选择了“Cancel”第二次创建项目,我还是使用相同的项目名称和项目路_r不存在
文章浏览阅读8.9w次,点赞4次,收藏43次。前言:在Android上使用系统自带的代理,限制灰常大,仅支持系统自带的浏览器。这样像QQ、飞信、微博等这些单独的App都不能使用系统的代理。如何让所有软件都能正常代理呢?ProxyDroid这个软件能帮你解决!使用方法及步骤如下:一、推荐从Google Play下载ProxyDroid,目前最新版本是v2.6.6。二、对ProxyDroid进行配置(基本配置:) (1) Auto S_proxydroid使用教程
文章浏览阅读1.1w次,点赞4次,收藏17次。Android Studio提供了一个很实用的工具Android设备监视器(Android device monitor),该监视器中最常用的一个工具就是DDMS(Dalvik Debug Monitor Service),是 Android 开发环境中的Dalvik虚拟机调试监控服务。可以进行的操作有:为测试设备截屏,查看特定进程中正在运行的线程以及堆栈信息、Logcat、广播状态信息、模拟电话_安卓摄像头调试工具