Android包体积优化

·  阅读 1010

Android Studio中提供一套完整的包体优化方案,只需在项目级build.gradle中启用缩减、混淆处理和优化功能,它能大大降低了包体积优化投入的成本,减少软件包体积大小,详见缩减应用大小(英文版更新远快于中文版)。除此之外,本文中还提供一些其它优化思路。

统一依赖

依赖统一能够有效减少开发过程中引入的冗余代码以及隐形BUG,这里仅简单介绍依赖统一的几种方案(进一步学习请查看参考内容):

  • 借助ext{...}定义extra属性(唯一不支持变量跳转的方案)
  • buildSrc中定义静态变量
  • Composing builds+自定义gradle plugin
  • version catalog

上述方式虽然解决本地项目依赖的版本问题,但是远程依赖的依赖版本无法控制,如果需要进一步精细化控制,可以使用fore强制全局使用统一依赖(位于项目级build.gradle文件中)。

allprojects {
    configurations.all {
        resolutionStrategy {
            force 'androidx.activity:activity:1.3.1',
                    'androidx.activity:activity-ktx:1.3.1'
        }
    }
}
复制代码

动态库管理

在某些特定场景下(编解码音视频、加解密数据等等),使用原生开发是更佳的选择,这也必然付出一定代价,比如软件包体积增加,这时就需要开发者考虑如何降低动态库生成大小。

针对ABI打包

项目打包过程中可能会打包所有对应CPU架构版本的动态库,对此可以使用splits创建多个单独版本的APK。除非应用渠道不支持分发对应CPU架构的应用,使用abiFilters指定创建包含特定ABI版本的APK。

splits {

    // Configures multiple APKs based on ABI.
    abi {

        // Enables building multiple APKs.
        enable true

        // By default all ABIs are included, so use reset() and include to specify that we only
        // want APKs for x86, armeabi-v7a, and mips.
        reset()

        // Specifies a list of ABIs that Gradle should create APKs for.
        include 'armeabi-v7a', 'arm64-v8a', 'x86_64', 'x86'

        // Specify that we want to also generate a universal APK that includes all ABIs.
        universalApk true
    }
}
复制代码

合并原生代码库

第三方原生代码库可以以不同的形态添加到项目中,对于不同的形态处理方式也各不相同。

  1. 源码文件:尽量编译成静态库。
  2. 库文件,动态库(.so)或静态库(.a):最好是选择引入静态库,其次是将静态库预先合并成动态库之后再引入。
  3. Prefab,详见Native dependencies in AARs:最近查看到Native Dependencies in Android Studio 4.0这篇文章,发现google发布的预制库如下图所示,寥寥无几,只能说Prefab未来可期。。。

在开始之前,先简单学习CMake的一些知识,然后介绍上述提到的处理方式(这里使用Snapshot中的源码进行介绍)。

CMake

它是一个开源且跨平台的构建工具,开发者仅需在CMakeLists.txt脚本中使用几条简单的CMake命令就可以控制整个源码的编译流程。一般命令:

  • include_directories([AFTER|BEFORE] [SYSTEM] dir1 [dir2 ...]):只会在指定目录下搜索头文件(不会递归搜索)。此命令相当于gcc(clang)命令中的-i。
  • aux_source_directory(<dir> <variable>):搜索指定目录下的源文件,并记录到指定变量中。
  • add_library(<name> [STATIC | SHARED | MODULE] [EXCLUDE_FROM_ALL] [<source>...]):向项目中添加一个名为name的库,不仅要指出它将被编译成静态库还是动态库,还要指出它需要的所有源文件。
  • set_target_properties(target1 target2... PROPERTIES prop1 value1 prop2 value2...)修改targetX中propX属性对应的valueX值。
  • add_library(<name> <type> IMPORTED [GLOBAL]):向项目中导入一个名为name的库,由tpye指定具体是动态库还是静态库。当然你还需要使用set_target_properties指明IMPORTED_LOCATION对应的属性值,也就是导入库的位置。此命令相当于gcc(clang)命令中的-L。
  • target_link_libraries(<target> ... <item>... ...)进行链接操作,生成最终的库文件。

将第三方源码尽量编译成静态库

Snapshot中用到libyuv,由于不需要外部使用,直接将其编译成静态库,结果在脚本下方。

cmake_minimum_required(VERSION 3.4.1)
include_directories(${CMAKE_CURRENT_SOURCE_DIR})
#include_directories(${CMAKE_CURRENT_SOURCE_DIR}/inclue/libyuv)
aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR}/source SOURCE)
add_library(yuv STATIC ${SOURCE})
复制代码

将libyuv编译成动态库时的结果:

正常打包的结果:

优先导入静态库

这里将导入的FFmpeg编译后的静态库(可自行参考上一篇文章种的shell脚本进行编译)。由于需要移除videosnap\src\main\cpp\libs\arm64-v8a(这里仅列出arm64-v8a的路径)下的libffmpeg_core.so文件且新添加多个静态库文件,这时需要添加链接库的名称。由于旧脚本中原来已经写过,只需要修改下图中的脚本。

将ffmpeg编译成静态库打包后的结果:

预处理静态库

当前方案适用于原生库被多个库引用,不适宜直接引入静态库时使用。静态库转换成动态库时需要使用链接器ld,具体的命令介绍可以查看📎ld_help.txt。下面是将ffmpeg静态库编译成动态库的脚本,需要注意的是此版本使用的是NDK r21,由于NDK r23版本中已对一些工具链文件进行修改,因此使用高版本时需要注意修改脚本。

#!/bin/bash
make clean
NDK="/usr/ndk/android-ndk-r21"
HOST="linux-x86_64"
SYSROOT="$NDK/toolchains/llvm/prebuilt/$HOST/sysroot"
LLVM_TOOLCHAIN="$NDK/toolchains/llvm/prebuilt/$HOST/bin"

build(){
    ARCH=$1
    API=$2
    
    case $ARCH in
        "armeabi-v7a")
            TARGET="arm-linux-androideabi"
            SPECIAL_TARGET="armv7a-linux-androideabi"
            LDFLAGS="--fix-cortex-a8 $LDFLAGS"
            FF_ARCH="arm"
            CPU="armv7-a"
        ;;
        "arm64-v8a")
            TARGET="aarch64-linux-android"
            SPECIAL_TARGET=$TARGET
            CONFIGURATION="$CONFIGURATION --disable-pthreads"
            FF_ARCH="aarch64"
            CPU="armv8-a"
        ;;
        "x86")
            TARGET="i686-linux-android"
            SPECIAL_TARGET=$TARGET
            FF_ARCH="x86"
            CPU="x86"
        ;;
        "x86_64")
            TARGET="x86_64-linux-android"
            SPECIAL_TARGET=$TARGET
            FF_ARCH="x86_64"
            CPU="x86_64"
        ;;
    esac

    CC=$LLVM_TOOLCHAIN/$SPECIAL_TARGET$API-clang
    CXX=$LLVM_TOOLCHAIN/$SPECIAL_TARGET$API-clang++
    # CROSS_PREFIX=$LLVM_TOOLCHAIN/$SPECIAL_TARGET-
    AS=$LLVM_TOOLCHAIN/$TARGET-as
    AR=$LLVM_TOOLCHAIN/$TARGET-ar
    LD=$LLVM_TOOLCHAIN/$TARGET-ld
    PREFIX="$(pwd)/android/$ARCH"
    STRIP=$LLVM_TOOLCHAIN/$TARGET-strip
    
    #高版本
    # AR=$LLVM_TOOLCHAIN/llvm-ar
    # LD=$LLVM_TOOLCHAIN/ld
    # PREFIX="$(pwd)/android/$ARCH"
    # STRIP=$LLVM_TOOLCHAIN/llvm-strip
    
# 打包
$LD \
--sysroot=$SYSROOT \
-L$SYSROOT/usr/lib/$TARGET/$API \
-L$SYSROOT/usr/lib/$TARGET/ \
-rpath-link=$SYSROOT/usr/lib \
-L$PREFIX/lib \
-L$NDK/toolchains/llvm/prebuilt/$HOST/lib/gcc/$TARGET/4.9.x/ \
-allow-multiple-definition \
-O2 \
-soname libffmpeg_core.so -shared -nostdlib -Bsymbolic --whole-archive --no-undefined -o \
$PREFIX/lib/libffmpeg_core.so \
    $PREFIX/lib/libavcodec.a \
    $PREFIX/lib/libavformat.a \
    $PREFIX/lib/libswresample.a \
    $PREFIX/lib/libavutil.a \
    -lc -lm -ldl -lgcc \
    --dynamic-linker=/system/bin/linker \
    
# 裁剪
$STRIP $PREFIX/lib/libffmpeg_core.so
}
build "armeabi-v7a" "21"
build "arm64-v8a" "21"
build "x86_64" "21"
build "x86" "16"
复制代码

参考内容

分类:
Android
标签:
收藏成功!
已添加到「」, 点击更改