Фильтрация ресурсов из монолитного сервиса Play Services, чтобы уменьшить ваш APK

Было много написано о монолитной природе Служб Google Play и почему его следует разделить на несколько библиотек. На данный момент обходным решением для вашего APK является использование proguard для извлечения неиспользуемых ссылок. Это очень хорошо работает для classes.dex, но не для включенных ресурсов.

Я получаю около 1 МБ дополнительных неиспользуемых ресурсов и с помощью прилагаемого приложения Android Wear эти накладные расходы удваиваются. Поэтому мой APK на 2 МБ больше, чем нужно.

Мне интересно, есть ли простой способ в Gradle, чтобы исключить некоторые ресурсы, поступающие из AAR зависимостей из полученного APK.

Кажется, что параметры AAPT в плагине Gradle Android позволяют разрешать фильтрацию активов.

Я думал о подключении в какой-то пользовательский aapt script, который вызывал бы удаление для списка ресурсов с помощью aapt перед подписанием APK для выпуска.

Есть ли у кого-то еще более простое решение?

Ответы

Ответ 1

После некоторых исследований я нашел следующее субоптимальное решение. Мне пришлось перечислять все необходимые ресурсы вручную (к счастью, шаблоны применимы), а также удалять все файлы, на которые они ссылаются. Ниже приведен пример, который делает приложение API Wear с 1.5 МБ на 300 КБ, и APK работает нормально без проблем.

Мне пришлось создать свою собственную задачу stripResources и подключить ее между стандартными задачами подключаемого модуля Android: mergeReleaseResources и processReleaseResources.

task stripResources << {
    println "Custom resource stripping in: $buildDir"
    delete fileTree(dir: "$buildDir", include: "**/layout/confirmation_activity_layout.xml")
    delete fileTree(dir: "$buildDir", include: "**/layout/watch_card_content.xml")
    delete fileTree(dir: "$buildDir", include: "**/common_signin*.png")
    delete fileTree(dir: "$buildDir", include: "**/drawable/common_signin*.xml")
    delete fileTree(dir: "$buildDir", include: "**/generic_confirmation*.png")
    delete fileTree(dir: "$buildDir", include: "**/drawable/confirmation_*.xml")
    delete fileTree(dir: "$buildDir", include: "**/drawable/card_background.xml")
    delete fileTree(dir: "$buildDir", include: "**/card_frame*.png")
    delete fileTree(dir: "$buildDir", include: "**/go_to*.png")
    delete fileTree(dir: "$buildDir", include: "**/drawable/go_to_*.xml")
    delete fileTree(dir: "$buildDir", include: "**/ic_plusone*.png")
    delete fileTree(dir: "$buildDir", include: "**/powered_by_google*.png")
    // if you only have English you can teh following to filter out some GPS texts wich also take few hundreds of kb
    // delete fileTree(dir: "$buildDir", include: "**/values-*/values.xml")
}

    tasks.whenTaskAdded { task ->
        if (task.name == 'processReleaseManifest') {
            task.dependsOn stripResources
        }
    }

Вы можете выполнить аналогичную задачу для обычного Android APK.

Ответ 2

Начиная с версии 0.14 плагина Android Gradle, это может быть выполнено автоматически, как указано в этот пост:

android {
    buildTypes {
        release {
            minifyEnabled true
            shrinkResources true
        }
    }
}

shrinkResources - это флаг, который сообщает компилятору пропустить любые ресурсы, на которые не ссылаются. minifyEnabled - это новое имя для runProguard, которое должно быть включено для shrinkResources для работы.

Ответ 3

Благодаря Петру Налевке проницательный ответ я придумал следующее решение (a.k.a. уродливый хак).

Этот код удаляет все чертежи PNG под hdpi, xhdpi, xxhdpi и т.д., сохраняя только чертежи в mdpi (те, у которых самый маленький размер файла). Двоичные файлы удаляются только в том случае, если у них есть брат в mdpi, то есть если их можно безопасно удалить. Службы Google Play должны оставаться полностью работоспособными.

Задача регистрируется для каждого варианта приложения, поэтому этот код работает с несколькими вкусами продукта.

applicationVariants.all { variant ->
    def variantCamelName = variant.name.substring(0, 1).toUpperCase() + variant.name.substring(1)
    def stripResourcesTask = task("strip${variantCamelName}Resources") {
        doFirst {
            def resDir = "$buildDir/intermediates/res/merged/$variant.dirName"
            def gpsDir = "$buildDir/intermediates/exploded-aar/com.google.android.gms"
            fileTree(dir: "$gpsDir", include: "**/drawable-*/*.png").each { resFile ->
                if (!resFile.parentFile.name.endsWith("-mdpi")) {
                    def resName = resFile.name
                    def resDirName = resFile.parentFile.name
                    def mdpiFile = "$resFile.parentFile.parentFile/drawable-mdpi/$resName"
                    if (file(mdpiFile).file) {
                        def files = fileTree(dir: "$resDir", include: "**/${resDirName}/${resName}")
                        // files.each { f -> println "Deleting $f..." }
                        delete files
                    }
                }
            }
        }
    }
    tasks["merge${variantCamelName}Resources"].finalizedBy stripResourcesTask
}

Протестировано с помощью Android Studio 1.3.0, Gradle версия плагина 1.3.0, инструменты для сборки версии 22.0.1 и версия GPS 7.5.0.

Я немного Groovy noob, поэтому это, вероятно, не самое элегантное решение.