您的位置:首页 > 移动开发 > Android开发

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

2017-03-16 14:35 351 查看
一.携程插件修改后的使用方式:

        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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  携程 插件 aidl android