18

Android デベロッパー ブログの Fred Chung によるDalvik のカスタム クラス ロードの紹介によると、次のようになります。

Dalvik VM は、開発者がカスタム クラスの読み込みを実行するための機能を提供します。アプリケーションは、Dalvik 実行可能 (「dex」) ファイルをデフォルトの場所からロードする代わりに、内部ストレージやネットワーク経由などの別の場所からロードできます。

ただし、カスタム クラスのロードを行う必要がある開発者は多くありません。しかし、そのブログ投稿の指示に従っている人は、Google I/O 2013 で導入された Android 用の新しいビルド システムである Gradle で同じ動作を模倣する際に問題が発生する可能性があります。

古い (Ant ベースの) ビルド システムと同じ中間ステップを実行するために、新しいビルド システムをどのように適合させることができるでしょうか?

4

3 に答える 3

27

私のチームと私は最近、アプリで 64K メソッド参照に達しました。これは、dex ファイルでサポートされる最大数です。この制限を回避するには、プログラムの一部を複数のセカンダリ dex ファイルに分割し、実行時にロードする必要があります。

Ant ベースの古いビルド システムの質問で言及されているブログ投稿に従いましたが、すべて正常に機能していました。しかし最近、Gradle ベースの新しいビルド システムに移行する必要性を感じました。

この回答は、完全なブログ投稿を完全な例に置き換えることを意図したものではありません。代わりに、Gradle を使用してビルド プロセスを微調整し、同じことを達成する方法を簡単に説明します。これはおそらくそれを行うための 1 つの方法であり、現在チームで行っている方法であることに注意してください。必ずしもそれが唯一の方法であるとは限りません。

私たちのプロジェクトの構造は少し異なります。この例は、すべてのソース コードを .class ファイルにコンパイルし、それらを単一の .dex ファイルにアセンブルし、最後にその単一の .dex ファイルを .jar にパッケージ化する個別の Java プロジェクトとして機能します。ファイル。

はじめましょう...

ルートのbuild.gradleには、いくつかのデフォルトを定義する次のコードがあります。

ext.androidSdkDir = System.env.ANDROID_HOME

if(androidSdkDir == null) {
    Properties localProps = new Properties()
    localProps.load(new FileInputStream(file('local.properties')))

    ext.androidSdkDir = localProps['sdk.dir']
}

ext.buildToolsVersion = '18.0.1'
ext.compileSdkVersion = 18

この例は個別の Java プロジェクトですが、Android SDK のコンポーネントを使用する必要があるため、上記のコードが必要です。また、後で他のプロパティのいくつかも必要になります...したがって、メイン プロジェクトのbuild.gradleには、次の依存関係があります。

dependencies {
    compile files("${androidSdkDir}/platforms/android-${compileSdkVersion}/android.jar")
}

また、このプロジェクトのソース セットを簡素化していますが、これはプロジェクトには必要ない場合があります。

sourceSets {
    main {
        java.srcDirs = ['src']
    }
}

次に、組み込みタスクのデフォルト構成を変更して、すべての .class ファイルではなくjar、単にclasses.dexファイルを含めるようにします。

configure(jar) {
    include 'classes.dex'
}

ここで、実際にすべての .class ファイルを単一の .dex ファイルにアセンブルする新しいタスクが必要です。この場合、Protobuf ライブラリ JAR を .dex ファイルに含める必要もあります。だから私はここの例にそれを含めています:

task dexClasses << {
    String protobufJarPath = ''

    String cmdExt = Os.isFamily(Os.FAMILY_WINDOWS) ? '.bat' : ''

    configurations.compile.files.find {
        if(it.name.startsWith('protobuf-java')) {
            protobufJarPath = it.path
        }
    }

    exec {
        commandLine "${androidSdkDir}/build-tools/${buildToolsVersion}/dx${cmdExt}", '--dex',
                    "--output=${buildDir}/classes/main/classes.dex",
                    "${buildDir}/classes/main", "${protobufJarPath}"
    }
}

また、 build.gradleファイルのどこかに (通常はもちろん一番上に) 次のインポートがあることを確認してください。

import org.apache.tools.ant.taskdefs.condition.Os

ここで、最終的な .jar ファイルがアセンブルされる前にタスクが実行されるように、jarタスクをタスクに依存させる必要があります。dexClasses簡単なコード行でそれを行います。

jar.dependsOn(dexClasses)

これで完了です... 通常のassembleタスクで Gradle を呼び出すだけで、最終的な .jar ファイルに${buildDir}/libs/${archivesBaseName}.jarは単一のclasses.dexファイルが含まれます (MANIFEST.MF ファイルに加えて)。それをアプリのアセット フォルダーにコピーするだけで (私たちが行ったように Gradle を使用していつでも自動化できますが、それはこの質問の範囲外です)、残りのブログ投稿に従ってください。

質問がある場合は、コメントで叫んでください。できる限りのお手伝いをさせていただきます。

于 2013-08-11T16:15:46.940 に答える
2

Android Studio Gradle プラグインは、マルチデックスのネイティブ サポートを提供するようになりました。これにより、jar ファイルからクラスを手動でロードする必要なく、Android 65k メソッドの制限が効果的に解決されます。そのため、Fred Chung のブログはその目的で廃止されます。ただし、Android での実行時に jar ファイルからカスタム クラスをロードすることは、拡張性の目的 (たとえば、アプリのプラグイン フレームワークの作成) には依然として有用であるため、以下でその使用シナリオについて説明します。

Java プラグインではなく Android ライブラリ プラグインを使用して、Fred Chung のブログにある元のサンプル アプリを、こちらの github ページの Android Studio に移植しました。ブログのように既存の dex プロセスを変更して 2 つのモジュールに分割しようとする代わりに、jar ファイルに入れたいコードを独自のモジュールにassembleExternalJar入れ、必要なクラスを dex するカスタム タスクを追加しました。メインassembleタスクが終了した後のファイル。

これは、ライブラリの build.gradle ファイルの関連部分です。ライブラリ モジュールにメイン プロジェクトにない依存関係がある場合は、おそらくこのスクリプトを変更して追加する必要があります。

apply plugin: 'com.android.library'
// ... see github project for the full build.gradle file

// Define some tasks which are used in the build process
task copyClasses(type: Copy) { // Copy the assembled *.class files for only the current namespace into a new directory
    // get directory for current namespace (PLUGIN_NAMESPACE = 'com.example.toastlib')
    def namespacePath = PLUGIN_NAMESPACE.replaceAll("\\.","/")
    // set source and destination directories
    from "build/intermediates/classes/release/${namespacePath}/"
    into "build/intermediates/dex/${namespacePath}/"

    // exclude classes which don't have a corresponding .java entry in the source directory
    def remExt = { name -> name.lastIndexOf('.').with {it != -1 ? name[0..<it] : name} }
    eachFile {details ->
        def thisFile = new File("${projectDir}/src/main/java/${namespacePath}/", remExt(details.name)+".java")
        if (!(thisFile.exists())) {
            details.exclude()
        }
    }
}

task assembleExternalJar << {
    // Get the location of the Android SDK
    ext.androidSdkDir = System.env.ANDROID_HOME
    if(androidSdkDir == null) {
        Properties localProps = new Properties()
        localProps.load(new FileInputStream(file('local.properties')))
        ext.androidSdkDir = localProps['sdk.dir']
    }
    // Make sure no existing jar file exists as this will cause dx to fail
    new File("${buildDir}/intermediates/dex/${PLUGIN_NAMESPACE}.jar").delete();
    // Use command line dx utility to convert *.class files into classes.dex inside jar archive
    String cmdExt = Os.isFamily(Os.FAMILY_WINDOWS) ? '.bat' : ''
    exec {
        commandLine "${androidSdkDir}/build-tools/${BUILD_TOOLS_VERSION}/dx${cmdExt}", '--dex',
                    "--output=${buildDir}/intermediates/dex/${PLUGIN_NAMESPACE}.jar",
                    "${buildDir}/intermediates/dex/"
    }
    copyJarToOutputs.execute()
}

task copyJarToOutputs(type: Copy) {
    // Copy the built jar archive to the outputs folder
    from 'build/intermediates/dex/'
    into 'build/outputs/'
    include '*.jar'
}


// Set the dependencies of the build tasks so that assembleExternalJar does a complete build
copyClasses.dependsOn(assemble)
assembleExternalJar.dependsOn(copyClasses)

詳細については、私の github にあるサンプル アプリの完全なソース コードを参照してください。

于 2014-12-02T02:49:38.673 に答える