(欢迎关注我们的微信公众号:Java和Android大牛频道)

本文翻译自Android官方文档

在上一讲我们简述了Android如何分配和管理内存,以及如何避免内存泄露和内存溢出的部分知识点,这节我们继续讨论Android里的内存优化问题。

How Your App Should Manage Memory

Avoid wasting memory with bitmaps

当你加载 bitmap 时, 需要根据当前设备的分辨率加载相应分辨率的bitmap进入内存,如果下载下来的原图分辨率比设备分辨率高则要压缩它. 要小心bitmap的分辨率增加后所占用的内存也要进行相应的增加(平方级increase2的增长), 因为它是根据x和y的大小来增加内存占用的

注意: 在 Android 2.3.x(api level 10)以下, 无论图片的分辨率多大 bitmap 对象在内存中始终显示相同大小, 实际的像素数据被存储在底层 native 的内存中(c++内存). 因为内存分析工具无法跟踪 native 的内存状态,所以调试 bitmap 内存分配变得非常困难. 然而, 从 Android 3.0(api level 11)开始, bitmap 对象的内存数据开始在应用程序所在Dalvik虚拟机堆内存中进行分配, 提高了回收机率和调试的可能性。如果你在老版本中发现 bitmap 对象占用的内存大小始终一样时, 切换到3.0或以上系统的设备来进行调试

更多的使用bitmaps的提示, 参阅Managing Bitmap Memory。

Use optimized data containers

利用 Android 框架优化后的数据容器, 比如 SparseArray, SparseBooleanArray 和 LongSparseArray。传统的 HashMap 在内存上的实现十分的低效,因为它需要为 HashMap 中每一项在内存中建立映射关系. 另外, SparseArray类非常高效因为它避免了对key和value的自动封箱.

Be aware of memory overhead

在你开发你的app应用时,各个阶段都要很谨慎的考虑所使用的语言和库带来的内存上的成本和开销。通常情况下, 表面上看起来无关紧要的代码可能带来巨大的开销, 下面是一些例子:

1.相比于静态常量,枚举会有超过其两倍以上的内存开销,在 android 中你需要严格避免使用枚举

2.java 中的每个类(包含匿名内部类)大约使用500个字节,

3.每个类实例在内存(RAM)中占用12到16个字节,

4.在 HashMap 中放入一个数据时, 需要为额外的其它项分配内存, 总共占用32个字节。

app里这里或者那里虽然几个bytes(app里可能被设计成类或者对象)的很快的增加都会增加内存消耗。这会增加你分析堆内存的复杂度。你不容易意识到是许多小对象占用了你大量内存。

Be careful with code abstractions

我们都知道使用抽象是一个好的编程实践,因为抽象提高了代码的灵活性和可维护性。然而,抽象也产生了很大的开销:因为为了抽象代码能执行,通常地需要相当大的更多代码。为了映射进内存需要更多的时间和和更多的内存。因此,如果你的app不是必须使用抽象,你应该避免使用抽象

Use nano protobufs for serialized data

Protocol Buffers 是 Google 公司开发的一种跨平台的、与语言无关的数据描述语言,类似于XML能够将结构化数据序列化. 但是它更小, 更快, 更简单. 如果你决定使用它序列化你的数据, 你必须在你的客户端代码中一直使用nano protocol buffer, 因为正常的 protocol buffer 会产生极其冗余的代码, 在你的应用会引起很多问题: 增加了使用的内存, 增加了apk文件的大小, 执行速度较慢以及会把一些提示符号打入dex 包中。

For more information, see the "Nano version" section in the protobuf readme.

Avoid dependency injection frameworks

使用像 Guice 和 RoboGuice 依赖注入框架会有很大的吸引力, 因为它可以使我们的代码更加简洁和提供自适应的环境用来进行有用的测试和进行其它配置的更改.然而这些框架通过注解的方式扫描你的代码来执行一系列的初始化, 这会把一些我们不需要的大量的代码映射到内存中. 被映射后的数据会被分配到干净的内存中, 放入到内存中后很长一段时间都不会使用, 这样造成了内存大量的浪费.

Be careful about using external libraries

许多的外部依赖库往往不是在移动环境下写出来的, 这样当在移动环境中使用这些库时就会非常低效. 所以当你决定使用一个外部库时, 你就要承担外部库带来的移植问题和维护负担.在项目计划前期就要分析该类库的授权条件、代码量、内存的占用再来决定是否使用该库。

即使专门设计用于 android 的库也有潜在的风险, 因为每个库做的事情都不一样. 例如, 一个库可能使用的是 nano protobuf 另外一个库使用的是 micro protobuf, 现在在你的应用中有两个不同 protobuf 的实现. 这将会有不同的日志, 分析, 图片加载框架, 缓存等所有你不可预知的事情的发生。Proguard 不会给你优化这些, 因为所有低级别的 api 依赖需要你依赖的库里所包含的特征。当你使用从外部库继承的 activity 时尤其会成为一个问题(因为这往往产生大量的依赖)。库通常还会用到反射(这是常见的,因为你要花许多时间去调整ProGuard使它工作)等。也要小心不要陷入使用几十个依赖库去实现一两个特性的陷阱; 不要引入大量不需要使用的代码。一天结束时, 当你没有发现符合你要求的实现时, 最好的方式是创建一个属于自己的实现

Optimize overall performance

如何优化你的app的整体性能,可以参考文档:Best Practices for Performance.该文档里对于如何优化你的CPU性能也给出些tips,里面的一些tips也将有助于你优化你的内存。例如:减少布局文件里的view层次 You should also read about optimizing your UI with the layout debugging tools and take advantage of the optimization suggestions provided by the lint tool.

Use ProGuard to strip out any unneeded code

代码混淆工具 ProGuard 通过去除没有用的代码和通过语义模糊来重命名类、字段和方法来缩小、优化和混淆你的代码. 使用它能使你的代码更简洁, 更少量的RAM映射页

Use zipalign on your final APK

如果构建apk后你没有做后续的任何处理(包括根据你的证书进行签名), 你必须运行 zipalign 工具为你的apk进行优化, 如果不这样做会导致你的应用使用更多的内存,zipalign之后像资源这样的东西不会再从apk中映射(mmap)入内存。

注意:goole play store 不接受没有进行zipalign的apk

Analyze your RAM usage

一旦你的apk已build到一个相对稳定的版本,你应该在你的app的各个生命周期阶段分析你的应用的RAM使用情况。

For information about how to analyze your app, read Investigating Your RAM Usage.

使用多进程

一种更高级的技术能管理应用中的内存,分离组件技术能把单进程内存划分为多进程内存。该技术一定要谨慎的使用并且大多数的应用都不会跑多进程, 因为如果你操作不当反而会浪费更多的内存而不是减少内存。它主要用于后台和前台能各自负责不同业务的应用程序

当你构建一个音乐播放器应用并且长时间从一个 service 中播放音乐时使用多进程处理对你的应用来说更恰当。如果整个应用只有一个进程, 当前用户却在另外一个应用或服务中控制播放时, 却为了播放音乐而运行着许多不相关的用户界面会造成许多的内存浪费。像这样的应用可以分隔为两个进程:一个进程负责 UI 工作, 另外一个则在后台服务中运行其它的工作

在各个应用的 manifest 文件中为各个组件申明 android:process 属性就可以分隔为不同的进程。例如你可以指定你一运行的服务从主进程中分隔成一个新的进程来并取名为"background"(当然名字可以任意取)。

<service android:name=".PlaybackService"                       android:process=":background" />

**进程名字必须以冒号开头":"以确保该进程属于你应用中的私有进程。

在你决定创建一个新的进程之前必须理解这样做对内存的影响。为了说明每个进程的影响, 一个基本空进程会占用大约1.4兆的内存, 下面的堆内存信息说明这一点

 adb shell dumpsys meminfo com.example.android.apis:empty ** MEMINFO in pid 10172 [com.example.android.apis:empty] **    Pss     Pss       Shared  Private  Shared  Private    Heap    Heap     Heap  Total   Clean    Dirty       Dirty      Clean   Clean      Size       Alloc    Free
Native Heap   0      0        0              0             0          0          1864      1800      63Dalvik Heap   764  0        5228     316            0           0         5584       5499      85Dalvik Other  619  0         3784     448           0           0Stack             28    0         8           28             0           0Other dev      4      0         12          0              0           4.so mmap      287   0        2840     212          972         0.apk mmap    54     0           0           0           136         0.dex mmap   250   148       0            0           3704     148Other mmap   8       0        8            8             20          0Unknown       403     0       600     380              0          0TOTAL           2417   148   12480  1392       4832     152    7448    7299     148

注意: 上面关键的数据是 private dirty 和 private clean 两项, 第一项主要使用了大约是1.4兆的非分页内存(分布在Dalvik heap, native分配, book-keeping, 和库的加载), 另外执行业务代码使用150kb的内存。

空进程的内存占用是相当显著的, 当你的应用加入了许多业务后会增长得更加迅速。下面的例子是使用activity显示一些文字, 当前进程的内存使用状况的分析。

** MEMINFO in pid 10226 [com.example.android.helloactivity] **Pss     Pss  Shared Private  Shared Private    Heap    Heap    HeapTotal   Clean   Dirty   Dirty   Clean   Clean    Size   Alloc    Free------  ------  ------  ------  ------  ------  ------  ------  ------Native Heap     0         0       0         0         0        0           3000     2951      48Dalvik Heap    1074     0      4928    776     0       0            5744    5658       86Dalvik Other    802      0      3612     664    0       0Stack                28       0       8         28       0       0Ashmem           6         0      16          0       0       0Other dev        108      0      24        104     0       4.so mmap       2166      0     2824    1828   3756   0.apk mmap     48       0       0       0     632       0.ttf mmap        3       0       0       0      24       0.dex mmap     292       4       0       0    5672       4Other mmap    10       0       8       8      68       0Unknown        632       0     412     624       0       0TOTAL         5169       4   11832    4032   10152       8        8744    8609     134

这个比上面多花费了3倍的内存, 只是在界面上显示一些简单的文字, 用了大约4兆。从这里可以得出一个很重要的结论:如果你的想在应用中使用多进程, 只能有一个进程来负责 UI 的工作, 在其它进程中不能出现任何 UI的工作, 否则会迅速提高内存的使用率(尤其当你加载 bitmap 资源和其它资源时)。 一旦加入了UI的绘制工作就不可能会减少内存的使用了

另外, 当你的应用超过一个进程时, 保持代码的紧凑非常重要, 因为现在由相同实现造成的不必要的内存开销会复制到每一个进程中, 会造成内存浪费更严重的状况出现。例如, 你使用了枚举, 不同的进程在内存中都会创建和初始化这些常量.并且你所有的抽象适配器和其它临时的开销也会和前面一样被复制过来。

另外要关心的问题是多进程之间的依赖关系。例如, 当应用中运行默认的进程需要为UI进程提供内容, 后台进程的代码为进程本身提供内容还要留在内存中为UI运行提供支持,如果你的目标是在一个拥有重量级的UI进程的应用里拥有一个独立运行的后台进程, 那么你在UI进程中则不能直接依赖它, 而要在UI进程使用 service 处理它

更多相关文章

  1. 在android上使用valgrind检测内存泄漏
  2. 关于android中使用new Message的内存泄露问题
  3. Android内存管理-SoftReference的使用
  4. Android复制assets目录下的图片到内存
  5. Android JSON解析示例代码
  6. 【Android】Android Studio 1.5+ 中混合调试Native和Java代码

随机推荐

  1. Android动画之属性动画(Property Animatio
  2. Android控件系列之ProgressBar&在Android
  3. 【开源推荐】进阶实战,从一款音乐播放器开
  4. Android实践 -- Android Support Library
  5. Android studio登录界面之记住密码
  6. Android GestureDetector手势识别类学习
  7. Android系统多媒体框架添加对.wma格式的
  8. Linux C++工程师2小时了解Android记录
  9. Android之玩转MPAndroidChart让(折线图、
  10. Android 使用Parcelable序列化对象