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 を使用して設定できます。設定可能な属性は installOptions と timeOutInMs の 2 つで、対応する 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 週間が経過しています。この文を読むことで、あなたにとって何かの助けになることを願っています。

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