Android NDK 开发中 SO 包大小压缩方法详解

目录
  • 背景
  • 1.STL的使用方式
  • 2.不使用Exception和RTTI
    • RTTI
    • Exception
  • 3.使用 gc-sections去除没有用到的函数
  • 4.去除冗余代码
  • 5.设置编译器的优化flag
  • 6.设置编译器的 Visibility Feature
  • 7.设置编译器的Strip选项
  • 8.去除C++代码中的iostream相关代码
  • 总结

背景

这周在做Yoga包的压缩工作。Yoga本身是用BUCK脚本编译的,而最终编译出几个包大小大总共约为7M,不能满足项目中对于APK大小的限制,因此需要对它进行压缩。

这里先将Yoga编译脚本用CMAKE重新改写,以便可以在android studio中直接使用并输出一个AAR的包。后面又对它进行了压缩,最终将Yoga包的大小压缩到200多KB。

下面整理了一些可以用于减少NDK开发中Android SO包大小的方法:

1.STL的使用方式

对于C++的library,引用方式有2种:

  • 静态方式(static)
  • 动态方式(shared)

其中,静态方式在编译时会将用到的相关代码直接复制到目的文件中;而动态方式则会将相关的代码打成so文件,以便多次引用。由于编译器在编译时并不能知道所有被引用的地方,所以同时会打入了很多不相关的代码。

所以,如果项目中引用library的函数较多时,用动态方式可以避免多次拷贝,节省空间。相反,则直接使用静态方式会更节省空间。

NDK开发中,可以通过gradle的设置来配置:

defaultConfig{
  externalNativeBuild{
    cmake{
      // gnustl_shared 动态
      arguments "-DANDROID_STL=gnustl_static"
    }
  }
}

在Yoga中,项目里的stl使用较少时,安卓运行时使用static的方式,而不是shared,所以这里采用static的方式。在采取了这种方式后,包的大小从2.7M缩减到了2M。

2.不使用Exception和RTTI

C++的exception和RTTI功能在NDK中默认是关闭的,但是可以通过配置打开的。

Android.mk:

APP_CPPFLAGS += -fexceptions -frtti

CMake:

set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fexceptions -frtti")

Exception和RTTI会显著的增加包的体积,所以非必须的时候,没有必要使用。

RTTI

通过RTTI,能够通过基类的指针或引用来检索其所指对象的实际类型,即运行时获取对象的实际类型。C++通过下面两个操作符提供RTTI。

(1)typeid:返回指针或引用所指对象的实际类型。

(2)dynamic_cast:将基类类型的指针或引用安全的转换为派生类型的指针或引用。

在yoga中,RTTI的选项是默认打开的,而代码中其实并没有用到相关的功能,这里可以直接关闭。

Exception

使用C++的exception会增加包的大小,而目前JNI对C++的exception的支持是有bug的,比如下面这段代码就会引起程序的crash(对于低版本的android NDK)。

因此要在程序中引入exception要自己实现相关逻辑,yoga就是这么做的,这个又增加了一些包体大小。对于开发者来说,exception可以帮助快速定位问题,而对于使用者并不是那么重要,这里可以去掉。

try {
    ...
} catch (std::exception& e) {
    env->ThrowNew(env->FindClass("java/lang/Exception"), "Error occured");
}

在yoga中,在关闭RTTI和Exception功能并把exception相关的代码都去掉后,包的大小从2M缩减到的1.8M。

3.使用 gc-sections去除没有用到的函数

去除未使用的代码显然可以减少包体的大小,而在NDK的开发中,并不需要手动的来做这一点。可以开启编译器的gc-sections选项,让编译器自动的帮你做到这一点。

编译器可以配置自动去除未使用的函数和变量,以下是配置方式:

CMake:

# 去除未使用函数与变量
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -ffunction-sections -fdata-sections")
set(CMAKE_CXX_FLAGS "${CMAKE_C_FLAGS}")
# 设置去除未使用代码的链接flag
SET_TARGET_PROPERTIES(yoga PROPERTIES LINK_FLAGS "-Wl,--gc-sections")

Android.mk:

LOCAL_CPPFLAGS += -ffunction-sections -fdata-sections
LOCAL_CFLAGS += -ffunction-sections -fdata-sections
LOCAL_LDFLAGS += -Wl,--gc-sections

4.去除冗余代码

在NDK中,链接器还有一个选项 “-icf = safe”,可以用于去除代码中的冗余代码。但是要注意的是,这个选项也有可能去除定义好的inline函数,这里必须要做好权衡。

下面是配置方式:

CMake:

SET_TARGET_PROPERTIES(yoga PROPERTIES LINK_FLAGS "-Wl,--gc-sections,--icf=safe")

Android.mk:

LOCAL_LDFLAGS += -Wl,--gc-sections,--icf=safe

5.设置编译器的优化flag

编译器有个优化flag可以设置,分别是-Os(体积最小),-O3(性能最优)等。这里将编译器的优化flag设置为-Os,以便减少体积。

CMake:

set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Os")
set(CMAKE_CXX_FLAGS "${CMAKE_C_FLAGS}")

Android.mk

LOCAL_CPPFLAGS += -Os
LOCAL_CFLAGS += -Os

在采用了3,4,5这几种方式后,Yoga包的大小从1.8M减少到了1.7M。这里减少的比较少是因为Yoga在这方面已经做的挺好了,其他的库可能会更有效。

6.设置编译器的 Visibility Feature

还有个减少包体大小的方法,就是设置编译器的visibility feature。

Visibility Feature就是用来控制在哪些函数可以在符号表中被输入,由于C++并不是完全面向对象的,非类的方法并没有public这种修饰符,因此,要用Visibility Feature来控制哪些函数可以被外部调用。

而JNI提供了一个宏-JNIEXPORT来控制这点。所以只要对函数加上这个宏,像这样:

// JNIEXPORT就是控制可见的宏
// JNICALL在NDK这里没有什么意义,只是个标识宏
JNIEXPORT void JNICALL Java_ClassName_MethodName(JNIEnv *env, jobject obj, jstring javaString)

然后在编译器的FLAGS选项开启 -fvisibility = hidden 就可以。这样,不仅可以控制函数的可见性,并且可以减少包体的大小。

CMake:

set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fvisibility=hidden")
set(CMAKE_CXX_FLAGS "${CMAKE_C_FLAGS}")

7.设置编译器的Strip选项

我在把Yoga库编译成AAR包的过程中发现,它的体积明显会大于最后打包进APK的大小,这点非常不合理,但是无法找到原因。

最终搜索到这是谷歌NDK的一个bug,在打AAR包的过程中,无论是debug版本还是release版本,NDK toolchain不会自动的把方便调试的C++ 符号表(Symbol Table)中数据删除,而只会在打APK包的时候进行这一操作。这就导致了打成的AAR包中的SO体积明显偏大。

找到原因后这个问题就很好解决了,可以手动的在链接选项中加入 strip参数,配置如下所示:

SET_TARGET_PROPERTIES(yoga PROPERTIES LINK_FLAGS "-Wl,--gc-sections,--icf=safe,-s")

强制进行strip操作后,将Yoga包的体积从1.7M成功减少到了282KB。

8.去除C++代码中的iostream相关代码

使用STL中的iostream相关库会明显的增加包的体积,而NDK本身是有预编译库(android/log.h)可以代替这一功能的,在Yoga这里,用log的函数代替了iostream中的所有函数,如:

//代替所有的iostream库里函数
//cout << obj->toString() << endl;
__android_log_print(ANDROID_LOG_VERBOSE,"Yoga","Node is: %s",obj->toString().c_str());

在做完代替之后,yoga包的体积从282KB减少到了218KB。

总结

在做完这一系列工作后,最终成功的压缩了Yoga包的体积,从几M到最后输出一个218KB的AAR包提供使用。以上几种方法并不局限于Yoga包的缩减。在NDK开发中,要缩减SO包的体积都可以按照这几种方式尝试一下。

以上就是Android NDK 开发中 SO 包大小压缩方法详解的详细内容,更多关于Android NDK开发SO包压缩的资料请关注我们其它相关文章!

(0)

相关推荐

  • Android对so进行简单hook思路解析

    目录 1.什么是Hook 2.对App的so进行Hook的一种思路 3.一个最基本的JNI sample程序代表目标宿主Apk 4.开始Hook 4.1.编写Hook代码 1.什么是Hook Hook 技术又叫做钩子函数,在系统没有调用该函数之前,钩子程序就先捕获该消息,钩子函数先得到控制权,这时钩子函数既可以加工处理(改变)该函数的执行行为,还可以强制结束消息的传递.简单来说,就是把系统的程序拉出来变成我们自己执行代码片段. 2.对App的so进行Hook的一种思路 我们知道现在JNI在And

  • Android Studio gradle配置packagingOptions打包so库重复

    目录 正文 pickFirst 匹配 doNotStrip 设置 merge 将匹配的文件都添加到APK中 exclude 过滤 正文 在安卓开发中,通常会使用到gradle来编译,在安卓项目的app目录下的build.gradle中是用来对编译进行配置的,packagingOptions 是其中的一个打包配置,常见的设置项有exclude.pickFirst.doNotStrip.merge. 在日常代码开发中,我们需要知其然,而知其所以然,本文章知识也是Android日常瘦身的的必备知识.

  • Android性能优化getResources()与Binder导致界面卡顿优化

    目录 背景 观测 1. trace体现UI绘制操作严重耗时 2. 排查measure和layout慢的原因:可疑的多次binder 3. binder:在哪.谁为.为何频繁调用 4. binder:频繁调用的具体定位 结论 方案 背景 某轮测试发现,我们的设备运行一个第三方的App时,卡顿感非常明显: 界面加载很慢,菊花转半天 滑屏极度不跟手,目测观感帧率低于15 对比机(竞品)也会稍微一点卡,但是好很多,基本不会有很大感觉的卡顿 可以初步判定我们的设备存在性能问题,亟需优化,拉平到竞品水准.

  • Android进程间大数据通信LocalSocket详解

    目录 前言 服务端初始化 客户端初始化 数据传输 发送数据 接收数据 传输复杂数据 写数据 读数据 传输超大数据 写入 读取 前言 说起Android进行间通信,大家第一时间会想到AIDL,但是由于Binder机制的限制,AIDL无法传输超大数据. 那么我们如何在进程间传输大数据呢? Android中给我们提供了另外一个机制:LocalSocket 它会在本地创建一个socket通道来进行数据传输. 那么它怎么使用? 首先我们需要两个应用:客户端和服务端 服务端初始化 override fun

  • Android开发sensor旋转屏问题解决示例

    目录 一.查询 activity xml 配置screenOrientation信息: 二.事件发生时间点分析: 三.logcat查看sensor激活状态: 状态栏--控制按钮--“方向锁定” 开启时,en=0 即 sensor关闭! 四.查看APP是否注册旋转屏sensor 五.sensorservice 信息 bugreport dumpsys sensorservice信息查看 注册的sensor信息 相关sensor: bugreport device_orient 信息查看上报事件 六

  • Python中扩展包的安装方法详解

    前言 作为一个pythoner ,包的安装时必须懂的,这个语言跟matlab很类似,开源.共享,只要你有好的方法,都可以作为一个库,供大家下载使用,毕竟俗话说:"人生苦短,请用Python吗",下面话不多说,我们来一起看看详细的介绍吧. 方法如下: 1.单文件模块 将包拷贝到python安装目录下Lib下,eg:D:\py\Lib. 2.多文件模块 找到模块包(压缩文件zip或tar.gz)下载,进行解压,然后控制台中执行:python setup.py install xxx即可 3

  • Android GZip的使用-开发中网络请求的压缩实例详解

    Android  GZip: gzip是GNUzip的缩写,它是一个GNU自由软件的文件压缩程序. HTTP协议上的GZIP编码是一种用来改进WEB应用程序性能的技术.一般服务器中都安装有这个功能模块的,服务器端不需做改动. 当浏览器支持gzip 格式的时候, 服务器端会传输gzip格式的数据. 从Http 技术细节上讲,就是 http request 头中 有 "Accept-Encoding", "gzip" ,response 中就有返回头Content-En

  • Android  GZip的使用-开发中网络请求的压缩实例详解

    Android  GZip: gzip是GNUzip的缩写,它是一个GNU自由软件的文件压缩程序. HTTP协议上的GZIP编码是一种用来改进WEB应用程序性能的技术.一般服务器中都安装有这个功能模块的,服务器端不需做改动. 当浏览器支持gzip 格式的时候, 服务器端会传输gzip格式的数据. 从Http 技术细节上讲,就是 http request 头中 有 "Accept-Encoding", "gzip" ,response 中就有返回头Content-En

  • Android 往Framework中添加新资源的方法详解

    有时候我们想在标准的Framework中添加自己的新的资源怎么办呢?办法就是我们来尝试下.通过Eclipse的联系,我们可以联想到是否就是简单的把字符串放在res的各个文件夹里面.先来试试看,编译,系统立即报错.为什么呢?它提示你利用make update-api这个命令来更新public.xml文件或者把这个声明称hide类型.这个肯定不是我们想要的.所以方法有二:方法1:正常添加完资源后,执行make update-api函数.系统更新res/values/public.xml文件.方法2:

  • IOS开发中NSURL的基本操作及用法详解

    NSURL其实就是我们在浏览器上看到的网站地址,这不就是一个字符串么,为什么还要在写一个NSURL呢,主要是因为网站地址的字符串都比较复杂,包括很多请求参数,这样在请求过程中需要解析出来每个部门,所以封装一个NSURL,操作很方便. 1.URL URL是对可以从互联网上得到的资源的位置和访问方法的一种简洁的表示,是互联网上标准资源的地址.互联网上的每个文件都有一个唯一的URL,它包含的信息指出文件的位置以及浏览器应该怎么处理它. URL可能包含远程服务器上的资源的位置,本地磁盘上的文件的路径,甚

  • Go语音开发中常见Error类型处理示例详解

    目录 前言 透明错误处理策略 带来的问题 哨兵(Sentinel)错误处理策略 带来的问题 1.对errors.Error()的依赖 2.定义的错误类型会被公开 Error types 带来的问题 不透明错误处理策略(Opaque errors) 带来的问题 总结 前言 上文我们了解了 Error 在Go 中的设计理念.单从理念上来看, Error 的设计似乎有利于逻辑处理.但实际上,在开发过程中,我们还会遇到各种各样的困难.为了优化开发的流程,开发者们发明了很多处理 Error 的做法.今天我

  • JSP开发中Apache-HTTPClient 用户验证的实例详解

    JSP开发中Apache-HTTPClient 用户验证的实例详解 前言: 在微服务框架之外的系统中,我们经常会遇到使用httpClient进行接口调用的问题,除了进行白名单的设置,很多时候我们需要在接口调用的时候需要身份认证.翻了一下官方文档,解决方法很多,但是都不太符合实际业务场景,这里提供一种简单粗暴的解决方法. 解决方法:利用请求头,将验证信息保存起来. 实现代码: public class HttpClientUtils { protected static final Logger

  • python中seaborn包常用图形使用详解

    seaborn包是对matplotlib的增强版,需要安装matplotlib后才能使用. 所有图形都用plt.show()来显示出来,也可以使用下面的创建画布 fig,ax=plt.subplots() #一个画布 fig,(ax1,ax2) = plt.subplots( ncols=2) #两个画布 1)单个特征统计图countplot sn.countplot(train.mnth)#离散型特征可使用,描述样本点出现的次数. 2)单个特征统计图distplot sn.distplot(t

  • Java开发中synchronized的定义及用法详解

    概念 是利用锁的机制来实现同步的. 互斥性:即在同一时间只允许一个线程持有某个对象锁,通过这种特性来实现多线程中的协调机制,这样在同一时间只有一个线程对需同步的代码块(复合操作)进行访问.互斥性我们也往往称为操作的原子性. 可见性:必须确保在锁被释放之前,对共享变量所做的修改,对于随后获得该锁的另一个线程是可见的(即在获得锁时应获得最新共享变量的值),否则另一个线程可能是在本地缓存的某个副本上继续操作从而引起不一致. 用法 修饰静态方法: //同步静态方法 public synchronized

  • C#中调整图像大小的步骤详解

    在本篇文章中,我将介绍如何在C#中来调整你想要的图像大小.要实现这一目标,我们可以采取以下几个步骤: 1.首先要获取你想要调整大小的图像: string path = Server.MapPath("~/Images"); System.Drawing.Image img = System.Drawing.Image.FromFile(string.Concat(path,"/3904.jpg")); 2.将图像转换为Bitmap: Bitmap b = new B

随机推荐