Gradle核心(六):Gradle多項目、多渠道構建、測試

博客主頁html

參考資料
https://developer.android.goo...

Android Gradle 多項目構建

Android 項目通常分爲庫項目、應用項目、測試項目,對應的插件是com.android.librarycom.android.applicationcom.android.testjava

應用項目通常只有一個,最終打包成一個APK,庫項目能夠有多個,能夠被應用項目引用。python

多項目設置

在Gradle中能夠建立多個項目,而且能夠經過文件夾管理,最終在settings.gradle裏配置就能夠。android

// 項目結構
MyProject
  + app
  + libraries
     + lib1
     + lib2
settings.gradle

上面項目結構中,一個根項目MyProject,並有一個settings.gradle配置文件,Sub Project有一個應用項目 App,兩個庫項目 lib1 和 lib2 放在libraries文件夾下。git

settings.gradle文件中配置github

// void include(String[] projectPaths);
include ':app', ':libraries:lib1', ':libraries:lib1:lib2'

若是項目路徑不少,能夠下面方式指定配置shell

include ':example1'
project(":example1").projectDir = new File(rootDir, 'chapter/example1')

庫項目引用和配置

庫項目引用經過dependencies實現。Android Lib打包生成的是aar包,Java Lib打包生成的是jar包,aar包能夠有res資源。數據庫

dependencies {
  implementation project(':libraries:lib1')
}

引用Android庫項目,其實就是引用庫項目發佈的aar包。 默認Android庫項目發佈都是release版本,能夠配置修改默認發佈segmentfault

android {
    // 配置 發佈debug版本的aar包
    defaultPublishConfig "debug"

   // 若是配置多個flavor,能夠配置flavor + buildtype
   // defaultPublishConfig "flavorDebug"
}

發佈多個版本aar,默認狀況下,是不能同時發佈多個arr包,可是能夠開啓api

android {
    // 告訴Android Gradle插件,可同時發佈不一樣的aar包
    publishNonDefault true
}

其它項目就能夠引用不一樣的aar

dependencies {
  flavor1Implementation project(path: ':lib1', configuration: 'flavor1Release')
  flavor2Implementation project(path: ':lib1', configuration: 'flavor2Release')
}

發佈aar包到Maven中心庫

  1. build.gradle文件中應用Maven插件
apply plugin: 'com.android.library'
// 應用Maven倉庫
apply plugin: 'maven'
  1. 配置Maven構建三要素,分別是group:artifact:version
// build.gradle
apply plugin: 'com.android.library'
// 應用Maven倉庫
apply plugin: 'maven

group = 'com.custom.plugin'
version = '1.0.2'

爲了更好的聯調測試,提供快照版本SNAPSHOT,如:配置成1.0.0-SNAPSHOT。發佈到snapshot中心庫時,每次發佈版本號不會變化,只會在版本號後按順序號+1,如:1.0.0-1,1.0.0-2,1.0.0-3等。引用時版本號寫成1.0.0-SNAPSHOT便可,Maven會自動下載最新版本快照。

  1. 發佈配置,如:發佈哪一個Maven倉庫,使用的用戶名和密碼,發佈什麼格式的存檔,artifact是什麼等
boolean  needUploadToLocal = false;//是否將Library發佈到本地
boolean  isArchivesRelease = false;//是否將Library發佈到Release倉庫;false 爲發佈到SnapShot倉庫
//gradlew :sub-project:newsindiasdk:clean :sub-project:newsindiasdk:uploadArchives
//com.cmcm.onews.sdk:onews_sdk:5.3.1.12-SNAPSHOT@aar


apply plugin: 'maven'

//注意了,之後maven賬戶請在local.properties裏配置,eg:
//maven.u= your user
//maven.p= your pwd
Properties props = new Properties()
props.load(new FileInputStream(project.rootProject.file("local.properties")))
String u = props.get('maven.u');
String p = props.get('maven.p');


uploadArchives {
    repositories {
        mavenDeployer {
            if (needUploadToLocal) {
                pom.version = "Debug"
                repository(url: "D:/NewsArch")
            } else {
                pom.version = "6.3.1.3"

                if (isArchivesRelease) {
                    repository(url: "http://10.60.80.74:8081/nexus/content/repositories/cleanmasterrelease") {
                        authentication(userName: u, password: p)
                    }
                } else {
                    pom.version += "-SNAPSHOT"  // -SNAPSHOT
                    repository(url: "http://10.60.80.74:8081/nexus/content/repositories/cleanmastersnapshot") {
                        authentication(userName: u, password: p)
                    }
                }
            }
            pom.artifactId = "onews_sdk"
            pom.groupId = "com.cmcm.onews.sdk"
        }
    }
}
  1. 使用它們須要配置倉庫,由於是私有倉庫,使用時告訴Gradle
// Root Project中build.gradle
buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:3.0.1'
    }
}

allprojects {
    repositories {
        jcenter()
        // 發佈版本
        maven {
            url "http://10.60.80.74:8081/nexus/content/repositories/cleanmasterrelease"
        }
        // 快照版本
        maven {
            url "http://10.60.80.74:8081/nexus/content/repositories/cleanmastersnapshot"
        }
    }
}


// Sub Project中添加依賴
dependencies {
    implementation "com.cmcm.onews.sdk:onews_sdk:6.2.4.7-SNAPSHOT@aar"
}

Android Gradle 多渠道構建

多渠道構建基本原理

在Android Gradle中,有一個Build Variant概念,翻譯就是構建變體,構建的產物(APK)。

Build Variant = Project Flavor + Build Type

Build Type 構建類型,如:Release,Debug;Project Flavor 構建渠道,如:Baidu,Google。
Build Variant 構建變體,如:baiduRelease,baiduDebug,googleRelease,googleDebug

Android Gradle提供productFlavors 方法添加不一樣的渠道,參數接受域對象類型,ProductFlavor做爲閉包參數

android {
  productFlavors {
    baidu {}
    google {}
  }
}

配置發佈渠道後,Android Gradle就會產生不少Task,基本上都是基於 Project Flavor + Build Type方式生成的,如:assembleBaidu,assembleRelease,assembleBaiduRelease。assemble開頭的負責生成構建產物APK。

每一個Project Flavor,也就是每一個渠道,能夠定義本身的SourceSet,Dependencies依賴。

Flurry多渠道 和 友盟多渠道 構建

  1. Flurry多渠道配置

Flurry的統計是已Application劃分渠道的,每一個Application都有一個key。在Flurry上建立Application時自動生成,能夠爲每一個渠道配置不一樣的Flurry Key,使用BuildConfig配置。

android {
  productFlavors {
    baidu {
        buildConfigField 'String', 'FLURRY_KEY', "\"QHHJNNGGHJK\""
    }

    google {
        buildConfigField 'String', 'FLURRY_KEY', "\"kkkiihhhgggv\""
    }
  }
}


...
Flurry.init(this, BuildConfig.FLURRY_KEY);
  1. 友盟多渠道配置

友盟存在渠道概念,但它不是在代碼中指定的,而是在AndroidManifest.xml文件中配置的,經過配置meta-data標籤來設置。

<meta-data
            android:name="UMENG_CHANNEL"
            android:value="Channel ID" />

Channel ID就是渠道值,如:Baidu,Google。可經過manifestPlaceholders來動態改變渠道值。

多渠道構建定製

經過配置Android Gradle 插件的 ProductFlavor可靈活控制每一個渠道包.

  1. applicationId

它是ProductFlavor屬性,設置該渠道的包名,想爲渠道設置特別的包名,可使用applicationId這個屬性設置

android {
  productFlavors {
    baidu {
        applicationId "com.gradle.test.baidu"
    }
  }
}
  1. consumerProguardFiles

便是一個屬性,也有一個同名的方法,只對Android庫項目有用。consumerProguardFiles方法是一直添加,不會清空以前的混淆文件,而consumerProguardFiles屬性方式每次都是新的混淆文件列表,之前的配置會先被清空。

// 屬性
    public void setConsumerProguardFiles(@NonNull Iterable<?> proguardFileIterable) {
        getConsumerProguardFiles().clear();
        consumerProguardFiles(Iterables.toArray(proguardFileIterable, Object.class));
    }
   // 方法
   public void consumerProguardFiles(@NonNull Object... proguardFiles) {
        for (Object proguardFile : proguardFiles) {
            consumerProguardFile(proguardFile);
        }
    }

當發佈庫項目生成AAR時,使用consumerProguardFiles配置的混淆文件也會被打包到AAR裏一塊兒發佈,當應用項目引用這個AAR時,並啓動混淆時,會自動使用AAR包裏的混淆文件對AAR包裏代碼進行混淆,就不用對AAR包進行混淆配置,由於AAR自帶了。

android {
  productFlavors {
    baidu {
        consumerProguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
    }
  }
}
  1. manifestPlaceholders
  2. multiDexEnabled

啓動多個dex配置,用來突破65535方法問題。

  1. proguardFiles

配置混淆文件

  1. signingConfig

配置簽名

  1. testApplicationId

是一個屬性,用來適配測試包的包名,值通常爲App的包名+.test

android {
  productFlavors {
    baidu {
        testApplicationId "com.gradle.test"
    }
  }
}
  1. testFunctionalTest 和 testHandleProfiling

testFunctionalTest 表示是否爲功能測試,testHandleProfiling表示是否啓動分析功能。

android {
  productFlavors {
    baidu {
          testFunctionalTest true
          testHandleProfiling true
    }
  }
}
  1. testInstrumentationRunner

配置運行測試使用的 Instrumentation Runner 的全路徑的類名,且必須是android.app.Instrumentation 的子類。

android {
  productFlavors {
    baidu {
          testInstrumentationRunner "android.test.InstrumentationTestRunner"
    }
  }
}
  1. testInstrumentationRunnerArguments

與testInstrumentationRunner一塊兒使用,配置 Instrumentation Runner 使用的參數,最終使用的都是adb shell am instrument 這個命令。testInstrumentationRunnerArguments 參數被轉換傳遞給 am instrument這個命令使用,如:-e key value

android {
  productFlavors {
    baidu {
          testInstrumentationRunnerArguments.put("converage", 'true')
    }
  }
}
  1. versionName 和 versionCode

配置渠道的版本號和版本名稱

android {
  productFlavors {
    baidu {
          versionName "2.1.5"
          versionCode 215
    }
  }
}
  1. dimension

爲了基於不一樣標準構建App,能夠經過dimension 多維度的方式解決。

dimensionProductFlavor的屬性,接受一個字符串,該字符串就是維度名稱,做爲ProductFlavor的維度。維度名稱不是隨意指定的,在使用前須要聲明,能夠經過flavorDimensions 方法聲明。

android {
          // 聲明維度後,才能在productFlavors中使用
          // flavorDimensions 可同時指定多個維度,可是維度有順序優先級的,第一個優先級最大
          flavorDimensions 'api', 'version'
     
          productFlavors {
              demo {
                dimension 'version'
                ...
              }
     
             full {
                dimension 'version'
                 ...
             }
     
             minApi24 {.
               dimension 'api'
               minSdkVersion '24'
               versionNameSuffix "-minApi24"
               ...
             }
     
             minApi21 {
              dimension "api"
              minSdkVersion '21'
              versionNameSuffix "-minApi21"
              ...
            }
         }
      }

上例中,最後生成的variant(構建變體)會被幾個 ProductFlavor對象配置:

  1. Android中的defaultConfig配置,也是一個ProductFlavor
  2. api維度的ProductFlavor,被dimension 配置標記爲api的ProductFlavor
  3. version維度的ProductFlavor, 被dimension 配置標記爲version的ProductFlavor

維度優先級很重要,高優先級的flavor會替換掉低優先級的資源、代碼、配置等,上例中優先級:api>version>defaultConfig

經過dimension 指定維度後,Android Gradle會幫助生成相應 Task、SourceSet、Dependencies等。如今構建變體的產物=Api+Version+BuildType, 如:MinApi21DemoRelease、MinApi21FullRelease、MinApi21DemoDebug、MinApi21FullDebug等

提供多渠道構建的效率

生成多個渠道包主要是爲了跟蹤每一個渠道的狀況,如:新增、活躍、留存。除了根據渠道號區分每一個渠道外,大部分狀況下沒有什麼不一樣,惟一區別是屬於哪一個渠道。

由於Android Gradle對每一個渠道包都要執行構建過程,致使速度變慢。美團研究一個辦法,在APK的MEAT-INF目錄下添加空文件不用從新簽名原理。

  1. 利用Android Gradle打一個基本包(母包)
  2. 基於母包複製一個,文件名要區分產品,打包時間,版本,渠道
  3. 對複製的APK進行修改,在META-INF目錄下新增空文件,文件名必需要區分渠道,如:mtchannel_google
  4. 利用python腳本執行 2, 3 步驟操做

使用時,在APK啓動(Application onCreate)讀取APK中META-INF目錄下的前綴爲mtchannel_文件,若是找到,把文件名取出來,而後就能夠獲得渠道標識(google)了,美團實現的代碼:

public static String getChannel(Context context) {
        ApplicationInfo appinfo = context.getApplicationInfo();
        String sourceDir = appinfo.sourceDir;
        String ret = "";
        ZipFile zipfile = null;
        try {
            zipfile = new ZipFile(sourceDir);
            Enumeration<?> entries = zipfile.entries();
            while (entries.hasMoreElements()) {
                ZipEntry entry = ((ZipEntry) entries.nextElement());
                String entryName = entry.getName();
                if (entryName.startsWith("mtchannel")) {
                    ret = entryName;
                    break;
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (zipfile != null) {
                try {
                    zipfile.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

        String[] split = ret.split("_");
        if (split != null && split.length >= 2) {
            return ret.substring(split[0].length() + 1);

        } else {
            return "";
        }
    }

利用python腳本批處理,向APK中META-INF目錄寫入渠道文件,文件名前綴爲mtchannel_

import zipfile
zipped = zipfile.ZipFile(your_apk, 'a', zipfile.ZIP_DEFLATED) 
empty_channel_file = "META-INF/mtchannel_{channel}".format(channel=your_channel)
zipped.write(your_empty_file, empty_channel_file)

而後就是配置渠道列表,下載AndroidMultiChannelBuildTool工程後,在PythonTool/Info/channel.txt文件中添加渠道,渠道以換行隔開。

將想要批量打包的apk文件拷貝到PythonTool目錄下(與MultiChannelBuildTool.py同級),運行py腳本便可打包完成。(生成的渠道apk包在output_** 目錄下)

參考資料:
https://www.cnblogs.com/ct201...
https://github.com/GavinCT/An...
https://github.com/Meituan-Di...

Android Gradle 測試

Android爲測試程序提供了很好支持,既可使用傳統的JUnit測試,又可使用Android提供的Instrument測試。

基本概念

使用Android Studio新建一個項目時,會幫助咱們默認生成 mainandroidTest SourceSet。運行測試時,androidTest SourceSet會被構建成一個能夠安裝到設備上測試的APK,這個測試APK中有寫好的測試用例,會被執行來測試APP。

在androidTest SourceSet中能夠依賴各類測試庫,如:單元測試的,集成測試的,espresso UI測試的,uiautomator自動化測試的。
通常測試APK會統一配置,而不是針對每一個渠道都配置,在defaultConfig對測試APK配置後,會自動生成所需的包名、AndroidManifest.xml文件等信息。

android {
    compileSdkVersion 29
    buildToolsVersion "29.0.2"
    defaultConfig {
        testApplicationId "com.example.myapplication.test"
        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
        testFunctionalTest true
        testHandleProfiling true
    }
}

根據配置自動生成AndroidManifest.xml文件,android:targetPackage是Android自動生成的。

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.myapplication.test" >

    <uses-sdk
        android:minSdkVersion="21"
        android:targetSdkVersion="29" />

    <instrumentation
        android:name="androidx.test.runner.AndroidJUnitRunner"
        android:functionalTest="true"
        android:handleProfiling="true"
        android:label="Tests for com.example.myapplication"
        android:targetPackage="com.example.myapplication" />

    <application android:debuggable="true" >
        <uses-library android:name="android.test.runner" />
    </application>

</manifest>

也能夠在androidTest中配置依賴,正式APK不會編譯到APK中,只有Android測試的時候纔會被編譯到測試APK中。

dependencies {
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'androidx.test:runner:1.2.0'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
}

默認狀況下測試APK主要在debug模式下,debug模式下不會混淆代碼,有利於發現問題,且對測試代碼覆蓋率也有幫助。Android Gradle提供了testBuildType,能夠修改BuildType

android {
   // 修改測試的是release類型的apk,默認類型是debug
   testBuildType 'release'
}

怎麼運行寫好的測試代碼呢?
使用 gradlew connectedCheck 任務來運行測試。這個任務是一個引導性的任務,首先使用assembleAndroidTestassembleDebug 任務構建測試應用 和 被測試應用,而後經過*install任務安裝這兩個應用,再運行寫好的測試代碼,運行完後,卸載這兩個應用。

最後測試結果會保存在build/reports/androidTests/connected目錄下,能夠經過瀏覽器查看index.html測試結果。

本地單元測試

這種測試和原生的java測試同樣,不依賴android框架或只有很是少的依賴,直接運行在本地開發機器上,不須要運行在Android設備上。但有時也須要Android框架自己一些代碼依賴,如:Context,可使用模擬框架來模擬這種依賴關係,如:Mockito 和 JMock

AndroidTest測試有本身的SourceSet目錄 src/androidTest/java; 對於本地單元測試也有本身的目錄src/test/java,測試用例用來測試main這個SourceSet代碼。

Android本地單元測試,也使用JUnit這個流行的測試框架測試

dependencies {
    // JUnit3的測試用例須要集成junit.framework.TestCase,且測試方法要以test爲前綴
    // JUnit4只須要使用 @Test 註解標記就能夠,推薦使用JUnit4
    testImplementation 'junit:junit:4.12'

編寫好測試用例後, 運行 gradlew test 任務能夠運行全部的單元測試用例,而後在build/reports/tests目錄下生成測試報告。

若是想運行debug模式下的使用gradlew testDebugUnitTest任務。

在執行test任務時,若是想依賴Android框架,只能使用模擬對象框架,如:Mockito ,版本要是1.9.5以上,與Android單元測試兼容。

dependencies {
    testImplementation 'junit:junit:4.12'
    testImplementation 'org.mockito:mockito-all:1.10.19'
}

編寫須要測試的代碼,須要使用Context

public class Utils {

    private Context mContext;
    public Utils(Context context) {
        this.mContext = context;
    }


    public String getAppName(){
        return mContext.getString(R.string.app_name);
    }

}

若是要測試上面的代碼,由於須要一個Context,就要使用Mockito來模擬Context

import android.content.Context;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;

import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;

/**
 * kerwin <xujinbing839@pingan.com.cn> 2019-09-11
 */
@RunWith(MockitoJUnitRunner.class)
public class UtilsTest {

    private static final String APP_NAME = "MyApplication";

    @Mock
    Context mMockContext;

    @Test
    public void readAppNameFromContext() {
        when(mMockContext.getString(R.string.app_name)).thenReturn(APP_NAME);

        Utils utils = new Utils(mMockContext);
        String appName = utils.getAppName();

        assertThat(appName, is(APP_NAME));
    }
}

首先要告訴JUnit4,要使用MockitoJUnitRunner這個單元測試的運行者來執行,否則 @Mock 註解就不認識了。使用@Mock 註解模擬一個Context對象,mMockContext就是被Mockito模擬出來的。

when邏輯須要和Utils裏的getAppName方法邏輯同樣,而後使用thenReturn告訴模擬指望返回的值

使用 gradlew test 執行任務,查看報告結果。

參考文獻
https://static.javadoc.io/org...

Instrument測試

Instrument測試是基於Android設備或模擬器的測試,是一種高模擬和仿真測試。它可使用Android SDK框架的全部類和特性,如:Context。還提供了Instrumenttation類,能夠很方便的得到測試APK的Context、Activity。且可使用Instrument測試作單元測試、UI自動化測試、集成測試。

Instrument測試要生成一個測試的APK,因此要對測試APK配置。testInstrumentationRunner 這個runner能夠編寫基於JUnit4測試用例,且可搭配使用JUnit4新特性。

android {
    defaultConfig {
        // 指定生成測試APK的包名,默認:被測試APK包名+test
        testApplicationId "com.example.myapplication.test"
        // 配置使用Runner
        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }
}

// 添加依賴
dependencies {
    androidTestImplementation 'androidx.test:runner:1.2.0'
    androidTestImplementation 'androidx.test:rules:1.2.0'
    // Optional
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
    // Optional
    androidTestImplementation 'org.hamcrest:hamcrest-library:2.1'
    // Optional
    androidTestImplementation 'androidx.test.uiautomator:uiautomator-v18:2.2.0-alpha1'
}

rules庫,爲測試定義一些規則,實現自JUnit的rule,能夠對JUnit擴展。如:ActivityTestRule指定要測試的Activity。編寫好測試用例後,運行gradlew connectedAndroidTest 執行全部Instrument測試,在build/reports/androidTests目錄下查看報告.

import android.util.Log;

import androidx.test.filters.LargeTest;
import androidx.test.rule.ActivityTestRule;
import androidx.test.runner.AndroidJUnit4;

import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;

@RunWith(AndroidJUnit4.class)
// LargeTest標記,說明有更高的權限,如多線程、訪問數據庫、時間限制也更長
@LargeTest
public class ExampleInstrumentedTest {
  
    // 指定規則,測試MainActivity
    @Rule
    public ActivityTestRule<MainActivity> mActivityRule = new ActivityTestRule<>(MainActivity.class);

    @Before
    public void init() {
        Log.d("kerwin_test", "init :::" + Thread.currentThread().getName());
        //  init :::Instr: androidx.test.runner.AndroidJUnitRunner
    }

    @Test
    public void  valid() throws Throwable {
        mActivityRule.runOnUiThread(new Runnable() {
            @Override
            public void run() {
                mActivityRule.getActivity().findViewById(R.id.text1).performClick();
            }
        });
    }
}

測試選項配置

Android Gradle插件提供testOptions { } ,可對測試進行配置,如:生成測試報告的目錄。

TestOptions提供配置項:

  1. resultsDir 是一個屬性,配置生成測試結果目錄
  2. reportDir 是一個屬性,配置生成測試報告目錄
  3. unitTests 便是屬性,也是一個閉包,控制單元測試的執行
android {
    testOptions {
        resultsDir "${project.buildDir}/myResults"
        reportDir "${project.buildDir}/myReports"
    }
}

單個項目,測試報告能夠生成在指定的目錄下,有多個項目怎麼辦呢?

好比引用了多個庫項目,每一個庫項目也有本身的測試,生成本身的報告,這樣比較分散,不容易查看,若是統一塊兒來查看就方便了。Android 提供了另外一個插件 android-reporting ,應用後新增一個名爲 mergeAndroidReports 任務,執行完測試後調用便可。

// 在Root Project中的build.gradle 文件最後應用後,添加的任務也在Root項目中。
apply plugin: 'android-reporting'

而後執行 gradlew deviceCheck mergeAndroidReports --continue 任務。mergeAndroidReports合併報告,--continue在測試失敗的時候,也繼續執行其餘測試用例,一直執行完成爲止。合併後的報告在Root項目的build目錄中。

unitTests配置,對應的類型是UnitTestOptions,它是全部測試任務的一個集合。UnitTestOptions 對象有一個Test類型的域對象集合DomainObjectSet。對應源碼:

public static class UnitTestOptions {
     private DomainObjectSet<Test> testTasks = new DefaultDomainObjectSet<Test>(Test.class);

     public void all(final Closure<Test> configClosure) {
            testTasks.all(
                    new Action<Test>() {
                        @Override
                        public void execute(Test testTask) {
                            ConfigureUtil.configure(configClosure, testTask);
                        }
                    });
     }
}

all方法能夠遍全部的Test,它是Task類型。能夠對他們作一些配置,或者根據任務作一些判斷等。

android {
     testOptions {
        unitTests.all {
           println "testName: >>>>>>${it.name}"
        }
    }
}

代碼覆蓋率

有了測試用例,就要有相應的測試代碼覆蓋率統計,這樣才能知道代碼是否被測試用例徹底覆蓋,還有哪些沒有覆蓋到,如何進行補全測試用例。Android Gradle內置了代碼覆蓋lv的報告生成,默認是關閉的。

android {
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }

        debug {
            testCoverageEnabled true
        }
    }
}

testCoverageEnabled用於控制代碼覆蓋率統計是否開啓,它是BuildType的一個屬性,true表明開啓,默認是false。

運行 gradlew createDebugCoverageReport 任務後,就會自動執行測試用例,並生成測試代碼覆蓋率報告,報告路徑build/reports/coverage

Lint支持

Android提供了針對代碼、資源優化工具Lint。可幫助檢查哪些資源沒有被使用,哪些使用了新的API,哪些資源沒有國際化等,並生成一份報告,告訴哪些須要優化。

運行 gradle lint 任務便可生成報告,默認生成報告在build/reports/lint-results.html

Lint是一個命令行工具,在Android Tools目錄下。在Android Gradle插件提供了 lintOptions {}這個閉包配置Lint。

android {
    lintOptions {
        // 遇到錯誤終止構建
        abortOnError true
        // 警告也會被當前錯誤處理
        warningsAsErrors true
        // 須要檢查是否使用了新的API
        check 'NewApi'
    }
}

1. abortOnError

是一個屬性,接受boolean類型值,配置Lint發現錯誤時是否退出Gradle構建。默認true

2. absolutePaths

是一個屬性,接受boolean類型值,配置錯誤的輸出裏是否應該顯示絕對路徑,默認true,顯示相對路徑

3. check

是一個屬性,也是一個方法,配置哪些項目須要Lint檢查,這個項目就是Issue Id(s)

NewApi這個就是一個issue id,lint有不少可用的issue id,經過lint --list能夠查看可用的id。冒號前面是id,後面是對這個issue id的說明。可使用lint --show命令查看詳細說明.

Valid issue categories:
    Correctness
    Correctness:Messages
    Correctness:Chrome OS
    Security
    Performance
    Usability:Typography
    Usability:Icons
    Usability
    Accessibility
    Internationalization
    Internationalization:Bidirectional Text

Valid issue id's:
"ContentDescription": Image without contentDescription
"AddJavascriptInterface": addJavascriptInterface Called
"ShortAlarm": Short or Frequent Alarm
"AllCaps": Combining textAllCaps and markup
"AllowAllHostnameVerifier": Insecure HostnameVerifier
"AlwaysShowAction": Usage of showAsAction=always
"InvalidUsesTagAttribute": Invalid name attribute for uses element.
"MissingIntentFilterForMediaSearch": Missing intent-filter with action
      android.media.action.MEDIA_PLAY_FROM_SEARCH
"MissingMediaBrowserServiceIntentFilter": Missing intent-filter with action
      android.media.browse.MediaBrowserService.
"MissingOnPlayFromSearch": Missing onPlayFromSearch.
"ImpliedTouchscreenHardware": Hardware feature touchscreen not explicitly
      marked as optional
"MissingTvBanner": TV Missing Banner
"MissingLeanbackLauncher": Missing Leanback Launcher Intent Filter.
"MissingLeanbackSupport": Missing Leanback Support.
"PermissionImpliesUnsupportedHardware": Permission Implies Unsupported
      Hardware
"UnsupportedTvHardware": Unsupported TV Hardware Feature
"SupportAnnotationUsage": Incorrect support annotation usage
"ShiftFlags": Dangerous Flag Constant Declaration
"LocalSuppress": @SuppressLint on invalid element
"SwitchIntDef": Missing @IntDef in Switch
"UniqueConstants": Overlapping Enumeration Constants
"InlinedApi": Using inlined constants on older versions
"Override": Method conflicts with new inherited method
"ObsoleteSdkInt": Obsolete SDK_INT Version Check
"NewApi": Calling new methods on older versions
"UnusedAttribute": Attribute unused on older versions
"AppCompatMethod": Using Wrong AppCompat Method
"AppCompatCustomView": Appcompat Custom Widgets
"AppCompatResource": Menu namespace
"GoogleAppIndexingApiWarning": Missing support for Firebase App Indexing Api
"GoogleAppIndexingWarning": Missing support for Firebase App Indexing
"AppLinksAutoVerifyError": App Links Auto Verification Failure
"AppLinksAutoVerifyWarning": Potential App Links Auto Verification Failure
"AppLinkUrlError": URL not supported by app for Firebase App Indexing
"TestAppLink": Unmatched URLs
"InconsistentArrays": Inconsistencies in array element counts
"Assert": Assertions
"BadHostnameVerifier": Insecure HostnameVerifier
"BatteryLife": Battery Life Issues
"BackButton": Back button
"ButtonCase": Cancel/OK dialog button capitalization
"ButtonOrder": Button order
"ButtonStyle": Button should be borderless
"ByteOrderMark": Byte order mark inside files
"MissingSuperCall": Missing Super Call
"AdapterViewChildren": AdapterViews cannot have children in XML
"ScrollViewCount": ScrollViews can have only one child
"PermissionImpliesUnsupportedChromeOsHardware": Permission Implies Unsupported
      Chrome OS Hardware
"UnsupportedChromeOsHardware": Unsupported Chrome OS Hardware Feature
"GetInstance": Cipher.getInstance with ECB
"CommitTransaction": Missing commit() calls
"Recycle": Missing recycle() calls
"CommitPrefEdits": Missing commit() on SharedPreference editor
"ApplySharedPref": Use apply() on SharedPreferences
"ClickableViewAccessibility": Accessibility in Custom Views
"EasterEgg": Code contains easter egg
"StopShip": Code contains STOPSHIP marker
"MissingConstraints": Missing Constraints in ConstraintLayout
"VulnerableCordovaVersion": Vulnerable Cordova Version
"CustomViewStyleable": Mismatched Styleable/Custom View Name
"CutPasteId": Likely cut & paste mistakes
"SimpleDateFormat": Implied locale in date format
"SetTextI18n": TextView Internationalization
"Deprecated": Using deprecated resources
"MissingPrefix": Missing Android XML namespace
"MangledCRLF": Mangled file line endings
"DuplicateIncludedIds": Duplicate ids across layouts combined with include
      tags
"DuplicateIds": Duplicate ids within a single layout
"DuplicateDefinition": Duplicate definitions of resources
"ReferenceType": Incorrect reference types
"StringEscaping": Invalid string escapes
"UnpackedNativeCode": Missing android:extractNativeLibs=false
"UnsafeDynamicallyLoadedCode": load used to dynamically load code
"UnsafeNativeCodeLocation": Native code outside library directory
"EllipsizeMaxLines": Combining Ellipsize and Maxlines
"ExifInterface": Using android.media.ExifInterface
"ExtraText": Extraneous text in resource files
"FieldGetter": Using getter instead of field
"InvalidAnalyticsName": Invalid Analytics Name
"MissingFirebaseInstanceTokenRefresh": Missing Firebase Instance ID Token
      Refresh
"FontValidationError": Validation of font files
"FontValidationWarning": Validation of font files
"FullBackupContent": Valid Full Backup Content File
"ValidFragment": Fragment not instantiatable
"GetContentDescriptionOverride": Overriding getContentDescription() on a View
"PackageManagerGetSignatures": Potential Multiple Certificate Exploit
"AccidentalOctal": Accidental Octal
"UseOfBundledGooglePlayServices": Use of bundled version of Google Play
      services
"GradleCompatible": Incompatible Gradle Versions
"GradleDependency": Obsolete Gradle Dependency
"GradleDeprecated": Deprecated Gradle Construct
"DevModeObsolete": Dev Mode Obsolete
"DuplicatePlatformClasses": Duplicate Platform Classes
"GradleGetter": Gradle Implicit Getter Call
"GradlePluginVersion": Incompatible Android Gradle Plugin
"HighAppVersionCode": VersionCode too high
"GradleIdeError": Gradle IDE Support Issues
"GradlePath": Gradle Path Issues
"GradleDynamicVersion": Gradle Dynamic Version
"NotInterpolated": Incorrect Interpolation
"StringShouldBeInt": String should be int
"NewerVersionAvailable": Newer Library Versions Available
"MinSdkTooLow": API Version Too Low
"GridLayout": GridLayout validation
"HandlerLeak": Handler reference leaks
"HardcodedDebugMode": Hardcoded value of android:debuggable in the manifest
"HardcodedText": Hardcoded text
"HardwareIds": Hardware Id Usage
"IconDuplicatesConfig": Identical bitmaps across various configurations
"IconDuplicates": Duplicated icons under different names
"GifUsage": Using .gif format for bitmaps is discouraged
"IconColors": Icon colors do not follow the recommended visual style
"IconDensities": Icon densities validation
"IconDipSize": Icon density-independent size validation
"IconExpectedSize": Icon has incorrect size
"IconExtension": Icon format does not match the file extension
"IconLauncherShape": The launcher icon shape should use a distinct silhouette
"IconLocation": Image defined in density-independent drawable folder
"IconMissingDensityFolder": Missing density folder
"IconMixedNinePatch": Clashing PNG and 9-PNG files
"IconNoDpi": Icon appears in both -nodpi and dpi folders
"IconXmlAndPng": Icon is specified both as .xml file and as a bitmap
"ConvertToWebp": Convert to WebP
"WebpUnsupported": WebP Unsupported
"IncludeLayoutParam": Ignored layout params on include
"DisableBaselineAlignment": Missing baselineAligned attribute
"InefficientWeight": Inefficient layout weight
"NestedWeights": Nested layout weights
"Orientation": Missing explicit orientation
"Suspicious0dp": Suspicious 0dp dimension
"InstantApps": Instant App Issues
"DuplicateDivider": Unnecessary Divider Copy
"TrustAllX509TrustManager": Insecure TLS/SSL trust manager
"InvalidImeActionId": Invalid imeActionId declaration
"InvalidPackage": Package not included in Android
"DrawAllocation": Memory allocations within drawing code
"UseSparseArrays": HashMap can be replaced with SparseArray
"UseValueOf": Should use valueOf instead of new
"JavascriptInterface": Missing @JavascriptInterface on methods
"JobSchedulerService": JobScheduler problems
"KeyboardInaccessibleWidget": Keyboard inaccessible widget
"LabelFor": Missing labelFor attribute
"InconsistentLayout": Inconsistent Layouts
"InflateParams": Layout Inflation without a Parent
"StaticFieldLeak": Static Field Leaks
"DefaultLocale": Implied default locale in case conversion
"LocaleFolder": Wrong locale name
"GetLocales": Locale crash
"InvalidResourceFolder": Invalid Resource Folder
"WrongRegion": Suspicious Language/Region Combination
"UseAlpha2": Using 3-letter Codes
"LogConditional": Unconditional Logging Calls
"LongLogTag": Too Long Log Tags
"LogTagMismatch": Mismatched Log Tags
"AllowBackup": AllowBackup/FullBackupContent Problems
"MissingApplicationIcon": Missing application icon
"DeviceAdmin": Malformed Device Admin
"DuplicateActivity": Activity registered more than once
"DuplicateUsesFeature": Feature declared more than once
"GradleOverrides": Value overridden by Gradle build script
"IllegalResourceRef": Name and version must be integer or string, not
      resource
"MipmapIcons": Use Mipmap Launcher Icons
"MockLocation": Using mock location provider in production
"MultipleUsesSdk": Multiple <uses-sdk> elements in the manifest
"ManifestOrder": Incorrect order of elements in manifest
"MissingVersion": Missing application name/version
"OldTargetApi": Target SDK attribute is not targeting latest version
"UniquePermission": Permission names are not unique
"UsesMinSdkAttributes": Minimum SDK and target SDK attributes not defined
"WearableBindListener": Usage of Android Wear BIND_LISTENER is deprecated
"WrongManifestParent": Wrong manifest parent
"InvalidPermission": Invalid Permission Attribute
"ManifestResource": Manifest Resource References
"ManifestTypo": Typos in manifest tags
"FloatMath": Using FloatMath instead of Math
"MergeMarker": Code contains merge marker
"MergeRootFrame": FrameLayout can be replaced with <merge> tag
"IncompatibleMediaBrowserServiceCompatVersion": Obsolete version of
      MediaBrowserServiceCompat
"InnerclassSeparator": Inner classes should use $ rather than .
"Instantiatable": Registered class is not instantiatable
"MissingRegistered": Missing registered class
"MissingId": Fragments should specify an id or tag
"LibraryCustomView": Custom views in libraries should use res-auto-namespace
"ResAuto": Hardcoded Package in Namespace
"NamespaceTypo": Misspelled namespace declaration
"UnusedNamespace": Unused namespace
"NegativeMargin": Negative Margins
"NestedScrolling": Nested scrolling widgets
"NetworkSecurityConfig": Valid Network Security Config File
"MissingBackupPin": Missing Backup Pin
"PinSetExpiry": Validate <pin-set> expiration attribute
"NfcTechWhitespace": Whitespace in NFC tech lists
"UnlocalizedSms": SMS phone number missing country code
"ObjectAnimatorBinding": Incorrect ObjectAnimator Property
"AnimatorKeep": Missing @Keep for Animated Properties
"ObsoleteLayoutParam": Obsolete layout params
"OnClick": onClick method does not exist
"Overdraw": Overdraw: Painting regions more than once
"DalvikOverride": Method considered overridden by Dalvik
"OverrideAbstract": Not overriding abstract methods on older platforms
"ParcelCreator": Missing Parcelable CREATOR field
"UnusedQuantity": Unused quantity translations
"MissingQuantity": Missing quantity translation
"ImpliedQuantity": Implied Quantities
"ExportedPreferenceActivity": PreferenceActivity should not be exported
"PrivateApi": Using Private APIs
"PackagedPrivateKey": Packaged private key
"PrivateResource": Using private resources
"ProguardSplit": Proguard.cfg file contains generic Android rules
"Proguard": Using obsolete ProGuard configuration
"PropertyEscape": Incorrect property escapes
"UsingHttp": Using HTTP instead of HTTPS
"SpUsage": Using dp instead of sp for text sizes
"InOrMmUsage": Using mm or in dimensions
"PxUsage": Using 'px' dimension
"SmallSp": Text size is too small
"ParcelClassLoader": Default Parcel Class Loader
"PendingBindings": Missing Pending Bindings
"RecyclerView": RecyclerView Problems
"Registered": Class is not registered in the manifest
"RelativeOverlap": Overlapping items in RelativeLayout
"RequiredSize": Missing layout_width or layout_height attributes
"AaptCrash": Potential AAPT crash
"ResourceCycle": Cycle in resource definitions
"ResourceName": Resource with Wrong Prefix
"ValidRestrictions": Invalid Restrictions Descriptor
"RtlCompat": Right-to-left text compatibility issues
"RtlEnabled": Using RTL attributes without enabling RTL support
"RtlSymmetry": Padding and margin symmetry
"RtlHardcoded": Using left/right instead of start/end attributes
"ScrollViewSize": ScrollView size validation
"SdCardPath": Hardcoded reference to /sdcard
"SecureRandom": Using a fixed seed with SecureRandom
"TrulyRandom": Weak RNG
"ExportedContentProvider": Content provider does not require permission
"ExportedReceiver": Receiver does not require permission
"ExportedService": Exported service does not require permission
"SetWorldReadable": File.setReadable() used to make file world-readable
"SetWorldWritable": File.setWritable() used to make file world-writable
"GrantAllUris": Content provider shares everything
"WorldReadableFiles": openFileOutput() or similar call passing
      MODE_WORLD_READABLE
"WorldWriteableFiles": openFileOutput() or similar call passing
      MODE_WORLD_WRITEABLE
"ServiceCast": Wrong system service casts
"WifiManagerLeak": WifiManager Leak
"WifiManagerPotentialLeak": WifiManager Potential Leak
"SetJavaScriptEnabled": Using setJavaScriptEnabled
"SignatureOrSystemPermissions": signatureOrSystem permissions declared
"SQLiteString": Using STRING instead of TEXT
"SSLCertificateSocketFactoryCreateSocket": Insecure call to
      SSLCertificateSocketFactory.createSocket()
"SSLCertificateSocketFactoryGetInsecure": Call to
      SSLCertificateSocketFactory.getInsecure()
"StateListReachable": Unreachable state in a <selector>
"AuthLeak": Code might contain an auth leak
"StringFormatCount": Formatting argument types incomplete or inconsistent
"StringFormatMatches": String.format string doesn't match the XML format
      string
"StringFormatInvalid": Invalid format string
"PluralsCandidate": Potential Plurals
"UseCheckPermission": Using the result of check permission calls
"CheckResult": Ignoring results
"ResourceAsColor": Should pass resolved color instead of resource id
"MissingPermission": Missing Permissions
"Range": Outside Range
"ResourceType": Wrong Resource Type
"RestrictedApi": Restricted API
"WrongThread": Wrong Thread
"WrongConstant": Incorrect constant
"VisibleForTests": Visible Only For Tests
"ProtectedPermissions": Using system app permission
"TextFields": Missing inputType or hint
"TextViewEdits": TextView should probably be an EditText instead
"SelectableText": Dynamic text should probably be selectable
"MenuTitle": Missing menu title
"ShowToast": Toast created but not shown
"TooDeepLayout": Layout hierarchy is too deep
"TooManyViews": Layout has too many views
"ExtraTranslation": Extra translation
"MissingTranslation": Incomplete translation
"Typos": Spelling error
"TypographyDashes": Hyphen can be replaced with dash
"TypographyEllipsis": Ellipsis string can be replaced with ellipsis character
"TypographyFractions": Fraction string can be replaced with fraction
      character
"TypographyOther": Other typographical problems
"TypographyQuotes": Straight quotes can be replaced with curvy quotes
"UnsafeProtectedBroadcastReceiver": Unsafe Protected BroadcastReceiver
"UnprotectedSMSBroadcastReceiver": Unprotected SMS BroadcastReceiver
"UnusedResources": Unused resources
"UnusedIds": Unused id
"UseCompoundDrawables": Node can be replaced by a TextView with compound
      drawables
"UselessLeaf": Useless leaf layout
"UselessParent": Useless parent layout
"EnforceUTF8": Encoding used in resource files is not UTF-8
"VectorRaster": Vector Image Generation
"VectorDrawableCompat": Using VectorDrawableCompat
"VectorPath": Long vector paths
"InvalidVectorPath": Invalid vector paths
"ViewConstructor": Missing View constructors for XML inflation
"ViewHolder": View Holder Candidates
"ViewTag": Tagged object leaks
"WrongViewCast": Mismatched view type
"FindViewByIdCast": Add Explicit Cast
"Wakelock": Incorrect WakeLock usage
"WakelockTimeout": Using wakeLock without timeout
"InvalidWearFeatureAttribute": Invalid attribute for Wear uses-feature
"WearStandaloneAppFlag": Invalid or missing Wear standalone app flag
"WebViewLayout": WebViews in wrap_content parents
"WrongCall": Using wrong draw/layout method
"WrongCase": Wrong case for view tag
"InvalidId": Invalid ID declaration
"NotSibling": RelativeLayout Invalid Constraints
"UnknownId": Reference to an unknown id
"UnknownIdInLayout": Reference to an id that is not in the current layout
"SuspiciousImport": 'import android.R' statement
"WrongFolder": Resource file in the wrong res folder
"WrongThreadInterprocedural": Wrong Thread (Interprocedural)

4. checkAllWarnings

是一個屬性,接受boolean類型值,true表示須要檢查全部警告的issue,包括默認被關閉的issue;false不檢查

5. checkReleaseBuilds

是一個屬性,接受boolean類型值,配置在release構建的過程當中,Lint 是否檢查致命的錯誤問題,默認true,一旦發現有fatal級別的問題,release構建就會終止。

6. disable

用來關閉給定issue ids的Lint檢查,參數接受是issue id。

7. enable

與disable相反

8. explainIssues

是一個屬性,接受boolean類型值,配置Lint檢查出的錯誤報告是否應該包含解釋說明,默認開啓

9. htmlOutput

是一個屬性,接受一個File類型參數,配置HTML報告輸出的文件路徑

android {
    lintOptions {
        htmlOutput new File("${buildDir}/lintReports/lint-results.html")
    }
}

10. htmlReport

是一個屬性,接受boolean類型值,用於配置是否生成HTML報告,默認true

11. ignoreWarnings

是一個屬性,接受boolean類型值,用於配置Lint是否忽略警告級別的檢查,只檢查錯誤級別的。默認false,不忽略警告級別的檢查

12. lintConfig

是一個屬性,接受一個File類型參數,用於指定Lint的配置文件,這是一個XML格式的文件,能夠指定一些默認的設置。

13. noLines

是一個屬性,接受boolean類型值,若是true,error輸出將不會包含源代碼的行號,默認是false

14. quiet

是一個屬性,接受boolean類型值,表示是否開啓安靜模式,true表明安靜模式,Lint分析的進度或者其餘信息將不會顯示。默認false

15. severityOverrides

是一個只讀屬性,返回一個Map類型的結果,用來獲取issue的優先級。Map的key是issue id, value是優先級,優先級是"fatal"、"error"、"warning"、"informational"、"ignore"

16. showAll

是一個屬性,接受boolean類型值,用於標記是否應該顯示全部的輸出,不會對過長的消息截斷等

17. textOutput

是一個只讀屬性,也有對應同名方法,接受一個File類型參數,用於指定生成的text格式的報告路徑。若是指定stdout這個值,會被指向標準的輸出,通常是終端控制檯

18. textReport

是一個屬性,接受boolean類型值,用於配置是否生成text報告,默認false,不生成報告

19. warningsAsErrors

是一個屬性,接受boolean類型值,用於配置是否把全部的警告也當作錯誤處理,默認false。

20. xmlOutput

是一個屬性,接受一個File類型參數,用於生成XML報告的路徑

21. xmlReport

是一個屬性,接受boolean類型值,用於控制是否生成XML格式的報告,默認true

22. error、fatal、ignore、warning、informational

這5個方法用來配置issue的優先級,接受的都是issue id做爲參數。

/** A severity for Lint. Corresponds to com.android.tools.lint.detector.api.Severity#FATAL */
    int SEVERITY_FATAL         = 1;
    /** A severity for Lint. Corresponds to com.android.tools.lint.detector.api.Severity#ERROR */
    int SEVERITY_ERROR         = 2;
    /** A severity for Lint. Corresponds to com.android.tools.lint.detector.api.Severity#WARNING */
    int SEVERITY_WARNING       = 3;
    /** A severity for Lint. Corresponds to com.android.tools.lint.detector.api.Severity#INFORMATIONAL */
    int SEVERITY_INFORMATIONAL = 4;
    /** A severity for Lint. Corresponds to com.android.tools.lint.detector.api.Severity#IGNORE */
    int SEVERITY_IGNORE        = 5;
    /**
     * A severity for lint. This severity means that the severity should be whatever the default
     * is for this issue (this is used when the DSL just says "enable", and Gradle doesn't know
     * what the default severity is.)
     */
    int SEVERITY_DEFAULT_ENABLED = 6;

若是個人文章對您有幫助,不妨點個贊鼓勵一下(^_^)

相關文章
相關標籤/搜索