Android 内存优化之优化 so 文件

优化 so 大小能否减少 Android 应用的内存?从原理上讲是可以的,实际效果如何?一起来看看吧。

(本文待完善)

相关文章:

背景

我们知道,Android 应用占用的内存有一类是 Code。dumpsys meminfo 命令的输出中可以看到 Code 类别的内存:

关于 Code 内存,官网是这样解释的:

Memory that your app uses for code and resources, such as dex bytecode, optimized or compiled dex code, .so libraries, and fonts 来源

简单计算一下,发现 Code 内存大致包括 .so mmap 和 .apk mmap。

我们可以观察 so 优化前后 .so mmap 的变化来判断优化是否有效果。

不过还有另外一个问题:Android 应用中通常有很多 so,所以没法通过 .so mmap 来判断单个 so 的优化效果。该怎么办?

maps 文件

判断单个 so 的优化效果的方法是查看和分析进程对应的 proc/<pid>/maps 文件。

Understanding-linux-proc-id-maps 对 maps 文件有介绍。

Each row in /proc/$PID/maps describes a region of contiguous virtual memory in a process or thread. Each row has the following fields:

1
2
address           perms offset  dev   inode   pathname
08048000-08056000 r-xp 00000000 03:0c 64593 /usr/sbin/gpm
  • address - 进程地址空间的起始地址
  • permissions - 访问权限
  • pathname - 如果该区域是从文件映射来的,pathname 为文件名

注意,直接在 adb shell 用 cat 查看 maps 文件可能会遇到 permission denied 问题。可以 run-as 命令来避免该问题。

1
2
3
4
5
➜ ✗ adb shell
HWANE:/ $ run-as
run-as: usage: run-as <package-name> [--user <uid>] <command> [<args>]
run-as com.example.flutter.image.flutter_image_demo cat /proc/7331/maps \
| grep libapp.so

以下示例查看 com.example.flutter.image.flutter_image_demo 的 maps 文件。

1
2
3
HWANE:/ $ run-as com.example.flutter.image.flutter_image_demo  cat /proc/7331/maps | grep libandroid
7016edb000-7016ef6000 r-xp 00000000 fd:00 7502 /system/lib64/libandroid.so
7016ef7000-7016efb000 r--p 0001b000 fd:00 7502 /system/lib64/libandroid.so

方便起见,可以写个脚本来解析 maps 文件以快速统计每个 so 文件占用内存的总大小。源码见 gist

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
data_list = []
while (True):
raw_data = input('paste maps string here: ' if len(data_list) == 0 else '')
if (raw_data.strip() == ''):
break
data_list.append(raw_data)

mem_map = {}
for item in data_list:
tmp = item.split(' ')
addr = tmp[0].split('-')
if len(addr) == 2:
mem = (int(addr[1], 16) - int(addr[0], 16)) / 1024.0 / 1024.0

key = tmp[-1][-30:]
if key in mem_map:
mem_map[key].append(mem)
else:
li = []
li.append(mem)
mem_map[key] = li
else:
pass
for (k, v) in mem_map.items():
print(k, ' ', sum(v), 'MB')

-w785

测试数据

  • 测试目标:FlutterImageDemo、Flutter 项目优化前、Flutter 项目优化后
  • 测试工具:dumpsys meminfo 和 run-as
  • 测试指标:
    • libflutter.so 和 libapp.so 文件大小
    • .so mmap
    • maps 文件中 libflutter.so 和 libapp.so 大小

数据如下。

FlutterImageDemo

FlutterImageDemo 是一个简单的 Flutter 应用。

  • 包名 com.example.flutter.image.flutter_image_demo
  • 进程号 7331
  • APK 包解压出来的 libflutter.so 和 libapp.so 大小分别是 13.9MB 和 6.1MB

不得不吐槽下 Mac 上文件大小的计算方式对开发人员实现不友好。我重新算了一下,准确的大小分别是 13.2MB 和 5.8MB。

-w778

.so mmap 数据:

1
2
3
4
5
6
7
adb shell dumpsys meminfo com.example.flutter.image.flutter_image_demo

** MEMINFO in pid 7331 [com.example.flutter.image.flutter_image_demo] **
Pss Private Private SwapPss Heap Heap Heap
Total Dirty Clean Dirty Size Alloc Free
------ ------ ------ ------ ------ ------ ------
.so mmap 14010 584 12600 47

maps 数据如下:

1
2
3
4
5
6
7
8
9
10
HWANE:/ $ run-as com.example.flutter.image.flutter_image_demo  cat /proc/7331/maps | grep libflutter.so
6fffee6000-70005d9000 r--p 00000000 103:08 39455 /data/app/com.example.flutter.image.flutter_image_demo-FcAoMElVHeoIhc0Ac6coTQ==/lib/arm64/libflutter.so
70005e6000-7000ae9000 r-xp 00700000 103:08 39455 /data/app/com.example.flutter.image.flutter_image_demo-FcAoMElVHeoIhc0Ac6coTQ==/lib/arm64/libflutter.so
7000af6000-7000bd6000 rw-p 00c10000 103:08 39455 /data/app/com.example.flutter.image.flutter_image_demo-FcAoMElVHeoIhc0Ac6coTQ==/lib/arm64/libflutter.so
7000bd6000-7000c25000 r--p 00cf0000 103:08 39455 /data/app/com.example.flutter.image.flutter_image_demo-FcAoMElVHeoIhc0Ac6coTQ==/lib/arm64/libflutter.so
HWANE:/ $ run-as com.example.flutter.image.flutter_image_demo cat /proc/7331/maps | grep libapp.so
6fff5c3000-6fff5c5000 rw-p 00000000 103:08 45222 /data/app/com.example.flutter.image.flutter_image_demo-FcAoMElVHeoIhc0Ac6coTQ==/lib/arm64/libapp.so
6fff5c5000-6fff85d000 r-xp 00002000 103:08 45222 /data/app/com.example.flutter.image.flutter_image_demo-FcAoMElVHeoIhc0Ac6coTQ==/lib/arm64/libapp.so
6fff85d000-6fffb94000 r--p 0029a000 103:08 45222 /data/app/com.example.flutter.image.flutter_image_demo-FcAoMElVHeoIhc0Ac6coTQ==/lib/arm64/libapp.so
6fffb94000-6fffb95000 rw-p 005d1000 103:08 45222 /data/app/com.example.flutter.image.flutter_image_demo-FcAoMElVHeoIhc0Ac6coTQ==/lib/arm64/libapp.so

maps 解析后如下:

1
2
/lib/arm64/libflutter.so   13.14453125 MB
/lib/arm64/libapp.so 5.8203125 MB

Flutter 项目优化前

从优化前的 APK 包解压出来的 libflutter.so 和 libapp.so 大小分别是 5.9MB 和 9.6MB

.so mmap 数据:

1
2
3
4
5
6
adb shell dumpsys meminfo com.t.t.i.host
** MEMINFO in pid 15191 [com.t.t.i.host] **
Pss Private Private SwapPss Heap Heap Heap
Total Dirty Clean Dirty Size Alloc Free
------ ------ ------ ------ ------ ------ ------
.so mmap 16194 452 13896 127

maps 数据如下:

1
2
3
4
5
6
7
8
9
10
HWANE:/ $ run-as com.t.t.i.host cat /proc/15191/maps  | grep libflutter.so
c16c1000-c17ef000 r--p 00000000 103:08 132483 /data/app/com.t.t.i.host--zWkLmLHuo53FaZeCcpa7w==/lib/arm/libflutter.so
c17ef000-c1baa000 r-xp 0012e000 103:08 132483 /data/app/com.t.t.i.host--zWkLmLHuo53FaZeCcpa7w==/lib/arm/libflutter.so
c1baa000-c1c87000 rw-p 004e9000 103:08 132483 /data/app/com.t.t.i.host--zWkLmLHuo53FaZeCcpa7w==/lib/arm/libflutter.so
c1c87000-c1caf000 r--p 005c6000 103:08 132483 /data/app/com.t.t.i.host--zWkLmLHuo53FaZeCcpa7w==/lib/arm/libflutter.so
HWANE:/ $ run-as com.t.t.i.host cat /proc/15191/maps | grep libapp.so
c0a0f000-c0a11000 rw-p 00000000 103:08 129024 /data/app/com.t.t.i.host--zWkLmLHuo53FaZeCcpa7w==/lib/arm/libapp.so
c0a11000-c0f55000 r-xp 00002000 103:08 129024 /data/app/com.t.t.i.host--zWkLmLHuo53FaZeCcpa7w==/lib/arm/libapp.so
c0f55000-c13a6000 r--p 00546000 103:08 129024 /data/app/com.t.t.i.host--zWkLmLHuo53FaZeCcpa7w==/lib/arm/libapp.so
c13a6000-c13a7000 rw-p 00997000 103:08 129024 /data/app/com.t.t.i.host--zWkLmLHuo53FaZeCcpa7w==/lib/arm/libapp.so

maps 解析后如下:

1
2
Ccpa7w==/lib/arm/libflutter.so   5.9296875 MB
FaZeCcpa7w==/lib/arm/libapp.so 9.59375 MB

Flutter 项目优化后

从优化后的 APK 包解压出来的 libflutter.so 和 libapp.so 大小分别是 5.9MB 和 8.6MB

.so mmap 数据:

1
2
3
4
5
6
7
adb shell dumpsys meminfo com.t.t.i.host

** MEMINFO in pid 14331 [com.t.t.i.host] **
Pss Private Private SwapPss Heap Heap Heap
Total Dirty Clean Dirty Size Alloc Free
------ ------ ------ ------ ------ ------ ------
.so mmap 16053 452 13572 117

maps 数据如下:

1
2
3
4
5
6
7
8
9
10
127|HWANE:/ $ run-as com.t.t.i.host cat /proc/14331/maps | grep libflutter.so
c154b000-c1679000 r--p 00000000 103:08 136617 /data/app/com.t.t.i.host-Cf28XWHWRKP40xhjV1HTdA==/lib/arm/libflutter.so
c1679000-c1a34000 r-xp 0012e000 103:08 136617 /data/app/com.t.t.i.host-Cf28XWHWRKP40xhjV1HTdA==/lib/arm/libflutter.so
c1a34000-c1b11000 rw-p 004e9000 103:08 136617 /data/app/com.t.t.i.host-Cf28XWHWRKP40xhjV1HTdA==/lib/arm/libflutter.so
c1b11000-c1b39000 r--p 005c6000 103:08 136617 /data/app/com.t.t.i.host-Cf28XWHWRKP40xhjV1HTdA==/lib/arm/libflutter.so
HWANE:/ $ run-as com.t.t.i.host cat /proc/14331/maps | grep libapp.so
c0a8d000-c0a8f000 rw-p 00000000 103:08 131577 /data/app/com.t.t.i.host-Cf28XWHWRKP40xhjV1HTdA==/lib/arm/libapp.so
c0a8f000-c0fd2000 r-xp 00002000 103:08 131577 /data/app/com.t.t.i.host-Cf28XWHWRKP40xhjV1HTdA==/lib/arm/libapp.so
c0fd2000-c1328000 r--p 00545000 103:08 131577 /data/app/com.t.t.i.host-Cf28XWHWRKP40xhjV1HTdA==/lib/arm/libapp.so
c1328000-c1329000 rw-p 0089b000 103:08 131577 /data/app/com.t.t.i.host-Cf28XWHWRKP40xhjV1HTdA==/lib/arm/libapp.so

maps 解析后如下:

1
2
V1HTdA==/lib/arm/libflutter.so   5.9296875 MB
0xhjV1HTdA==/lib/arm/libapp.so 8.609375 MB

总结

  • 对比 libflutter.so 和 libapp.so 文件大小及 maps 的解析结果,发现数值几乎一致
  • 对比 .so mmap 内存,看起来跟 so 文件大小相关性不明显 (??? 哪里出错了?)
libflutter.so文件 libapp.so文件 .so mmap maps文件解析
FlutterImageDemo 13.2MB 5.8MB 13.7MB 13.1MB和5.8MB
优化前 5.9MB 9.6MB 15.8MB 5.9MB和9.6MB
优化后 5.9MB 8.6MB 15.7MB 5.9MB和8.6MB