gradle插件字节码插桩(三),读取清单文件(AndroidM
2017-11-08 本文已影响0人
和平菌
我们在插桩的过程中,需要知道我们的包名和过滤出我们的Activity来,所以我就想到了读取清单文件AndroidManifest
那么我们就继续回到我们的Transform类的transform方法中来。
首先我们要做的就是拿到清单文件
File rootLocation = null
try {
rootLocation = outputProvider.rootLocation
} catch (Throwable e) {
//android gradle plugin 3.0.0+ 修改了私有变量,将其移动到了IntermediateFolderUtils中去
rootLocation = outputProvider.folderUtils.getRootFolder()
}
if (rootLocation == null) {
throw new GradleException("can't get transform root location")
}
println ">>> rootLocation: ${rootLocation}"
// Compatible with path separators for window and Linux, and fit split param based on 'Pattern.quote'
def variantDir = rootLocation.absolutePath.split(getName() + Pattern.quote(File.separator))[1]
println ">>> variantDir: ${variantDir}"
我们首先要得到一个rootLocation,也就是build项目时候存放文件的目录,大致的结构就是
D:\......\项目名称\build\intermediates\transforms\xxxTask\debug\
然后我们从该目录里截取最后的那个文件夹名称也就是debug,赋值给变量variantDir
然后我们有一个方法来读取我们的清单文件的路径
/**
* 获取 AndroidManifest.xml 路径
*/
def static manifestPath(Project project, String variantDir) {
// Compatible with path separators for window and Linux, and fit split param based on 'Pattern.quote'
def variantDirArray = variantDir.split(Pattern.quote(File.separator))
String variantName = ""
variantDirArray.each {
//首字母大写进行拼接
variantName += it.capitalize()
}
println ">>> variantName:${variantName}"
//获取processManifestTask
def processManifestTask = project.tasks.getByName("process${variantName}Manifest")
//如果processManifestTask存在的话
//transform的task目前能保证在processManifestTask之后执行
if (processManifestTask) {
File result = null
//正常的manifest
File manifestOutputFile = null
//instant run的manifest
File instantRunManifestOutputFile = null
try {
manifestOutputFile = processManifestTask.getManifestOutputFile()
instantRunManifestOutputFile = processManifestTask.getInstantRunManifestOutputFile()
} catch (Exception e) {
manifestOutputFile = new File(processManifestTask.getManifestOutputDirectory(), "AndroidManifest.xml")
instantRunManifestOutputFile = new File(processManifestTask.getInstantRunManifestOutputDirectory(), "AndroidManifest.xml")
}
if (manifestOutputFile == null && instantRunManifestOutputFile == null) {
throw new GradleException("can't get manifest file")
}
//打印
println " manifestOutputFile:${manifestOutputFile} ${manifestOutputFile.exists()}"
println " instantRunManifestOutputFile:${instantRunManifestOutputFile} ${instantRunManifestOutputFile.exists()}"
//先设置为正常的manifest
result = manifestOutputFile
try {
//获取instant run 的Task
def instantRunTask = project.tasks.getByName("transformClassesWithInstantRunFor${variantName}")
//查找instant run是否存在且文件存在
if (instantRunTask && instantRunManifestOutputFile.exists()) {
println ' Instant run is enabled and the manifest is exist.'
if (!manifestOutputFile.exists()) {
//因为这里只是为了读取activity,所以无论用哪个manifest差别不大
//正常情况下不建议用instant run的manifest,除非正常的manifest不存在
//只有当正常的manifest不存在时,才会去使用instant run产生的manifest
result = instantRunManifestOutputFile
}
}
} catch (ignored) {
// transformClassesWithInstantRunForXXX may not exists
}
//最后检测文件是否存在,打印
if (!result.exists()) {
println ' AndroidManifest.xml not exist'
}
//输出路径
println " AndroidManifest.xml 路径:$result"
return result.absolutePath
}
return ""
}
接着我们对清单文件进行解析
def manifest = new XmlSlurper().parse(filePath)
这里的filePath就是我们上面拿到的清单文件的路径
然后我们就可以从manifest里取到我们想要的东西。
比如取到包名:
manifest.@package
比如取到所有的activity:
def activities = []
String pkg = manifest.@package
manifest.application.activity.each {
String name = it.'@android:name'
if (name.substring(0, 1) == '.') {
name = pkg + name
}
activities << name
}
再比如取到Application:
manifest.application.@name;
这样我们就通过解析清单文件拿到了所有的Activity 以及包名。那么就可以在我们的transform里进行过滤了。
事实上一开始我想到的是递归一个类,然后拿到最上面的非Object的父类,来判断该类是否是Activity,所以下了下面的代码。
private boolean isActivity(CtClass c){
CtClass sourceSuperClass = getSourceSuperClass(c)
if(sourceSuperClass != null && sourceSuperClass.getName().equals("android.app.Activity")){
return true
}
return false
}
/***
* 得到最根部的父类
*/
private CtClass getSourceSuperClass(CtClass c){
if(c != null) {
CtClass superClass = c.getSuperclass();
while (superClass != null) {
if (superClass.getName().equals("java.lang.Object")) {
break;
}
superClass = getSourceSuperClass(c);
}
return superClass;
}
return null;
}
但后来在一个开源项目里(replugin) 看到了读取清单文件路径的代码,非常感谢replugin。