banner
jzman

jzman

Coding、思考、自觉。
github

GradleシリーズのAndroid Gradle高度な設定

本篇文章主要在之前学习的基础上,从实际开发的角度学习如何对 Android Gradle 来进行自定义以满足不同的开发需求,下面是 Gradle 系列的几篇文章:

下面是主要内容:

  1. 変更生成的 Apk ファイル名
  2. バージョン情報の統一管理
  3. サインファイル情報の非表示
  4. 動的に AndroidManifest ファイルを構成
  5. カスタム BuildConfig
  6. 動的にカスタムリソースを追加
  7. Java コンパイルオプション
  8. adb 操作オプションの設定
  9. DEX オプションの設定
  10. 未使用のリソースを自動的にクリーンアップ
  11. 65535 メソッド制限を突破

変更生成的 Apk ファイル名#

変更打包出力の Apk のファイル名は主に 3 つの属性を使用します:

applicationVariants //AndroidアプリGradleプラグイン
libraryVariants     //AndroidライブラリGradleプラグイン
testVariants        //上記2つのプラグインに適用

以下は、打包生成の Apk ファイル名を変更するためのコードの参考です:

android{
    //...
    
    /**
     * 変更打包生成のapkのファイル名
     */
    applicationVariants.all { variant ->
        variant.outputs.all { output ->
            if (output.outputFile != null && output.outputFile.name.endsWith('.apk') &&
                    'release' == variant.buildType.name) {
                //出力ファイル名
                outputFileName = "AndroidGradleProject_v${variant.versionName}_${buildTime()}.apk"
            }
        }
    }   
}
//現在の時間
def static buildTime() {
    def date = new Date()
    return date.format("yyyMMdd")
}

この時、release モードで Apk を構築するタスクを実行すると、生成された Apk の名前が変更されます。もちろん、debug モードで対応するファイル名を生成するように設定することもできます。

バージョン情報の統一管理#

各アプリにはバージョンがあり、バージョンは一般的に 3 つの部分で構成されています:major.minor.patch。最初は主バージョン番号、2 番目は副バージョン番号、3 番目はパッチ番号で、1.0.0 のような形式のバージョン番号です。Android 開発における最も基本的なバージョン設定方法は、build.gradle の defaultConfig に対応するバージョン番号とバージョン名を設定することです。以下のように参考してください:

//最も基本的なバージョン設定方法
android{
    defaultConfig {
        versionCode 1
        versionName "1.0"
        //...
    }
}

実際の開発では、通常、このようなバージョン関連の情報を独立したバージョン管理ファイルに定義して統一管理します。version.gradle ファイルを以下のように定義します:

ext{
    //アプリのバージョン番号、バージョン名
    appversionCode = 1
    appVersionName = "1.0"
    //他のバージョン番号...
}

次に、build.gradle で version.gradle ファイルに定義されたバージョン番号、バージョン名を使用します。以下のように参考してください:

//version.gradleファイルをインポート
apply from: "version.gradle"
android {
    //...
    defaultConfig {
        //version.gradleで定義されたバージョン番号を使用
        versionCode appversionCode
        //version.gradleで定義されたバージョン名を使用
        versionName appVersionName
        //...
    }
}

もちろん、アプリのバージョン番号だけでなく、使用されるいくつかのサードパーティライブラリのバージョンもこの方法で統一管理できます。

サインファイル情報の非表示#

サインファイル情報は非常に重要な情報です。サインファイル情報をプロジェクトに直接設定すると安全ではありません。では、サインファイルを安全に保つにはどうすればよいでしょうか。サインファイルをローカルに置くのは安全ではないため、サーバーに置くのが安全です。パッケージング時にサーバーからサインファイル情報を読み取ることができます。もちろん、このサーバーは正式な Apk をパッケージングするための専用のコンピュータでも構いません。サインファイルとキー情報を環境変数として設定し、パッケージング時に環境変数からサインファイルとキー情報を直接読み取ることができます。

4 つの環境変数 STORE_FILE、STORE_PASSWORD、KEY_ALIAS、KEY_PASSWORD を設定し、それぞれサインファイル、サインファイルパスワード、サインファイルキーエイリアス、サインファイルキーのパスワードに対応します。環境変数の設定については具体的には説明しませんが、コードは以下のように参考してください:

android {
    //サインファイル設定
    signingConfigs {
        //設定されたサインファイル情報に対応する環境変数を読み取る
        def appStoreFile = System.getenv('STORE_FILE')
        def appStorePassword = System.getenv('STORE_PASSWORD')
        def appKeyAlias = System.getenv('KEY_ALIAS')
        def appKeyPassword = System.getenv('KEY_PASSWORD')
        //関連するサインファイル情報が取得できない場合は、デフォルトのサインファイルを使用
        if(!appStoreFile || !appStorePassword || !keyAlias || !keyPassword){
            appStoreFile = "debug.keystore"
            appStorePassword = "android"
            appKeyAlias = "androiddebugkey"
            appKeyPassword = "android"
        }
        release {
            storeFile file(appStoreFile)
            storePassword appStorePassword
            keyAlias appKeyAlias
            keyPassword appKeyPassword
        }
        debug {
            //デフォルトでは、debugモードのサインはAndroid SDKによって自動生成されたdebugサインファイル証明書に設定されています
            //.android/debug.keystore
        }
    }
}

注意点として、環境変数を設定した後、新しく設定した環境変数を読み取れない場合は、コンピュータを再起動すると読み取れるようになります。専用のサーバーを使用してパッケージングやサインファイル情報を読み取る方法については、実践後に紹介します。

動的に AndroidManifest ファイルを構成#

動的に AndroidManifest を構成するとは、AndroidManifest ファイル内のいくつかの内容を動的に変更することを指します。友盟などのサードパーティの統計プラットフォームで分析統計を行う際には、一般的に AndroidManifest ファイル内にチャネル名を指定する必要があります。以下のように示します:

<meta-data android:value="CHANNEL_ID" android:name="CHANNEL"/>

ここで CHANNEL_ID は異なるチャネルの名前に置き換える必要があります。例えば、baidu、miui などの各チャネル名です。これらの変化するパラメータを動的に変更するには、Manifest プレースホルダーと manifestPlaceholder を使用する必要があります。manifestPlaceholder は ProductFlavor の属性で、Map 型であり、複数のプレースホルダーを設定できます。具体的なコードは以下のように参考してください:

android{
    //次元
    flavorDimensions "channel"
    productFlavors{
        miui{
            dimension "channel"
            manifestPlaceholders.put("CHANNEL","google")
        }
        baidu{
            dimension "channel"
            manifestPlaceholders.put("CHANNEL","baidu")
        }
    }
}

上記のコードでは flavorDimensions 属性が設定されています。この属性は次元として理解できます。例えば、release と debug は 1 つの次元であり、異なるチャネルは 1 つの次元であり、無料版または有料版は別の次元です。これらの 3 つの次元を考慮する場合、生成される Apk の形式は 2 * 2 * 2 で 8 つの異なる Apk が生成されます。Gradle 3.0 以降、1 つの次元でも複数の次元でも、必ず flavorDimensions を使用して制約を設ける必要があります。上記のコードでは、次元 channel が定義されており、buildType 内の debug と release が加わるため、生成される異なる Apk の数は 4 つになります。以下の図のようになります:

image

もちろん、flavorDimensions が設定されていない場合、以下のようなエラーが発生します。具体的には:

Error flavors must now belong to a named flavor dimension.

実際の開発では、実際の状況に応じて対応する flavorDimensions を設定すればよいです。

次に、AndroidManifest ファイル内でプレースホルダーを使用してパッケージング時に渡されたパラメータを紹介します。AndroidManifest ファイル内にを追加します。以下のように示します:

<meta-data android:value="${CHANNEL}" android:name="channel"/>

最後に、対応するチャネルパッケージタスクを実行します。例えば、assembleBaiduRelease を実行すると、AndroidManifest 内のチャネルが baidu に置き換えられます。コマンドを使用して実行することも、Android Studio で対応するタスクを選択して実行することもできます。実行コマンドは以下のようになります:

gradle assembleBaiduRelease

Android Studio を使用する場合、右側の Gradle コントロールパネルを開き、対応するタスクを見つけて相応のタスクを実行します。以下の図のようになります:

image

対応するタスクを選択して実行すると、対応する Apk が生成されます。Android Killer を使用して生成された Apk を逆コンパイルして開くと、AndroidManifest ファイルは以下のようになります:

<?xml version="1.0" encoding="utf-8" standalone="no"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.manu.androidgradleproject">
    <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:supportsRtl="true" android:theme="@style/AppTheme" roundIcon="@mipmap/ic_launcher_round">
        <!--AndroidManifestファイルの変更に成功-->
        <meta-data android:name="channel" android:value="baidu"/>
        <activity android:name="com.manu.androidgradleproject.MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>
                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity>
        <meta-data android:name="android.support.VERSION" android:value="26.1.0"/>
        <meta-data android:name="android.arch.lifecycle.VERSION" android:value="27.0.0-SNAPSHOT"/>
    </application>
</manifest>

上記の例では、チャネルの名前が一致しており、チャネル名の置き換えを簡単に行うことができます。以下のように参考してください:

productFlavors.all{ flavor ->
    manifestPlaceholders.put("CHANNEL",name)
}

この小節の重要な点は、manifestPlaceholders プレースホルダーの使用に関することです。

カスタム BuildConfig#

BuildConfig は、Android Gradle 構築スクリプトがコンパイルされた後に生成されるクラスで、デフォルトで生成される BuildConfig の内容は以下の通りです:

/**
 * 自動生成されたファイル。変更しないでください
 */
package com.manu.androidgradleproject;

public final class BuildConfig {
  public static final boolean DEBUG = false;
  public static final String APPLICATION_ID = "com.manu.androidgradleproject";
  public static final String BUILD_TYPE = "release";
  public static final String FLAVOR = "baidu";
  public static final int VERSION_CODE = 1;
  public static final String VERSION_NAME = "1.0";
}

上記の BuildConfig のいくつかの定数は、アプリに関する重要な情報です。その中で DEBUG は debug モードでは true、release モードでは false です。また、アプリのパッケージ名、構築タイプ、構築チャネル、バージョン番号、バージョン名も含まれています。したがって、開発中にこれらの値を使用する必要がある場合、BuildConfig から直接取得できます。例えば、パッケージ名の取得は一般的に context.getPackageName () ですが、BuildConfig から直接取得することで便利であり、アプリのパフォーマンス向上にも寄与します。したがって、構築時にこのファイルにいくつかの追加の有用な情報を追加することができます。buildConfigField メソッドを使用します。具体的には以下のようになります:

/**
 * type:生成フィールドのタイプ
 * name:生成フィールドの定数名
 * value:生成フィールドの定数値
 */
public void buildConfigField(String type, String name, String value) {
    //...
}

以下は、buildConfigField メソッドを使用して各チャネルに関連するアドレスを設定する例です。以下のように参考してください:

android{
    //次元
    flavorDimensions "channel"
    productFlavors{
        miui{
            dimension "channel"
            manifestPlaceholders.put("CHANNEL","miui")
            buildConfigField 'String' ,'URL','"http://www.miui.com"'
        }
        baidu{
            dimension "channel"
            manifestPlaceholders.put("CHANNEL","baidu")
            //buildConfigFieldメソッドのパラメータvalueの内容はシングルクォート内にあり、valueがStringの場合、Stringのダブルクォートは省略できません
            buildConfigField 'String' ,'URL','"http://www.baidu.com"'
        }
    }
}

パッケージング時に自動的に追加されたフィールドが生成され、構築が完了した後に BuildConfig ファイルを確認すると、上記の追加されたフィールドが生成されています。以下のように参考してください:

/**
 * 自動生成されたファイル。変更しないでください
 */
package com.manu.androidgradleproject;

public final class BuildConfig {
  public static final boolean DEBUG = false;
  public static final String APPLICATION_ID = "com.manu.androidgradleproject";
  public static final String BUILD_TYPE = "release";
  public static final String FLAVOR = "baidu";
  public static final int VERSION_CODE = -1;
  public static final String VERSION_NAME = "";
  // Product flavorからのフィールド: baidu
  public static final String URL = "http://www.baidu.com";
}

これで、カスタム BuildConfig の学習は終了です。もちろん、buildConfigField は構築タイプにも使用できます。重要なのは buildConfigField メソッドの使用です。

動的にカスタムリソースを追加#

Android 開発では、リソースファイルはすべて res ディレクトリに配置されます。また、Android Gradle 内で定義することもできます。カスタムリソースを使用するには、resValue メソッドを使用する必要があります。このメソッドは BuildType および ProductFlavor オブジェクト内で使用できます。resValue メソッドを使用すると、対応するリソースが生成され、res/values ファイル内で定義するのと同様の方法で使用できます。

android{
    //...
    productFlavors {
        miui {
            //...
           /**
            * resValue(String type,String name,String value)
            * type:生成フィールドのタイプ(id、string、boolなど)
            * name:生成フィールドの定数名
            * value:生成フィールドの定数値
            */
            resValue 'string', 'welcome','miui'
        }

        baidu {
            //...
            resValue 'string', 'welcome','baidu'
        }
    }

}

異なるチャネルパッケージを生成する際、R.string.welcome で取得される値は異なります。例えば、生成された百度のチャネルパッケージでは R.string.welcome の値は baidu、生成された小米のチャネルパッケージでは R.string.welcome の値は miui です。構築時に生成されるリソースの位置は build/generated/res/resValues/baidu/... 以下の generated.xml ファイル内にあります。ファイル内容は以下のようになります:

<?xml version="1.0" encoding="utf-8"?>
<resources>

    <!-- 自動生成されたファイル。変更しないでください -->

    <!-- Product flavorからの値: baidu -->
    <string name="welcome" translatable="false">baidu</string>

</resources>

Java コンパイルオプション#

Android Gradle では、Java ソースコードのコンパイルバージョンを設定することもできます。ここでは compileOptions メソッドを使用します。compileOptions では、encoding、sourceCompatibility、targetCompatibility の 3 つの属性を設定できます。これらの属性を使用して Java 関連のコンパイルオプションを設定します。具体的には以下のように参考してください:

//Javaコンパイルオプションの設定
android {
    compileSdkVersion 26
    buildToolsVersion '26.0.2'
    compileOptions{
        //ソースファイルのエンコーディングを設定
        encoding = 'utf-8'
        //Javaソースコードのコンパイルレベルを設定()
        sourceCompatibility = JavaVersion.VERSION_1_8
//        sourceCompatibility  "1.8"
//        sourceCompatibility  1.8
//        sourceCompatibility  "Version_1_8"
        //Javaバイトコードのバージョンを設定
        targetCompatibility = JavaVersion.VERSION_1_8
    }
}

adb 操作オプションの設定#

adb の正式名称は Android Debug Bridge で、adb は主に携帯電話に接続していくつかの操作を行うために使用されます。例えば、Apk のデバッグ、Apk のインストール、ファイルのコピーなどの操作です。Android Gradle では adbOptions を使用して設定できます。設定できる属性は 2 つあり、installOptions と timeOutInMs です。また、対応する setter メソッドを使用して設定することもできます。具体的には以下のように参考してください:

android{
    //adb設定オプション
    adbOptions{
        //adbコマンドの実行タイムアウト時間を設定
        timeOutInMs = 5 * 1000
        /**
         * adb installのインストール操作の設定項目を設定
         * -l:アプリケーションをロック
         * -r:既存のアプリケーションを置き換え
         * -t:テストパッケージを許可
         * -s:アプリケーションをSDカードにインストール
         * -d:アプリケーションのダウングレードを許可
         * -g:アプリにすべての実行時権限を付与
         */
        installOptions '-r', '-s'
    }    
}

installOptions の設定は adb install [-lrtsdg] コマンドに対応します。Apk のインストール、実行、デバッグ時に CommandRejectException が発生した場合は、timeOutInMs を設定して解決を試みることができます。単位はミリ秒です。

DEX オプションの設定#

Android のソースコードは class バイトコードにコンパイルされ、Apk にパッケージングされる際に dx コマンドによって Android 仮想マシンが実行可能な DEX ファイルに最適化されます。DEX 形式のファイルは Android 仮想マシン専用に設計されており、ある程度その実行速度を向上させます。デフォルトでは、dx に割り当てられるメモリは 1024M です。Android Gradle では、dexOptions の 5 つの属性:incremental、javaMaxHeapSize、jumboMode、threadCount、preDexLibraries を使用して DEX を関連設定できます。具体的には以下のように参考してください:

android{
    //DEXオプションの設定
    dexOptions{
        //dx増分モードを有効にするかどうかを設定
        incremental true
        //dxコマンドに割り当てられる最大ヒープメモリを設定
        javaMaxHeapSize '4g'
        //jumboモードを有効にするかどうかを設定。プロジェクトのメソッド数が65535を超える場合、jumboモードを有効にしないと構築に成功しません
        jumboMode true
        //Android Gradleがdxコマンドを実行する際に使用するスレッド数を設定し、dxの実行効率を向上させます
        threadCount 2
        /**
         * dex Librariesライブラリプロジェクトを実行するかどうかを設定。これを有効にすると、増分構築の速度が向上しますが、cleanの速度に影響します。デフォルトはtrueです
         * dxの--multi-dexオプションを使用して複数のdexを生成し、ライブラリプロジェクトとの競合を避けるためにfalseに設定できます
         */
        preDexLibraries true
    }
}

未使用リソースの自動クリーンアップ#

Android 開発では、Apk をパッケージングする際、同じ機能の下で Apk のサイズをできるだけ小さくしたいと考えます。そのためには、パッケージング前に未使用のリソースファイルを削除するか、パッケージング時に無駄なリソースを Apk に含めないようにする必要があります。Android Lint を使用して未使用のリソースをチェックできますが、サードパーティライブラリ内の無駄なリソースを削除することはできません。また、Resource Shrinking を使用して、パッケージング前にリソースをチェックし、使用されていない場合は Apk に含めないようにすることができます。具体的には以下のように参考してください:

//未使用リソースの自動クリーンアップ
android{
    buildTypes {
        release {
            //混乱を有効にし、特定のリソースがコード内で使用されていないことを確認し、無駄なリソースを自動的にクリーンアップします。両者を組み合わせて使用します
            minifyEnabled true
            /**
             * パッケージング時にすべてのリソースをチェックし、参照されていない場合はApkに含めません。サードパーティライブラリの未使用リソースも処理します
             * デフォルトでは無効です
             */
            shrinkResources true
            //zipalign最適化を有効にします
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
        debug{
        }
    }
    //...
}

有用なリソースが Apk に含まれないのを防ぐために、Android Gradle は keep メソッドを提供して、どのリソースがクリーンアップされないかを設定します。res/raw/ 以下に新しい xml ファイルを作成して keep メソッドを使用します。以下のように参考してください:

<!--keep.xmlファイル-->
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools"
    tools:keep="@layout/l_used*_c,@layout/l_used_a,@layout/l_used_b*"
    tools:discard="@layout/l_used"
    tools:shrinkMode="safe"/>

設定可能な 3 つの属性:keep は保持するリソースファイルを示し、カンマで区切られたリソースリストを使用できます。ワイルドカードとして (*) を使用できます。discard は削除するリソースを示し、keep と同様です。shrinkMode は自動クリーンアップのモードを設定します。一般的には safe に設定します。strict に設定すると、使用される可能性のあるリソースが削除される可能性があります。

さらに、ProductFlavor が提供する resConfigs および resConfig メソッドを使用して、どのリソースを Apk に含めるかを設定できます。使用方法は以下のようになります:

android{
    defaultConfig{
       //パラメータはAndroid開発時のリソース限定子である可能性があります
        resConfigs 'zh'
        //...
    }
}

上記の自動クリーンアップの方法は、Apk に含まれないだけで、実際のプロジェクトでは削除されていません。ログを通じてどのリソースがクリーンアップされたかを確認し、プロジェクト内で削除するかどうかを決定します。

65535 メソッド制限を突破#

Android 開発では、メソッド数が 65535 を超えると例外が発生することがあります。この制限がある理由は何でしょうか。Java ソースファイルが 1 つの DEX ファイルにパッケージングされ、このファイルは最適化され、Dalvik 仮想マシンで実行可能なファイルです。Dalvik が DEX ファイルを実行する際、DEX ファイル内のメソッドをインデックスするために short を使用しているため、単一の DEX ファイルで定義できるメソッドの最大数は 65535 個です。解決策は、メソッド数が 65535 を超える場合に複数の DEX ファイルを作成することです。

Android 5.0 以降の Android システムは ART の実行方式を使用し、複数の DEX ファイルをネイティブにサポートしています。ART はアプリをインストールする際に事前コンパイルを実行し、複数の DEX ファイルを 1 つの oat ファイルに統合して実行します。Android 5.0 以前は、Dalvik 仮想マシンは単一の DEX ファイルしかサポートしていません。65535 を超えるメソッド数の制限を突破するには、Multidex ライブラリを使用する必要があります。ここでは詳しくは述べません。

まとめ#

本篇文章の多くの内容は実際の開発に役立つことができます。この文章は学びながら検証する形で完成しました。断続的に 1 週間の時間がかかりました。前回の更新からすでに 1 週間が経過しています。この文を読んで、あなたにとって役立つことを願っています。

読み込み中...
文章は、創作者によって署名され、ブロックチェーンに安全に保存されています。