通常我们是将工程设置成project模式,这个模式下我们的工程有很多目录:
buildToolsVersion: android构建工具的版本,其中包括了打包工具aapt、dx等等,这个工具的目录位于…your_sdk_path/build-tools/XX.XX.XX,通过SDK Manager 更新;在SDK Manager中安装选择版本,buildToolsVersion的版本需要>=CompileSdkVersion; 高版本的build-tools 可以构建低版本编译的android程序。
minSdkVersion:指定项目最低兼容的版本,这里指定为15,表示最低兼容到Android 4.0系统
targetSdkVersion:指定的值表示在该目标版本上已经做过充分测试,系统会为该应用启动一些对应该目标系统的最新功能特性,Android系统平台的行为变更,只有targetSdkVersion的属性值被设置为大于或等于该系统平台的API版本时,才会生效。例如,若指定targetSdkVersion值为22,则表示该程序最高只在Android5.1版本上做过充分测试,在Android6.0系统上(对应targetSdkVersion为23)拥有的新特性如系统运行时权限等功能就不会被启用。
buildTypes:一般包含两个选项,一个是debug,用于指定生成测试版安装文件的配置,可以忽略不写;另一个是release,用于指定生成正式版安装文件的配置。其中minifyEnabled表明是否对代码进行混淆,true表示对代码进行混淆。proguardFiles指定混淆的规则文件,这里指定了proguard-android.txt文件和proguard-rules.pro文件两个文件,proguard-android.txt文件为默认的混淆文件,里面定义了一些通用的混淆规则。proguard-rules.pro文件位于当前项目的根目录下,可以在该文件中定义一些项目特有的混淆规则。
**dependencies**:该闭包定义了项目的依赖关系,一般项目都有三种依赖方式:本地依赖、库依赖和远程依赖。本地依赖可以对本地的jar包或目录添加依赖关系,库依赖可以对项目中的库模块添加依赖关系,远程依赖可以对jcener库上的开源项目添加依赖关系。
当我们在Androd Studio里将写好的代码通过点击run app就可以在build/outputs/apk目录下生成一个APK文件,那一系列android代码是怎么变成这个文件的呢?先看一张官方提供的构建图
可以总结如下:
Zipalign是一个android平台上整理APK文件的工具,它首次被引入是在Android 1.6版本的SDK软件开发工具包中。它能够对打包的Android应用程序进行优化, 以使Android操作系统与应用程序之间的交互作用更有效率,这能够让应用程序和整个系统运行得更快。用Zipalign处理过的应用程序执行时间达到最低限度,当设备运行APK应用程序时占更少的RAM(Random Access Memory)随机访问内存,我们强烈推荐在新的和已经发布的程序上使用zipalign工具来得到优化后的版本。
Zipalign对apk文件中未压缩的数据在4个字节边界上对齐,当资源文件通过内存映射对齐到4字节边界时,访问资源文件的代码才是有效率的。4字节对齐后,android系统就可以通过调用mmap函数读取文件,进程可以像读写内存一样对普通文件的操作,系统共享内存IPC,以在读取资源上获得较高的性能。 如果资源本身没有进行对齐处理,它就必须显式地读取它们——这个过程将会比较缓慢且会花费额外的内存
可以在build.gradle文件中操作
release {
//对齐优化
zipAlignEnabled true
}
debug {
//对齐优化
zipAlignEnabled false
}
使用命令行
zipalign工具一般在android-sdk-2.2\build-tools\其中一个版本目录下
然后在dos窗口中定位到当前目录,使用zipalign -v 4 优化前的名字.apk 优化后的名字.apk
其中-v 表示输出优化后的详细信息,4代表对齐为4个字节
验证一个apk有没有使用对齐优化
使用如下命令:zipalign -c -v 4 source.apk ;如果最终出现了Verification succesful提示,就表明是优化过的apk
zipalign优化的最根本目的是帮助操作系统更高效率的根据请求索引资源,将resource-handling code统一将Data structure alignment(数据结构对齐标准:DSA)限定为4-byte boundaries;如果不采取对齐的标准,处理器无法准确和快速的在内存地址中定位相关资源。
平时开发中APP的打包和提交测试的工作基本上是由开发人员来完成的,随着需求越来越多,项目复杂度越来越大,迭代需求很快,这样打包时间就很长,并且从严格的研发流程来讲,开发人员应该只负责提交代码,这些工作应该由测试和运维人员来做,但是可能岗位有限等原因,没办法只能另外想办法了,所以如果有一个自动化构建的工具在后台不断的去更新代码,然后进行项目编译,APP打包,最后提交到测试平台,那这样就很完美了
没错,Jenkins就是这样一个持续集成工具,且开源;它不光可以进行Android平台的打包,也可以用来进行iOS打包、NodeJs打包、Jave服务打包等
官方地址为:https://jenkins.io/。Jenkins是使用Java开发的,官方提供一个war包,并且自带servlet容器,可以独立运行也可以放在Tomcat中运行。我们这里使用独立运行的方式,运行命令:java -jar jenkins.war
详细使用可参考https://blog.csdn.net/ncepudmx/article/details/77451314
Gradle是一个基于Apache Ant和Apache Maven概念的项目自动化建构工具。它使用一种基于Groovy的特定领域语言(DSL)来声明项目设置,抛弃了基于XML的各种繁琐配置。简单理解就是Gradle是一个构建工具,它是用来帮助我们构建app的,构建包括编译、打包等过程。我们可以为Gradle指定构建规则,然后它就会根据我们的“命令”自动为我们构建APP,我们所使用的Android Studio就是使用Gradle来构建项目的
在Gradle出现之前,有几个基于Java的构建工具:Ant、Maven,它们被应用于Java或者Android开发中,我们来看看它们
全称Another Neat Tool,它是由 James Duncan Davidson 开发的(Tomcat 最初的开发者),最初是用来构建 Tomcat。在2000年,Ant成为一个独立的项目并被发布出来。Ant 是由 Java 编写的构建工具,它的核心代码是由Java编写的,因此具有平台无关性,构建脚本是XML格式的(默认为bulid.xml),如果你熟悉XML,那么Ant 就比较容易上手,它的构建脚本如下
Ant的构建脚本由三个基本元素组成:一个project(工程)、多个target(目标)和可用的task(任务)。
Ant有以下缺点:
Ant无法获取运行时的信息。
XML作为构建脚本的语言,如果构建逻辑复杂,那么构建脚本就会又长又难以维护。
Ant需要配合Ivy(一种管理项目依赖工具),否则Ant很难管理依赖。
Ant在如何组织项目结构方面没有给出任何指导,这导致Ant虽然灵活性高,但这样的灵活导致每个构建脚本都是唯一的而且很难被理解。
Maven于2004年发布,它的目标是改进开发人员在使用Ant时面临的一些问题。Maven最初是为了简化Jakarta Turbine项目的构建,它经历了Maven到Maven3的发展,Maven作为后来者, 继承了Ant的项目构建功能, 同样采用了XML作为构建脚本的格式。Maven具有依赖管理和项目管理的功能,提供了中央仓库,能帮助我们自动下载库文件。
Maven的构建脚本的样式如下所示
4.0.0
com.mycompany.app
my-app
1.0-SNAPSHOT
jar
Maven Quick Start Archetype
http://maven.apache.org
junit
junit
4.11
test
Maven相比Ant的优点:
Ant是过程式的,开发者需要显示的指定每个目标,以及完成该目标锁需要执行的任务。每一个项目,开发着都需要重新编写这一过程,这样会产生大量的重复。Maven是声明式的,项目的构建过程和过程中的各个阶段都由插件实现,开发者只需要声明项目的基本元素就可以了,这很大程度消除了重复。
Ant本身是没有依赖管理,需要配合Ivy来管理依赖,而Maven本身就提供了依赖管理。
Maven 使用约定而不是配置,它为工程提供了合理的默认行为,项目会知道去哪个目录寻找源代码以及构建运行时有那些任务去执行,如果你的项目遵从默认值,那么只需要写几行XML配置脚本就可以了。而Ant是使用配置且没有默认行为的。
Maven的缺点:
Maven的提供了默认的结构和生命周期,这些可能不适合你的项目需求。
为Maven写定制的扩展过于累赘。
Maven的中央仓库比较混乱,当无法从中央仓库中得到需要的类库时,我们可以手工下载复制到本地仓库中,也可以建立组织内部的仓库服务器。
国内连接Maven的中央仓库比较慢,需要连接国内的Maven镜像仓库。
Maven缺乏文档,不便于使用和理解。
最后看看Gradle的构建脚本
apply plugin:'java'
group='com.mycompany.app'
archivesBaseName='my-app'
version='1.0-SNAPSHOT'
repositories{
mavenCentral()
}
dependencies{
testCompile 'junit:4.11'
}
Gradle优点:
就这样Android就选择了Gradle来作为app的构建工具了
一个常用的构建脚本build.gradle
apply plugin: 'com.android.application'
android {
compileSdkVersion 22
buildToolsVersion "25.0.1"
defaultConfig {
applicationId "com.mango.yang"
minSdkVersion 16
targetSdkVersion 22
versionCode 1
versionName "1.00build005"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
multiDexEnabled true//dex突破65535的限制
}
signingConfigs { //签名配置
release {
keyAlias 'keyAlias ' //别名
keyPassword 'keyAlias ' //密码
storeFile file('.\\keyAlias .keystore') //路径一般放在module的根路径下面
storePassword 'keyAlias '
}
}
buildTypes {//构建app
debug {//测试版本
minifyEnabled false
}
release {//正式版本
pseudoLocalesEnabled true //如果没有提供混淆规则文件,则设置默认的混淆规则文件
signingConfig signingConfigs.release //引用签名信息
buildConfigField "boolean", "LOG_DEBUG", "false"// 不显示Log
zipAlignEnabled true // zip align(对齐,排列)优化。
shrinkResources true // 去掉没有用的资源文件
minifyEnabled true//开启混淆
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'//引用混淆配置文件
}
}
packagingOptions {
exclude 'META-INF/ASL2.0'
exclude 'META-INF/LICENSE'
exclude 'META-INF/NOTICE'
exclude 'META-INF/MANIFEST.MF'
}
//如果有AIDL文件
sourceSets {
main {
java.srcDirs = ['src/main/java', 'src/main/aidl']
}
}
//忽略Lint错误 有时候会由于Lint错误而终止。当这些错误来自第三方库中时,我们往往想要忽略这些错误从而继续构建进程
lintOptions {
abortOnError false
disable "ResourceType"
}
//多渠道打包
productFlavors {
hsq{}
hsq_dx{}
hsq_wx{}
baidu{}
yingyongbao{}
ppzhushou{}
anzhi{}
zhushou360{}
huawei{}
lenovomm{}
wandoujia{}
mumayi{}
meizu{}
youyi{}
sougou{}
}
// 批量渠道包值替换(有些第三方库中需要用到渠道名)
productFlavors.all { flavor ->
// 友盟、极光推送渠道包, UMENG_CHANNEL 是根据你AndroidManifest.xml来配置的,请看下面。
flavor.manifestPlaceholders = [UMENG_CHANNEL: name, JPUSH_CHANNEL: name]
}
}
dependencies {//依赖
compile fileTree(include: ['*.jar'], dir: 'libs')
compile 'com.android.support:support-v4:22.+'
compile project(':videoplayer')
compile files('libs/butterknife-7.0.1.jar')
.....
}
说到项目构建就不得不说代码混淆配置了,那proguard是什么呢?
ProGuard是一个混淆代码的开源项目,ProGuard功能如下
总而言之,Proguard可以移除代码中的无用类,字段,方法和属性同时可以混淆(类,字段,方法,属性的)命名。最终结果可以使我们的apk文件体积更加小,也会让我们的apk更加难以被他人逆向工程,这对于那些特别时包含一些安全性功能的apk来说是相当重要的
这样就可以回答我们为什么要使用Proguard混淆?
Java 是一种跨平台的、解释型语言,Java 源代码编译成中间”字节码”存储于 class 文件中。由于跨平台的需要,Java 字节码中包括了很多源代码信息,如变量名、方法名,并且通过这些名称来访问变量和方法,这些符号带有许多语义信息,很容易被反编译成 Java 源代码。为了防止这种现象,我们可以使用 Java 混淆器对 Java 字节码进行混淆。混淆就是对发布出去的程序进行重新组织和处理,使得处理后的代码与处理前代码完成相同的功能,而混淆后的代码很难被反编译,即使反编译成功也很难得出程序的真正语义。被混淆过的程序代码,仍然遵照原来的档案格式和指令集,执行结果也与混淆前一样,只是混淆器将代码中的所有变量、函数、类的名称变为简短的英文字母代号,在缺乏相应的函数名和程序注释的况下,即使被反编译,也将难以阅读。同时混淆是不可逆的,在混淆的过程中一些不影响正常运行的信息将永久丢失,这些信息的丢失使程序变得更加难以理解。
####Proguard工作原理
混淆就是移除没有用到的代码,然后对代码里面的类、变量、方法重命名为人可读性很差的简短名字。
那么有一个问题,ProGuard怎么知道这个代码没有被用到呢?
这里引入一个Entry Point(入口点)概念,Entry Point是在ProGuard过程中不会被处理的类或方法。在压缩的步骤中,ProGuard会从上述的Entry Point开始递归遍历,搜索哪些类和类的成员在使用,对于没有被使用的类和类的成员,就会在压缩段丢弃,在接下来的优化过程中,那些非Entry Point的类、方法都会被设置为private、static或final,不使用的参数会被移除,此外,有些方法会被标记为内联的,在混淆的步骤中,ProGuard会对非Entry Point的类和方法进行重命名。
那么这个入口点怎么来呢?就是从ProGuard的配置文件来,只要这个配置了,那么就不会被移除。
对于ProGuard的原理更详细的介绍可以参考Proguard使用手册
如何编写Proguard混淆文件呢
一般三步走
混淆文件的基本配置信息,可以当做模板给每个APP使用
#指定代码压缩比 在0~7之间,默认为5,一般不需要改
-optimizationpasses 5
#混淆时不使用大小写混合,混淆后类名称为小写
-dontusemixedcaseclassnames
#告诉Proguard 不要跳过对非公开类的处理,默认是跳过,如果应用程序引入的有jar包,并且混淆jar包里面的class
-dontskipnonpubliclibraryclasses
#混淆第三方库类的成员
-dontskipnonpubliclibraryclassmembers
#不做预校验,preverify是proguard的4个步骤之一,Android不需要preverify,去掉这一步可加快混淆速度
-dontpreverify
# 混淆时记录日志,混淆后就会生成映射文件,包含有 类名->混淆后类名 的映射关系
# 然后使用printmapping指定映射文件的名称
-verbose
-printmapping proguardMapping.txt
# 指定混淆时采用的算法,后面的参数是一个过滤器
# 这个过滤器是谷歌推荐的算法,一般不改变
-optimizations !code/simplification/arithmetic,!field/*,!class/merging/*
# 假如项目中有用到注解,应加入这行配置,对JSON实体映射也很重要,比如fastJson
-keepattributes *Annotation*
#类型转换错误 添加如下代码以便过滤泛型(不写可能会出现类型转换错误,一般情况把这个加上就是了),即避免泛型被混淆
-keepattributes Signature
# 抛出异常时保留代码行号,在异常分析中可以方便定位
-keepattributes SourceFile,LineNumberTable,Exceptions
#保留我们使用的四大组件,自定义的Application等等这些类不被混淆
#因为这些子类都有可能被外部调用
-keep public class * extends android.app.Activity
-keep public class * extends android.app.Appliction
-keep public class * extends android.app.Service
-keep public class * extends android.content.BroadcastReceiver
-keep public class * extends android.content.ContentProvider
-keep public class * extends android.app.backup.BackupAgentHelper
-keep public class * extends android.preference.Preference
-keep public class * extends android.view.View
-keep public class * extends android.app.Fragment
-keep public class com.android.vending.licensing.ILicensingService
#保留support下的所有类及其内部类不被混淆
-keep class android.support.** {*;}
#保留R下面的资源
-keep class **.R$* {*;}
#保留本地native方法不被混淆
-keepclasseswithmembernames class * {
native ;
}
#保持指定规则的方法不被混淆(Android layout 布局文件中为控件配置的onClick方法不能混淆)
-keepclassmembers class * extends android.app.Activity{
public void *(android.view.View);
}
#保留枚举类不被混淆
-keepclassmembers enum * {
public static **[] values();
public static ** valueOf(java.lang.String);
}
#保持自定义控件指定规则的方法不被混淆
-keep public class * extends android.view.View{
*** get*();
void set*(***);
public (android.content.Context);
public (android.content.Context, android.util.AttributeSet);
public (android.content.Context, android.util.AttributeSet, int);
}
# 保留Parcelable序列化的类不被混淆
-keep class * implements android.os.Parcelable {
public static final android.os.Parcelable$Creator *;
}
#需要序列化和反序列化的类不能被混淆(注:Java反射用到的类也不能被混淆)
-keepnames class * implements java.io.Serializable
#保护实现接口Serializable的类中,指定规则的类成员不被混淆
-keepclassmembers class * implements java.io.Serializable {
static final long serialVersionUID;
private static final java.io.ObjectStreamField[] serialPersistentFields;
private void writeObject(java.io.ObjectOutputStream);
private void readObject(java.io.ObjectInputStream);
java.lang.Object writeReplace();
java.lang.Object readResolve();
}
#对于带有回调函数onXXEvent的不被混淆
-keepclassmembers class * {
void *(**On*Event);
}
对于-dontusemixedcaseclassnames这里有点要说明一下,proguard会默认我们的操作系统能够区分大小写字母的文件,如b.java和B.java会被认为是两个不同的文件,但是window系统并不这样认为(window系统对大小写不敏感的)。因此在window系统下必须在proguard文件中指明-dontusemixedcaseclassnames选项。如果没这样做并且我们的项目中类的数量超过26个的话,那么proguard就会默认混用大小写文件名,进而导致class文件相互覆盖。所以为了安全起见,我们都默认设置该选项。
#实体类不要混淆,项目中实体类基本上在同一个包下,下面这样写一次就够了
-keep public class com.mango.yang.entity.** {
public void set*(***);
public *** get*();
public *** is*();
}
# 保留内部类不被混淆,$符号就是用来分割内部类与外部类的标志,这里只是举个例子,保留MainActivity下的内部类不被混淆
-keep class com.mango.yang.MainActivity$* { *; }
#对WebView的处理
-keepclassmembers class fqcn.of.javascript.interface.for.webview {
public *;
}
-keepclassmembers class * extends android.webkit.webViewClient {
public void *(android.webkit.WebView, java.lang.String, android.graphics.Bitmap);
public boolean *(android.webkit.WebView, java.lang.String);
}
-keepclassmembers class * extends android.webkit.webViewClient {
public void *(android.webkit.webView, jav.lang.String);
}
#保留addJavaScriptInterface方法注入java对象不被混淆,这里JSInterface 你可以取自己的名称,包名也是
-keepclassmembers class com.mango.yang.JS.JSInterface {
;
}
#保留反射类
-keep class com.mango.yang.reflex.** { *; }
这里分两种情况,一种是我们用的第三方开源库,另一种是我们使用的第三方的SDK。开源库是没有必要混淆的,因为源码都是开源的,大家都可以用的代码。而第三方SDK也没有必要混淆,因为这些SDK都是经过混淆的,所以对于这些SDK我们要做的是避免这些SDK的类和方法在我们的app代码中被混淆
# 不混淆 GSON
-keep class com.google.gson.** { *; }
-keep class com.google.gson.JsonObject {*;}
#支付宝
-libraryjars libs/alipaysdk.jar
-dontwarn com.alipay.android.app.**
-keep public class com.alipay.** { *; }
#极光推送
-dontoptimize
-dontpreverify
-dontwarn cn.jpush.**
-keep class cn.jpush.** { *; }
#EventBus
-keepattributes *Annotation*
-keepclassmembers class ** {
@org.greenrobot.eventbus.Subscribe ;
}
-keep enum org.greenrobot.eventbus.ThreadMode { *; }
# Only required if you use AsyncExecutor
-keepclassmembers class * extends org.greenrobot.eventbus.util.ThrowableFailureEvent {
(java.lang.Throwable);
}
#retroift
-dontwarn retrofit2.**
-keep class retrofit2.** { *; }
-keepattributes Signature
-keepattributes Exceptions
#ButterKnife
-keep class butterknife.** { *; }
-dontwarn butterknife.internal.**
-keep class **$$ViewBinder { *; }
-keepclasseswithmembernames class * {
@butterknife.* ;
}
-keepclasseswithmembernames class * {
@butterknife.* ;
}
#fastjson
-dontwarn com.alibaba.fastjson.**
-keep class com.alibaba.fastjson.** { *; }
#rxjava
-dontwarn sun.misc.**
-keepclassmembers class rx.internal.util.unsafe.*ArrayQueue*Field* {
long producerIndex;
long consumerIndex;
}
-keepclassmembers class rx.internal.util.unsafe.BaseLinkedQueueProducerNodeRef {
rx.internal.util.atomic.LinkedQueueNode producerNode;
}
-keepclassmembers class rx.internal.util.unsafe.BaseLinkedQueueConsumerNodeRef {
rx.internal.util.atomic.LinkedQueueNode consumerNode;
}