Android Studio多渠道打包和程式碼混淆教程
http://coolshell.info/blog/2015/03/android-studio-prefrence.html
什麼是Gradle
Gradle是一種依賴管理工具,基於Groovy語言,面向Java應用為主,它拋棄了基於XML的各種繁瑣配置,取而代之的是一種基於Groovy的領域特定(DSL)語言。Android Studio中新建專案成功後自動下載Gradle。 Gradle有幾個基本元件:
1.整個專案的gradle配置檔案build.gradle
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:1.1.0'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects {
repositories {
mavenCentral()
}
}
內容主要包含了兩個方面:一個是宣告倉庫的源,我這裡用的是mavenCentral(), jcenter可以理解成是一個新的中央遠端倉庫,相容maven中心倉庫,而且效能更優。另一個是聲明瞭android gradle plugin的版本,android studio 1.1正式版必須要求支援gradle plugin 1.1的版本。
2.app資料夾下這個Module的gradle配置檔案,也可以算是整個專案最主要的gradle配置檔案
apply plugin: 'com.android.application'
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:1.1.0'
}
}
android {
compileSdkVersion 17
buildToolsVersion "21.1.2"
defaultConfig {
applicationId "com.lippi.recorder"
minSdkVersion 15
targetSdkVersion 17
versionCode 1
versionName '1.4'
// dex突破65535的限制
multiDexEnabled true
// AndroidManifest.xml 裡面UMENG_CHANNEL的value為 ${UMENG_CHANNEL_VALUE}
manifestPlaceholders = [UMENG_CHANNEL_VALUE: "channel_name"]
}
sourceSets {
main {
manifest.srcFile 'src/main/AndroidManifest.xml'
java.srcDirs = ['src/main/java']
resources.srcDirs = ['src/main/resources']
aidl.srcDirs = ['src/main/aidl']
renderscript.srcDirs = ['src/maom']
res.srcDirs = ['src/main/res']
assets.srcDirs = ['src/main/assets']
jniLibs.srcDir 'src/main/jniLibs'
}
// Move the tests to tests/java, tests/res, etc...
instrumentTest.setRoot('tests')
// Move the build types to build-types/<type>
// For instance, build-types/debug/java, build-types/debug/AndroidManifest.xml, ...
// This moves them out of them default location under src/<type>/... which would
// conflict with src/ being used by the main source set.
// Adding new build types or product flavors should be accompanied
// by a similar customization.
debug.setRoot('build-types/debug')
release.setRoot('build-types/release')
}
//執行lint檢查,有任何的錯誤或者警告提示,都會終止構建,我們可以將其關掉。
lintOptions {
abortOnError false
}
//簽名
signingConfigs {
debug {
storeFile file("/home/lippi/.android/debug.keystore")
}
relealse {
//這樣寫就得把demo.jk檔案放在專案目錄
storeFile file("recorder.jks")
storePassword "recorder"
keyAlias "recorder"
keyPassword "recorder"
}
}
buildTypes {
debug {
// 顯示Log
buildConfigField "boolean", "LOG_DEBUG", "true"
versionNameSuffix "-debug"
minifyEnabled false
zipAlignEnabled false
shrinkResources false
signingConfig signingConfigs.debug
}
release {
// 不顯示Log
buildConfigField "boolean", "LOG_DEBUG", "false"
//混淆
minifyEnabled true
//Zipalign優化
zipAlignEnabled true
// 移除無用的resource檔案
shrinkResources true
//前一部分代表系統預設的android程式的混淆檔案,該檔案已經包含了基本的混淆宣告
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard.cfg'
//簽名
signingConfig signingConfigs.relealse
}
}
//渠道Flavors,配置不同風格的app
productFlavors {
GooglePlay {}
xiaomi {}
umeng {}
_360 {}
baidu {}
wandoujia {}
}
//批量配置
productFlavors.all { flavor ->
flavor.manifestPlaceholders = [UMENG_CHANNEL_VALUE: name]
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_7
targetCompatibility JavaVersion.VERSION_1_7
}
applicationVariants.all { variant ->
variant.outputs.each { output ->
def outputFile = output.outputFile
if (outputFile != null && outputFile.name.endsWith('.apk')) {
def fileName = outputFile.name.replace(".apk", "-${defaultConfig.versionName}.apk")
output.outputFile = new File(outputFile.parent, fileName)
}
}
}
dependencies {
compile fileTree(include: ['*.jar'], dir: 'libs')
compile 'org.apache.commons:commons-math:2.1'
compile 'org.slf4j:slf4j-log4j12:1.7.5'
}
}
-
檔案開頭apply plugin是最新gradle版本的寫法,以前的寫法是apply plugin: ‘android’, 如果還是以前的寫法,請改正過來。
-
buildToolsVersion這個需要你本地安裝該版本才行,很多人匯入新的第三方庫,失敗的原因之一是build version的版本不對,這個可以手動更改成你本地已有的版本或者開啟 SDK Manager 去下載對應版本。
-
applicationId代表應用的包名,也是最新的寫法,這裡就不在多說了。
- android 5.0開始預設安裝jdk1.7才能編譯
-
minifyEnabled(混淆)也是最新的語法,很早之前是runProguard,這個也需要更新下。
- proguardFiles這部分有兩段,前一部分代表系統預設的android程式的混淆檔案,該檔案已經包含了基本的混淆宣告,免去了我們很多事,這個檔案的目錄在 /tools/proguard/proguard-android.txt , 後一部分是我們專案裡的自定義的混淆檔案,目錄就在 app/proguard-rules.txt , 如果你用Studio 1.0建立的新專案預設生成的檔名是 proguard-rules.pro , 這個名字沒關係,在這個檔案裡你可以宣告一些第三方依賴的一些混淆規則,後面會具體講到。
compile project(‘:extras:ShimmerAndroid’)這一行是因為專案中存在其他Module,你可以理解成Android Library,由於Gradle的普及以及遠端倉庫的完善,這種依賴漸漸的會變得非常不常見,但是你需要知道有這種依賴的。
3.gradle目錄下有個 wrapper 資料夾,裡面可以看到有兩個檔案,我們主要看下 gradle-wrapper.properties 這個檔案的內容:
#Fri Dec 19 21:59:01 CST 2014
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-2.2.1-all.zip
可以看到裡面聲明瞭gradle的目錄與下載路徑以及當前專案使用的gradle版本,這些預設的路徑我們一般不會更改的,這個檔案裡指明的gradle版本不對也是很多導包不成功的原因之一
4.settings.gradle
這個檔案是全域性的專案配置檔案,裡面主要宣告一些需要加入gradle的module
include ':recorder'
檔案中recorder是專案的module,如果還有其他module按照相同的格式加上去。
Gradle多渠道打包
由於國內Android市場眾多渠道,為了統計每個渠道的下載及其它資料統計,就需要我們針對每個渠道單獨打包,如果讓你打幾十個市場的包豈不煩死了,不過有了Gradle,這再也不是事了。 以友盟統計為例,在AndroidManifest.xml裡面會有這麼一段:
<meta-data
android:name="UMENG_CHANNEL"
android:value="Channel_ID" />
裡面的Channel_ID就是渠道標示。我們的目標就是在編譯的時候這個值能夠自動變化。 * 第一步 在AndroidManifest.xml裡配置PlaceHolder
<meta-data
android:name="UMENG_CHANNEL"
android:value="${UMENG_CHANNEL_VALUE}" />
- 第二步 在build.gradle 設定productFlavors
android {
productFlavors {
xiaomi {}
_360 {}
baidu {}
wandoujia {}
}
productFlavors.all {
flavor -> flavor.manifestPlaceholders = [UMENG_CHANNEL_VALUE: name]
}
}
然後直接執行 ./gradlew assembleRelease
然後就等待打包完成吧。
assemble 這個命令,會結合 Build Type 建立自己的task,如:
-
./gradlew assembleDebug
-
./gradlew assembleRelease
除此之外 assemble 還能和 Product Flavor 結合建立新的任務,其實 assemble 是和 Build Variants 一起結合使用的,而 Build Variants = Build Type + Product Flavor , 舉個例子大家就明白了:
如果我們想打包wandoujia渠道的release版本,執行如下命令就好了:
./gradlew assembleWandoujiaRelease
如果我們只打wandoujia渠道版本,則:
./gradlew assembleWandoujia
此命令會生成wandoujia渠道的Release和Debug版本
同理我想打全部Release版本:
./gradlew assembleRelease
這條命令會把Product Flavor下的所有渠道的Release版本都打出來。
程式碼混淆
下面是常見的的proguard.cfg配置項:
#指定程式碼的壓縮級別
-optimizationpasses 5
#包明不混合大小寫
-dontusemixedcaseclassnames
#不去忽略非公共的庫類
-dontskipnonpubliclibraryclasses
#優化 不優化輸入的類檔案
-dontoptimize
#預校驗
-dontpreverify
#混淆時是否記錄日誌
-verbose
# 混淆時所採用的演算法
-optimizations !code/simplification/arithmetic,!field/*,!class/merging/*
#保護註解
-keepattributes *Annotation*
# 保持哪些類不被混淆
-keep public class * extends android.app.Fragment
-keep public class * extends android.app.Activity
-keep public class * extends android.app.Application
-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 com.android.vending.licensing.ILicensingService
#如果有引用v4包可以新增下面這行
-keep public class * extends android.support.v4.app.Fragment
#忽略警告
-ignorewarning
##記錄生成的日誌資料,gradle build時在本專案根目錄輸出##
#apk 包內所有 class 的內部結構
-dump class_files.txt
#未混淆的類和成員
-printseeds seeds.txt
#列出從 apk 中刪除的程式碼
-printusage unused.txt
#混淆前後的對映
-printmapping mapping.txt
########記錄生成的日誌資料,gradle build時 在本專案根目錄輸出-end######
#####混淆保護自己專案的部分程式碼以及引用的第三方jar包library#######
#-libraryjars libs/umeng-analytics-v5.2.4.jar
#三星應用市場需要新增:sdk-v1.0.0.jar,look-v1.0.1.jar
#-libraryjars libs/sdk-v1.0.0.jar
#-libraryjars libs/look-v1.0.1.jar
#如果不想混淆 keep 掉
-keep class com.lippi.recorder.iirfilterdesigner.** {*; }
#友盟
-keep class com.umeng.**{*;}
#專案特殊處理程式碼
#忽略警告
-dontwarn com.lippi.recorder.utils**
#保留一個完整的包
-keep class com.lippi.recorder.utils.** {
*;
}
-keep class com.lippi.recorder.utils.AudioRecorder{*;}
#如果引用了v4或者v7包
-dontwarn android.support.**
####混淆保護自己專案的部分程式碼以及引用的第三方jar包library-end####
-keep public class * extends android.view.View {
public <init>(android.content.Context);
public <init>(android.content.Context, android.util.AttributeSet);
public <init>(android.content.Context, android.util.AttributeSet, int);
public void set*(...);
}
#保持 native 方法不被混淆
-keepclasseswithmembernames class * {
native <methods>;
}
#保持自定義控制元件類不被混淆
-keepclasseswithmembers class * {
public <init>(android.content.Context, android.util.AttributeSet);
}
#保持自定義控制元件類不被混淆
-keepclassmembers class * extends android.app.Activity {
public void *(android.view.View);
}
#保持 Parcelable 不被混淆
-keep class * implements android.os.Parcelable {
public static final android.os.Parcelable$Creator *;
}
#保持 Serializable 不被混淆
-keepnames class * implements java.io.Serializable
#保持 Serializable 不被混淆並且enum 類也不被混淆
-keepclassmembers class * implements java.io.Serializable {
static final long serialVersionUID;
private static final java.io.ObjectStreamField[] serialPersistentFields;
!static !transient <fields>;
!private <fields>;
!private <methods>;
private void writeObject(java.io.ObjectOutputStream);
private void readObject(java.io.ObjectInputStream);
java.lang.Object writeReplace();
java.lang.Object readResolve();
}
#保持列舉 enum 類不被混淆 如果混淆報錯,建議直接使用上面的 -keepclassmembers class * implements java.io.Serializable即可
#-keepclassmembers enum * {
# public static **[] values();
# public static ** valueOf(java.lang.String);
#}
-keepclassmembers class * {
public void *ButtonClicked(android.view.View);
}
#不混淆資源類
-keepclassmembers class **.R$* {
public static <fields>;
}
#避免混淆泛型 如果混淆報錯建議關掉
#–keepattributes Signature
#移除log 測試了下沒有用還是建議自己定義一個開關控制是否輸出日誌
#-assumenosideeffects class android.util.Log {
# public static boolean isLoggable(java.lang.String, int);
# public static int v(...);
# public static int i(...);
# public static int w(...);
# public static int d(...);
# public static int e(...);
#}
#如果用用到Gson解析包的,直接新增下面這幾行就能成功混淆,不然會報錯。
#gson
#-libraryjars libs/gson-2.2.2.jar
-keepattributes Signature
# Gson specific classes
-keep class sun.misc.Unsafe { *; }
# Application classes that will be serialized/deserialized over Gson
-keep class com.google.gson.examples.android.model.** { *; }