1. 程式人生 > >Android Studio多渠道打包和程式碼混淆教程

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.** { *; }