{"id":1825,"date":"2023-03-28T22:42:01","date_gmt":"2023-03-28T14:42:01","guid":{"rendered":"https:\/\/www.appblog.cn\/?p=1825"},"modified":"2023-04-22T09:20:06","modified_gmt":"2023-04-22T01:20:06","slug":"android-bytecode-instrumentation-implementation-gradle-asm","status":"publish","type":"post","link":"https:\/\/www.appblog.cn\/index.php\/2023\/03\/28\/android-bytecode-instrumentation-implementation-gradle-asm\/","title":{"rendered":"Android\u5b57\u8282\u7801\u63d2\u6869\u5b9e\u73b0\uff08Gradle + ASM\uff09"},"content":{"rendered":"<p>\u5728Android\u7f16\u8bd1\u8fc7\u7a0b\u4e2d\uff0c\u5f80\u5b57\u8282\u7801\u91cc\u63d2\u5165\u81ea\u5b9a\u4e49\u7684\u5b57\u8282\u7801\uff0c\u79f0\u4e3a\u5b57\u8282\u7801\u63d2\u6869\u6216\u51fd\u6570\u63d2\u6869\u3002<\/p>\n<p>\u51fd\u6570\u63d2\u6869\u53ef\u4ee5\u5e2e\u52a9\u6211\u4eec\u5b9e\u73b0\u5f88\u591a\u624b\u672f\u5200\u5f0f\u7684\u4ee3\u7801\u8bbe\u8ba1\uff0c\u5982\u65e0\u57cb\u70b9\u7edf\u8ba1\u4e0a\u62a5\u3001\u8f7b\u91cf\u7ea7AOP\u7b49\u3002\u5e94\u7528\u5230\u5728Android\u4e2d\uff0c\u53ef\u4ee5\u7528\u6765\u505a\u7528\u884c\u4e3a\u7edf\u8ba1\u3001\u65b9\u6cd5\u8017\u65f6\u7edf\u8ba1\u7b49\u529f\u80fd\u3002<\/p>\n<h2>\u5b57\u8282\u7801\u5b9e\u6218<\/h2>\n<p><!-- more --><\/p>\n<h3>\u9700\u6c42\u5206\u6790<\/h3>\n<p>\u9700\u6c42\uff1a\u5728Android\u5e94\u7528\u4e2d\uff0c\u8bb0\u5f55\u6bcf\u4e2a\u9875\u9762\u7684\u6253\u5f00\\\u5173\u95ed<\/p>\n<p>\u4e00\u822c\u6765\u8bf4\u5c31\u662f\u8bb0\u5f55Activity\u7684\u521b\u5efa\u548c\u9500\u6bc1\uff08\u8fd9\u91cc\u4ee5Activity\u533a\u5206\u9875\u9762\uff09\u3002\u6240\u4ee5\u53ea\u8981\u5728Activity\u7684<code>onCreate()<\/code>\u548c<code>onDestroy()<\/code>\u4e2d\u63d2\u5165\u5bf9\u5e94\u7684\u4ee3\u7801\u5373\u53ef\u3002<\/p>\n<p>\u5982\u4f55\u4e3aActivity\u63d2\u5165\u4ee3\u7801\uff1f<br \/>\n\u4e00\u4e2a\u4e2a\u5199\uff1f\u4e0d\u53ef\u80fd\uff01\u6bd5\u7adf\u6211\u4eec\u662f\u9ad8\uff08\u61d2\uff09\u6548\uff08\u60f0\uff09\u7684\u7a0b\u5e8f\u5458<br \/>\n\u5199\u5728BaseActivity\u4e2d\uff1f\u597d\u50cf\u53ef\u4ee5\uff0c\u4e0d\u8fc7\u9879\u76ee\u4e2d\u5982\u679c\u6709\u7b2c\u4e09\u65b9\u7684\u9875\u9762\u5c31\u663e\u5f97\u6709\u4e9b\u65e0\u529b\u4e86\uff0c\u800c\u4e14\u4e0d\u901a\u7528<\/p>\n<p>\u6211\u4eec\u5e0c\u671b\u5b9e\u73b0\u4e00\u4e2a\u53ef\u4ee5\u81ea\u52a8\u5728Activity\u7684<code>onCreate()<\/code>\u548c<code>onDestroy()<\/code>\u4e2d\u63d2\u5165\u4ee3\u7801\u7684\u5de5\u5177\uff0c\u53ef\u4ee5\u5728\u4efb\u610f\u5de5\u7a0b\u4e2d\u4f7f\u7528\u3002<\/p>\n<p>\u4e8e\u662f\uff0c\u81ea\u5b9a\u4e49Gradle\u63d2\u4ef6 + ASM\uff08Java\u5b57\u8282\u7801\u64cd\u7eb5\u6846\u67b6\uff09\u4fbf\u6210\u4e3a\u4e00\u4e2a\u4e0d\u9519\u7684\u9009\u62e9<\/p>\n<h3>\u5b9e\u73b0\u601d\u8def<\/h3>\n<p>\u5bf9<strong>Android\u6253\u5305\u8fc7\u7a0b<\/strong>\u548c<strong>\u81ea\u5b9a\u4e49Gradle\u63d2\u4ef6<\/strong>\u4e86\u89e3\u540e\u53d1\u73b0\uff0cjava\u6587\u4ef6\u4f1a\u5148\u8f6c\u5316\u4e3a<code>class<\/code>\u6587\u4ef6\uff0c\u7136\u540e\u5728\u8f6c\u5316\u4e3a<code>dex<\/code>\u6587\u4ef6\u3002\u800c\u901a\u8fc7Gradle\u63d2\u4ef6\u63d0\u4f9b\u7684<code>Transform API<\/code>\uff0c\u53ef\u4ee5\u5728\u7f16\u8bd1\u6210<code>dex<\/code>\u6587\u4ef6\u4e4b\u524d\u5f97\u5230<code>class<\/code>\u6587\u4ef6\u3002\u5f97\u5230<code>class<\/code>\u6587\u4ef6\u4e4b\u540e\uff0c\u4fbf\u53ef\u4ee5\u901a\u8fc7<strong>ASM<\/strong>\u5bf9\u5b57\u8282\u7801\u8fdb\u884c\u4fee\u6539\uff0c\u5373\u53ef\u5b8c\u6210<strong>\u5b57\u8282\u7801\u63d2\u6869<\/strong>\u3002<\/p>\n<p>\u6b65\u9aa4\u5982\u4e0b\uff1a<\/p>\n<p>\uff081\uff09\u4e86\u89e3Android\u6253\u5305\u8fc7\u7a0b\uff0c\u627e\u5230\u63d2\u5165\u70b9\u5373<code>class<\/code>\u8f6c\u6362\u6210<code>dex<\/code>\u8fc7\u7a0b<\/p>\n<p>\uff082\uff09\u4e86\u89e3<strong>\u81ea\u5b9a\u4e49Gradle\u63d2\u4ef6<\/strong>\u3001<strong>Transform API<\/strong>\uff0c\u5728<code>Transform#transform()<\/code>\u4e2d\u5f97\u5230<code>class<\/code>\u6587\u4ef6<\/p>\n<p>\uff083\uff09\u627e\u5230<code>FragmentActivity<\/code>\u7684<code>class<\/code>\u6587\u4ef6\uff0c\u901a\u8fc7<strong>ASM\u5e93<\/strong>\uff0c\u5728<code>onCreate()<\/code>\u4e2d\u63d2\u5165\u4ee3\u7801\uff08\u4e3a\u4ec0\u4e48\u662f<code>FragmentActivity<\/code>\u800c\u4e0d\u662f<code>Activity<\/code>\u540e\u9762\u4f1a\u8bf4\u5230\uff09<\/p>\n<p>\uff084\uff09\u5c06\u539f\u6587\u4ef6\u66ff\u6362\u4e3a\u4fee\u6539\u540e\u7684class\u6587\u4ef6<\/p>\n<p><img decoding=\"async\" src=\"http:\/\/www.yezhou.me\/AppBlog\/images\/Android\/Android\u6253\u5305\u63d2\u6869\u70b9.png\" alt=\"Android\u6253\u5305\u63d2\u6869\u70b9\" \/><\/p>\n<blockquote>\n<p><strong>class\u6587\u4ef6<\/strong>\uff1ajava\u6e90\u6587\u4ef6\u7ecf\u8fc7javac\u540e\u751f\u6210\u4e00\u79cd\u7d27\u51d1\u76848\u4f4d\u5b57\u8282\u7684\u4e8c\u8fdb\u5236\u6d41\u6587\u4ef6<\/p>\n<p><strong>\u63d2\u5165\u70b9<\/strong>\uff1a<code>dex<\/code>\u8282\u70b9\uff0c\u8868\u793a\u5c06<code>class<\/code>\u6587\u4ef6\u6253\u5305\u5230<code>dex<\/code>\u6587\u4ef6\u7684\u8fc7\u7a0b\uff0c\u5176\u8f93\u5165\u5305\u62ec<code>class<\/code>\u6587\u4ef6\u4ee5\u53ca\u7b2c\u4e09\u65b9\u4f9d\u8d56\u7684<code>class<\/code>\u6587\u4ef6<\/p>\n<p>\u5173\u4e8e<strong>Transform API<\/strong>\uff1a\u4ece1.5.0-beta1\u5f00\u59cb\uff0cGradle\u63d2\u4ef6\u5305\u542b\u4e00\u4e2aTransform API\uff0c\u5141\u8bb8\u7b2c\u4e09\u65b9\u63d2\u4ef6\u5728\u5c06\u7f16\u8bd1\u540e\u7684\u7c7b\u6587\u4ef6\u8f6c\u6362\u4e3adex\u6587\u4ef6\u4e4b\u524d\u5bf9\u5176\u8fdb\u884c\u64cd\u4f5c<\/p>\n<p>\u5173\u4e8e\u6df7\u6dc6\uff1a\u5173\u4e8e\u6df7\u6dc6\u53ef\u4ee5\u4e0d\u7528\u5f53\u5fc3\u3002\u6df7\u6dc6\u5176\u5b9e\u662f\u4e2a<code>ProguardTransform<\/code>\uff0c\u5728\u81ea\u5b9a\u4e49\u7684Transform\u4e4b\u540e\u6267\u884c\u3002<\/p>\n<\/blockquote>\n<h3>\u52a8\u624b\u5b9e\u73b0<\/h3>\n<p>\u4e3b\u8981\u5b9e\u73b0\u4ee5\u4e0b\u529f\u80fd\uff1a<\/p>\n<ul>\n<li>\u81ea\u5b9a\u4e49Gradle\u63d2\u4ef6<\/li>\n<li>\u5904\u7406class\u6587\u4ef6<\/li>\n<li>\u66ff\u6362<\/li>\n<\/ul>\n<h4>\u81ea\u5b9a\u4e49Gradle\u63d2\u4ef6<\/h4>\n<p><a target=\"_blank\" rel=\"noopener\" href=\"http:\/\/www.appblog.cn\/2020\/02\/11\/%E5%9C%A8Android%20Studio%E4%B8%AD%E8%87%AA%E5%AE%9A%E4%B9%89Gradle%E6%8F%92%E4%BB%B6\/\" title=\"\u5728Android Studio\u4e2d\u81ea\u5b9a\u4e49Gradle\u63d2\u4ef6\">\u5728Android Studio\u4e2d\u81ea\u5b9a\u4e49Gradle\u63d2\u4ef6<\/a><br \/>\n<a target=\"_blank\" rel=\"noopener\" href=\"http:\/\/www.appblog.cn\/2020\/02\/12\/Android%20Gradle%20Plugin%E6%89%93%E5%8C%85Apk%E8%BF%87%E7%A8%8B%E4%B8%AD%E7%9A%84Transform%20API\/\" title=\"Android Gradle Plugin\u6253\u5305Apk\u8fc7\u7a0b\u4e2d\u7684Transform API\">Android Gradle Plugin\u6253\u5305Apk\u8fc7\u7a0b\u4e2d\u7684Transform API<\/a><\/p>\n<h5>\u76ee\u5f55\u7ed3\u6784<\/h5>\n<p>\u76ee\u5f55\u7ed3\u6784\u5206\u4e3a\u4e24\u90e8\u5206\uff1a<strong>\u63d2\u4ef6\u90e8\u5206<\/strong>\uff08<code>src\/main\/groovy<\/code>\u4e2d\uff09\u3001<strong>ASM\u90e8\u5206<\/strong>\uff08<code>src\/main\/java<\/code>\u4e2d\uff09<\/p>\n<pre><code>lifecycle-plugin\n  src\n    main\n      groovy\n        cn.appblog.plugin.lifecycle\n          LifecyclePlugin.groovy\n      java\n        cn.appblog.plugin.lifecycle\n          LifecycleClassVisitor.java\n          LifecycleOnCreateMethodVisitor.java\n          LifecycleOnDestroyMethodVisitor.java\n      resources\n        META-INF\n          gradle-plugins\n            cn.appblog.gradle.properties\n  build.gradle<\/code><\/pre>\n<h5>build.gradle<\/h5>\n<p><code>build.gradle<\/code>\u5185\u5bb9\u4e3a<\/p>\n<pre><code>apply plugin: &#039;groovy&#039;\napply plugin: &#039;maven&#039;\n\nrepositories {\n    mavenCentral()\n    jcenter()\n}\n\ndependencies {\n    \/\/gradle sdk\n    implementation gradleApi()\n    \/\/groovy sdk\n    implementation localGroovy()\n\n    implementation &#039;com.android.tools.build:gradle:3.5.3&#039;\n}\n\n\/\/\u6307\u5b9a\u7f16\u8bd1\u7684\u7f16\u7801\ntasks.withType(JavaCompile){\n    options.encoding = &quot;UTF-8&quot;\n}\n\n\/\/group\u548cversion\u5728\u540e\u9762\u4f7f\u7528\u81ea\u5b9a\u4e49\u63d2\u4ef6\u7684\u65f6\u5019\u4f1a\u7528\u5230\ngroup=&#039;cn.appblog.plugin&#039;\nversion=&#039;0.0.1&#039;\n\nuploadArchives {\n    repositories {\n        mavenDeployer {\n            \/\/\u63d0\u4ea4\u5230\u8fdc\u7a0b\u670d\u52a1\u5668\uff1a\n            \/\/ repository(url: &quot;http:\/\/www.xxx.com\/repos&quot;) {\n            \/\/    authentication(userName: &quot;admin&quot;, password: &quot;admin&quot;)\n            \/\/ }\n            \/\/\u672c\u5730\u7684Maven\u5730\u5740:\u5f53\u524d\u5de5\u7a0b\u4e0b\n            repository(url: uri(&#039;D:\/repos&#039;))\n        }\n    }\n}<\/code><\/pre>\n<h5>gradle-plugins<\/h5>\n<p>properties\u6587\u4ef6\u5185\u5bb9\u4e3a<\/p>\n<pre><code>implementation-class=cn.appblog.plugin.lifecycle.LifecyclePlugin<\/code><\/pre>\n<h5>LifecyclePlugin.groovy<\/h5>\n<p>\u7ee7\u627f<code>Transform<\/code>\uff0c\u5b9e\u73b0<code>Plugin<\/code>\u63a5\u53e3\uff0c\u901a\u8fc7<code>Transform#transform()<\/code>\u5f97\u5230<code>Collection&lt;TransformInput&gt; inputs<\/code>\uff0c\u91cc\u9762\u6709\u6211\u4eec\u60f3\u8981\u7684<code>class<\/code>\u6587\u4ef6\u3002<\/p>\n<pre><code class=\"language-java\">package cn.appblog.plugin.lifecycle\n\nimport com.android.annotations.NonNull\nimport com.android.build.api.transform.*\nimport com.android.build.gradle.AppExtension\nimport com.android.build.gradle.internal.pipeline.TransformManager\nimport org.apache.commons.codec.digest.DigestUtils\nimport org.apache.commons.io.FileUtils\nimport org.apache.commons.io.IOUtils\nimport org.gradle.api.Plugin\nimport org.gradle.api.Project\nimport org.objectweb.asm.ClassReader\nimport org.objectweb.asm.ClassVisitor\nimport org.objectweb.asm.ClassWriter\n\nimport java.util.jar.JarEntry\nimport java.util.jar.JarFile\nimport java.util.jar.JarOutputStream\nimport java.util.zip.ZipEntry\n\nimport static org.objectweb.asm.ClassReader.EXPAND_FRAMES\n\nclass LifecyclePlugin extends Transform implements Plugin&lt;Project&gt; {\n\n    @Override\n    void apply(Project project) {\n        \/\/registerTransform\n        def android = project.extensions.getByType(AppExtension)\n        android.registerTransform(this)\n    }\n\n    @Override\n    String getName() {\n        return &quot;LifecyclePlugin&quot;\n    }\n\n    @Override\n    Set&lt;QualifiedContent.ContentType&gt; getInputTypes() {\n        return TransformManager.CONTENT_CLASS\n    }\n\n    @Override\n    Set&lt;? super QualifiedContent.Scope&gt; getScopes() {\n        return TransformManager.SCOPE_FULL_PROJECT\n    }\n\n    @Override\n    boolean isIncremental() {\n        return false\n    }\n\n    @Override\n    void transform(@NonNull TransformInvocation transformInvocation) {\n        println &#039;--------------- LifecyclePlugin visit start --------------- &#039;\n        def startTime = System.currentTimeMillis()\n        Collection&lt;TransformInput&gt; inputs = transformInvocation.inputs\n        TransformOutputProvider outputProvider = transformInvocation.outputProvider\n        \/\/\u5220\u9664\u4e4b\u524d\u7684\u8f93\u51fa\n        if (outputProvider != null)\n            outputProvider.deleteAll()\n        \/\/\u904d\u5386inputs\n        inputs.each { TransformInput input -&gt;\n            \/\/\u904d\u5386directoryInputs\n            input.directoryInputs.each { DirectoryInput directoryInput -&gt;\n                handleDirectoryInputs(directoryInput, outputProvider)\n            }\n\n            \/\/\u904d\u5386jarInputs\n            input.jarInputs.each { JarInput jarInput -&gt;\n                handleJarInputs(jarInput, outputProvider)\n            }\n        }\n        def cost = (System.currentTimeMillis() - startTime) \/ 1000\n        println &#039;--------------- LifecyclePlugin visit end --------------- &#039;\n        println &quot;LifecyclePlugin cost \uff1a $cost s&quot;\n    }\n\n    \/**\n     * \u5904\u7406\u6587\u4ef6\u76ee\u5f55\u4e0b\u7684class\u6587\u4ef6\n     *\/\n    static void handleDirectoryInputs(DirectoryInput directoryInput, TransformOutputProvider outputProvider) {\n        \/\/\u662f\u5426\u662f\u76ee\u5f55\n        if (directoryInput.file.isDirectory()) {\n            \/\/\u5217\u51fa\u76ee\u5f55\u6240\u6709\u6587\u4ef6\uff08\u5305\u542b\u5b50\u6587\u4ef6\u5939\uff0c\u5b50\u6587\u4ef6\u5939\u5185\u6587\u4ef6\uff09\n            directoryInput.file.eachFileRecurse { File file -&gt;\n                def name = file.name\n                if (checkClassFile(name)) {\n                    println &#039;----------- deal with &quot;class&quot; file &lt;&#039; + name + &#039;&gt; -----------&#039;\n                    ClassReader classReader = new ClassReader(file.bytes)\n                    ClassWriter classWriter = new ClassWriter(classReader, ClassWriter.COMPUTE_MAXS)\n                    ClassVisitor cv = new LifecycleClassVisitor(classWriter)\n                    classReader.accept(cv, EXPAND_FRAMES)\n                    byte[] code = classWriter.toByteArray()\n                    FileOutputStream fos = new FileOutputStream(\n                            file.parentFile.absolutePath + File.separator + name)\n                    fos.write(code)\n                    fos.close()\n                } else {\n                    println &#039;----------- directory &quot;class&quot; file &lt;&#039; + name + &#039;&gt; need not deal -----------&#039;\n                }\n            }\n        }\n        \/\/\u5904\u7406\u5b8c\u8f93\u5165\u6587\u4ef6\u4e4b\u540e\uff0c\u8981\u628a\u8f93\u51fa\u7ed9\u4e0b\u4e00\u4e2a\u4efb\u52a1\n        def dest = outputProvider.getContentLocation(directoryInput.name,\n                directoryInput.contentTypes, directoryInput.scopes,\n                Format.DIRECTORY)\n        FileUtils.copyDirectory(directoryInput.file, dest)\n    }\n\n    \/**\n     * \u5904\u7406Jar\u4e2d\u7684class\u6587\u4ef6\n     *\/\n    static void handleJarInputs(JarInput jarInput, TransformOutputProvider outputProvider) {\n        if (jarInput.file.getAbsolutePath().endsWith(&quot;.jar&quot;)) {\n            \/\/\u91cd\u540d\u540d\u8f93\u51fa\u6587\u4ef6,\u56e0\u4e3a\u53ef\u80fd\u540c\u540d,\u4f1a\u8986\u76d6\n            def jarName = jarInput.name\n            def md5Name = DigestUtils.md5Hex(jarInput.file.getAbsolutePath())\n            if (jarName.endsWith(&quot;.jar&quot;)) {\n                jarName = jarName.substring(0, jarName.length() - 4)\n            }\n            JarFile jarFile = new JarFile(jarInput.file)\n            Enumeration enumeration = jarFile.entries()\n            File tmpFile = new File(jarInput.file.getParent() + File.separator + &quot;classes_temp.jar&quot;)\n            \/\/\u907f\u514d\u4e0a\u6b21\u7684\u7f13\u5b58\u88ab\u91cd\u590d\u63d2\u5165\n            if (tmpFile.exists()) {\n                tmpFile.delete()\n            }\n            JarOutputStream jarOutputStream = new JarOutputStream(new FileOutputStream(tmpFile))\n            \/\/\u7528\u4e8e\u4fdd\u5b58\n            while (enumeration.hasMoreElements()) {\n                JarEntry jarEntry = (JarEntry) enumeration.nextElement()\n                String entryName = jarEntry.getName()\n                ZipEntry zipEntry = new ZipEntry(entryName)\n                InputStream inputStream = jarFile.getInputStream(jarEntry)\n                \/\/\u63d2\u6869class\n                if (checkClassFile(entryName)) {\n                    \/\/class\u6587\u4ef6\u5904\u7406\n                    println &#039;----------- deal with &quot;jar&quot; class file &lt;&#039; + entryName + &#039;&gt; -----------&#039;\n                    jarOutputStream.putNextEntry(zipEntry)\n                    ClassReader classReader = new ClassReader(IOUtils.toByteArray(inputStream))\n                    ClassWriter classWriter = new ClassWriter(classReader, ClassWriter.COMPUTE_MAXS)\n                    ClassVisitor cv = new LifecycleClassVisitor(classWriter)\n                    classReader.accept(cv, EXPAND_FRAMES)\n                    byte[] code = classWriter.toByteArray()\n                    jarOutputStream.write(code)\n                } else {\n                    println &#039;----------- jar &quot;class&quot; file &lt;&#039; + entryName + &#039;&gt; need not deal -----------&#039;\n                    jarOutputStream.putNextEntry(zipEntry)\n                    jarOutputStream.write(IOUtils.toByteArray(inputStream))\n                }\n                jarOutputStream.closeEntry()\n            }\n            \/\/\u7ed3\u675f\n            jarOutputStream.close()\n            jarFile.close()\n            def dest = outputProvider.getContentLocation(jarName + md5Name,\n                    jarInput.contentTypes, jarInput.scopes, Format.JAR)\n            FileUtils.copyFile(tmpFile, dest)\n            tmpFile.delete()\n        }\n    }\n\n    \/**\n     * \u68c0\u67e5class\u6587\u4ef6\u662f\u5426\u9700\u8981\u5904\u7406\n     * @param fileName\n     * @return\n     *\/\n    static boolean checkClassFile(String name) {\n        \/\/\u53ea\u5904\u7406\u9700\u8981\u7684class\u6587\u4ef6\n        return (name.endsWith(&quot;.class&quot;) &amp;&amp; !name.startsWith(&quot;R\\$&quot;)\n                &amp;&amp; !&quot;R.class&quot;.equals(name) &amp;&amp; !&quot;BuildConfig.class&quot;.equals(name)\n                &amp;&amp; (&quot;android\/support\/v4\/app\/FragmentActivity.class&quot;.equals(name) || &quot;androidx\/appcompat\/app\/AppCompatActivity.class&quot;.equals(name)))\n    }\n\n}<\/code><\/pre>\n<p>\u4e3b\u8981\u770b\u65b9\u6cd5<code>transform()<\/code>\uff0c\u901a\u8fc7\u53c2\u6570<code>inputs<\/code>\u53ef\u4ee5\u62ff\u5230\u6240\u6709\u7684<code>class<\/code>\u6587\u4ef6\u3002<code>inputs<\/code>\u4e2d\u5305\u62ec<code>directoryInputs<\/code>\u548c<code>jarInputs<\/code>\uff0c<code>directoryInputs<\/code>\u4e3a\u6587\u4ef6\u5939\u4e2d\u7684<code>class<\/code>\u6587\u4ef6\uff0c\u800c<code>jarInputs<\/code>\u4e3ajar\u5305\u4e2d\u7684<code>class<\/code>\u6587\u4ef6\u3002<\/p>\n<p>\u5bf9\u5e94\u4e24\u4e2a\u5904\u7406\u65b9\u6cd5<code>LifecyclePlugin#handleDirectoryInput()<\/code>\u3001<code>LifecyclePlugin#handleJarInputs()<\/code><\/p>\n<p>\u8fd9\u4e24\u4e2a\u65b9\u6cd5\u90fd\u5728\u505a\u540c\u4e00\u4ef6\u4e8b\uff0c\u5c31\u662f\u904d\u5386<code>directoryInputs<\/code>\u3001<code>jarInputs<\/code>\uff0c\u5f97\u5230\u5bf9\u5e94\u7684<code>class<\/code>\u6587\u4ef6\uff0c\u7136\u540e\u4ea4\u7ed9<code>ASM<\/code>\u5904\u7406\uff0c\u6700\u540e\u8986\u76d6\u539f\u6587\u4ef6\u3002<\/p>\n<p>\u53d1\u73b0\uff1a\u5728<code>input.jarInputs<\/code>\u4e2d\u5e76\u6ca1\u6709<code>android.jar<\/code>\uff0c\u56e0\u4e3a<code>android.jar<\/code>\u4e0d\u5728\u7f16\u8bd1\u671f\uff0c\u64cd\u4f5c\u4e0d\u4e86\u3002\u672c\u60f3\u5728Activity\u4e2d\u505a\u5904\u7406\uff0c\u56e0\u4e3a\u627e\u4e0d\u5230<code>android.jar<\/code>\uff0c\u53ea\u597d\u9000\u800c\u6c42\u5176\u6b21\u9009\u62e9<code>android.support.v4.app<\/code>\u4e2d\u7684<code>FragmentActivity<\/code>\u3002<\/p>\n<h5>\u5904\u7406class\u6587\u4ef6<\/h5>\n<p>\u5728<code>handleDirectoryInputs<\/code>\u548c<code>handleJarInputs<\/code>\u4e2d\uff0c\u53ef\u4ee5\u770b\u5230ASM\u7684\u90e8\u5206\u4ee3\u7801\u3002\u8fd9\u91cc\u4ee5<code>handleDirectoryInputs<\/code>\u4e3a\u4f8b\u3002<\/p>\n<p><code>handleDirectoryInputs<\/code>\u4e2dASM\u4ee3\u7801\uff1a<\/p>\n<pre><code class=\"language-java\">ClassReader classReader = new ClassReader(file.bytes)\nClassWriter classWriter = new ClassWriter(classReader, ClassWriter.COMPUTE_MAXS)\nClassVisitor cv = new LifecycleClassVisitor(classWriter)\nclassReader.accept(cv, EXPAND_FRAMES)<\/code><\/pre>\n<p>\u5176\u4e2d\uff0c\u5173\u952e\u5904\u7406\u7c7b<code>LifecycleClassVisitor<\/code><\/p>\n<h5>LifecycleClassVisitor<\/h5>\n<p>\u7528\u4e8e\u8bbf\u95ee<code>class<\/code>\u7684\u5de5\u5177\uff0c\u5728<code>visitMethod()<\/code>\u91cc\u5bf9<strong>\u7c7b\u540d<\/strong>\u548c<strong>\u65b9\u6cd5\u540d<\/strong>\u8fdb\u884c\u5224\u65ad\u662f\u5426\u9700\u8981\u5904\u7406\u3002\u82e5\u9700\u8981\uff0c\u5219\u4ea4\u7ed9<code>MethodVisitor<\/code>\u3002<\/p>\n<pre><code class=\"language-java\">package cn.appblog.plugin.lifecycle;\n\nimport org.objectweb.asm.ClassVisitor;\nimport org.objectweb.asm.MethodVisitor;\nimport org.objectweb.asm.Opcodes;\n\npublic class LifecycleClassVisitor extends ClassVisitor implements Opcodes {\n\n    private String mClassName;\n\n    public LifecycleClassVisitor(ClassVisitor cv) {\n        super(Opcodes.ASM5, cv);\n    }\n\n    @Override\n    public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {\n        \/\/System.out.println(&quot;LifecycleClassVisitor : visit -----&gt; started \uff1a&quot; + name);\n        this.mClassName = name;\n        super.visit(version, access, name, signature, superName, interfaces);\n    }\n\n    @Override\n    public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {\n        \/\/System.out.println(&quot;LifecycleClassVisitor : visitMethod : &quot; + name);\n        MethodVisitor mv = cv.visitMethod(access, name, desc, signature, exceptions);\n        \/\/\u5339\u914dFragmentActivity\n        if (&quot;android\/support\/v4\/app\/FragmentActivity&quot;.equals(this.mClassName) || &quot;androidx\/appcompat\/app\/AppCompatActivity&quot;.equals(this.mClassName)) {\n            if (&quot;onCreate&quot;.equals(name) ) {\n                \/\/\u5904\u7406onCreate\n                System.out.println(&quot;LifecycleClassVisitor : change method ----&gt; &quot; + name);\n                return new LifecycleOnCreateMethodVisitor(mv);\n            } else if (&quot;onDestroy&quot;.equals(name)) {\n                \/\/\u5904\u7406onDestroy\n                System.out.println(&quot;LifecycleClassVisitor : change method ----&gt; &quot; + name);\n                return new LifecycleOnDestroyMethodVisitor(mv);\n            }\n        }\n        return mv;\n    }\n\n    @Override\n    public void visitEnd() {\n        \/\/System.out.println(&quot;LifecycleClassVisitor : visit -----&gt; end&quot;);\n        super.visitEnd();\n    }\n}<\/code><\/pre>\n<p>\u5728<code>visitMethod()<\/code>\u4e2d\u5224\u65ad\u662f\u5426\u4e3a<code>FragmentActivity<\/code>\uff0c\u4e14\u4e3a\u65b9\u6cd5<code>onCreate<\/code>\u6216<code>onDestroy<\/code>\uff0c\u7136\u540e\u4ea4\u7ed9<code>LifecycleOnDestroyMethodVisitor<\/code>\u6216<code>LifecycleOnCreateMethodVisitor<\/code>\u5904\u7406\u3002<\/p>\n<blockquote>\n<p>\u63d0\u793a\uff1a<code>ClassVisitor#visitMethod()<\/code>\u53ea\u80fd\u8bbf\u95ee\u5f53\u524d\u7c7b\u5b9a\u4e49\u7684<code>method<\/code>\uff08\u4e00\u5f00\u59cb\u60f3\u8bbf\u95ee\u7236\u7c7b\u7684\u65b9\u6cd5\uff0c\u9677\u5165\u8bef\u533a\uff09<br \/>\n\u5982\uff0c\u5728<code>MainActivity<\/code>\u4e2d\u53ea\u91cd\u5199\u4e86<code>onCreate()<\/code>\uff0c\u6ca1\u6709\u91cd\u5199<code>onDestroy()<\/code>\u3002\u90a3\u4e48\u5728<code>visitMethod()<\/code>\u4e2d\u53ea\u4f1a\u51fa\u73b0<code>onCreate()<\/code>\uff0c\u4e0d\u4f1a\u6709<code>onDestroy()<\/code><\/p>\n<\/blockquote>\n<p>\u56de\u5230\u9700\u6c42\uff0c\u6211\u4eec\u5e0c\u671b\u5728<code>onCreate()<\/code>\u4e2d\u63d2\u5165\u5bf9\u5e94\u7684\u4ee3\u7801\uff0c\u6765\u8bb0\u5f55\u9875\u9762\u88ab\u6253\u5f00\uff08\u8fd9\u91cc\u901a\u8fc7Log\u4ee3\u66ff\uff09<\/p>\n<pre><code class=\"language-java\">Log.i(&quot;TAG&quot;, &quot;-------&gt; onCreate : &quot; + this.getClass().getSimpleName());<\/code><\/pre>\n<p>\u4e8e\u662f\uff0c\u5728<code>LifecycleOnCreateMethodVisitor<\/code>\u4e2d\u5982\u4e0b\u5904\u7406\uff08<code>LifecycleOnDestroyMethodVisitor<\/code>\u4e0e<code>LifecycleOnCreateMethodVisitor<\/code>\u76f8\u4f3c\uff09<\/p>\n<h5>LifecycleOnCreateMethodVisitor<\/h5>\n<pre><code class=\"language-java\">package cn.appblog.plugin.lifecycle;\n\nimport org.objectweb.asm.MethodVisitor;\nimport org.objectweb.asm.Opcodes;\n\npublic class LifecycleOnCreateMethodVisitor extends MethodVisitor {\n\n    public LifecycleOnCreateMethodVisitor(MethodVisitor mv) {\n        super(Opcodes.ASM4, mv);\n    }\n\n    @Override\n    public void visitCode() {\n        super.visitCode();\n        \/\/\u65b9\u6cd5\u6267\u884c\u524d\u63d2\u5165\n        mv.visitLdcInsn(&quot;yezhou&quot;);\n        mv.visitTypeInsn(Opcodes.NEW, &quot;java\/lang\/StringBuilder&quot;);\n        mv.visitInsn(Opcodes.DUP);\n        mv.visitMethodInsn(Opcodes.INVOKESPECIAL, &quot;java\/lang\/StringBuilder&quot;, &quot;&lt;init&gt;&quot;, &quot;()V&quot;, false);\n        mv.visitLdcInsn(&quot;-------&gt; onCreate : &quot;);\n        mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, &quot;java\/lang\/StringBuilder&quot;, &quot;append&quot;, &quot;(Ljava\/lang\/String;)Ljava\/lang\/StringBuilder;&quot;, false);\n        mv.visitVarInsn(Opcodes.ALOAD, 0);\n        mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, &quot;java\/lang\/Object&quot;, &quot;getClass&quot;, &quot;()Ljava\/lang\/Class;&quot;, false);\n        mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, &quot;java\/lang\/Class&quot;, &quot;getSimpleName&quot;, &quot;()Ljava\/lang\/String;&quot;, false);\n        mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, &quot;java\/lang\/StringBuilder&quot;, &quot;append&quot;, &quot;(Ljava\/lang\/String;)Ljava\/lang\/StringBuilder;&quot;, false);\n        mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, &quot;java\/lang\/StringBuilder&quot;, &quot;toString&quot;, &quot;()Ljava\/lang\/String;&quot;, false);\n        mv.visitMethodInsn(Opcodes.INVOKESTATIC, &quot;android\/util\/Log&quot;, &quot;i&quot;, &quot;(Ljava\/lang\/String;Ljava\/lang\/String;)I&quot;, false);\n        mv.visitInsn(Opcodes.POP);\n    }\n\n    @Override\n    public void visitInsn(int opcode) {\n        \/\/\u65b9\u6cd5\u6267\u884c\u540e\u63d2\u5165\n        \/*\n        if (opcode == Opcodes.RETURN) {\n            mv.visitLdcInsn(&quot;TAG&quot;);\n            mv.visitTypeInsn(Opcodes.NEW, &quot;java\/lang\/StringBuilder&quot;);\n            mv.visitInsn(Opcodes.DUP);\n            mv.visitMethodInsn(Opcodes.INVOKESPECIAL, &quot;java\/lang\/StringBuilder&quot;, &quot;&lt;init&gt;&quot;, &quot;()V&quot;, false);\n            mv.visitLdcInsn(&quot;-------&gt; onCreate : end \uff1a&quot;);\n            mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, &quot;java\/lang\/StringBuilder&quot;, &quot;append&quot;, &quot;(Ljava\/lang\/String;)Ljava\/lang\/StringBuilder;&quot;, false);\n            mv.visitVarInsn(Opcodes.ALOAD, 0);\n            mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, &quot;java\/lang\/Object&quot;, &quot;getClass&quot;, &quot;()Ljava\/lang\/Class;&quot;, false);\n            mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, &quot;java\/lang\/Class&quot;, &quot;getSimpleName&quot;, &quot;()Ljava\/lang\/String;&quot;, false);\n            mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, &quot;java\/lang\/StringBuilder&quot;, &quot;append&quot;, &quot;(Ljava\/lang\/String;)Ljava\/lang\/StringBuilder;&quot;, false);\n            mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, &quot;java\/lang\/StringBuilder&quot;, &quot;toString&quot;, &quot;()Ljava\/lang\/String;&quot;, false);\n            mv.visitMethodInsn(Opcodes.INVOKESTATIC, &quot;android\/util\/Log&quot;, &quot;i&quot;, &quot;(Ljava\/lang\/String;Ljava\/lang\/String;)I&quot;, false);\n            mv.visitInsn(Opcodes.POP);\n        }\n        *\/\n        super.visitInsn(opcode);\n    }\n}<\/code><\/pre>\n<p>\u53ea\u9700\u8981\u5728<code>visitCode()<\/code>\u4e2d\u63d2\u5165\u4e0a\u9762\u7684\u4ee3\u7801\uff0c\u5373\u53ef\u5b9e\u73b0<code>onCreate()<\/code>\u5185\u5bb9\u6267\u884c\u4e4b\u524d\uff0c\u5148\u6267\u884c\u6211\u4eec\u63d2\u5165\u7684\u4ee3\u7801\u3002<\/p>\n<p>\u5982\u679c\u60f3\u5728<code>onCreate()<\/code>\u5185\u5bb9\u6267\u884c\u4e4b\u540e\u63d2\u5165\u4ee3\u7801\uff0c\u8be5\u600e\u4e48\u505a\uff1f\u548c\u4e0a\u9762\u76f8\u4f3c\uff0c\u53ea\u8981\u5728<code>visitInsn()<\/code>\u65b9\u6cd5\u4e2d\u63d2\u5165\u5bf9\u5e94\u7684\u4ee3\u7801\u5373\u53ef\u3002\u4ee3\u7801\u5982\u4e0b\uff1a<\/p>\n<pre><code class=\"language-java\">@Override\npublic void visitInsn(int opcode) {\n    \/\/\u5224\u65adRETURN\n    if (opcode == Opcodes.RETURN) {\n        \/\/\u5728\u8fd9\u91cc\u63d2\u5165\u4ee3\u7801\n        ...\n    }\n    super.visitInsn(opcode);\n}<\/code><\/pre>\n<p>\u5982\u679c\u5bf9\u5b57\u8282\u7801\u4e0d\u662f\u5f88\u4e86\u89e3\uff0c\u770b\u5230\u4e0a\u9762<code>visitCode()<\/code>\u4e2d\u7684\u4ee3\u7801\u53ef\u80fd\u4f1a\u89c9\u5f97\u65e2\u719f\u6089\u53c8\u964c\u751f\uff0c\u90a3\u662fASM\u63d2\u5165\u5b57\u8282\u7801\u7684\u7528\u6cd5\u3002<br \/>\n\u5982\u679c\u5199\u4e0d\u6765\uff0c\u6ca1\u5173\u7cfb\uff0c\u8fd9\u91cc\u4ecb\u7ecd\u4e00\u4e2a\u63d2\u4ef6<code>ASM Bytecode Outline<\/code>\u3002<\/p>\n<h5>LifecycleOnDestroyMethodVisitor<\/h5>\n<pre><code class=\"language-java\">package cn.appblog.plugin.lifecycle;\n\nimport org.objectweb.asm.MethodVisitor;\nimport org.objectweb.asm.Opcodes;\n\npublic class LifecycleOnDestroyMethodVisitor extends MethodVisitor {\n\n    public LifecycleOnDestroyMethodVisitor(MethodVisitor mv) {\n        super(Opcodes.ASM4, mv);\n    }\n\n    @Override\n    public void visitCode() {\n        super.visitCode();\n        \/\/\u65b9\u6cd5\u6267\u884c\u524d\u63d2\u5165\n        mv.visitLdcInsn(&quot;yezhou&quot;);\n        mv.visitTypeInsn(Opcodes.NEW, &quot;java\/lang\/StringBuilder&quot;);\n        mv.visitInsn(Opcodes.DUP);\n        mv.visitMethodInsn(Opcodes.INVOKESPECIAL, &quot;java\/lang\/StringBuilder&quot;, &quot;&lt;init&gt;&quot;, &quot;()V&quot;, false);\n        mv.visitLdcInsn(&quot;-------&gt; onDestroy : &quot;);\n        mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, &quot;java\/lang\/StringBuilder&quot;, &quot;append&quot;, &quot;(Ljava\/lang\/String;)Ljava\/lang\/StringBuilder;&quot;, false);\n        mv.visitVarInsn(Opcodes.ALOAD, 0);\n        mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, &quot;java\/lang\/Object&quot;, &quot;getClass&quot;, &quot;()Ljava\/lang\/Class;&quot;, false);\n        mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, &quot;java\/lang\/Class&quot;, &quot;getSimpleName&quot;, &quot;()Ljava\/lang\/String;&quot;, false);\n        mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, &quot;java\/lang\/StringBuilder&quot;, &quot;append&quot;, &quot;(Ljava\/lang\/String;)Ljava\/lang\/StringBuilder;&quot;, false);\n        mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, &quot;java\/lang\/StringBuilder&quot;, &quot;toString&quot;, &quot;()Ljava\/lang\/String;&quot;, false);\n        mv.visitMethodInsn(Opcodes.INVOKESTATIC, &quot;android\/util\/Log&quot;, &quot;i&quot;, &quot;(Ljava\/lang\/String;Ljava\/lang\/String;)I&quot;, false);\n        mv.visitInsn(Opcodes.POP);\n    }\n\n    @Override\n    public void visitInsn(int opcode) {\n        super.visitInsn(opcode);\n    }\n}<\/code><\/pre>\n<h5>\u66ff\u6362<\/h5>\n<p><code>class<\/code>\u6587\u4ef6\u7684\u63d2\u6869\u5df2\u7ecf\u8bf4\u5b8c\uff0c\u5269\u4e0b\u6700\u540e\u4e00\u6b65\u66ff\u6362\u3002\u773c\u5c16\u7684\u540c\u5b66\u5e94\u8be5\u53d1\u73b0\uff0c\u4ee3\u7801\u4e0a\u9762\u5df2\u7ecf\u51fa\u73b0\u8fc7\u4e86\u3002\u8fd8\u662f\u4ee5<code>LifecyclePlugin#handleDirectoryInputs()<\/code>\u4e2d\u7684\u4ee3\u7801\u4e3a\u4f8b\uff1a<\/p>\n<pre><code>byte[] code = classWriter.toByteArray()\nFileOutputStream fos = new FileOutputStream(\n      file.parentFile.absolutePath + File.separator + name)\nfos.write(code)\nfos.close()<\/code><\/pre>\n<p>\u4ece<code>classWriter<\/code>\u5f97\u5230<code>class<\/code>\u4fee\u6539\u540e\u7684byte\u6d41\uff0c\u7136\u540e\u901a\u8fc7\u6d41\u7684\u5199\u5165\u8986\u76d6\u539f\u6765\u7684class\u6587\u4ef6\uff08Jar\u5305\u7684\u8986\u76d6\u4f1a\u7a0d\u5fae\u590d\u6742\u4e00\u70b9\uff0c\u8fd9\u91cc\u5c31\u4e0d\u7ec6\u8bf4\u4e86\uff09<\/p>\n<p><code>File.separator<\/code>\uff1a\u6587\u4ef6\u7684\u5206\u9694\u7b26\u3002\u4e0d\u540c\u7cfb\u7edf\u5206\u9694\u7b26\u53ef\u80fd\u4e0d\u4e00\u6837\u3002\u5982\uff1a\u540c\u6837\u4e00\u4e2a\u6587\u4ef6\uff0cWindows\u4e0b\u662f<code>C:\\tmp\\test.txt<\/code>\uff1bLinux \u4e0b\u5374\u662f<code>\/tmp\/test.txt<\/code><\/p>\n<h4>\u63d2\u4ef6\u4f7f\u7528<\/h4>\n<p>\u5de5\u7a0b\u76ee\u5f55\u4e0b\u7684<code>build.gradle<\/code>\u914d\u7f6e\u63d2\u4ef6<\/p>\n<pre><code>buildscript {\n    repositories {\n        maven {  \/\/\u672c\u5730Maven\u4ed3\u5e93\u5730\u5740\n            url uri(&#039;D:\/repos&#039;)\n        }\n    }\n    dependencies {\n        classpath &#039;cn.appblog.plugin:lifecycle-plugin:0.0.1&#039;\n    }\n}<\/code><\/pre>\n<p>\u521b\u5efa\u4e00\u4e2aAndroid\u9879\u76eeapp\uff0c\u5728<code>app.gradle<\/code>\u4e2d\u5f15\u7528\u63d2\u4ef6<\/p>\n<pre><code>apply plugin: &#039;cn.appblog.gradle&#039;<\/code><\/pre>\n<p>\u8fd0\u884c\u540e\uff0c\u6309\u6b65\u9aa4\u64cd\u4f5c\uff1a\u6253\u5f00<code>MainActivity<\/code> \u2014&gt; \u6253\u5f00<code>SecondActivity<\/code> \u2014&gt; \u8fd4\u56de<code>MainActivity<\/code><\/p>\n<p>\u67e5\u770b\u6548\u679c\uff1a<\/p>\n<pre><code>cn.appblog.asmdemo I\/yezhou: -------&gt; onCreate : MainActivity\ncn.appblog.asmdemo I\/yezhou: -------&gt; onCreate : SecondActivity\ncn.appblog.asmdemo I\/yezhou: -------&gt; onDestroy : SecondActivity<\/code><\/pre>\n<p>\u53ef\u4ee5\u53d1\u73b0\uff0c\u9875\u9762\u6253\u5f00\\\u5173\u95ed\u90fd\u4f1a\u6253\u5370\u5bf9\u5e94\u7684log\u3002\u8bf4\u660e\u6211\u4eec\u63d2\u5165\u7684\u4ee3\u7801\u88ab\u6267\u884c\u4e86\uff0c\u800c\u4e14\uff0c\u4f7f\u7528\u65f6\u5bf9\u9879\u76ee\u6ca1\u6709\u4efb\u4f55\u201c\u5165\u4fb5\u201d\u3002<\/p>\n<h2>\u7ed3\u8bed<\/h2>\n<p>\u672c\u6587\u5185\u5bb9\u6d89\u53ca\u77e5\u8bc6\u8f83\u591a\uff0c\u5728\u719f\u6089<strong>Android\u6253\u5305\u8fc7\u7a0b<\/strong>\u3001<strong>\u5b57\u8282\u7801<\/strong>\u3001<strong>Gradle Transform API<\/strong>\u3001<strong>ASM<\/strong>\u7b49\u4e4b\u524d\uff0c\u9605\u8bfb\u8d77\u6765\u4f1a\u5f88\u56f0\u96be\u3002\u4e0d\u8fc7\uff0c\u5728\u4e86\u89e3\u5e76\u5b66\u4e60\u8fd9\u4e9b\u77e5\u8bc6\u7684\u4e4b\u540e\uff0c\u76f8\u4fe1\u4f60\u5bf9Android\u4f1a\u6709\u65b0\u7684\u8ba4\u8bc6\u3002<\/p>\n<p>\u672c\u6587\u8f6c\u8f7d\u81f3\uff1a<a target=\"_blank\" rel=\"noopener\" href=\"https:\/\/www.jianshu.com\/p\/16ed4d233fd1\">https:\/\/www.jianshu.com\/p\/16ed4d233fd1<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>\u5728Android\u7f16\u8bd1\u8fc7\u7a0b\u4e2d\uff0c\u5f80\u5b57\u8282\u7801\u91cc\u63d2\u5165\u81ea\u5b9a\u4e49\u7684\u5b57\u8282\u7801\uff0c\u79f0\u4e3a\u5b57\u8282\u7801\u63d2\u6869\u6216\u51fd\u6570\u63d2\u6869\u3002 \u51fd\u6570\u63d2\u6869\u53ef\u4ee5\u5e2e\u52a9\u6211\u4eec\u5b9e\u73b0 [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[202],"tags":[461,64],"class_list":["post-1825","post","type-post","status-publish","format-standard","hentry","category-android-build","tag-asm","tag-gradle"],"_links":{"self":[{"href":"https:\/\/www.appblog.cn\/index.php\/wp-json\/wp\/v2\/posts\/1825","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.appblog.cn\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.appblog.cn\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.appblog.cn\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.appblog.cn\/index.php\/wp-json\/wp\/v2\/comments?post=1825"}],"version-history":[{"count":0,"href":"https:\/\/www.appblog.cn\/index.php\/wp-json\/wp\/v2\/posts\/1825\/revisions"}],"wp:attachment":[{"href":"https:\/\/www.appblog.cn\/index.php\/wp-json\/wp\/v2\/media?parent=1825"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.appblog.cn\/index.php\/wp-json\/wp\/v2\/categories?post=1825"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.appblog.cn\/index.php\/wp-json\/wp\/v2\/tags?post=1825"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}