Android性能优化-内存优化

   2016-10-17 0
核心提示:前言在任何软件开发环境中,RAM都是比较珍贵的资源。在移动操作系统上更是这样,因为它们的物理内存通常受限。尽管在ART和Dalvik虚拟机都会进行垃圾回收的巡航,但这并不意味着你可以忽略何时,何地分配和释放内存。你应该避免内存泄露,通常此后又一些静态成

前言

在任何软件开发环境中,RAM都是比较珍贵的资源。在移动操作系统上更是这样,因为它们的物理内存通常受限。尽管在ART和Dalvik虚拟机都会进行垃圾回收的巡航,但这并不意味着你可以忽略何时,何地分配和释放内存。你应该避免内存泄露,通常此后又一些静态成员变量导致,也应该在恰当的时间(定义的一些生命周期回调的方法里)释放所有 Reference 对象。

这里见识了你如何减少app内存的使用。因为android是基于java的,所以对于内存的管理,可以参考java相关的书籍,下面章节中也会有所讲解。关于如何分析app运行中的内存使用,可以参考 Tools for analyzing RAM usage 。关于ART和Dalvik虚拟机管理内存的更多细节,可以参考 Overview of Android Memory Management .

一 监测可用内存和内存使用

Android 框架,AndrStudio和Android SDK都提供了分析app内存使用的途径。Android框架暴露了几个API,允许你的app动态的减少内存使用、AndroidStudio和Android SDK提供了几种工具帮你分析app的内存使用情况。

分析RAM使用的工具

  1. Device Monitor 拥有一个 Dalvik Debug Monitor Server (DDMS) 工具,可以帮助你检测app进程中内存的分配。你可以通过该信息去分析app的总体内存使用情况。比如,先执行垃圾回收事件然后再去看那些仍然保留在内存中的对象。通过这种方式去定位app中所进行的内存分配或者遗留在内存中的对象。

    更多关于DDMS的使用请参考 Using DDMS

  2. Android Studio中的Memory Monitor 可以向你展示某一个过程中的内存分配情况。该工具以图形化的方式展示了某一时段可用的和已经分配的java内存,以及发送的垃圾回收事件。也可以触发垃圾回收事件并获取app运行期间java堆内存的快照。Memory Monitor tool 的输出信息也可以帮你定位到app密集发生垃圾回收事件的点,这些点会降低了app速度。

    关于如何使用Memory Monitor tool的更多信息可以参考 Viewing Heap Updates .

  3. 垃圾回收事件也会展示在 Traceview viewer中。 Traceview 允许你以时间线的方式查看trace log文件,并可以分析一个事件段内都发生了什么。你可以使用该工具确定在你的代码在垃圾回收事件发生时都做了什么操作。

    更多信息关于如何使用Traceview viewer, 可以参考 Profiling with Traceview and dmtracedump .

    ?

  4. Android Studio中的Allocation Tracker tool可以帮助你分析app是如何分配内存的。Allocation Tracker 记录了app内存的分配并在快照中列出了所有的分配对象。可以使用该工具追踪哪些地方分配了过多的对象。

    更多关于如何使用Allocation Tracker tool,可以参考 Allocation Tracker Walkthrough .

依据事件释放内存

根据RAM的物理内存和设备的操作行为,Android设备可以在变化的可用内存中运行。在内存压力的情况下,系统的广播信号会提示,app可以监听这些信号然后对内存的使用做恰当的处理。

可以使用 ComponentCallbacks2 来监听内存信号响应app生命周期或者设备的事件。 onTrimMemory() 方法可以帮助你监听app在前台或者后台时内存相关的事件。

在Activity中实现 onTrimMemory() 回调,如下:

import android.content.ComponentCallbacks2;
// Other import statements ...

public class MainActivity extends AppCompatActivity
implements ComponentCallbacks2 {


// Other activity code ...

/**
* Release memory when the UI becomes hidden or when system resources become low.
* @param level the memory-related event that was raised.
*/

public void onTrimMemory(int level) {

// Determine which lifecycle or system event was raised.
switch (level) {

case ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN:

/*
Release any UI objects that currently hold memory.

The user interface has moved to the background.
*/


break;

case ComponentCallbacks2.TRIM_MEMORY_RUNNING_MODERATE:
case ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW:
case ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL:

/*
Release any memory that your app doesn't need to run.

The device is running low on memory while the app is running.
The event raised indicates the severity of the memory-related event.
If the event is TRIM_MEMORY_RUNNING_CRITICAL, then the system will
begin killing background processes.
*/


break;

case ComponentCallbacks2.TRIM_MEMORY_BACKGROUND:
case ComponentCallbacks2.TRIM_MEMORY_MODERATE:
case ComponentCallbacks2.TRIM_MEMORY_COMPLETE:

/*
Release as much memory as the process can.

The app is on the LRU list and the system is running low on memory.
The event raised indicates where the app sits within the LRU list.
If the event is TRIM_MEMORY_COMPLETE, the process will be one of
the first to be terminated.
*/


break;

default:

/*
Release any non-critical data structures.

The app received an unrecognized memory level value
from the system. Treat this as a generic low-memory message.
*/

break;
}
}
}

onTrimMemory() 方法是在Android 4.0(API 14)中加入的,你可以使用在其它的低版本中使用 onLowMemory() 回调,相当于 TRIM_MEMORY_COMPLETE 事件。

确定你应该使用多少内存

为了允许多进程,Android对每个app在堆内存的大小上设置了严格的限制。由于不同的设备的总的可用RAM内存不同,准确的堆内存的大小限制也不同。如果你的app达到了堆的内存限制,并且尝试分配更多内存,系统就会抛出OOM。

为了避免OOM,可以通过调用 getMemoryInfo() 方法去查询当前设备的可用内存堆内存空间。该方法会返回一个 ActivityManager.MemoryInfo 对象,该对象提供了设备的内存状态信息,包括可用内存,总内存和内存阀值(当内存低于该值得时候,系统将会杀死进程)。 ActivityManager.MemoryInfo 类也暴露了一些简单的boolean字段, lowMemory 就直接告诉你你的设备是否运行在低内存环境。下面的代码片段展示了如何使用在你的应用中使用 getMemoryInfo() 方法:

public void doSomethingMemoryIntensive() {

// Before doing something that requires a lot of memory,
// check to see whether the device is in a low memory state.
ActivityManager.MemoryInfo memoryInfo = getAvailableMemory();

if (!memoryInfo.lowMemory) {
// Do memory intensive work ...
}
}

// Get a MemoryInfo object for the device's current memory status.
private ActivityManager.MemoryInfo getAvailableMemory() {
ActivityManager activityManager = (ActivityManager) this.getSystemService(ACTIVITY_SERVICE);
ActivityManager.MemoryInfo memoryInfo = new ActivityManager.MemoryInfo();
activityManager.getMemoryInfo(memoryInfo);
return memoryInfo;
}

二 从代码角度优化内存

一些android的特性,java classes ,以及代码结构有时候会占用更多内存,我们应该通过选择更高效的方案来减少内存的使用。

节省的使用Service

在后台保留一个不需要的service,是最糟糕的内存管理方式之一。如果你需要通过service在后台去执行任务,除非当它将要去执行一个任务的时候,否则不应该一直在后台驻留。在完成任务后记得将service停掉,否则可能在不经意间导致了内存泄露(这里应该是指service占用了无用的资源)。

当你启动一个service的时候,系统会更好的保留service所运行的进程。这样会导致service 进程非常的消耗资源,因为被一个service占用的RAM部分,其它Servie就不可用了。这样会减少系统在LRU cache中缓存的进程数量,降低了app的切换效率。当内存紧张时,甚至会导致内存抖动,并且系统无法维护足够的进程来托管当前运行的所有服务。。

尽量避免使用持久化的service,因为它持有占有了可用内存。建议使用其它替代方案,比如 JobScheduler 。关于 JobScheduler 如何调度后台进程,可以参考 Background Optimizations

如果必须使用service,最好是使用 IntentService 来限制service的生命周期,一旦处理完启动它的Intent,该IntentService就会将自己停掉。更多信息可以参考 Running in a Background Service .

使用内存更高效的代码结构

一些编程语言中的类没有针对移动设备进行优化。比如,通用HashMap实现可能是相当的内存低效,因为它需要为每个映射关系创建单独的对象。

Android框架中提供了几种优化过的数据结构,比如 SparseArray , SparseBooleanArrayLongSparseArray ,比如 SparseArray 避免了对key的自动装箱(导致每个实体会多创建对象),所以更高效。

小心的使用代码抽象

开发人员经常简单地使用抽象作为一个好的编程实践,因为抽象可以提高代码的灵活性和维护性。但是抽象会带来严重的开销:通常它们需要额外执行相当多的更多代码,需要更多的时间和RAM将代码映射到内存中。因此,如果你的抽象不能带来比较大的好处,那么请避免使用抽象。

比如,枚举相对于静态通常需要两倍甚至更多的内存。你应该严格避免在android中使用枚举。

使用nano protobufs进行序列化

Protocol buffers 是由Google研发,跨语言、跨平台,扩展性非常好的序列化数据结构,类似XML,但是更小更快,更简单。如果你决定使用protobufs进行序列化,你应该在客户端代码中使用nano protobufs。因为一般的protobufs会生成冗余的代码,导致各种问题,比如增加RAM的使用,APK大小,比较低的执行效率。

更多信息可以参考 protobuf readme “Nano version”部分

避免内存抖动

上面提到,通常垃圾回收事件不会影响到你的app性能。但是,在短时间内突然发生很多的垃圾回收事件就会占用了帧的时间。系统花费在垃圾回收上的时间越多,那么用于渲染或者音频的时间就越少。

通常,内存抖动会导致大量的垃圾收集事件发生。在实践中,内存抖动是指在在一个特定时间内分配了很多临时对象。

比如,你可能在for 循环中分配了多个临时对象。或者在onDraw中创建了新的Paint或者Bitmap对象。这两种情况下,app都会快速创建大量的对象。这样会快速消耗掉young generation中的可用内存,强制垃圾回收器触发回收事件。

通过 Analyze your RAM usage 可以帮你找到代码中哪些地方导致了内存抖动。

一旦定位到了问题,就应该试着在性能问题严重的地方减少对象的分配。考虑将他们移到内部循环外面,可能的话,也可以通过 Factory模式 来实现。

三 移除内存敏感的资源和库

一些资源和库会在你不知道的情况下占用掉很多内存。总览下apk的大小,包含了哪些可能导致内存浪费的第三方的库和内嵌资源。通过移除冗余的。不必要的资源和库来提高内存的使用。

减小总体的APK大小

您可以通过减少应用程序的整体大小来显着降低应用程序的内存使用量。Bitmap 大小、资源、动画帧数,和第三方库都可能增大了APK的大小。Android Studio 和 Android SDK 提供了一些工具可以帮你减少资源和外部依赖。

如何如何减少APK的大小,可以参考 Reduce APK Size .

小心的使用注解框架

比如Guice或者RoboGuice的依赖注解框架虽然简化了你的代码书写,并为测试或者其它的可能变化配置信息提供了适配。但是这些依赖框架并没有针对移动设备做优化。

比如,这些框架通常会通过扫描你的代码或者注解来进行初始化。系统会将这些映射页面分配到内存中,以便Android可以删除它们; 但这些映射页面所占用的内存会在很长一段时间之后才会被删除。

如果你需要使用注解框架,考虑使用 Dagger 。比如,Dagger不会使用反射扫描代码。Dagger的严格实现意味着它可以用于Android应用中而不会增加不必要的内存使用。

小心的使用外部库

外部库的代码通常不会为移动环境而写,执行在移动设备上也更低效。当你决定使用一个外部库的时候,可能需要为移动设备做优化。在你决定使用之前,先考虑代码量的大小和RAM占用空间。

有时一些针对移动设备优化的库由于不同的实现也会产生问题。比如,某个库可能使用了nano版本的protobufs,然而另一个使用了micro protobufs,就会导致两个版本的实现。这样就会导致两份

logging,analytics,Image loading框架、缓存以及其它一些不期望的问题。

尽管 ProGuard 会帮助你移除API和资源,但是不会移除一个库的内部依赖。如果你使用了依赖库中的Activity子类(会导致比较宽泛的依赖关系,和你现有的Activity就会有很大冲突),问题将尤为严重。又或者,如果这个库中使用了反射等技术,你还要花大量时间去处理混淆。所以在你决定使用一个库的时候,需要慎重的考虑它是否非常匹配你的需求,否则你应该考虑自己实现一套。

 
反对 0举报 0 评论 0
 

免责声明:本文仅代表作者个人观点,与乐学笔记(本网)无关。其原创性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容、文字的真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
    本网站有部分内容均转载自其它媒体,转载目的在于传递更多信息,并不代表本网赞同其观点和对其真实性负责,若因作品内容、知识产权、版权和其他问题,请及时提供相关证明等材料并与我们留言联系,本网站将在规定时间内给予删除等相关处理.

  • Android开发艺术探索学习笔记(三)—Android性能优化之Bitmap导致的内存溢出
    Android开发艺术探索学习笔记(三)—Android性能
    原本计划是按照章节顺序学习《Android开发艺术探索》这本书的,Android性能优化这部分也是本书的最后一章。但是周末的时候,友盟线下反馈的公司项目的一个错误让我不得不提前学习这一块的知识。先看看线下反馈的错误吧:java.lang.OutOfMemoryError:应用程序
  • Android 性能优化之内存泄漏分析工具 LeakCanary
    Android 性能优化之内存泄漏分析工具 LeakCanar
    前言Android内存泄漏的分析与解决是每个Android程序员进阶路上的必备技能。今天就和大家分享一下我的一点点学习心得。开始使用首先在module的build.gradle添加依赖,不同的版本需要添加不同的依赖dependencies {compile fileTree(dir: 'libs', include: ['*.j
  • 携程移动端 UI 界面性能优化实践
    携程移动端 UI 界面性能优化实践
    人类大脑与眼睛对一个画面的连贯性感知其实是有一个界限的,譬如我们看电影会觉得画面很自然连贯,其帧率通常为 24fps;那么,用手机当然也需要感知屏幕操作的连贯性(尤其是动画过渡),所以在手机领域 Android/iOS 索性就把达到这种流畅的帧率规定为 60fps。基
  • Android 性能优化:多线程系列开篇
    Android 性能优化:多线程系列开篇
    前言 Android Performance Patterns Season 5 主要介绍了 Android 多线程环境下的性能问题。通过介绍 Android 提供的多种多线程工具类 (AsyncTask, HandlerThread, IntentService, ThreadPool),让我们熟悉各个组件的适用场景,从而在特定场景下选择性能最好
  • Android性能优化-App后台优化
    原文链接Background Optimizations前言后台进程是内存和电池敏感的。一个隐式的broadcast可能会启动很多监听它的后台进程,即使这些进程可能做得工作不多。这可能丢设备性能和用户体验都有比较大的影响。为了缓解这种问题,7.0(API 24)做了以下限制:Target
  • Android应用性能优化系列视图篇——ListView自适应导致的严重性能问题
    Android应用性能优化系列视图篇——ListView自
    ListView是Android中最常用的视图之一,使用的频率仅仅次于三大基础布局,虽然由于使用性和扩展性等原因备受争议,且尽管后来出现了RecyclerView的替代方案,但是ListView仍然广泛地使用在我们的项目中。自从ListView出道至今,已经不知道衍生出了多少问题,
  • Android性能优化-减小APK大小
    Android性能优化-减小APK大小
    原文链接: Reduce APK Size 前言用户通常会避免下载比较大的应用,特别是连接到2G和3G网络,或者按流量收费的设备。这篇文章描述了如何减小apk的大小,帮助你让更多的用户下载你的app。一 理解APK的结构在讨论如何减小apk大小之前,理解apk的结构很有必要。
  • Android性能优化之UI渲染优化
    Android性能优化之UI渲染优化
    原文转自:http://www.cnblogs.com/yezhennan/p/5442031.htmlUI性能测试性能优化都需要有一个目标,UI的性能优化也是一样。你可能会觉得“我的app加载很快”很重要,但我们还需要了解终端用户的期望,是否可以去量化这些期望呢?我们可以从人机交互心理学的角
  • Google官方Android性能优化典范第1季
    Google官方Android性能优化典范第1季
    原文转自:http://www.cnblogs.com/yezhennan/p/5431738.html大多数用户感知到的卡顿等性能问题的最主要根源都是因为渲染性能。从设计师的角度,他们希望App能够有更多的动画,图片等时尚元素来实现流畅的用户体验。但是Android系统很有可能无法及时完成那些
  • Google官方Android性能优化典范第3季
    Google官方Android性能优化典范第3季
    原文转自:http://www.cnblogs.com/yezhennan/p/5443580.html (1)Fun with ArrayMaps 程序内存的管理是否合理高效对应用的性能有着很大的影响,有的时候对容器的使用不当也会导致内存管理效率低下。Android为移动操作系统特意编写了一些更加高效的容器,例如
点击排行