Android Gradle知識梳理

Android Studio已經成爲現在Android 開發的主要工具,在開發過程中學習使用gradle顯得格外重要,本篇博客,我們一起學習gradle打包的一些知識。

Gradle 安裝

1.安裝JDK,並配置JAVA_HOME環境變量。因爲Gradle是用Groovy編寫的,而Groovy基於JAVA。另外,Java版本要不小於1.5.
2.下載。地址是:http://www.gradle.org/downloads
這裏寫圖片描述
選擇Download,選擇相應的complete版本即可
這裏寫圖片描述
3.解壓。如果你下載的是gradle-xx-all.zip的完整包,它會有以下內容:
二進制文件
用戶手冊(包括PDF和HTML兩種版本)
DSL參考指南
API手冊(包括Javadoc和Groovydoc)
樣例
源代碼,僅供參考使用。

4.配置環境變量。配置GRADLE_HOME到你的gradle根目錄當中,然後把%GRADLE_HOME%/bin(linux或mac的是$GRADLE_HOME/bin)加到PATH的環境變量。

配置完成之後,運行gradle -v,檢查一下是否安裝無誤。如果安裝正確,它會打印出Gradle的版本信息,包括它的構建信息,Groovy, Ant, Ivy, 當前JVM和當前系統的版本信息。

這裏寫圖片描述

另外,可以通過GRADLE_OPTS或JAVA_OPTS來配置Gradle運行時的JVM參數。不過,JAVA_OPTS設置的參數也會影響到其他的JAVA應用程序。

Gradle構建基礎

Projects和tasks
先介紹兩個概念,projects和tasks,它們是Gradle中的兩個重要概念。
任何一個Gradle構建,都是由一個或多個projects組成的。Project就是你想要用Gradle做什麼,比如構建一個jar包,構建一個web應用。Project也不單指構建操作,部署你的應用或搭建一個環境,也可以是一個project。
一個project由多個task組成。每個task代表了構建過程當中的一個原子性操作,比如編譯,打包,生成javadoc,發佈等等這些操作。

編寫第一個構建腳本
新建一個文件build.gradle,然後添加以下代碼:

task hello {  
    doLast {  
        println 'Hello, Gradle!'  
    }  
} 
  • 1
  • 2
  • 3
  • 4
  • 5

這是一個非常簡單的構建腳本,它定義了一個叫hello的task,task的內容是在最後打印出“Hello, Gradle!”。
輸入命令gradle hello來執行它:

這裏寫圖片描述
這裏寫圖片描述
Gradle是領域驅動設計的構建工具,在它的實現當中,Project接口對應上面的project概念,Task接口對應上面的task概念,實際上除此之外還有一個重要的領域對象,即Action,對應的是task裏面具體的某一個操作。一個project由多個task組成,一個task也是由多個action組成。
當執行gradle hello的時候,Gradle就會去調用這個hello task來執行給定操作(Action)。這個操作其實就是一個用Groovy代碼寫的閉包,代碼中的task是Project類裏的一個方法,通過調用這裏的task方法創建了一個Task對象,並在對象的doLast方法中傳入println ‘Hello, Gradle!’這個閉包。這個閉包就是一個Action。
Task是Gradle裏定義的一個接口,表示上述概念中的task。它定義了一系列的諸如doLast, doFirst等抽象方法,具體可以看gradle api裏org.gradle.api.Task的文檔。

在上面執行了gradle hello後,除了輸出“Hello, Gradle!”之外,我們發現像“:hello”這樣的其他內容。這其實是Gradle打印出來的日誌,如果不想輸出這些內容,可以在gradle後面加上參數 -q。即:gradle -q hello。

快速定義任務
上面的代碼,還有一種更簡潔的寫法,如下:

task hello << {  
    println 'Hello, Gradle!'  
}  
  • 1
  • 2
  • 3

task hello << {
println ‘Hello, Gradle!’
}
代碼即腳本
Gradle腳本是採用Groovy編寫的,所以也像Groovy一樣,以腳本方式來執行代碼,如下面例子:

task upper << {  
    String someString = 'myName'  
    println "Original: " + someString   
    println "Upper case: " + someString.toUpperCase()  
}  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

執行結果如下,它將定義的字符串轉爲大寫:

D:\testGradle>gradle -q hello
Hello, Gradle!
D:\testGradle>gradle -q upper
Original: myName
Upper case: MYNAME
  • 1
  • 2
  • 3
  • 4
  • 5

我們在寫Gradle腳本的時候,可以像寫Groovy代碼一樣。而Groovy是基於Java的,兼容Java語法
任務依賴
我們可以通過以下方式創建依賴:

task hello << {  
    print 'Hello, '  
}  
task intro(dependsOn: hello) << {  
    println "Gradle!"  
}  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

定義一個任務hello,輸出“Hello, ”,然後定義一個任務intro,並依賴hello,輸出“Gradle!”。結果是打印出“Hello, Gradle!”,如下:

D:\testGradle>gradle -q intro
Hello, Gradle!
  • 1
  • 2

另外,被依賴的task不必放在前面聲明,在後面也是可以的,這一點在後面將會用到。

動態任務
藉助於強大的Groovy,我們還可以動態地創建任務。如下代碼:
我們還可以動態地創建任務。如下代碼:

4.times { counter ->  
    task "task$counter" << {  
        println "I'm task number $counter"  
    }  
}  
  • 1
  • 2
  • 3
  • 4
  • 5

我們定義了4個task,分別是task0, task1, task2, task3。我們來執行task1,如下:

D:\testGradle>gradle -q task1
I'm task number 1
  • 1
  • 2

任務操縱
在Gradle當中,任務創建之後可以通過API進行訪問,這是Gradle與Ant的不同之處。
增加依賴
還是以上面的例子,但是我們添加一行代碼,如下:

4.times { counter ->  
    task "task$counter" << {  
        println "I'm task number $counter"  
    }  
}  
task1.dependsOn task0, task3  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

然後還是執行 gradle -q task1

gradle -q task1
I'm task number 0
I'm task number 3
I'm task number 1
  • 1
  • 2
  • 3
  • 4

它先執行了task0和task3,因爲task1依賴於這兩個。

增加任務行爲

task hello << {  
    println 'Hello, Gradle!'  
}  
hello.doFirst {  
    println 'I am first.'  
}  
hello.doLast {  
    println 'I am last.'  
}  
hello << {  
    println 'I am the the last'  
}  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

執行後的輸出:

gradle -q hello
I am first.
Hello, Gradle!
I am last.
I am the the last
  • 1
  • 2
  • 3
  • 4
  • 5

短標記法
如果你對groovy有一定了解,那你也許會注意到,每個task都是一個構建腳本的屬性,所以可以通過“$”這種短標記法來訪問任務。如下:

task hello << {  
    println 'Hello, Gradle!'  
}  
hello.doLast {  
    println "Greetings from the $hello.name task."  
}  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

執行結果:

gradle -q hello
Hello, Gradle!
Greetings from the hello task.
  • 1
  • 2
  • 3

注意,通過這種方法訪問的任務一定是要已經定義的。

增加自定義屬性

task myTask {  
    ext.myProperty = "myValue"  
}  

task printTaskProperties << {  
    println myTask.myProperty  
}  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
輸出結果:
gradle -q printTaskProperties
myValue
  • 1
  • 2
  • 3
  • 4

定義默認任務

defaultTasks 'clean', 'run'  

task clean << {  
    println 'Default Cleaning!'  
}  

task run << {  
    println 'Default Running!'  
}  

task other << {  
    println "I'm not a default task!"  
}  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
執行結果:
gradle -q
Default Cleaning!
Default Running!
  • 1
  • 2
  • 3
  • 4

DAG配置

Gradle使用DAG(Directed acyclic graph,有向非循環圖)來決定任務執行的順序。通過這一特性,我們可以實現依賴任務做不同輸出。
如下代碼:

task distribution << {  
    println "We build the zip with version=$version"  
}  

task release(dependsOn: 'distribution') << {  
    println 'We release now'  
}  

gradle.taskGraph.whenReady {taskGraph ->  
    if (taskGraph.hasTask(release)) {  
        version = '1.0'  
    } else {  
        version = '1.0-SNAPSHOT'  
    }  
}  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
執行結果如下:
gradle -q
Default Cleaning!
Default Running!
D:\testGradle>gradle -q distribution
We build the zip with version=1.0-SNAPSHOT
D:\testGradle> gradle -q release
We build the zip with version=1.0
We release now
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

在上面的腳本代碼中,whenReady會在release任務執行之前影響它,即使這個任務不是主要的任務(即不是通過命令行傳入參數來調用)。

————–接下來是Android Gradle打包的小技巧———–

替換AndroidManifest中的佔位符

把配置中的${app_label}替換爲@string/app_name

android{
    defaultConfig{
        manifestPlaceholders = [app_label:"@string/app_name"]
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5

如果只想替換debug版本:

android{
    buildTypes {
        debug {
            manifestPlaceholders = [app_label:"@string/app_name_debug"]
        }
        release {
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

更多的需求是替換渠道編號:

android{
    productFlavors {
        // 把dev產品型號的apk的AndroidManifest中的channel替換dev
        "dev"{
            manifestPlaceholders = [channel:"dev"]
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

獨立配置簽名信息

對於簽名相關的信息,直接寫在gradle當然不好,特別是一些開源項目,可以添加到gradle.properties:

RELEASE_KEY_PASSWORD=xxxx 
RELEASE_KEY_ALIAS=xxx
RELEASE_STORE_PASSWORD=xxx
RELEASE_STORE_FILE=../.keystore/xxx.jks
  • 1
  • 2
  • 3
  • 4

然後在build.gradle中引用即可:

android {
    signingConfigs {
        release {
            storeFile file(RELEASE_STORE_FILE)
            storePassword RELEASE_STORE_PASSWORD
            keyAlias RELEASE_KEY_ALIAS
            keyPassword RELEASE_KEY_PASSWORD
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

如果不想提交到版本庫,可以添加到local.properties中,然後在build.gradle中讀取。

多渠道打包

多渠道打包的關鍵之處在於,定義不同的product flavor, 並把AndroiManifest中的channel渠道編號替換爲對應的flavor標識:

android {
    productFlavors {
        dev{
            manifestPlaceholders = [channel:"dev"]
        }
        official{
            manifestPlaceholders = [channel:"official"]
        }
        // ... ...
        wandoujia{
            manifestPlaceholders = [channel:"wandoujia"]
        }
        xiaomi{
            manifestPlaceholders = [channel:"xiaomi"]
        }
        "360"{
            manifestPlaceholders = [channel:"360"]
        }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

注意一點,這裏的flavor名如果是數字開頭,必須用引號引起來。
構建一下,就能生成一系列的Build Variant了:

devDebug
devRelease
officialDebug
officialRelease
wandoujiaDebug
wandoujiaRelease
xiaomiDebug
xiaomiRelease
360Debug
360Release
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

其中debug, release是gradle默認自帶的兩個build type, 下一節還會繼續說明。選擇一個,就能編譯出對應渠道的apk了。

自定義Build Type

前面說到默認的build type有兩種debug和release,區別如下:

// release版本生成的BuildConfig特性信息
public final class BuildConfig {
  public static final boolean DEBUG = false;
  public static final String BUILD_TYPE = "release";
}
// debug版本生成的BuildConfig特性信息
public final class BuildConfig {
  public static final boolean DEBUG = true;
  public static final String BUILD_TYPE = "debug";
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

現在有一種需求,增加一種build type,介於debug和release之間,就是和release版本一樣,但是要保留debug狀態(如果做過rom開發的話,類似於user debug版本),我們稱爲preview版本吧。
其實很簡單:

android {
    signingConfigs {
        debug {
            storeFile file(RELEASE_STORE_FILE)
            storePassword RELEASE_STORE_PASSWORD
            keyAlias RELEASE_KEY_ALIAS
            keyPassword RELEASE_KEY_PASSWORD
        }
        preview {
            storeFile file(RELEASE_STORE_FILE)
            storePassword RELEASE_STORE_PASSWORD
            keyAlias RELEASE_KEY_ALIAS
            keyPassword RELEASE_KEY_PASSWORD
        }
        release {
            storeFile file(RELEASE_STORE_FILE)
            storePassword RELEASE_STORE_PASSWORD
            keyAlias RELEASE_KEY_ALIAS
            keyPassword RELEASE_KEY_PASSWORD
        }
    }
    buildTypes {
        debug {
            manifestPlaceholders = [app_label:"@string/app_name_debug"]
        }
        release {
            manifestPlaceholders = [app_label:"@string/app_name"]
        }
        preview{
            manifestPlaceholders = [app_label:"@string/app_name_preview"]
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33

另外,build type還有一個好處,如果想要一次性生成所有的preview版本,執行assemblePreview即可,debug和releae版本同理。

build type中的定製參數

上面我們在不同的build type替換${app_label}爲不同的字符串,這樣安裝到手機上就能明顯的區分出不同build type的版本。
除此之外,可能還可以配置一些參數,我這裏列幾個我在工作中用到的:

android {
        debug {
            manifestPlaceholders = [app_label:"@string/app_name_debug"]
            applicationIdSuffix ".debug"
            minifyEnabled false
            signingConfig signingConfigs.debug
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
        release {
            manifestPlaceholders = [app_label:"@string/app_name"]
            minifyEnabled true
            shrinkResources true
            signingConfig signingConfigs.release
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
        preview{
            manifestPlaceholders = [app_label:"@string/app_name_preview"]
            applicationIdSuffix ".preview"
            debuggable true // 保留debug信息
            minifyEnabled true
            shrinkResources true
            signingConfig signingConfigs.preview
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

這些都用的太多了,稍微解釋一下:

// minifyEnabled 混淆處理
// shrinkResources 去除無用資源
// signingConfig 簽名
// proguardFiles 混淆配置
// applicationIdSuffix 增加APP ID的後綴
// debuggable 是否保留調試信息
// ... ...
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

多工程全局配置

隨着產品渠道的鋪開,往往一套代碼需要支持多個產品形態,這就需要抽象出主要代碼到一個Library,然後基於Library擴展幾個App Module。
相信每個module的build.gradle都會有這個代碼:

android {
    compileSdkVersion 22
    buildToolsVersion "23.0.1"
    defaultConfig {
        minSdkVersion 10
        targetSdkVersion 22
        versionCode 34
        versionName "v2.6.1"
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

當升級sdk、build tool、target sdk等,幾個module都要更改,非常的麻煩。最重要的是,很容易忘記,最終導致app module之間的差異不統一,也不可控。
強大的gradle插件在1.1.0支持全局變量設定,一舉解決了這個問題。
先在project的根目錄下的build.gradle定義ext全局變量:

ext {
    compileSdkVersion = 22
    buildToolsVersion = "23.0.1"
    minSdkVersion = 10
    targetSdkVersion = 22
    versionCode = 34
    versionName = "v2.6.1"
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

然後在各module的build.gradle中引用如下:

Android {
    compileSdkVersion rootProject.ext.compileSdkVersion
    buildToolsVersion rootProject.ext.buildToolsVersion
    defaultConfig {
        applicationId "com.xxx.xxx"
        minSdkVersion rootProject.ext.minSdkVersion
        targetSdkVersion rootProject.ext.targetSdkVersion
        versionCode rootProject.ext.versionCode
        versionName rootProject.ext.versionName
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

然後每次修改project級別的build.gradle即可實現全局統一配置。

自定義導出的APK名稱

默認android studio生成的apk名稱爲app-debug.apk或者app-release.apk,當有多個渠道的時候,需要同時編出50個渠道包的時候,就麻煩了,不知道誰是誰了。
這個時候,就需要自定義導出的APK名稱了,不同的渠道編出的APK的文件名應該是不一樣的。

android {
    // rename the apk with the version name
    applicationVariants.all { variant ->
        variant.outputs.each { output ->
            output.outputFile = new File(
                    output.outputFile.parent,
                    "lol-${variant.buildType.name}-${variant.versionName}-${variant.productFlavors[0].name}.apk".toLowerCase())
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

當apk太多時,如果能把apk按debug,release,preview分一下類就更好了(事實上,對於我這樣經常發版的人,一編往往就要編四五十個版本的人,debug和release版本全混在一起沒法看,必須分類),簡單:

android {
    // rename the apk with the version name
    // add output file sub folder by build type
    applicationVariants.all { variant ->
        variant.outputs.each { output ->
            output.outputFile = new File(
                    output.outputFile.parent + "/${variant.buildType.name}",
                    "lol-${variant.buildType.name}</span>-<span class="hljs-subst">${variant.versionName}-${variant.productFlavors[0].name}.apk".toLowerCase())
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

現在生成了類似於lol-dev-preview-v2.4.0.0.apk這樣格式的包了,preview的包自然就放在preview的文件夾下,清晰明瞭。

混淆技巧

混淆能讓反編譯的代碼可讀性變的很差,而且還能顯著的減少APK包的大小。

第一個技巧

相信很多朋友對混淆都覺得麻煩,甚至說,非常亂。因爲添加混淆規則需要查詢官方說明文檔,甚至有的官方文檔還沒說明。當你引用了太多庫後,添加混淆規則將使一場噩夢。
這裏介紹一個技巧,不用查官方文檔,不用逐個庫考慮添加規則。
首先,除了默認的混淆配置(android-sdk/tools/proguard/proguard-android.txt), 自己的代碼肯定是要自己配置的:
接下來是麻煩的第三方庫,一般來說,如果是極光推的話,它的包名是cn.jpush, 添加如下代碼即可:

dontwarn cn.jpush.**
-keep class cn.jpush.** { *; }
  • 1
  • 2

其他的第三庫也是如此,一個一個添加,太累!其實可以用第三方反編譯工具(比如jadx:https://github.com/skylot/jadx ),打開apk後,一眼就能看到引用的所有第三方庫的包名,把所有不想混淆或者不確定能不能混淆的,直接都添加又有何不可:

#####################################




### 第三方庫或者jar包
#

-dontwarn cn.jpush.**
-keep class cn.jpush.* { ; }
-dontwarn com.squareup.**
-keep class com.squareup.* { ; }
-dontwarn com.octo.**
-keep class com.octo.* { ; }
-dontwarn de.**
-keep class de.* { ; }
-dontwarn javax.**
-keep class javax.* { ; }
-dontwarn org.**
-keep class org.* { ; }
-dontwarn u.aly.**
-keep class u.aly.* { ; }
-dontwarn uk.**
-keep class uk.* { ; }
-dontwarn com.baidu.**
-keep class com.baidu.* { ; }
-dontwarn com.facebook.**
-keep class com.facebook.* { ; }
-dontwarn com.google.**
-keep class com.google.* { ; }
## ... ...

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

第二個技巧

一般release版本混淆之後,像友盟這樣的統計系統如果有崩潰異常,會記錄如下:

java.lang.NullPointerException: java.lang.NullPointerException
    at com.xxx.TabMessageFragment$7.run(Unknown Source)
  • 1
  • 2

這個Unknown Source是很要命的,排除錯誤無法定位到具體行了,大大降低調試效率。
當然,友盟支持上傳Mapping文件,可幫助定位,mapping文件的位置在:

project > module
> build > outputs > {flavor name} > {build type} > mapping.txt
  • 1
  • 2

如果版本一多,mapping.txt每次都要重新生成,還要上傳,終歸還是麻煩。
其實,在proguard-rules.pro中添加如下代碼即可:

-keepattributes SourceFile,LineNumberTabl
  • 1

當然apk包會大那麼一點點(我這裏6M的包,大個200k吧),但是再也不用mapping.txt也能定位到行了,爲了這種解脫,這個代價是值的!

動態設置一些額外信息

假如想把當前的編譯時間、編譯的機器、最新的commit版本添加到apk,而這些信息又不好寫在代碼裏,強大的gradle給了我創造可能的自信:

android {
    defaultConfig {
        resValue "string", "build_time", buildTime()
        resValue "string", "build_host", hostName()
        resValue "string", "build_revision", revision()
    }
}
def buildTime() {
    return new Date().format("yyyy-MM-dd HH:mm:ss")
}
def hostName() {
    return System.getProperty("user.name") + "@" + InetAddress.localHost.hostName
}
def revision() {
    def code = new ByteArrayOutputStream()
    exec {
        commandLine 'git', 'rev-parse', '--short', 'HEAD'
        standardOutput = code
    }
    return code.toString()
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

上述代碼實現了動態的添加了3個字符串資源: build_time、build_host、build_revision, 然後在其他地方可像如引用字符串一樣使用如下:

// 在Activity裏調用
getString(R.string.build_time)  
getString(R.string.build_host)  
getString(R.string.build_revision) 
  • 1
  • 2
  • 3
  • 4

給自己留個”後門”: 點七下

爲了調試方便,我們往往會在debug版本留一個顯示我們想看的界面,如何進入到一個界面,我們可以仿照android開發者選項的方式,點七下才顯示,我們來實現一個:

private int clickCount = 0;
private long clickTime = 0;
sevenClickView.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {
        if (clickTime == 0) {
            clickTime = System.currentTimeMillis();
        }
        if (System.currentTimeMillis() - clickTime > 500) {
            clickCount = 0;
        } else {
            clickCount++;
        }
        clickTime = System.currentTimeMillis();
        if (clickCount > 6) {
            // 點七下條件達到,跳到debug界面
        }
    }
});
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

release版本肯定是不能暴露這個界面的,也不能讓人用am在命令行調起,如何防止呢,可以在release版本把這個debug界面的exported設爲false。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章