JDK17 Gradle8.9によるAndroidアプリ(ハイブリッド及びWeb系)のパッケージングについて、詳細を細々と書き記します。最近の主流は2025年時点でもJDK11だそうですが、JDKを将来的にUPGradeされる方向けの記事になりそうです。また、アプリに広告を挿入する際Cordovaを用いてadmob-plus-cordovaを導入する時、Kotlinのプラグインが必要になってきます。そのカバーをするため、記事に起こしました。

投稿者:

筆者の環境で、Android版のアプリを構築する際にCordovaをミドルウェアとしているのですが、JDK17とGradle8.9でアプリをビルドしています。

AIに助力を得ながら奮闘した記事をまとめたいと思いますので、ハイブリッドアプリを作ってみたいまたは、Web系言語で構成されたファイル群をAndroid版アプリとしてみたいとお考えの方はご覧ください。

先に触れておきますが、RPGMakerMV/MZでは以下の記事のような苦労はほとんどないのですが、それでもアプリのビルドが上手く行かないな、アプリのビルドを効率よくできないかなとお考えの方もご覧いただければ、後々のアプリの更新やGooglePlayコンソールからの要求を満たすのに必要な知見となりますので、広くオープンに公開いたします。

まず、Android版ビルドに必要な筆者PC内部のスペック(コマンドプロンプトを管理者権限で起動しています。)

という感じで sdkmanager –listのコマンドを叩くと、非常に長いリストのAndroid Sdkの内部にインストールされているAndroid APIが見れます。

AndroidStudioでのインストールを推奨しますが、SDKManagerを使ってAPI33以降をインストールすると、このリストに似ている感じになります。(画像はAPI33~36までインストールされています)

再三にわたって言及していますが、Windowsのシステム環境変数にPATHを通しています。

node.jsは最新の安定版(LTS)をインストールしていて、

後述しますが、cordovaは12.0がインストールされています、おそらく14.0がインストールされると思いますが、npm -g cordovaを使うと、14.0 でも問題はないと思います。

npmは11.4.2なので2025年6月29日時点で最新版のはずです。

さて、cordova 14.0.0なのですが、npmにインストールされています。

以前のサークルリーグというアプリでは14.0.0で動作していました。

アプリに広告を入れる関係からCordovaをダウングレードし、デシュガーファイルを作成してcordova12.0.0をインストールするようにしています。これはadmob-plus-cordovaとの整合性を保つためです。

後述部分のデシュガーファイルというあまり聞き慣れないファイルをGeminiが作成してくれました。または作成はGeminiが行い、ファイルの配置は手動でしたが…。

デシュガーファイルの中身

#!/usr/bin/env node

/**
 * Android の Core Library Desugaring および Java 8 (Desugar) サポートを有効にするフック。
 * Cordova Android 10+ (Gradle ベース) および最新の Android Gradle Plugin に対応。
 *
 * このスクリプトは `after_prepare` フェーズで実行され、
 * `platforms/android/app/build.gradle`、`platforms/android/project.properties`、
 * `platforms/android/gradle.properties` ファイルを修正します。
 *
 * ログは `console.log` を使用して Cordova CLI に表示されます。
 */

const fs = require('fs');
const path = require('path');

module.exports = function(context) {
    console.log('Running hook: Enable Core Library Desugaring for Android...');

    // プロジェクトのルートディレクトリを取得 (c:\circle など)
    const rootdir = context.opts.projectRoot;
    console.log(`DEBUG HOOK: rootdir (using process.cwd()) received = ${process.cwd()}`);

    // Android プラットフォームのパスを取得
    const androidPlatformPath = path.join(rootdir, 'platforms', 'android');
    if (!fs.existsSync(androidPlatformPath)) {
        console.error('Android platform not found at:', androidPlatformPath);
        throw new Error('Android platform directory does not exist. Please add the Android platform first.');
    }

    // build.gradle ファイルのパス
    const buildGradlePath = path.join(androidPlatformPath, 'app', 'build.gradle');
    // project.properties ファイルのパス
    const projectPropertiesPath = path.join(androidPlatformPath, 'project.properties');
    // gradle.properties ファイルのパス
    const gradlePropertiesPath = path.join(androidPlatformPath, 'gradle.properties');

    console.log(`DEBUG HOOK: androidPlatformPath = ${androidPlatformPath}`);
    console.log(`DEBUG HOOK: buildGradlePath = ${buildGradlePath}`);
    console.log(`DEBUG HOOK: projectPropertiesPath = ${projectPropertiesPath}`);
    console.log(`DEBUG HOOK: gradlePropertiesPath = ${gradlePropertiesPath}`);


    // === 1. build.gradle の修正 ===
    // Core Library Desugaring の有効化 (compileOptions と dependencies)
    try {
        let buildGradleContent = fs.readFileSync(buildGradlePath, 'utf8');

        // compileOptions の追加
        const compileOptionsBlock = `
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }`;
        if (!buildGradleContent.includes('sourceCompatibility JavaVersion.VERSION_1_8')) {
            const androidBlockRegex = /(android\s*\{[^}]*?)(defaultConfig\s*\{)/;
            if (buildGradleContent.match(androidBlockRegex)) {
                buildGradleContent = buildGradleContent.replace(
                    androidBlockRegex,
                    `$1\n${compileOptionsBlock}\n    $2`
                );
                console.log('Added compileOptions for desugaring.');
            } else {
                console.warn('Could not find "android { defaultConfig {" block. Skipping compileOptions modification.');
            }
        } else {
            console.log('compileOptions for desugaring already exists. Skipping.');
        }

        // desugar_jdk_libs の追加
        const desugarDependency = `    implementation 'com.android.tools:desugar_jdk_libs:2.1.5'`; // 最新の安定版を指定
        const dependenciesEndMarker = '    // SUB-PROJECT DEPENDENCIES END';
        if (!buildGradleContent.includes(desugarDependency)) {
            if (buildGradleContent.includes(dependenciesEndMarker)) {
                buildGradleContent = buildGradleContent.replace(
                    dependenciesEndMarker,
                    `${desugarDependency}\n${dependenciesEndMarker}`
                );
                console.log('Added/Updated desugar_jdk_libs dependency after SUB-PROJECT DEPENDENCIES END.');
            } else {
                // フォールバック: dependencies ブロックの最後に直接追加
                const dependenciesBlockRegex = /(dependencies\s*\{[^}]*)\}/;
                if (buildGradleContent.match(dependenciesBlockRegex)) {
                    buildGradleContent = buildGradleContent.replace(
                        dependenciesBlockRegex,
                        `$1\n    ${desugarDependency}\n}`
                    );
                    console.log('Added desugar_jdk_libs dependency to dependencies block.');
                } else {
                    console.error('Could not find dependencies block. Skipping desugar_jdk_libs dependency modification.');
                }
            }
        } else {
            console.log('desugar_jdk_libs dependency already exists. Skipping.');
        }

        // WorkManager dependency resolution strategy を追加
        // Android S+ (API 31+) での PendingIntent の IMMUTABLE/MUTABLE フラグ要件に対応
        // Google Mobile Ads SDK のバージョンも強制する
        const resolutionStrategyBlock = `
configurations.all {
    resolutionStrategy {
        force 'androidx.work:work-runtime:2.8.1' // WorkManager 2.7.0+ は FLAG_IMMUTABLE をサポート
        // NEW ADDITION: Force Google Mobile Ads SDK to a newer version (e.g., 23.1.0)
        force 'com.google.android.gms:play-services-ads:23.1.0' 
        force 'com.google.android.gms:play-services-ads-lite:23.1.0'
    }
}
`;
        const existingResolutionStrategyRegex = /configurations\.all\s*\{\s*resolutionStrategy\s*\{[^}]*\}\s*\}/s;
        if (!buildGradleContent.match(existingResolutionStrategyRegex)) {
            const topLevelGradleBlock = /apply plugin: 'com.android.application'/; // アプリケーションプラグイン適用後が適切
            if (buildGradleContent.match(topLevelGradleBlock)) {
                buildGradleContent = buildGradleContent.replace(
                    topLevelGradleBlock,
                    `$&\n\n${resolutionStrategyBlock}`
                );
                console.log('Added WorkManager and GMA SDK resolution strategy to build.gradle.');
            } else {
                console.warn('Could not find "apply plugin: \'com.android.application\'" in build.gradle. Appending WorkManager and GMA SDK resolution strategy at the end.');
                buildGradleContent += resolutionStrategyBlock;
            }
        } else {
            console.log('WorkManager and GMA SDK resolution strategy already exists. Skipping.');
            // 既存のブロック内に新しい force ルールを追加するロジック (より堅牢な実装)
            if (!buildGradleContent.includes("force 'com.google.android.gms:play-services-ads:23.1.0'")) {
                const resolutionStrategyEnd = /resolutionStrategy\s*\{[^}]*\}/;
                buildGradleContent = buildGradleContent.replace(
                    resolutionStrategyEnd,
                    `resolutionStrategy {\n        force 'androidx.work:work-runtime:2.8.1'\n        force 'com.google.android.gms:play-services-ads:23.1.0'\n        force 'com.google.android.gms:play-services-ads-lite:23.1.0'\n    }`
                );
                console.log('Updated existing resolution strategy with GMA SDK versions.');
            }
        }


        fs.writeFileSync(buildGradlePath, buildGradleContent, 'utf8');
        console.log('Core Library Desugaring successfully enabled in build.gradle.');
    } catch (err) {
        console.error('Failed to modify build.gradle:', err);
        throw err;
    }

    // === 2. project.properties の修正 ===
    // Java compatibility properties の追加
    try {
        console.log('Adding Java compatibility properties to project.properties...');
        let projectPropertiesContent = fs.readFileSync(projectPropertiesPath, 'utf8');
        const javaTargetProperty = 'cordova.gradle.target.jdk=1.8';
        const jvmTargetProperty = 'cordova.gradle.jvm.target=1.8';

        let modified = false;
        if (!projectPropertiesContent.includes(javaTargetProperty)) {
            projectPropertiesContent += `\n${javaTargetProperty}`;
            modified = true;
        } else {
            console.log('cordova.gradle.target.jdk already exists in project.properties. Skipping.');
        }

        if (!projectPropertiesContent.includes(jvmTargetProperty)) {
            projectPropertiesContent += `\n${jvmTargetProperty}`;
            modified = true;
        } else {
            console.log('cordova.gradle.jvm.target already exists in project.properties. Skipping.');
        }

        if (modified) {
            fs.writeFileSync(projectPropertiesPath, projectPropertiesContent, 'utf8');
            console.log('Java compatibility properties updated in project.properties.');
        } else {
            console.log('Java compatibility properties are up-to-date in project.properties. Skipping write.');
        }
    } catch (err) {
        console.error('Failed to modify project.properties:', err);
        throw err;
    }

    // === 3. gradle.properties の修正 ===
    // D8 desugaring と warning suppression の追加
    try {
        console.log('Adding D8 desugaring and warning suppression to gradle.properties...');
        let gradlePropertiesContent = fs.readFileSync(gradlePropertiesPath, 'utf8');
        const d8ArtifactTransform = 'android.enableDexingArtifactTransform=true';
        const d8Desugaring = 'android.enableD8.desugaring=true';
        const suppressSdkWarning = 'android.suppressUnsupportedCompileSdk=35'; // targetSdkVersion に合わせる
        const nonTransitiveRClass = 'android.nonTransitiveRClass=false'; // R.java の参照を簡素化

        let modified = false;

        if (!gradlePropertiesContent.includes(d8ArtifactTransform)) {
            gradlePropertiesContent += `\n${d8ArtifactTransform}`;
            modified = true;
        } else {
            console.log('android.enableDexingArtifactTransform already exists in gradle.properties. Skipping.');
        }

        if (!gradlePropertiesContent.includes(d8Desugaring)) {
            gradlePropertiesContent += `\n${d8Desugaring}`;
            modified = true;
        } else {
            console.log('android.enableD8.desugaring already exists in gradle.properties. Skipping.');
        }

        if (!gradlePropertiesContent.includes(suppressSdkWarning)) {
            gradlePropertiesContent += `\n${suppressSdkWarning}`;
            modified = true;
        } else {
            console.log('android.suppressUnsupportedCompileSdk already exists in gradle.properties. Skipping.');
        }

        if (!gradlePropertiesContent.includes(nonTransitiveRClass)) {
            gradlePropertiesContent += `\n${nonTransitiveRClass}`;
            modified = true;
        } else {
            console.log('android.nonTransitiveRClass already exists in gradle.properties. Skipping.');
        }

        if (modified) {
            fs.writeFileSync(gradlePropertiesPath, gradlePropertiesContent, 'utf8');
            console.log('Gradle properties updated.');
        } else {
            console.log('Gradle properties are up-to-date. Skipping write.');
        }

    } catch (err) {
        console.error('Failed to modify gradle.properties:', err);
        throw err;
    }

    console.log('Enable Core Library Desugaring for Android hook finished.');
};

デシュガーファイルがプロジェクトフォルダのhooksというところにありafterprepareというフォルダの中にデシュガーファイルを配置しています。

このファイルが無ければ、JDK17とGradle8.9でのビルドの成功率が格段に違います。

このサイトはコピーガードや右クリック禁止をしているので、写経するよりも、GeminiにJDK17とGradle8.9でのビルドがFAILDEしてしまうという旨のプロンプトとビルド失敗した時のエラーをコピペすると、デシュガーファイルを作成しますという流れになりました。

GeminiにJDK17とGradle8.9でのビルドがFAILDEしてしまうという旨のプロンプトで指示を出してもなかなかデシュガーファイルを作成してくれない場合は、追加のプロンプトで、デシュガーファイルを作成してほしいというようなプロンプトを追加すると良いでしょう。

ここまでが、JDK17とGradle8.9でのビルドに必要なPCの内部スペックです。

ここからは、admob-plus-cordova@2.0.0alpha19が動作するようにするための解説を行います。

hooksスクリプトを動かすためのパラメータとKotlinのプラグインを動かすパラメータ

あとは、npmに書かれてあるごくごくシンプルなjsを追加するだけで、広告が実装されると思われます。人それぞれ違うエラーに遭遇するかも…。

ビルドが上手く行ったら、広告の実装に近づいた証拠です。

Geminiが色々とJavaScriptを改変してくれますが、admob-plus-cordovaはKotlinで作成されたプラグインなので、Kotlinのパラメータが必要になってきます。Kotlinのビルドを通してやることにより、広告の実装が非常にシンプルにできるようになります。

追記なのですが、admob-plus-cordovaプラグインをプロジェクトの中にインストールする時、

cordova plugin add admob-plus-cordova –variable APP_ID=”ca-app-pub-あなたの実際のアプリID”の記述が必要になってきますのでお気を付けください。

admob-plus-cordovaを動かすためのシンプルなコードについてはここを参照してみてください。

ではご健闘を祈ります。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

CAPTCHA