美文网首页Android Gradle
gradle插件字节码插桩(三),读取清单文件(AndroidM

gradle插件字节码插桩(三),读取清单文件(AndroidM

作者: 和平菌 | 来源:发表于2017-11-08 18:46 被阅读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。

    相关文章

      网友评论

        本文标题:gradle插件字节码插桩(三),读取清单文件(AndroidM

        本文链接:https://www.haomeiwen.com/subject/wfbymxtx.html