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

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

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

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

image.png

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

image.png

image.png

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

image.png

好了整体下来,你已经掌握的基本的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, InterruptedException, 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, mappingCollector, config, 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, int access, String name, String signature, String superName, String[] interfaces) {
            super.visit(version, access, name, signature, superName, interfaces);
            this.className = name;
            this.isActivityOrSubClass = isActivityOrSubClass(className, collectedClassExtendMap);
            this.isNeedTrace = MethodCollector.isNeedTrace(configuration, className, mappingCollector);
            if ((access & Opcodes.ACC_ABSTRACT) > 0 || (access & Opcodes.ACC_INTERFACE) > 0) {
                this.isABSClass = true;
            }
        }
        @Override
        public MethodVisitor visitMethod(int access, String name, String desc,
                                         String signature, String[] exceptions) {
            if (isABSClass) {
                return super.visitMethod(access, name, desc, signature, exceptions);
            } else {
                if (!hasWindowFocusMethod) {
                    //判断方法名是否是onWindowFocusChanged
                    hasWindowFocusMethod = MethodCollector.isWindowFocusChangeMethod(name, desc);
                }
                MethodVisitor methodVisitor = cv.visitMethod(access, name, desc, signature, exceptions);
                //方法插入规则
                return new TraceMethodAdapter(api, methodVisitor, access, name, desc, 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, int access, String name, String desc, String className,
                                     boolean hasWindowFocusMethod, boolean isActivityOrSubClass, boolean isNeedTrace) {
            super(api, mv, access, name, desc);
            TraceMethod traceMethod = TraceMethod.create(0, access, className, name, 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", "(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, className,
                            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, TraceBuildConstants.MATRIX_TRACE_CLASS, "o", "(I)V", 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, TraceBuildConstants.MATRIX_TRACE_CLASS, "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, sIndex, 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", msg.what, 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, sIndex, false);
            } else {
                sIndex = 0;
                mergeData(methodId, sIndex, 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, "[at] visibleScene[%s] has %s focus!", getVisibleScene(), "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(), firstScreenCost, coldCost, false);
            }
        } else if (isWarmStartUp()) {
            //热启动,就只需要记录最后一个activity创建的时间
            isWarmStartUp = false;
            long warmCost = uptimeMillis() - lastCreateActivity;
            MatrixLog.i(TAG, "#WarmStartup# activity:%s, warmCost:%d, now:%d, lastCreateActivity:%d", activityName, warmCost, uptimeMillis(), lastCreateActivity);
            if (warmCost > 0) {
                analyse(0, 0, warmCost, true);
            }
        }
    }

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

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

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

目录
相关文章
|
7月前
|
存储 索引
买东西太多折扣套路,使用SwiftUI搭建一个折扣计算器App帮你计算吧(上)
买东西太多折扣套路,使用SwiftUI搭建一个折扣计算器App帮你计算吧(上)
59 1
|
7月前
买东西太多折扣套路,使用SwiftUI搭建一个折扣计算器App帮你计算吧(下)
买东西太多折扣套路,使用SwiftUI搭建一个折扣计算器App帮你计算吧(下)
53 0
|
监控 Java 测试技术
Matrix源码分析系列-如何计算App启动耗时(一)
Matrix源码分析系列-如何计算App启动耗时
150 0
Matrix源码分析系列-如何计算App启动耗时(一)
|
存储 分布式计算 搜索推荐
U-App移动统计算力升级!支持跨应用、多事件的打包计算
近日,友盟+U-APP移动统计产品算力升级,正式推出汇总分析功能。借助于友盟+百万级/秒的数据服务能力,开发者可快速汇总、比较多个APP应用的数据,提升精细化运营的能力。
U-App移动统计算力升级!支持跨应用、多事件的打包计算
|
iOS开发
通过Html启动IOS的APP
通过Html启动IOS的APP
96 0
|
XML Android开发 UED
Android APP启动黑屏及解决方案
相信做过Android的朋友都知道,当一个APP启动时,界面会首先展示一个白屏或者黑屏,然后再进入欢迎页,稍作停留最后进入APP主页。那么这个黑屏或者白屏到底是怎么一回事呢?
592 0
|
机器学习/深度学习
PIE-engine APP教程 ——基于水体指数或监督分类方法的水体频率计算
PIE-engine APP教程 ——基于水体指数或监督分类方法的水体频率计算
213 0
PIE-engine APP教程 ——基于水体指数或监督分类方法的水体频率计算
|
XML 前端开发 定位技术
Android MVVM框架使用(十三)UI更新 (App启动白屏优化、适配Android10.0深色模式)
Android MVVM框架使用(十三)UI更新 (App启动白屏优化、适配Android10.0深色模式)
367 0
Android MVVM框架使用(十三)UI更新 (App启动白屏优化、适配Android10.0深色模式)
|
存储 Android开发 UED
Android 音乐APP(二)启动白屏优化、定位当前播放歌曲
Android 音乐APP(二)启动白屏优化、定位当前播放歌曲
246 0
Android 音乐APP(二)启动白屏优化、定位当前播放歌曲

热门文章

最新文章