直接原因
尝试创建新的SubStream
abstract class SimpleClassCreatorTransform extends Transform {
String prepareToCreateClass(TransformInvocation transformInvocation) {
...
return transformInvocation.outputProvider.getContentLocation("main",
getOutputTypes(), getScopes(),
Format.DIRECTORY)
}
}
用Scope
集合作为TransformOutputProvider.getContentLocation()
有问题
class SplitComponentTransform extends SimpleClassCreatorTransform {
@Override
Set<? super QualifiedContent.Scope> getScopes() {
return TransformManager.SCOPE_FULL_PROJECT
}
}
详见build
目录下的__content__.json
,基本没有混合Scope
的SubStream
[
{
"scopes": [
"EXTERNAL_LIBRARIES"
],
"format": "JAR",
"types": [ "DEX_ARCHIVE" ],
"name": "...",
"index": 0,
"present": true
},
{
"scopes": [ "SUB_PROJECTS" ],
"format": "JAR",
"types": [ "DEX_ARCHIVE" ],
"name": "...",
"index": 352,
"present": true
},
{
"scopes": [ "PROJECT" ],
"format": "DIRECTORY",
"types": [ "DEX_ARCHIVE" ],
"name": "...",
"index": 354,
"present": true
},
]
根本原因
- 每个
Transform
都会"承上启下"地对应一个IntermediateStream
- 每个
IntermediateStream
对应多个SubStream
- 上下游的
SubStream
按Scope
匹配,大部分情况下游是上游的超集,但如果是部分包含则抛异常
影响范围
所有使用Transform
且Scope
不包含EXTERNAL_LIBRARIES
的插件。
解决方法
重现代码
新建一个Android
工程,然后把下面代码粘贴到主工程的build.gradle
最后。
import com.android.annotations.NonNull
import com.android.build.api.transform.DirectoryInput
import com.android.build.api.transform.Format
import com.android.build.api.transform.JarInput
import com.android.build.api.transform.Status
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.gradle.BaseExtension
import com.android.build.gradle.internal.pipeline.TransformManager
import com.android.build.api.transform.Transform
import com.android.build.api.transform.QualifiedContent
import com.google.common.collect.ImmutableSet
import org.apache.commons.io.FileUtils
import org.objectweb.asm.ClassWriter
import org.objectweb.asm.Opcodes
abstract class TransformBase extends Transform {
@Override
boolean isIncremental() {
return false
}
@Override
Set<QualifiedContent.ContentType> getInputTypes() {
return ImmutableSet.of(QualifiedContent.DefaultContentType.CLASSES)
}
private static File getOutputFile(TransformInvocation invocation, QualifiedContent content, Format format) {
def provider = invocation.outputProvider
return provider.getContentLocation(content.name, content.contentTypes, content.scopes, format)
}
private static void transformJar(TransformInvocation invocation, JarInput jar) {
if (jar.status == Status.REMOVED) {
return
}
def outFile = getOutputFile(invocation, jar, Format.JAR)
FileUtils.copyFile(jar.file, outFile)
}
private static void transformDirectory(TransformInvocation invocation, DirectoryInput dir) {
if (!dir.file.exists()) {
return
}
def statusMap = dir.getChangedFiles()
def outDir = getOutputFile(invocation, dir, Format.DIRECTORY)
FileUtils.copyDirectory(dir.file, outDir, { statusMap.getOrDefault(it, Status.NOTCHANGED) != Status.REMOVED })
}
@Override
void transform(@NonNull TransformInvocation transformInvocation)
throws TransformException, InterruptedException, IOException {
transformInvocation.inputs.each { TransformInput input ->
input.jarInputs.each { transformJar(transformInvocation, it) }
input.directoryInputs.each { transformDirectory(transformInvocation, it) }
}
}
}
class CopyTransform extends TransformBase {
@Override
String getName() {
return "copy"
}
@Override
Set<? super QualifiedContent.Scope> getScopes() {
return ImmutableSet.of(
QualifiedContent.Scope.PROJECT,
QualifiedContent.Scope.SUB_PROJECTS
)
}
}
class GenClassTransform extends TransformBase {
private static final String FOO_CLASS_NAME = "Foo"
private static final String FOO_CLASS_PACKAGE = "com.fish47.foo"
@Override
String getName() {
return "genClass"
}
@Override
Set<? super QualifiedContent.Scope> getScopes() {
return TransformManager.SCOPE_FULL_PROJECT
}
private static getGenerateClassFile(File dir) {
def subDir = FOO_CLASS_PACKAGE.replace(".", File.separator)
def subPath = String.format("%s%c%s.class", subDir, File.separatorChar, FOO_CLASS_NAME)
return new File(dir, subPath)
}
private static byte[] getGenerateClassBytes() {
def fullName = String.format("%s/%s", FOO_CLASS_PACKAGE.replace(".", "/"), FOO_CLASS_NAME)
def clzAcc = Opcodes.ACC_PUBLIC | Opcodes.ACC_SUPER
def cw = new ClassWriter(0)
cw.visit(49, clzAcc, fullName, null, "java/lang/Object", null)
def mv = cw.visitMethod(Opcodes.ACC_PUBLIC, "<init>", "()V", null, null)
mv.visitVarInsn(Opcodes.ALOAD, 0)
mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false)
mv.visitInsn(Opcodes.RETURN)
mv.visitMaxs(1, 1)
mv.visitEnd()
cw.visitEnd()
return cw.toByteArray()
}
private static generateFooClass(TransformInvocation invocation) {
def provider = invocation.outputProvider
def types = ImmutableSet.of(QualifiedContent.DefaultContentType.CLASSES)
def scopes = TransformManager.SCOPE_FULL_PROJECT
def outDir = provider.getContentLocation("main", types, scopes, Format.DIRECTORY)
def classFile = getGenerateClassFile(outDir)
FileUtils.writeByteArrayToFile(classFile, getGenerateClassBytes())
}
@Override
void transform(@NonNull TransformInvocation transformInvocation)
throws TransformException, InterruptedException, IOException {
super.transform(transformInvocation)
generateFooClass(transformInvocation)
}
}
def androidExtension = project.extensions.findByName("android")
if (androidExtension instanceof BaseExtension) {
// ok
// androidExtension.registerTransform(new CopyTransform())
// androidExtension.registerTransform(new GenClassTransform())
// crash
androidExtension.registerTransform(new GenClassTransform())
androidExtension.registerTransform(new CopyTransform())
}