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包拖到这个窗口就行,如下:
而我们原代码是这样,跟我们预想的效果一致。
好了整体下来,你已经掌握的基本的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思想也是一个不错的东西,值得我们深入学习下。希望这次分析对你有帮助。