Transform使用

一、Transform介绍

从 1.5.0-beta1 开始,Gradle 插件包含一个 Transform API,允许 3rd 方插件在将编译的类文件转换为 dex 文件之前对其进行操作。(API 在 1.4.0-beta2 中存在,但在 1.5.0-beta1 中已完全修改)此 API 的目标是简化注入自定义类操作而无需处理任务,并为操作的内容提供更大的灵活性。内部代码处理(jacoco、progard、multi-dex)在 1.5.0-beta1 中已经全部转移到这个新机制。

简单来说,TransformAPI可以让我们在编译打包安卓项目时,在源码编译为class字节码后,处理成dex文件前,对字节码做一些操作。

二、Transform方法

实现自定义的Transform一般要复写如下几个方法,下面对每个方法做一下详细解释:

  • getName()
    getName()方法用于指明自定义的Transform的名称,在gradle执行该任务时,会将该Transform的名称再加上前后缀,如上面图中所示的,最后的task名称是transformClassesWithXXXForXXX这种格式。如下:
> Task :app:transformClassesWithMyTransformForDebug

MyTransform就是我们自定义的Transform。

  • getInputTypes()
    用于指明 Transform 的输入类型,可以作为输入过滤的手段。在 TransformManager 类中定义了很多类型,我们常用的就是TransformManager.CONTENT_CLASS

  • getScopes()
    用于指明 Transform 的作用域。同样,在 TransformManager 类中定义了几种范围,常用的是 SCOPE_FULL_PROJECT ,代表所有 Project 。确定了 ContentType 和 Scope 后就确定了该自定义 Transform 需要处理的资源流。比如 CONTENT_CLASS 和 SCOPE_FULL_PROJECT 表示了所有项目中 java 编译成的 class 组成的资源流。

  • isIncremental()
    指明该 Transform 是否支持增量编译。需要注意的是,即使返回了 true ,在某些情况下运行时,它还是会返回 false 的。

  • transform(TransformInvocation transformInvocation)
    一般在这个方法中对字节码做一些处理。

完整的自定类Transform如下:

package com.example.test_plugin;

import com.android.build.api.transform.DirectoryInput;
import com.android.build.api.transform.JarInput;
import com.android.build.api.transform.QualifiedContent;
import com.android.build.api.transform.Transform;
import com.android.build.api.transform.TransformException;
import com.android.build.api.transform.TransformInput;
import com.android.build.api.transform.TransformInvocation;
import com.android.build.api.transform.TransformOutputProvider;
import com.android.build.gradle.internal.pipeline.TransformManager;

import java.io.IOException;
import java.util.Collection;
import java.util.Set;


public class MyTransform extends Transform {

    public MyTransform() {
        System.out.println("Hello MyTransform..1111.");
    }

    @Override
    public String getName() {
        return "MyTransform";
    }

    @Override
    public Set<QualifiedContent.ContentType> getInputTypes() {
        return TransformManager.CONTENT_CLASS;
    }

    @Override
    public Set<? super QualifiedContent.Scope> getScopes() {
        return TransformManager.SCOPE_FULL_PROJECT;
    }

    @Override
    public boolean isIncremental() {
        return false;
    }

    @Override
    public void transform(TransformInvocation transformInvocation) throws TransformException, InterruptedException, IOException {
        super.transform(transformInvocation);
        System.out.println("Hello MyTransform..2222222222222222.");
        //是否支持增量编译
        boolean incremental = transformInvocation.isIncremental();
        //获取输出
        TransformOutputProvider outputProvider = transformInvocation.getOutputProvider();
        //如果不支持增量编译,需要把之前生成的都删除,不复用缓存
        if (!incremental) {
            outputProvider.deleteAll();
        }
        for (TransformInput input : transformInvocation.getInputs()) {
            //处理jar
            Collection<JarInput> jarInputs = input.getJarInputs();
            if (jarInputs!= null && jarInputs.size()>0){
                for (JarInput jarInput : jarInputs) {
                    System.out.println("-----------name = "+jarInput.getName());
                }
            }
        }

        for (TransformInput input : transformInvocation.getInputs()) {
            //处理jar
            Collection<DirectoryInput> directoryInputs = input.getDirectoryInputs();
            if (directoryInputs!= null && directoryInputs.size()>0){
                for (DirectoryInput directoryInput :
                        directoryInputs) {
                    String name = directoryInput.getName();
                    System.out.println("-----------name = "+name);
                }
            }
        }
    }
    
}

三、使用Transform

将插件发布到本地仓库中后,我们在另外一个工程集成插件。
setting.gradle中添加本地仓库依赖

pluginManagement {
    repositories {
        //加上mavenLocal()会在我们user目录中的.m2文件夹下加载插件
        mavenLocal()
        gradlePluginPortal()
        google()
        mavenCentral()
    }
}
dependencyResolutionManagement {
    repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
    repositories {
        google()
        mavenCentral()
    }
}
rootProject.name = "AGPTest2"
include ':app'

接下来在project的build.gradle中添加自定义插件的classpath,buildscript 一定要放在文件的最上面,即plugins{}的上面。

buildscript {
    dependencies {
      // 自定义的插件
        classpath "com.example.test_plugin:MyPlugin:1.0.0"
    }
}

plugins {
    id 'com.android.application' version '7.2.2' apply false
    id 'com.android.library' version '7.2.2' apply false
}



task clean(type: Delete) {
    delete rootProject.buildDir
}

然后点击sync同步工程,build打印如下:


> Configure project :app
//这个是自定义plugin中apply方法中的打印
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.
//这个是MyTransform构造函数中加的打印
Hello MyTransform..1111.

> Task :prepareKotlinBuildScriptModel UP-TO-DATE

BUILD SUCCESSFUL in 5s

同时也可以看到一个打印

API 'android.registerTransform' is obsolete.
It will be removed in version 8.0 of the Android Gradle plugin.
The Transform API is removed to improve build performance. Projects that use the
Transform API force the Android Gradle plugin to use a less optimized flow for the
build that can result in large regressions in build times. It’s also difficult to
use the Transform API and combine it with other Gradle features; the replacement
APIs aim to make it easier to extend the build without introducing performance or
correctness issues.

There is no single replacement for the Transform API—there are new, targeted
APIs for each use case. All the replacement APIs are in the
`androidComponents {}` block.
For more information, see https://developer.android.com/studio/releases/gradle-plugin-api-updates#transform-api.
To determine what is calling android.registerTransform, use -Pandroid.debug.obsoleteApi=true on the command line to display more information.
Affected Modules: app

网上查了下gradle插件7.0以后的版本已经将Transform类废弃了,且在gradle插件8.0后的版本中删除。也就是说我们要是使用8.0的插件就要重新去适配了。

下面我们点击makeproject编译一下,看下日志打印

> Task :app:transformClassesWithMyTransformForDebug
Hello MyTransform..2222222222222222.
-----------name = e7ec8f42bf9ecfd490b4d2f6650f62c44d97ce8b
-----------name = 5e85c75d151005536ab1b95f769308b4176c0370
-----------name = ceeec43640bb729acc5da4ae8aa5484027d464e9
-----------name = 4586c2585f55ebd93f1225d84efe4c9b0c641ede
-----------name = e4f913597bef93f7aab8d178381df67b837d6e5f
-----------name = 29a643378b50632bde83cd9ad75cc7d0b0b6c827
-----------name = 5e7b19787e83c522b7fa80f4369c441caebe416a
-----------name = d66cab69d9f8f6517cd4b37c637a9c071e4d47f8
-----------name = 16ae442b038162ee3afda0d5a5342317cca2f2f8
-----------name = eb3cd54684b5f9b0deb4a49f940d719ebcf782af
-----------name = cd052027d5bcbe254b57571f0d0a9c5d96221c5a
-----------name = 5ee16216ca36e0d891ba50bfa4d53c0dcc34f9d4
-----------name = 88f9b54a2e1a5559f78d14a0440ef949a7f49174
-----------name = adeeb9135b1f0ca214043160d4c76b7f8f83d621
-----------name = c3ccb50a3a7db3fa1a78d74fb63ebc4c0d99657b
-----------name = 48a1b65441cb63987d2678214c4a39022407f480
-----------name = 40915a26fb6370c09465623a422a0669a36a3c2b
-----------name = 13a00d5ddee0a60336a86b30f5d9df15cbcfd367
-----------name = d07572def3a6da380429006e45e7f3567c2f71d4
-----------name = a3ea4023c9ab427f538e16e3b293b47a2653b3b4
-----------name = 8d0e0767185c160d8dba2702cc48a15aa2b0c162
-----------name = 29c3923b3f9177532529fcfb63c121b526c10daa
-----------name = f4d1a6d741eb208668817d980477083eced0f516
-----------name = 5e74fcb46599fa0a2b5f446d5fedf15e8c785a80
-----------name = bdf81e81e6085244c3f7c00a31d814fe58819faf
-----------name = 0c39cb4bff00f99545a55c689c057da46cfa8473
-----------name = 0ebd3d833a3291efe364da0fb08ecc34e294826e
-----------name = 3c2ade5802777fc6c5ad08f1f675003f73c9a1eb
-----------name = b84f24406382ded9b327796147be46005af17e51
-----------name = e26ece7351729f1e089c009f8f39d7908017a07e
-----------name = 91464501c2b25aaaceb76f0ca98a7cbe30912de0
-----------name = 1e0ab6a8775dddbf0075405823728830af4d3bb8
-----------name = bbfb7a466dfddd80f750335c9b2b63f9bc8e319c
-----------name = d9c50cfdd3fd04d3f5ccd4b5be8259713463f54d
-----------name = 597321e777dc86031329325e6644efa5e006a447
-----------name = d6b2a06a050f3fe1bd5e7d22eab82822cc41a5f9
-----------name = ab2dabf3b2c8afab1e2e25bfff6db7ca0640dc11
-----------name = 5a3370db105b6d5b65e161490af592b37c383076
-----------name = 4dc558cf34ce4e5f42bfdad7d68b09b1dc4faaf7
-----------name = a9db3a407db96c9052dd2b9b2d50c286a7a04c74
-----------name = 62af1ffde5bad5bf3c26d0372c8b7d8b3de6e31a
-----------name = dd2f57f1ac126f50f918bbdd3452bb8db05a0269
-----------name = 159d7220100c5bb15845f5eeebcd88df83776dd1
-----------name = 6b8af78f2ead300fb55abe5499f044cbfb0e89f1
-----------name = 68e7df6c8e6a81d5e23caa53db7f446d47beb246
-----------name = 27918f1d3618ed8cd80d47ecf93ca58f2be0a357
-----------name = 2bda8da70e0b8dacf32533ea74b0861cfc96ac07
-----------name = ec9549ae6717cf79f7abc0c074a969a53a0fce14
-----------name = cf9a6d0dbd42d1cc0c99c7f0da512b3b51d34a78
-----------name = 2bcb6cb60b5fd3fd390a75c4dd9b0603a47e2e09
-----------name = 1b5cc4d85fdc7e38ca388a0de3cfb1cb4b09879f
-----------name = c004cca63a24f3abe43c4f247b827eee30d2bbd1
-----------name = 738bde333103bb986360d94ae0b316c67161c49c
-----------name = c53b27a931aec69a656098a7015674c2784a3fcf
-----------name = 186c6b371c294a35776381254c1039e97e5c0860
-----------name = 85ff3092a968484cde3ad1c61b84113bfce18f37
-----------name = c5e5d904920026492d32b34d17fa9f8d0928ee31

可以看到transform方法中的加的日志成功打印了。

Transform为我们提供了在.class生成dex之前加载和修改class文件的入口,那么如何去修改class字节码,我们在下一篇ASM文章中介绍。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,406评论 6 503
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,732评论 3 393
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,711评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,380评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,432评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,301评论 1 301
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,145评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,008评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,443评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,649评论 3 334
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,795评论 1 347
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,501评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,119评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,731评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,865评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,899评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,724评论 2 354

推荐阅读更多精彩内容