android插件化开发--修改携程插件支持aidl,dependencies

一.携程插件修改后的使用方式:

        1.设置local.properties文件下solidMode,当等于false时,可以在主工程添加依赖,运行常规开发模式;

   当等于true时作为插件运行,许用命令行执行打包操作
   2.设置apk_module_config.xml 修改要添加的插件包名和资源id(0x01系统资源id,0x7f默认资源id)
   3.将插件build.gradle文件设置和CallmePlugin项目下build.gradle一样
   4.打包命令:gradle assembleRelease repackAll
   5.安装Release APK in /build-outputs/***-release-final.apk
二.打包流程:



三.修改后的build.gradle

    1.主工程build.gradle

apply plugin:'com.android.application'

project.ext {
    build_Type=''
}
android {
    compileSdkVersion 25
    buildToolsVersion "25.0.0"

    signingConfigs {
        demo {
            keyAlias 'demo'
            keyPassword '123456'
            storePassword '123456'
            storeFile file('../demo.jks')
        }
    }

    defaultConfig {
        applicationId "ctrip.android.sample"
        versionCode 1
        versionName "1.0"
        minSdkVersion 14
        targetSdkVersion 23
    }

    dexOptions {
        javaMaxHeapSize "4g"
      //  preDexLibraries = false
    }

    buildTypes {
        debug {
            debuggable true
            minifyEnabled false
            signingConfig signingConfigs.demo
            build_Type='debug'
        }
        release {
            minifyEnabled false
            signingConfig signingConfigs.demo
        }
    }
}

dependencies {
    compile fileTree(include: ['*.jar'], dir: 'libs')
    compile project(':pluginbase')
    if (!solidMode) {
        compile project(':CallmePlugin')
    }
}



//打包后产出物复制到build-outputs目录。apk、manifest、mapping
task copyReleaseOutputs(type:Copy){
    from ("$buildDir/outputs/apk/app-release.apk") {
        rename 'app-release.apk', 'demo-base-release.apk'
    }
    from "$buildDir/intermediates/manifests/full/release/AndroidManifest.xml"
    from ("$buildDir/outputs/mapping/release/mapping.txt") {
        rename 'mapping.txt', 'demo-base-mapping.txt'
    }

    into new File(rootDir, 'build-outputs')
}

//assembleRelease<<{
//    copyReleaseOutputs.execute()
//}

clean {
    delete buildDir
    delete "${rootDir}/build-outputs/demo-base-release.apk"
    delete "${rootDir}/build-outputs/AndroidManifest.xml"
    delete "${rootDir}/build-outputs/demo-base-mapping.txt"
    delete "${rootDir}/build-outputs/demo-mapping-final.txt"
    delete "${rootDir}/build-outputs/demo-release-reloaded.apk"
    delete "${rootDir}/build-outputs/demo-release-resigned.apk"
    delete "${rootDir}/build-outputs/demo-release-repacked.apk"
    delete "${rootDir}/build-outputs/demo-release-final.apk"
}


import org.apache.tools.ant.taskdefs.condition.Os

def getZipAlignPath(){
    def zipAlignPath = "${android.sdkDirectory}/build-tools/${android.buildToolsVersion}/zipalign"
    if(Os.isFamily(Os.FAMILY_WINDOWS)){
        zipAlignPath += '.exe'
    }
    assert (new File(zipAlignPath)).exists() : '没有找到zipalign应用程序!'

    return zipAlignPath
}

import java.util.zip.ZipEntry
import java.util.zip.ZipFile
import java.util.zip.ZipOutputStream

// 打包过程中很多手工zip过程:
// 1,为了压缩resources.arsc文件而对标准产出包重新压缩
// 2,以及各子apk的纯手打apk包
// 但对于音频等文件,压缩会导致资源加载报异常
// 重新打包方法,使用STORED过滤掉不应该压缩的文件们
// 后缀名列表来自于android源码
def repackApk(originApk, targetApk){
    def noCompressExt = [".jpg", ".jpeg", ".png", ".gif",
                         ".wav", ".mp2", ".mp3", ".ogg", ".aac",
                         ".mpg", ".mpeg", ".mid", ".midi", ".smf", ".jet",
                         ".rtttl", ".imy", ".xmf", ".mp4", ".m4a",
                         ".m4v", ".3gp", ".3gpp", ".3g2", ".3gpp2",
                         ".amr", ".awb", ".wma", ".wmv"]

    ZipFile zipFile = new ZipFile(originApk)
    ZipOutputStream zos = new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(targetApk)))
    zipFile.entries().each{ entryIn ->
        if(entryIn.directory){
            println "${entryIn.name} is a directory"
        }
        else{
            def entryOut = new ZipEntry(entryIn.name)
            def dotPos = entryIn.name.lastIndexOf('.')
            def ext = (dotPos >= 0) ? entryIn.name.substring(dotPos) : ""
            def isRes = entryIn.name.startsWith('res/')
            if(isRes && ext in noCompressExt){
                entryOut.method = ZipEntry.STORED
                entryOut.size = entryIn.size
                entryOut.compressedSize = entryIn.size
                entryOut.crc = entryIn.crc
            }
            else{
                entryOut.method = ZipEntry.DEFLATED
            }
            zos.putNextEntry(entryOut)
            zos << zipFile.getInputStream(entryIn)
            zos.closeEntry()
        }
    }
    zos.finish()
    zos.close()
    zipFile.close()
}

// multidex默认会把manifest中注册的所有组件以及它们的直接引用类放在主dex里,
// 以保证至少在查找组件的时候涉及到的类加载正确。
// 但第一级+第二级已经会导致主dex超标。
// 所以在此hack修改CreateManifestKeepList类,让它不要顾忌activity、service、receiver
// 以保障主dex足够小不至于爆掉
def patchKeepSpecs() {
    def taskClass = "com.android.build.gradle.internal.tasks.multidex.CreateManifestKeepList";
    def clazz = this.class.classLoader.loadClass(taskClass)
    def keepSpecsField = clazz.getDeclaredField("KEEP_SPECS")
    keepSpecsField.setAccessible(true)
    def keepSpecsMap = (Map) keepSpecsField.get(null)
/*    if (keepSpecsMap.remove("activity") != null) {
        // println "KEEP_SPECS patched: removed 'activity' root"
    } else {
        // println "Failed to patch KEEP_SPECS: no 'activity' root found"
    }
    if (keepSpecsMap.remove("service") != null) {
        // println "KEEP_SPECS patched: removed 'service' root"
    } else {
        // println "Failed to patch KEEP_SPECS: no 'service' root found"
    }
    if (keepSpecsMap.remove("receiver") != null) {
        // println "KEEP_SPECS patched: removed 'receiver' root"
    } else {
        // println "Failed to patch KEEP_SPECS: no 'receiver' root found"
    }*/
}
patchKeepSpecs()
//解析完任务
// dex命令默认保障方法数索引不超过65535,
// 但在编译期pass掉第一关的dex,有可能在运行期卡在dexopt上,
// 所以指定最大index数50000,远小于65535,安全第一。
afterEvaluate {
    // println tasks.withType(com.android.build.gradle.tasks.Dex)

    tasks.matching {
        it.name.startsWith('dex')
    }.each { dx ->
        // println "found dex task $dx.name, add parameters"
        if (dx.additionalParameters == null) {
            dx.additionalParameters = []
        }
//        dx.additionalParameters += '--minimal-main-dex'
        dx.additionalParameters += '--set-max-idx-number=50000'

    }
    try{
        //打包完成执行copy操作
        tasks.getByName("assembleRelease"){
            it.doLast{
                copyReleaseOutputs.execute()
            }
        }
    }catch (Exception e){
        //打包完成执行copy操作
        tasks.getByName("assembleDebug"){
            it.doLast{
                copyReleaseOutputs.execute()
            }
        }
    }

}


//base apk的assets中填充各子apk
//输入:Ctrip-base-release.apk
//输出:Ctrip-release-reloaded.apk
task reload(type:Zip){
    inputs.file  "$rootDir/build-outputs/demo-base-release.apk"
    inputs.files fileTree(new File(rootDir,'build-outputs')).include('*.so')
    outputs.file "$rootDir/build-outputs/demo-release-reloaded.apk"

    into 'assets/baseres/',{
        from fileTree(new File(rootDir,'build-outputs')).include('*.so')
    }

    from zipTree("$rootDir/build-outputs/demo-base-release.apk"), {
        exclude('**/META-INF/*.SF')
        exclude('**/META-INF/*.RSA')
    }

    destinationDir file("$rootDir/build-outputs/")

    archiveName 'demo-release-reloaded.apk'
}

//对apk重新压缩,调整各文件压缩比到正确
//输入:Ctrip-release-reloaded.apk
//输出:Ctrip-release-repacked.apk
task repack (dependsOn: 'reload') {
    inputs.file "$rootDir/build-outputs/demo-release-reloaded.apk"
    outputs.file "$rootDir/build-outputs/demo-release-repacked.apk"

    doLast{
        println "release打包之后,重新压缩一遍,以压缩resources.arsc"

        def oldApkFile = file("$rootDir/build-outputs/demo-release-reloaded.apk")

        assert oldApkFile != null : "没有找到release包!"

        def newApkFile = new File(oldApkFile.parentFile, 'demo-release-repacked.apk')

        //重新打包
        repackApk(oldApkFile.absolutePath, newApkFile.absolutePath)

        assert newApkFile.exists() : "没有找到重新压缩的release包!"
    }
}

//对apk重签名
//输入:Ctrip-release-repacked.apk
//输出:Ctrip-release-resigned.apk
task resign(type:Exec,dependsOn: 'repack'){
    inputs.file "$rootDir/build-outputs/demo-release-repacked.apk"
    outputs.file "$rootDir/build-outputs/demo-release-resigned.apk"

    workingDir "$rootDir/build-outputs"
    executable "${System.env.'JAVA_HOME'}/bin/jarsigner"

    def argv = []
    argv << '-verbose'
    argv << '-sigalg'
    argv << 'SHA1withRSA'
    argv << '-digestalg'
    argv << 'SHA1'
    argv << '-keystore'
    argv << "$rootDir/demo.jks"
    argv << '-storepass'
    argv << '123456'
    argv << '-keypass'
    argv << '123456'
    argv << '-signedjar'
    argv << 'demo-release-resigned.apk'
    argv << 'demo-release-repacked.apk'
    argv << 'demo'

    args = argv
}


//重新对jar包做对齐操作
//输入:Ctrip-release-resigned.apk
//输出:Ctrip-release-final.apk
task realign (dependsOn: 'resign') {
    inputs.file "$rootDir/build-outputs/demo-release-resigned.apk"
    outputs.file "$rootDir/build-outputs/demo-release-final.apk"

    doLast{
        println '重新zipalign,还可以加大压缩率!'

        def oldApkFile = file("$rootDir/build-outputs/demo-release-resigned.apk")
        assert oldApkFile != null : "没有找到release包!"

        def newApkFile = new File(oldApkFile.parentFile,'demo-release-final.apk')

        def cmdZipAlign = getZipAlignPath()
        def argv = []
        argv << '-f'    //overwrite existing outfile.zip
        // argv << '-z'    //recompress using Zopfli
        argv << '-v'    //verbose output
        argv << '4'     //alignment in bytes, e.g. '4' provides 32-bit alignment
        argv << oldApkFile.absolutePath
        argv << newApkFile.absolutePath

        project.exec {
            commandLine cmdZipAlign
            args argv
        }

        assert newApkFile.exists() : "没有找到重新zipalign的release包!"
    }
}

/**
 * 用来连接文件的task
 */
class ConcatFiles extends DefaultTask {
    @InputFiles
    FileCollection sources

    @OutputFile
    File target

    @TaskAction
    void concat() {
        File tmp = File.createTempFile('concat', null, target.getParentFile())
        tmp.withWriter { writer ->
            sources.each { file ->
                file.withReader { reader ->
                    writer << reader
                }
            }
        }
        target.delete()
        tmp.renameTo(target)
    }
}

//合并base和所有模块的mapping文件
task concatMappings(type: ConcatFiles){
    sources = fileTree(new File(rootDir,'build-outputs')).include('*mapping.txt')
    target = new File(rootDir,'build-outputs/demo-mapping-final.txt')
}

task repackAll(dependsOn: ['realign','concatMappings'])

2.插件工程

if (solidMode) {
    apply plugin: 'com.android.application'
    project.ext {
        packageName = 'com.gstd.ssms.plugin'
        apk = packageName.replace('.', '_')
        apkName = apk+'_cl:1_v:1'
    }
    apply from: '../sub-project-build.gradle'
}else{
    apply plugin: 'com.android.library'
}


dependencies {
    compile fileTree(include: ['*.jar'], dir: 'libs')
    compile 'com.google.code.gson:gson:2.5'
    compile 'com.github.bumptech.glide:glide:3.7.0'
    compile 'com.android.support:support-v4:25.0.1'
}

android {
    compileSdkVersion 25
    buildToolsVersion '25.0.0'

    defaultConfig {
        minSdkVersion 14
        targetSdkVersion 23
    }
    lintOptions {
        abortOnError false
    }
}
3. sub-project-build.gradle

buildscript {
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath 'net.sf.proguard:proguard-gradle:5.2.1'
    }
}

import org.apache.tools.ant.taskdefs.condition.Os

project.ext {
    BUILD_TOOLS_VERSION = '23.0.3'
    TARGET_SDK_VERSION = 23

    sdk = [:]
    if (Os.isFamily(Os.FAMILY_WINDOWS)) {
        println "sub-project-build******************************************************" + sdk.aapt + sdkDir + "------"
        sdk.aapt = "${rootDir}/aapt_win.exe"
        sdk.dex = "$sdkDir/build-tools/$BUILD_TOOLS_VERSION/dx.bat"
        sdk.aidl = "$sdkDir\\build-tools\\$BUILD_TOOLS_VERSION\\aidl.exe"
    } else if (Os.isFamily(Os.FAMILY_MAC)) {
        sdk.aapt = "${rootDir}/aapt_mac"
        sdk.dex = "$sdkDir/build-tools/$BUILD_TOOLS_VERSION/dx"
    } else if (Os.isFamily(Os.FAMILY_UNIX)) {
        sdk.aapt = "${rootDir}/aapt_linux"
        sdk.dex = "$sdkDir/build-tools/$BUILD_TOOLS_VERSION/dx"
    }
    sdk.androidJar = "$sdkDir/platforms/android-$TARGET_SDK_VERSION/android.jar"
    if (TARGET_SDK_VERSION >= 23) {
        sdk.apacheJar = "$sdkDir/platforms/android-23/optional/org.apache.http.legacy.jar";
    }
}

//初始化,确保必要目录都存在
task init << {
    new File(rootDir, 'build-outputs').mkdirs()

    buildDir.mkdirs()

    new File(buildDir, 'gen/r').mkdirs()

    new File(buildDir, 'intermediates').mkdirs()

    new File(buildDir, 'intermediates/classes').mkdirs()
    new File(buildDir, 'intermediates/classes/release').mkdirs()
    new File(buildDir, 'intermediates/classes-obfuscated').mkdirs()

    new File(buildDir, 'intermediates/res').mkdirs()

    new File(buildDir, 'intermediates/dex').mkdirs()

    new File(buildDir, 'generated').mkdir()

    new File(buildDir, 'generated/source').mkdir()

    new File(buildDir, 'generated/source/aidl').mkdir()

    new File(buildDir, 'generated/source/aidl/release').mkdir()
}

//清除build产出物
task clean(type: Delete)<<{
    delete buildDir
    delete "${rootDir}/build-outputs/${apkName}-mapping.txt"
    delete "${rootDir}/build-outputs/${apkName}.so"
}

task aaptReleasePlugin(type: Exec, dependsOn: 'init') {

    inputs.file "$sdk.androidJar"
    //  inputs.file "${rootDir}/build-outputs/demo-base-release.apk"
    inputs.file "$projectDir/src/main/AndroidManifest.xml"
    inputs.dir "$projectDir/src/main/res"
    inputs.dir "$projectDir/src/main/assets"
    //  inputs.file "${rootDir}/app/build/generated/source/r/release/com/gstd/app/R.java"

    outputs.dir "$buildDir/gen/r"
    outputs.file "$buildDir/intermediates/res/resources.zip"
    outputs.file "$buildDir/intermediates/res/aapt-rules.txt"

    workingDir buildDir
    executable sdk.aapt

    def resourceId = ''
    def parseApkXml = (new XmlParser()).parse(new File(rootDir, 'apk_module_config.xml'))
    parseApkXml.Module.each { module ->
        if (module.@packageName == "${packageName}") {
            resourceId = module.@resourceId
            println "find packageName: " + module.@packageName + " ,resourceId:" + resourceId
        } else {
            println "----------------not font---------------"
        }
    }
    def argv = []
    argv << 'package'   //打包
    argv << "-v"
    argv << '-f' //强制覆盖已有文件
    argv << "-I"
    argv << "$sdk.androidJar"        //添加一个已有的固化jar包
    //argv << '-I'
    //argv << "${rootDir}/build-outputs/demo-base-release.apk"
    argv << '-M'
    argv << "$projectDir/src/main/AndroidManifest.xml"    //指定manifest文件
    argv << '-S'
    argv << "$projectDir/src/main/res"                    //res目录
    argv << '-A'
    argv << "$projectDir/src/main/assets"                 //assets目录
    argv << '-m'        //make package directories under location specified by -J
    argv << '-J'
    argv << "$buildDir/generated/source/r/release/"         //哪里输出R.java定义
    argv << '-F'
    argv << "$buildDir/intermediates/res/resources-release.ap_"   //指定apk的输出位置
    argv << '-G'        //-G  A file to output proguard options into.
    argv << "$buildDir/intermediates/res/aapt-rules.txt"
    // argv << '--debug-mode'      //manifest的application元素添加android:debuggable="true"
    argv << '--custom-package'      //指定R.java生成的package包名
    argv << "${packageName}"
    argv << '-0'    //指定哪些后缀名不会被压缩
    argv << 'apk'
    //argv << '--public-R-path'
    //argv << "${rootDir}/app/build/generated/source/r/release/com/gstd/app/R.java"
    argv << '--apk-module'
    argv << "$resourceId"

    args = argv

}


task copyReleaseOutputsApk(type:Copy){
    from ("$buildDir/outputs/apk/${project.name}-release-unsigned.apk") {
        rename "${project.name}-release-unsigned.apk", "${apkName}.so"
    }
    into new File(rootDir, 'build-outputs')
}

afterEvaluate {
    tasks.getByName('compileReleaseJavaWithJavac') {
        it.doFirst {
            println 'aaptReleasePlugin -------------------'
            aaptReleasePlugin.execute()
        }
    }
    tasks.getByName("assembleRelease") {
        it.doFirst {
            copyReleaseOutputsApk.execute();
        }
    }
}
四.使用方式:

1.设置local.properties文件下solidMode,当等于false时,可以在主工程添加依赖,运行常规开发模式;
  当等于true时作为插件运行,许用命令行执行打包操作
2.设置apk_module_config.xml 修改要添加的插件包名和资源id(0x01系统资源id,0x7f默认资源id)
3.将插件build.gradle文件设置和CallmePlugin项目下build.gradle一样
4.打包命令:gradle assembleRelease repackAll
5.安装Release APK in /build-outputs/***-release-final.apk





















  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值