Replugin 源码分析------replugin-host-gradle插件源码分析

Posted Wastrel_xyz

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Replugin 源码分析------replugin-host-gradle插件源码分析相关的知识,希望对你有一定的参考价值。

前言

Replugin是360开源的一个插件化框架,源码地址:https://github.com/Qihoo360/RePlugin,属于一种占坑类的插件化方案,整个框架分为四部分:宿主编译插件、插件工程编译插件、宿主依赖库、插件依赖库。今天分享的是宿主编译插件源码,该插件的作用是在编译时将四大组件的坑位预置进宿主程序中,并生成内置插件的配置信息。

一、源码结构

很显然标准的gradle插件工程,编码语言为groovy,编译工具为gradle,本文不讨论gradle插件工程相关知识,本文只分析该插件在宿主编译时做了哪些工作。

二、源码分析

replugin-host-gradle.properties文件可以看到整个plugin的入口在类com.qihoo360.replugin.gradle.host.Replugin中,首先就从这个文件开始入手吧。

源码路径:com.qihoo360.replugin.gradle.host.Replugin.groovy

	/**整个插件的调用入口方法*/	
    @Override
    public void apply(Project project) 
        this.project = project
        /* Extensions */
        //创建extensions,然后再bulid.gradle文件里就可以,修改RepluginConfig里的默认配置了。
        project.extensions.create(AppConstant.USER_CONFIG, RepluginConfig)
		/*app工程才会执行,Library项目直接过滤了*/
        if (project.plugins.hasPlugin(AppPlugin)) 

            def android = project.extensions.getByType(AppExtension)
  
            android.applicationVariants.all  variant ->

                addShowPluginTask(variant)
				//获取用户bulid.gradle文件里的配置
                if (config == null) 
                    config = project.extensions.getByName(AppConstant.USER_CONFIG)
                    checkUserConfig(config)
                

                def generateBuildConfigTask = VariantCompat.getGenerateBuildConfigTask(variant)
                def appID = generateBuildConfigTask.appPackageName
                def newManifest = ComponentsGenerator.generateComponent(appID, config)
                println "$TAG countTask=$config.countTask"

                def variantData = variant.variantData
                def scope = variantData.scope

                //host generate task
                def generateHostConfigTaskName = scope.getTaskName(AppConstant.TASK_GENERATE, "HostConfig")
                def generateHostConfigTask = project.task(generateHostConfigTaskName)
				//生成RePluginHostConfig.java
                generateHostConfigTask.doLast 
                    FileCreators.createHostConfig(project, variant, config)
                
                generateHostConfigTask.group = AppConstant.TASKS_GROUP

                //depends on build config task
                if (generateBuildConfigTask) 
                    generateHostConfigTask.dependsOn generateBuildConfigTask
                    generateBuildConfigTask.finalizedBy generateHostConfigTask
                

                //json generate task
                def generateBuiltinJsonTaskName = scope.getTaskName(AppConstant.TASK_GENERATE, "BuiltinJson")
                def generateBuiltinJsonTask = project.task(generateBuiltinJsonTaskName)
				//plugins-builtin.json
                generateBuiltinJsonTask.doLast 
                    FileCreators.createBuiltinJson(project, variant, config)
                
                generateBuiltinJsonTask.group = AppConstant.TASKS_GROUP

                //depends on mergeAssets Task
                def mergeAssetsTask = VariantCompat.getMergeAssetsTask(variant)
                if (mergeAssetsTask) 
                    generateBuiltinJsonTask.dependsOn mergeAssetsTask
                    mergeAssetsTask.finalizedBy generateBuiltinJsonTask
                

                variant.outputs.each  output ->
                    VariantCompat.getProcessManifestTask(output).doLast 
                        println "$AppConstant.TAG processManifest: $it.outputs.files"
                        it.outputs.files.each  File file ->
                            updateManifest(file, newManifest)
                        
                    
                
            
        
    

上面代码出现多次使用了一个工具类VariantCompat,该类是做gradle版本兼容的,主要目的是从Android Gradle Task 里获取对应的task,例如方法VariantCompat.getGenerateBuildConfigTask(variant)就是获取generateBuildConfigTask的,可以在Android Studio中打开gradle任务视图,根据不同的variant即可获取到不同的任务。

生成坑位代码
  /**
     * 动态生成插件化框架中需要的组件
     *
     * @param applicationID 宿主的 applicationID
     * @param config 用户配置
     * @return String       插件化框架中需要的组件
     */
   def newManifest = ComponentsGenerator.generateComponent(appID, config)
   	//生成结果
  	// <provider android:name='com.qihoo360.replugin.component.process.ProcessPitProviderPersist' android:authorities='com.qihoo360.replugin.sample.host.loader.p.main' android:exported='false' android:process=':GuardService' />
 	//  ...
  	// <activity android:name='com.qihoo360.replugin.sample.host.loader.a.ActivityN1NRTS0' android:configChanges='keyboard|keyboardHidden|orientation|screenSize' android:exported='false' android:screenOrientation='portrait' android:theme='@android:style/Theme.Translucent.NoTitleBar'/>
	//...
	// <service android:name='com.qihoo360.replugin.component.service.server.PluginPitServiceP2' android:process=':p2' android:exported='false' />
	//...

该方法通过gradle文件中的配置坑位个数来生成四大组件的预置坑位,生成代码很简单,就是for循环生成xml文件。有兴趣可以点进去看,源码路径:com.qihoo360.replugin.gradle.host.handlemanifest.ComponentsGenerator.groovy

生成内置插件json配置

源码位置:com.qihoo360.replugin.gradle.host.creator.impl.json.PluginBuiltinJsonCreator#getFileContent;

 		//查找插件文件并抽取信息,如果没有就直接返回null
        File pluginDirFile = new File(fileDir?.getAbsolutePath() + File.separator + config.pluginDir)
        //遍历assets/plugins下的所有插件。
        new File(fileDir.getAbsolutePath() + File.separator + config.pluginDir)
                .traverse(type: FileType.FILES, nameFilter: ~/.*\\$config.pluginFilePostfix/) 

            PluginInfoParser parser = null
            try 
            	//利用开源库解析插件APK的版本号等Meta信息。
                parser = new PluginInfoParser(it.absoluteFile, config)
             catch (Exception e) 
                if (config.enablePluginFileIllegalStopBuild) 
                    throw new Exception(e)
                
            
            if (null != parser) 
                pluginInfos << parser.pluginInfo
            
        
        //插件为0个
        if (pluginInfos.isEmpty()) 
            return null
        
        //构建插件们的json信息
        def jsonOutput = new JsonOutput()
        String pluginInfosJson = jsonOutput.toJson(pluginInfos)
        //格式化打印插件们的json信息
        return pluginInfosJson

从代码中看到,此处直接从assets里扫描并生成内置插件的json信息。这里解析插件apk文件用到了一个开源库net.dongliu:apk-parser:2.2.0https://github.com/hsiafan/apk-parser,有兴趣可以关注一下。

更新到Manifest文件中

直接上代码吧,没有任何难度,就是简单的替换,找到所有的AndroidManifest.xml把生产的坑位append上去。

 def updateManifest(def file, def newManifest) 
        // 除了目录和AndroidManifest.xml之外,还可能会包含manifest-merger-debug-report.txt等不相干的文件,过滤它
        if (file == null || !file.exists() || newManifest == null) return
        if (file.isDirectory()) 
            println "$AppConstant.TAG updateManifest: $file"
            file.listFiles().each 
                updateManifest(it, newManifest)
            
         else if (file.name.equalsIgnoreCase("AndroidManifest.xml")) 
            appendManifest(file, newManifest)
        
    

    def appendManifest(def file, def content) 
        if (file == null || !file.exists()) return
        println "$AppConstant.TAG appendManifest: $file"
        def updatedContent = file.getText("UTF-8").replaceAll("</application>", content + "</application>")
        file.write(updatedContent, 'UTF-8')
    

总结

看完源码后,Replugin宿主的gradle插件没有干什么高大上的活,都是一些配置生成的体力活。理论上来讲对编译性能影响也不大,耗时多得地方可能就在扫描内置插件的任务上了。但是Replugin生成了非常多得坑位,接入之后,最终的AndroidManifest.xml会多出200多个坑位,但这两百多个坑位是没有对应的类文件的,后续阅读宿主程序源码后再行分析。整体来看Replugin在编译器对宿主的入侵是很小的。

以上是关于Replugin 源码分析------replugin-host-gradle插件源码分析的主要内容,如果未能解决你的问题,请参考以下文章

Replugin 源码分析------replugin-host-gradle插件源码分析

Replugin 源码分析------replugin-host-gradle插件源码分析

唯一插件化Replugin源码及原理深度剖析--唯一Hook点原理

唯一插件化Replugin源码及原理深度剖析--唯一Hook点原理

唯一插件化Replugin源码及原理深度剖析--初始化之框架核心

唯一插件化Replugin源码及原理深度剖析--插件的安装加载原理