现状

网上关于Android studio打包jar的教程很多,基本思路如下

  1. 项目build.gradle中增加一个Jar任务,
  2. 指定打包路径。如下:
task buildJar(dependsOn: ['assembleDebug'], type: Jar) {    ....    def srcClassDir = [project.buildDir.absolutePath + "/intermediates/classes/debug"];    from srcClassDir    include "**/*.class"    ....}

这样做个人觉得有几个问题:

  1. 只能给当前项目应用module打包/intermediates/classes/debug

    对于依赖的aar,如support v7,编译输出class是在/intermediates/exploded-aar/
    对于依赖的jar包,目测在intermediates中根本找不到

  2. 不能混淆,当然你也可以在build.gradle写一个ProGuardTask,具体可参见这篇文章,这里直接复制其最终生成build.gradle如下:

   import com.android.build.gradle.AppPlugin   import com.android.build.gradle.LibraryPlugin   import proguard.gradle.ProGuardTask   apply plugin: 'com.android.application'   android {       compileSdkVersion 23       buildToolsVersion "23.0.2"       defaultConfig {           applicationId "org.chaos.demo.jar"           minSdkVersion 19           targetSdkVersion 22           versionCode 1           versionName "1.0"       }       buildTypes {           release {               minifyEnabled true               proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'           }       }   }   dependencies {       compile fileTree(dir: 'libs', include: ['*.jar'])   }   //dependsOn 可根据实际需要增加或更改   task buildJar(dependsOn: ['compileReleaseJavaWithJavac'], type: Jar) {       appendix = "demo"       baseName = "androidJar"       version = "1.0.0"       classifier = "release"       //后缀名       extension = "jar"       //最终的 Jar 包名,如果没设置,默认为 [baseName]-[appendix]-[version]-[classifier].[extension]       archiveName = "AndroidJarDemo.jar"       //需打包的资源所在的路径集       def srcClassDir = [project.buildDir.absolutePath + "/intermediates/classes/release"];       //初始化资源路径集       from srcClassDir       //去除路径集下部分的资源   //    exclude "org/chaos/demo/jar/MainActivity.class"   //    exclude "org/chaos/demo/jar/MainActivity\$*.class"   //    exclude "org/chaos/demo/jar/BuildConfig.class"   //    exclude "org/chaos/demo/jar/BuildConfig\$*.class"   //    exclude "**/R.class"   //    exclude "**/R\$*.class"       //只导入资源路径集下的部分资源       include "org/chaos/demo/jar/**/*.class"       //注: exclude include 支持可变长参数   }   task proguardJar(dependsOn: ['buildJar'], type: ProGuardTask) {       //Android 默认的 proguard 文件       configuration android.getDefaultProguardFile('proguard-android.txt')       //manifest 注册的组件对应的 proguard 文件       configuration project.buildDir.absolutePath + "/intermediates/proguard-rules/release/aapt_rules.txt"       configuration 'proguard-rules.pro'       String inJar = buildJar.archivePath.getAbsolutePath()       //输入 jar       injars inJar       //输出 jar       outjars inJar.substring(0, inJar.lastIndexOf('/')) + "/proguard-${buildJar.archiveName}"       //设置不删除未引用的资源(类,方法等)       dontshrink       Plugin plugin = getPlugins().hasPlugin(AppPlugin) ?               getPlugins().findPlugin(AppPlugin) :               getPlugins().findPlugin(LibraryPlugin)       if (plugin != null) {           List runtimeJarList           if (plugin.getMetaClass().getMetaMethod("getRuntimeJarList")) {               runtimeJarList = plugin.getRuntimeJarList()           } else if (android.getMetaClass().getMetaMethod("getBootClasspath")) {               runtimeJarList = android.getBootClasspath()           } else {               runtimeJarList = plugin.getBootClasspath()           }           for (String runtimeJar : runtimeJarList) {               //给 proguard 添加 runtime               libraryjars(runtimeJar)           }       }   }

看起来真不太舒服不是?(无意冒犯)

  1. 对于一个强迫症的程序员,除了代码要整洁之外,编译脚本文件build.gradle不整洁也不能忍
apply plugin: 'com.android.application'apply plugin: 'jar-gradle-plugin'android {    compileSdkVersion 24    buildToolsVersion "24.0.0"    defaultConfig {        applicationId "com.adison.testjarplugin"        minSdkVersion 15        targetSdkVersion 24        versionCode 1        versionName "1.0"    }    buildTypes {        release {            minifyEnabled true            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'        }    }}dependencies {    compile fileTree(dir: 'libs', include: ['*.jar'])    testCompile 'junit:junit:4.12'    compile 'com.android.support:appcompat-v7:24.0.0'    compile 'com.android.support:design:24.0.0'}BuildJar{    //输出目录    outputFileDir= project.buildDir.path+"/jar"    //输出原始jar包名    outputFileName="test.jar"    //输出混淆jar包名    outputProguardFileName="test_proguard.jar"    //混淆配置    proguardConfigFile="proguard-rules.pro"    //是否需要默认的混淆配置proguard-android.txt    needDefaultProguard=true}

这样感觉是不是好些了哈

实践

关于第一个问题,我们可以利用Android Transform Task解决,其官方说明如下:

Starting with 1.5.0-beta1, the Gradle plugin includes a Transform API allowing 3rd party plugins to manipulate compiled class files before they are converted to dex files.(The API existed in 1.4.0-beta2 but it’s been completely revamped in 1.5.0-beta1)

可见Transform Task的输入文件肯定包含apk所有依赖class及其本身class,我们只要取得其输入文件就行了

关于第三个问题,我们写一个Gradle插件,把业务逻辑都交给插件处理就好了,关于Gradle及自定义Gradle插件可以参考Gradle深入与实战系列文章,在此不展开说明。废话不多说,直接上插件代码:

class BuildJarPlugin implements Plugin<Project> {    public static final String EXTENSION_NAME = "BuildJar";    @Override    public void apply(Project project) {        DefaultDomainObjectSet variants        if (project.getPlugins().hasPlugin(AppPlugin)) {            variants = project.android.applicationVariants;            project.extensions.create(EXTENSION_NAME, BuildJarExtension);              applyTask(project, variants);        }    }    private void applyTask(Project project, variants) {        project.afterEvaluate {            BuildJarExtension jarExtension = BuildJarExtension.getConfig(project);            def includePackage = jarExtension.includePackage            def excludeClass = jarExtension.excludeClass            def excludePackage = jarExtension.excludePackage            def excludeJar = jarExtension.excludeJar            variants.all { variant ->                if (variant.name.capitalize() == "Debug") {                    def dexTask = project.tasks.findByName(BuildJarUtils.getDexTaskName(project, variant))                    if (dexTask != null) {                        def buildJarBeforeDex = "buildJarBeforeDex${variant.name.capitalize()}"                        def buildJar = project.tasks.create("buildJar", Jar)                        buildJar.setDescription("构建jar包")                        Closure buildJarClosure = {                            //过滤R文件和BuildConfig文件                            buildJar.exclude("**/BuildConfig.class")                            buildJar.exclude("**/BuildConfig\$*.class")                            buildJar.exclude("**/R.class")                            buildJar.exclude("**/R\$*.class")                            buildJar.archiveName = jarExtension.outputFileName                            buildJar.destinationDir = project.file(jarExtension.outputFileDir)                            if (excludeClass != null && excludeClass.size() > 0) {                                excludeClass.each {                                    //排除指定class                                    buildJar.exclude(it)                                }                            }                            if (excludePackage != null && excludePackage.size() > 0) {                                excludePackage.each {                                    //过滤指定包名下class                                    buildJar.exclude("${it}/**/*.class")                                }                            }                            if (includePackage != null && includePackage.size() > 0) {                                includePackage.each {                                    //仅仅打包指定包名下class                                    buildJar.include("${it}/**/*.class")                                }                            } else {                                //默认全项目构建jar                                buildJar.include("**/*.class")                            }                        }                        project.task(buildJarBeforeDex) << {                            Set inputFiles = BuildJarUtils.getDexTaskInputFiles(project, variant, dexTask)                            inputFiles.each { inputFile ->                                def path = inputFile.absolutePath                                if (path.endsWith(SdkConstants.DOT_JAR) && !BuildJarUtils.isExcludedJar(path, excludeJar)) {                                    buildJar.from(project.zipTree(path))                                } else if (inputFile.isDirectory()) {                                    //intermediates/classes/debug                                    buildJar.from(inputFile)                                }                            }                        }                        def buildProguardJar = project.tasks.create("buildProguardJar", ProGuardTask);                        buildProguardJar.setDescription("混淆jar包")                        buildProguardJar.dependsOn buildJar                        //设置不删除未引用的资源(类,方法等)                        buildProguardJar.dontshrink();                        //忽略警告                        buildProguardJar.ignorewarnings()                        //需要被混淆的jar包                        buildProguardJar.injars(jarExtension.outputFileDir + "/" + jarExtension.outputFileName)                        //混淆后输出的jar包                        buildProguardJar.outjars(jarExtension.outputFileDir + "/" + jarExtension.outputProguardFileName)                        //libraryjars表示引用到的jar包不被混淆                        // ANDROID PLATFORM                        buildProguardJar.libraryjars(project.android.getSdkDirectory().toString() + "/platforms/" + "${project.android.compileSdkVersion}" + "/android.jar")                        // JAVA HOME                        def javaBase = System.properties["java.home"]                        def javaRt = "/lib/rt.jar"                        if (System.properties["os.name"].toString().toLowerCase().contains("mac")) {                            if (!new File(javaBase + javaRt).exists()) {                                javaRt = "/../Classes/classes.jar"                            }                        }                        buildProguardJar.libraryjars(javaBase + "/" + javaRt)                        //混淆配置文件                        buildProguardJar.configuration(jarExtension.proguardConfigFile)                        if (jarExtension.needDefaultProguard) {                            buildProguardJar.configuration(project.android.getDefaultProguardFile('proguard-android.txt'))                        }                        //applymapping                        def applyMappingFile=jarExtension.applyMappingFile                        if(applyMappingFile!=null){                            buildProguardJar.applymapping(applyMappingFile)                        }                        //输出mapping文件                        buildProguardJar.printmapping(jarExtension.outputFileDir + "/" + "mapping.txt")                        def buildJarBeforeDexTask = project.tasks[buildJarBeforeDex]                        buildJarBeforeDexTask.dependsOn dexTask.taskDependencies.getDependencies(dexTask)                        buildJar.dependsOn buildJarBeforeDexTask                        buildJar.doFirst(buildJarClosure)                    }                }            }        }    }}

插件使用

既然标题说了这是一个通用的打包jar插件,那么一些基本特性,如过滤包名指定包名等是必须要支持的,目前该插件支持特性如下:

  1. 按需打包jar:
    • 全项目打包jar
    • 指定输出Jar包的包名路径列表
    • 过滤指定包名路径列表
    • 过滤指定class
    • 过滤指定jar
  2. 支持混淆打包jar
  3. 支持applymapping

具体使用说明

  1. 引入依赖

    dependencies {       classpath 'com.android.tools.build:gradle:2.1.3'       classpath 'com.adison.gradleplugin:jar:1.0.1'   }

  2. 应用插件

    apply plugin: 'jar-gradle-plugin'BuildJar{   //输出目录   outputFileDir= project.buildDir.path+"/jar"   //输出原始jar包名   outputFileName="test.jar"   //输出混淆jar包名   outputProguardFileName="test_proguard.jar"   //混淆配置   proguardConfigFile="proguard-rules.pro"   //是否需要默认的混淆配置proguard-android.txt   needDefaultProguard=true   applyMappingFile="originMapping/mapping.txt"   //需要输出jar的包名列表,当此参数为空时,则默认全项目输出,支持多包,如 includePackage=['com/adison/testjarplugin/include','com/adison/testjarplugin/include1'...]   includePackage=['com/adison/testjarplugin/include']   //不需要输出jar的jar包列表,如['baidu.jar','baidu1.jar'...]   excludeJar=[]   //不需要输出jar的类名列表,如['baidu.calss','baidu1.class'...]   excludeClass=['com/adison/testjarplugin/TestExcude.class']   //不需要输出jar的包名列表,如 excludePackage=['com/adison/testjarplugin/exclude','com/adison/testjarplugin/exclude1'...]   excludePackage=['com/adison/testjarplugin/exclude']}
  3. 使用
    • 打包普通jar
./gradlew buildJar
  • 打包混淆jar
./gradlew buildProguardJar

使用可参见使用demo

插件源码

https://github.com/adisonhyh/buildJar

更多相关文章

  1. 一款常用的 Squid 日志分析工具
  2. GitHub 标星 8K+!一款开源替代 ls 的工具你值得拥有!
  3. RHEL 6 下 DHCP+TFTP+FTP+PXE+Kickstart 实现无人值守安装
  4. Linux 环境下实战 Rsync 备份工具及配置 rsync+inotify 实时同步
  5. QT项目编译Android(安卓)版本时,错误合集
  6. Android(安卓)在SQLite中存取二进制图片
  7. Native Service 创建过程
  8. Binary XML file line #11: You must supply a layout_width att
  9. android状态栏显示进度

随机推荐

  1. PHP运算符知识点整理
  2. php检测字符串是否包含字符串
  3. PHP 实现常用数据结构之链表
  4. php判断数组某个值是否存在
  5. php生成一个不重复的会员号
  6. tideways+toolkit对php代码进行性能分析
  7. php+nodeJs+thrift协议,实现zookeeper节点
  8. PHP写时复制(Copy On Write)
  9. PHP中的闭包
  10. PHP 中使用 TUS 协议来实现大文件的断点