【问题标题】:Custom Class Loading in Dalvik with Gradle (Android New Build System)使用 Gradle 在 Dalvik 中自定义类加载(Android 新构建系统)
【发布时间】:2013-08-11 16:15:46
【问题描述】:

根据 Fred Chung 在 Android 开发者博客上对Custom Class Loading in Dalvik 的介绍:

Dalvik VM 为开发人员提供了执行自定义的工具 类加载。而不是从 默认位置,应用程序可以从其他位置加载它们 内部存储或网络等位置。

但是,没有多少开发人员需要进行自定义类加载。但是那些按照该博客文章中的说明进行操作的人可能会在使用 Gradle(Google I/O 2013 中引入的新 Android 构建系统)模仿相同行为时遇到一些问题。

究竟如何调整新的构建系统以执行与旧的(基于 Ant)构建系统相同的中间步骤?

【问题讨论】:

    标签: android gradle dalvik dynamic-class-loaders


    【解决方案1】:

    我和我的团队最近在我们的应用程序中达到了 64K 方法引用,这是 dex 文件中支持的最大数量。为了绕过这个限制,我们需要将部分程序分割成多个二级dex文件,并在运行时加载。

    我们按照问题中提到的旧的、基于 Ant 的构建系统的博客文章进行操作,一切正常。但我们最近觉得有必要迁移到基于 Gradle 的新构建系统。

    此答案并不打算用完整的示例替换完整的博客文章。相反,它将简单地解释如何使用 Gradle 来调整构建过程并实现相同的目标。请注意,这可能只是其中一种方式,也是我们目前在团队中的方式。这并不一定意味着它是唯一的方式。

    我们的项目结构有些不同,这个示例作为一个单独的 Java 项目工作,它将所有源代码编译成 .class 文件,将它们组装成单个 .dex 文件,最后将单个 .dex 文件打包成.jar 文件。

    开始吧……

    在根目录 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']
        }
    }
    

    接下来,我们将内置 jar 任务的默认配置更改为仅包含 classes.dex 文件而不是所有 .class 文件:

    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 任务依赖于我们的dexClasses 任务,以确保我们的任务在最终的.jar 文件组装之前执行。我们用一行简单的代码来做到这一点:

    jar.dependsOn(dexClasses)
    

    我们完成了...只需使用通常的 assemble 任务调用 Gradle,您的最终 .jar 文件 ${buildDir}/libs/${archivesBaseName}.jar 将包含单个 classes.dex 文件(除了 MANIFEST .MF 文件)。只需将其复制到您的应用资产文件夹中(您始终可以像我们所做的那样使用 Gradle 自动执行该操作,但这超出了本问题的范围),然后按照博文的其余部分进行操作。

    如果您有任何问题,请在 cmets 中留言。我会尽我所能提供帮助。

    【讨论】:

    • 您介意在stackoverflow.com/questions/18629021/… 中提供一些意见吗?谢谢。
    • 全面且非常有用。显然,你很忙,但作为一个公共 github 存储库,这将是一个重大贡献 :) 感谢您提供的详细信息。
    • configure(jar) { include 'classes.dex' } 对我不起作用 - 我收到错误消息:“评估项目时出现问题> 无法在项目中找到属性 'jar'。”
    • @goRGon 这假定您正在应用的插件是 java 插件,而不是 android 或 android-plugin。如果您使用其中之一,则需要为此调整脚本。
    • @Ricardo,谢谢。我认为如果你提到你应用插件'java'会很棒,因为最初的问题是关于Android构建系统。如果我理解正确,在主项目中使用 java 插件会导致在没有 android-library 插件帮助的情况下手动构建所有依赖的 Android 库项目,不是吗?但看起来谷歌无法自定义其 android 插件以使用动态 dex-classes 加载......
    【解决方案2】:

    Android Studio Gradle 插件现在提供原生 multidex support,它有效地解决了 Android 65k 方法限制,而无需手动从 jar 文件加载类,因此让 Fred Chung 的博客为此目的而过时。但是,在 Android 运行时从 jar 文件中加载自定义类对于可扩展性的目的仍然有用(例如,制作 plugin framework for your app),因此我将在下面解决该使用场景:

    我使用 Android 库插件而不是 Java 插件,将 Fred Chung 博客上的原始示例应用程序移植到我的 github page over here 上的 Android Studio。我没有像在博客中那样尝试修改现有的 dex 进程以拆分为两个模块,而是将我们想要进入 jar 文件的代码放入它自己的模块中,并添加了一个自定义任务 assembleExternalJar,其中 dexes 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 上示例应用的完整源代码。

    【讨论】:

    【解决方案3】:

    查看我的回答over here。重点是:

    • 使用动态创建的dexCamelCase任务上的additionalParameters属性将--multi-dex传递给dx创建多个dex文件。
    • 使用multidex类加载器使用多个dex文件。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2012-11-27
      • 2013-08-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-05-21
      • 1970-01-01
      相关资源
      最近更新 更多