Replugin 소스 해석의 replugin-host-gradle

18820 단어
개술
Replugin에 대한 소개 및 간단한 사용은 공식 문서를 참조할 수 있으며, 여기서 더 이상 설명하지 않습니다.본고는 이전에 사용자 정의gradle 플러그인에 대해 어느 정도의 기초를 가지고 이해하기 쉽도록Gradle 학습-----Gradle 사용자 정의 플러그인을 참조해야 한다.NOTE: 글에서 두 가지 플러그인을 언급할 것입니다. 본문을 읽을 때 플러그인의 상하문 상황을 언급하여 개념을 혼동하지 않도록 주의하십시오.
1:replugin  : replugin          ,     android            , android     。
2:gradle  (         ): gradle         , gradle     。

카탈로그 개요
\qihoo\RePlugin\replugin-host-gradle\src            
│         
└─main        
    ├─groovy       
    │  └─com            
    │      └─qihoo360     
    │          └─replugin      
    │              └─gradle      
    │                  └─host     
    │                      │  AppConstant.groovy                     #        
    │                      │  RePlugin.groovy    <====== (  )  #                 
    │                      │  
    │                      ├─creator
    │                      │  │  FileCreators.groovy                #      
    │                      │  │  IFileCreator.groovy                #        
    │                      │  │  
    │                      │  └─impl
    │                      │      ├─java
    │                      │      │      RePluginHostConfigCreator.groovy        # RePluginHostConfig.java    
    │                      │      │      
    │                      │      └─json
    │                      │              PluginBuiltinJsonCreator.groovy        # plugins-builtin.json    
    │                      │              PluginInfo.groovy                      #       
    │                      │              PluginInfoParser.groovy                #   manifest   xml     PluginInfo  
    │                      │              
    │                      └─handlemanifest
    │                              ComponentsGenerator.groovy        #                
    │                              
    └─resources
        └─META-INF
            └─gradle-plugins
                    replugin-host-gradle.properties                  #    gradle      

포털 클래스 링크
분해하다
1: AndroidManifest 사전 생성.xml의 구성 요소 구덩이 위치
@Override
    public void apply(Project project) {

        println "${TAG} Welcome to replugin world ! "

        this.project = project

        /* Extensions */
        project.extensions.create(AppConstant.USER_CONFIG, RepluginConfig) //  gradle    repluginHostConfig  

        if (project.plugins.hasPlugin(AppPlugin)) {

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

                addShowPluginTask(variant)

                if (config == null) {
                    config = project.extensions.getByName(AppConstant.USER_CONFIG)
                    checkUserConfig(config)
                }

                def appID = variant.generateBuildConfig.appPackageName
                println "${TAG} appID: ${appID}"
                def newManifest = ComponentsGenerator.generateComponent(appID, config)

                ...
            }
        }
    }

  • 먼저 RepluginConfig 클래스의 상수 구성 정보를 AppConstant에 지정합니다.USER_CONFIG(RepluginHostConfig) 구성은 다음checkUserConfig(config)에 사용됩니다.만약 우리가 설정하지 않는다면 기본 설정은 다음과 같다
  • class RepluginConfig {
    
        /**         (  UI   Persistent   ) */
        def countProcess = 3
    
        /**         ? */
        def persistentEnable = true
    
        /**       (        Persistent   ,       )*/
        def persistentName = ':GuardService'
    
        /**            */
        def countNotTranslucentStandard = 6
        def countNotTranslucentSingleTop = 2
        def countNotTranslucentSingleTask = 3
        def countNotTranslucentSingleInstance = 2
    
        /**           */
        def countTranslucentStandard = 2
        def countTranslucentSingleTop = 2
        def countTranslucentSingleTask = 2
        def countTranslucentSingleInstance = 3
    
        /**        TaskAffinity     */
        def countTask = 2
    
        /**
         *      AppCompat  
         * com.android.support:appcompat-v7:25.2.0
         */
        def useAppCompat = false
    
        /** HOST           */
        def compatibleVersion = 10
    
        /** HOST      */
        def currentVersion = 12
    
        /** plugins-builtin.json       ,    "plugins-builtin.json" */
        def builtInJsonFileName = "plugins-builtin.json"
    
        /**        plugins-builtin.json   ,       */
        def autoManageBuiltInJsonFile = true
    
        /** assert               ,    assert   "plugins" */
        def pluginDir = "plugins"
    
        /**           ,   ".jar"      jar   */
        def pluginFilePostfix = ".jar"
    
        /**                  jar (         jar)       ,    true */
        def enablePluginFileIllegalStopBuild = true
    }
    
  • 프로젝트에 AppPlugin 형식 플러그인이 있는지 판단합니다. 즉, 저희가 숙주 프로젝트에서 이 종류의 플러그인을 적용했는지, 즉build에 있는지 판단합니다.gradle에 apply plugin:'com.android.application'.그래서 우리가 apply plugin: 'replugin-host-gradle'를 사용할 때는 반드시 apply plugin: 'com.android.application' 이후에 사용해야 한다.libraryPlugin이 있는지 여부를 판단하려면 다음과 같이 쓰십시오: if (project.plugins.hasPlugin(LibraryPlugin))
  • 프로젝트의 AppExtension 형식인 extension, 즉com을 가져옵니다.android.응용 프로그램 프로젝트의android extension.바로 당신의 앱 모듈의build입니다.gradle에서 정의한 클립: android { ...}android extension의 Application variants 목록을 옮겨다니다.이것은 Hook Android gradle 플러그인의 한 방식이라고 할 수 있다. 응용 프로그램Variants를 두루 훑어보면 속성, 이름, 설명, 출력 파일 이름 등을 수정할 수 있기 때문이다. Android library 라이브러리라면 응용 프로그램Variants를libraryVariants로 대체할 수 있다.앱에 있는build입니다.gradle에서 클립을 이렇게 정의했습니다:
  • buildTypes {
         release {
             applicationVariants.all { variant ->
                 variant.outputs.each { output ->
                     def outputFile = output.outputFile
                     def fileName = "xxx_${variant.productFlavors[0].name}_v${defaultConfig.versionName}_${releaseTime()}.apk"
                     output.outputFile = new File(outputFile.parent, fileName)
                 }
             }
         }
     }
    

    사실 이것도 플러그인의 생성 방식이다. Hook Android gradle 플러그인은variants 속성 값을 동적으로 수정하고 패키지 출력의 apk 파일 이름을 수정한다.사용자 정의gradle 플러그인을 만듭니다.Gradle은 다양한 방식을 제공합니다 (Gradle 학습 -----Gradle 사용자 정의 플러그인 참조): 1:build.gradle 스크립트에서 직접 만들기 (상기 코드는 다음과 같습니다) 2: 독립된 모듈에서 만들기 (replugin-host-gradle 즉)
  • 호출addShowPluginTask(variant) 방법은 다음과 같다
  • //    【        】   
        def addShowPluginTask(def variant) {
            def variantData = variant.variantData
            def scope = variantData.scope
            def showPluginsTaskName = scope.getTaskName(AppConstant.TASK_SHOW_PLUGIN, "")
            def showPluginsTask = project.task(showPluginsTaskName)
    
            showPluginsTask.doLast {
                IFileCreator creator = new PluginBuiltinJsonCreator(project, variant, config)
                def dir = creator.getFileDir()
    
                if (!dir.exists()) {
                    println "${AppConstant.TAG} The ${dir.absolutePath} does not exist "
                    println "${AppConstant.TAG} pluginsInfo=null"
                    return
                }
    
                String fileContent = creator.getFileContent()
                if (null == fileContent) {
                    return
                }
    
                new File(dir, creator.getFileName()).write(fileContent, 'UTF-8')
            }
            showPluginsTask.group = AppConstant.TASKS_GROUP
    
            //get mergeAssetsTask name
            String mergeAssetsTaskName = variant.getVariantData().getScope().getMergeAssetsTask().name
            //get real gradle task
            def mergeAssetsTask = project.tasks.getByName(mergeAssetsTaskName)
    
            //depend on mergeAssetsTask so that assets have been merged
            if (mergeAssetsTask) {
                showPluginsTask.dependsOn mergeAssetsTask
            }
    
        }
    

    그러나 방법에서 지정한task는androidgradle task에 연결되지 않았습니다. 즉,task가 실행하지 않습니다.이task는 디버깅할 때 플러그인 정보를 보기 편합니다.
  • 다음 호출checkUserConfig, 원본 코드는 다음과 같다:
  •  /**
         *        
         */
        def checkUserConfig(config) {
    /*
            def persistentName = config.persistentName
            if (persistentName == null || persistentName.trim().equals("")) {
                project.logger.log(LogLevel.ERROR, "
    ---------------------------------------------------------------------------------") project.logger.log(LogLevel.ERROR, " ERROR: persistentName can'te be empty, please set persistentName in replugin. ") project.logger.log(LogLevel.ERROR, "---------------------------------------------------------------------------------
    ") System.exit(0) return } */ doCheckConfig("countProcess", config.countProcess) doCheckConfig("countTranslucentStandard", config.countTranslucentStandard) doCheckConfig("countTranslucentSingleTop", config.countTranslucentSingleTop) doCheckConfig("countTranslucentSingleTask", config.countTranslucentSingleTask) doCheckConfig("countTranslucentSingleInstance", config.countTranslucentSingleInstance) doCheckConfig("countNotTranslucentStandard", config.countNotTranslucentStandard) doCheckConfig("countNotTranslucentSingleTop", config.countNotTranslucentSingleTop) doCheckConfig("countNotTranslucentSingleTask", config.countNotTranslucentSingleTask) doCheckConfig("countNotTranslucentSingleInstance", config.countNotTranslucentSingleInstance) doCheckConfig("countTask", config.countTask) println '--------------------------------------------------------------------------' // println "${TAG} appID=${appID}" println "${TAG} useAppCompat=${config.useAppCompat}" // println "${TAG} persistentName=${config.persistentName}" println "${TAG} countProcess=${config.countProcess}" println "${TAG} countTranslucentStandard=${config.countTranslucentStandard}" println "${TAG} countTranslucentSingleTop=${config.countTranslucentSingleTop}" println "${TAG} countTranslucentSingleTask=${config.countTranslucentSingleTask}" println "${TAG} countTranslucentSingleInstance=${config.countTranslucentSingleInstance}" println "${TAG} countNotTranslucentStandard=${config.countNotTranslucentStandard}" println "${TAG} countNotTranslucentSingleTop=${config.countNotTranslucentSingleTop}" println "${TAG} countNotTranslucentSingleTask=${config.countNotTranslucentSingleTask}" println "${TAG} countNotTranslucentSingleInstance=${config.countNotTranslucentSingleInstance}" println "${TAG} countTask=${config.countTask}" println '--------------------------------------------------------------------------' }

    처음에 불러온 repluginHostConfig 설정 정보를 읽고 데이터 형식의 정확성을 검사합니다.
  • 관건 코드, 다음 줄 코드는 숙주 중 안드로이드 매니페스트를 해결했다.xml의 구성 요소 갱도 생성, 구조 개관 중의gradle Flow와 결합하여 보면 여기는 구성 요소 갱도 생성의 xml 코드일 뿐이고 최종 xml 파일은 후속task에서 조립된 것으로 잠시 후에 설명할 수 있습니다.
  • def newManifest = ComponentsGenerator.generateComponent(appID, config)
    

    이 메서드의 소스는 다음과 같습니다.
    def static generateComponent(def applicationID, def config) {
             //      AppCompat  (       )
             if (config.useAppCompat) {
                 themeNTS = THEME_NTS_NOT_APP_COMPAT
             } else {
                 themeNTS = THEME_NTS_NOT_USE_APP_COMPAT
             }
    
             def writer = new StringWriter()
             def xml = new MarkupBuilder(writer)
    
             /* UI    */
             xml.application {
                 /*     */
                 config.countTranslucentStandard.times {
                     activity(
                             "${name}": "${applicationID}.${infix}N1NRTS${it}",
                             "${cfg}": "${cfgV}",
                             "${exp}": "${expV}",
                             "${ori}": "${oriV}",
                             "${theme}": "${themeTS}")
                     ...
                 }
    
                 ...
    
                 /*      */
                 config.countNotTranslucentStandard.times{
    
                 }
                 ...
    
             }
             //    application   
             def normalStr = writer.toString().replace("", "").replace("", "")
    
     //        println "${TAG} normalStr: ${normalStr}"
    
             //              
             normalStr + generateMultiProcessComponent(applicationID, config)
         }
    

    Groovy의 Markup Builder api를 기반으로 Replugin Config 클래스의 설정에 따라 구성 요소 슬롯의 xml 문자열을 조합하는 것이 주요 역할입니다.나무를 쌓는 것처럼 그 중 한 조를 연구로 삼았다.
    config.countTranslucentStandard.times {
                 activity(
                         "${name}": "${applicationID}.${infix}N1NRTS${it}",
                         "${cfg}": "${cfgV}",
                         "${exp}": "${expV}",
                         "${ori}": "${oriV}",
                         "${theme}": "${themeTS}")
             }
    

    NOTE: config.countTranslucentStandard.times의 의미: config.countTranslucentStandard의 값에 따라 순환하여 생성된 갱위:
    
    

    :바꾸기
    RePluginHostConfig 구성 파일 생성
    @Override
        public void apply(Project project) {
    
           ...
    
            if (project.plugins.hasPlugin(AppPlugin)) {
    
                def android = project.extensions.getByType(AppExtension)
                android.applicationVariants.all { variant ->
    
                    ...
    
                    def variantData = variant.variantData
                    def scope = variantData.scope
    
                    //host generate task
                    def generateHostConfigTaskName = scope.getTaskName(AppConstant.TASK_GENERATE, "HostConfig")
                    def generateHostConfigTask = project.task(generateHostConfigTaskName)
    
                    generateHostConfigTask.doLast {
                        FileCreators.createHostConfig(project, variant, config)
                    }
                    generateHostConfigTask.group = AppConstant.TASKS_GROUP
    
                    //depends on build config task
                    String generateBuildConfigTaskName = variant.getVariantData().getScope().getGenerateBuildConfigTask().name
                    def generateBuildConfigTask = project.tasks.getByName(generateBuildConfigTaskName)
                    if (generateBuildConfigTask) {
                        generateHostConfigTask.dependsOn generateBuildConfigTask
                        generateBuildConfigTask.finalizedBy generateHostConfigTask
                    }
    
                    ...
    
                }
            }
        }
    
    

    계속해서 apply 방법으로 돌아가서 RePluginHostConfig를 생성할 때가 되었습니다. 즉, 주석에 있는host generate task
  • HostConfig의gradle task 이름을 먼저 생성하고 프로젝트의 task () 방법을 사용하여 이 Task를 만듭니다.
  • generateHostConfigTask에 대한task 작업을 지정합니다. RePluginHostConfig를 자동으로 생성합니다.java에서 BuildConfig 디렉토리로 이동합니다.
  •  generateHostConfigTask.doLast {
         FileCreators.createHostConfig(project, variant, config)
     }
    

    주: createHostConfig(...)도 설정 클래스RepluginConfig의 설정 정보를 조합하여 만든 자바 파일입니다.
    //depends on build config task
    if (generateBuildConfigTask) {
     generateHostConfigTask.dependsOn generateBuildConfigTask
     generateBuildConfigTask.finalizedBy generateHostConfigTask
    }
    

    이 task에서 생성된 RePluginHostConfig 때문에java는 컴파일 출력 디렉터리 ..\replugin-sample\host\app\build\generated\source\buildConfig\{productFlavors}\{buildTypes}\... 에 저장하기를 원하기 때문에 이task는BuildConfig 생성에 의존합니다.java의task를 BuildConfigTask로 설정하고 HostConfigTask를 실행합니다.gradle의task 관련 지식은gradle 홈페이지나 모 검색엔진에서 학습할 수 있으며 사전형 지식점에 속하므로 필요할 때 찾아보세요.
    plugins-builtin을 생성합니다.json 플러그인 정보 파일
    @Override
        public void apply(Project project) {
    
           ...
    
            if (project.plugins.hasPlugin(AppPlugin)) {
    
                def android = project.extensions.getByType(AppExtension)
                android.applicationVariants.all { variant ->
    
                    ...
    
                    //json generate task
                    def generateBuiltinJsonTaskName = scope.getTaskName(AppConstant.TASK_GENERATE, "BuiltinJson")
                    def generateBuiltinJsonTask = project.task(generateBuiltinJsonTaskName)
    
                    generateBuiltinJsonTask.doLast {
                        FileCreators.createBuiltinJson(project, variant, config)
                    }
                    generateBuiltinJsonTask.group = AppConstant.TASKS_GROUP
    
                    //depends on mergeAssets Task
                    String mergeAssetsTaskName = variant.getVariantData().getScope().getMergeAssetsTask().name
                    def mergeAssetsTask = project.tasks.getByName(mergeAssetsTaskName)
                    if (mergeAssetsTask) {
                        generateBuiltinJsonTask.dependsOn mergeAssetsTask
                        mergeAssetsTask.finalizedBy generateBuiltinJsonTask
                    }
    
                    ...
    
                }
            }
        }
    
    

    계속해서 apply 방법으로 돌아가서plugins-builtin을 생성해야 합니다.json 이것은 플러그인 정보를 포함하는 파일입니다. 즉, 주석에 있는 json generate task 입니다.
  • 먼저 gradle task 이름을 생성하고 프로젝트의 task () 방법을 사용하여 이 Task를 만듭니다.
  • generateBuiltinJsonTask의task 작업을 지정했습니다. 숙주\assets\plugins 디렉터리에 있는 플러그인 파일을 스캔하고 apk 파일 규칙을 바탕으로 플러그인 정보, 패키지 이름, 버전 번호 등을 분석하여 json 파일로 조립합니다.
  • generateBuiltinJsonTask.doLast {
     FileCreators.createBuiltinJson(project, variant, config)
    }
    

    generateBuiltinJsonTask의 실행 의존도 설정
    //depends on build config task
    if (mergeAssetsTask) {
     generateBuiltinJsonTask.dependsOn mergeAssetsTask
     mergeAssetsTask.finalizedBy generateBuiltinJsonTask
    }
    
    

    이task에서 만든plugins-builtin 때문에.json은 컴파일 출력 디렉터리...\replugin-sample\host\app\build\intermediates\assets\{productFlavors}\{buildTypes}\...에 저장하기를 원하기 때문에 이task는mergeassets 파일의task에 의존하고 mergeAssetsTask로 설정한 후 실행BuiltinJsonTask합니다.
    안드로이드 매니페스트를 조립하다.xml
    output.processManifest.doLast {
        def manifestPath = output.processManifest.outputFile.absolutePath
        def updatedContent = new File(manifestPath).getText("UTF-8").replaceAll("", newManifest + "")
        new File(manifestPath).write(updatedContent, 'UTF-8')
    }
    
    
  • 갱위 xml 문자열을 기존 xml 탭의 설정 정보와 하나로 합친다.이로써 replugin-host-gradle 플러그인의 작업은 모두 끝났다.
  • 좋은 웹페이지 즐겨찾기