Managing Your App’s Memory 管理APP内存

Managing Your App’s Memory 管理APP内存

Random-accessmemory (RAM) is a valuable resource in any software development environment,but it's even more valuable on a mobile operating system where physical memoryis often constrained. Although Android's Dalvik virtual machine performsroutine garbage collection, this doesn't allow you to ignore when and whereyour app allocates and releases memory.

随机访问存储(RAM)在任何软件开发设备中都是有价值的资源,但是它在移动操作系统上甚至更有价值,因为在移动设备上物理存储常常有严格限制。尽管Android的Dalvik虚拟机会执行例常垃圾收集,这并不允许你忽视app分配和释放内存的时间和地点。

 

In order for the garbage collector to reclaimmemory from your app, you need to avoid introducing memory leaks (usuallycaused by holding onto object references in global members) and release any Reference objects at theappropriate time (as defined by lifecycle callbacks discussed further below).For most apps, the Dalvik garbage collector takes care of the rest: the systemreclaims your memory allocations when the corresponding objects leave the scopeof your app's active threads.

为使垃圾收集器从你的app中回收内存,你需要避免内存泄露(通常是在全局成员中持有对象引用导致的)并在合适的时间释放任何对象引用(就像下面讨论的生命周期回调的定义一样)。对大多数app而言,Dalvik垃圾收集器会负责其它工作:当对应的对象离开app活动线程区域的时候,系统回收你的内存分配。

 

This document explains how Android managesapp processes and memory allocation, and how you can proactively reduce memoryusage while developing for Android. For more information about generalpractices to clean up your resources when programming in Java, refer to otherbooks or online documentation about managing resource references. If you’relooking for information about how to analyze your app’s memory once you’vealready built it, read Investigating Your RAM Usage.

这个文档解释了Android管理app进程和内存分配的方法,以及你在为Android开发时主动减少内存使用的方法。为获取在Java编程时有效管理资源的一般原则的相关信息,请参考关于资源管理的其它书或者在线文档。如果你正在查找关于构建完app之后,分析app内存的方法,请阅读Investigating Your RAM Usage

 

How Android Manages MemoryAndroid怎样管理内存

Androiddoes not offer swap space for memory, but it does use paging and memory-mapping(mmapping) to manage memory. This means that any memory you modify—whether byallocating new objects or touching mmapped pages—remains resident in RAM andcannot be paged out. So the only way to completely release memory from your appis to release object references you may be holding, making the memory availableto the garbage collector. That is with one exception: any files mmapped inwithout modification, such as code, can be paged out of RAM if the system wantsto use that memory elsewhere.

Android并不为内存提供swap空间,但是它使用分页(paging)和内存映射(mmapping)来管理内存。这就意味着,任何内存修改——或者通过分配新对象,或者触碰映射页(touchingmmapped pages)——都会保持常驻RAM,并且不会被页置换到外部。所以,从app中完全释放内存的唯一方法是释放你可能持有的对象引用,使垃圾收集器能够访问内存。这有一个例外:任何未经修改的映射文件,例如代码,

如果系统想使用对应的内存的话,都会被页置换到RAM外部。

 

Sharing Memory  共享内存

In order to fit everything it needs in RAM, Android triesto share RAM pages across processes. It can do so in the following ways:

为适配它在RAM中需要的任何东西,Android尝试在进程间共享RAM。它可以用下面的方法做到这些:

 

·        Each app process isforked from an existing process called Zygote. The Zygote process starts whenthe system boots and loads common framework code and resources (such asactivity themes). To start a new app process, the system forks the Zygoteprocess then loads and runs the app's code in the new process. This allows mostof the RAM pages allocated for framework code and resources to be shared acrossall app processes.

每一个app进程都是从一个叫做Zygote的已经存在的进程fork而来。Zygote进程是在系统启动和装载通用框架代码和资源(例如activity主题)的时候启动的。为启动一个新的app进程,系统fork Zygote进程,然后在新进程中装载并运行app的代码。这允许为框架代码和资源分配的大多数RAM页能够在app进程间共享。

 

·        Most static data ismmapped into a process. This not only allows that same data to be sharedbetween processes but also allows it to be paged out when needed. Examplestatic data include: Dalvik code (by placing it in a pre-linked .odex file for direct mmapping), app resources (by designing the resourcetable to be a structure that can be mmapped and by aligning the zip entries ofthe APK), and traditional project elements like native code in .so files.

大多数静态数据被映射到一个进程。这不仅允许相同的数据在进程间共享,也允许这些数据在需要的时候被置换出内存。这些静态数据的例子有:Dalvik代码(通过把它放置到预链接文件.odex来直接映射),app资源(通过将资源表格设计为可以被映射的结构并排列APK的zip条目),以及类似于存储于so文件的本地代码的传统工程元素。

 

·        In many places, Androidshares the same dynamic RAM across processes using explicitly allocated sharedmemory regions (either with ashmem or gralloc). For example, window surfacesuse shared memory between the app and screen compositor, and cursor buffers useshared memory between the content provider and client.

在很多地方,Android通过显式地分配共享内存区域(使用ashemen或者gralloc)来在进程间共享相同的动态Ram例如,window surface在app和screen compositor之间使用共享内存,cursor buffers在content provider和client之间共享内存。

 

Due to the extensive use of shared memory, determininghow much memory your app is using requires care. Techniques to properly determineyour app's memory use are discussed in InvestigatingYour RAM Usage.

由于共享内存的扩展需要,决定app的内存使用量需要小心。恰当的确定app内存使用量的技术在InvestigatingYour RAM Usage进行讨论。

 

Allocating andReclaiming App Memory  分配和回收app内存

Here are some facts about how Android allocates thenreclaims memory from your app:

这是一些关于Android分配然后从你的app回收内存的一些事实:

 

·        The Dalvik heap for eachprocess is constrained to a single virtual memory range. This defines thelogical heap size, which can grow as it needs to (but only up to a limit thatthe system defines for each app).

每个进程的Dalvik堆被限制为一个单一虚拟内存区。这定义了逻辑堆大小,它在需要的时候可以增长(只能增长到系统为每个app定义的大小限制)。

 

·        The logical size of theheap is not the same as the amount of physical memory used by the heap. Wheninspecting your app's heap, Android computes a value called the ProportionalSet Size (PSS), which accounts for both dirty and clean pages that are sharedwith other processes—but only in an amount that's proportional to how many appsshare that RAM. This (PSS) total is what the system considers to be your physicalmemory footprint. For more information about PSS, see the InvestigatingYour RAM Usage guide.

堆的逻辑大小与堆使用的物理内存的数量不一样。当检查你的app堆的时候,Android计算一个被称作PSS(Proportional Set Size,均衡集大小?)的值,这个值解释与其它进程共享的脏和干净的数据页——只是以共享RAM的app数量成比例的数量进行。这个(PSS)整体上是系统认为的你物理内存的大小。为获取PSS的更多信息,参阅InvestigatingYour RAM Usage

 

·        The Dalvik heap does notcompact the logical size of the heap, meaning that Android does not defragmentthe heap to close up space. Android can only shrink the logical heap size whenthere is unused space at the end of the heap. But this doesn't mean thephysical memory used by the heap can't shrink. After garbage collection, Dalvikwalks the heap and finds unused pages, then returns those pages to the kernelusing madvise. So, paired allocations and deallocations of large chunks shouldresult in reclaiming all (or nearly all) the physical memory used. However,reclaiming memory from small allocations can be much less efficient because thepage used for a small allocation may still be shared with something else thathas not yet been freed.

Dalvik堆并不压缩堆的逻辑大小,这意味着Android并不整理堆来关闭内存空间。当在堆的末尾有未使用的空间时,Android可以收缩逻辑堆大小。但是这并不意味着堆使用的物理内存不能被收缩。垃圾收集后,Dalvik检查堆,寻找未使用的页,然后使用madvise将这些页归还到内核。所以,成对出现的堆大块内存的分配和撤销分配会导致回收全部(或者接近全部)使用的物理内存。然而,从小的分配中回收内存可能比较没有效率,因为一次小的分配使用的页可能仍旧与还没有释放的东西共享。

 

Restricting AppMemory  限制app内存

To maintain a functional multi-tasking environment,Android sets a hard limit on the heap size for each app. The exact heap sizelimit varies between devices based on how much RAM the device has availableoverall. If your app has reached the heap capacity and tries to allocate morememory, it will receive an OutOfMemoryError.

为维持功能性的多任务环境,Android对每个app的堆大小做了严格的限定。堆大小的精确限制在不同设备上因全部可用RAM量的不同而不同。如果你的app达到了堆的容量限制后,尝试分配更多的内存,会收到OutOfMemoryError

 

In some cases, you might want to query the system todetermine exactly how much heap space you have available on the currentdevice—for example, to determine how much data is safe to keep in a cache. Youcan query the system for this figure by calling getMemoryClass(). This returns an integer indicating the number ofmegabytes available for your app's heap. This is discussed further below,under Check how muchmemory you should use.

在有些情况下,你可能向查询系统以准确确定在当前设备上有多少可用堆空间——例如,确定在缓存中保存多少数据是安全的。你可以通过调用getMemoryClass()来向系统查询这个数据。这个函数返回一个整数,代表你的app堆的可用兆字节数量。这在下面Check how muchmemory you should use一节会进一步讨论。

 

Switching Apps  切换app

Instead of using swap space when the user switchesbetween apps, Android keeps processes that are not hosting a foreground("user visible") app component in a least-recently used (LRU) cache.For example, when the user first launches an app, a process is created for it,but when the user leaves the app, that process does not quit. Thesystem keeps the process cached, so if the user later returns to the app, theprocess is reused for faster app switching.

当用户在app之间切换的时候,Android在一个最近最少使用(least-recently used ,LRU)缓存中维护没有持有前台(“用户可视”)app组件的进程。例如,当用户第一次启动app的时候,系统会为它创建一个进程,但是当用户离开app的时候,那个进程并不退出。系统会缓存那个进程,所以如果用户稍后返回app,进程会重复使用,从而更快地进行切换。

 

If your app has a cached process and it retains memorythat it currently does not need, then your app—even while the user is not usingit—is constraining the system's overall performance. So, as the system runs lowon memory, it may kill processes in the LRU cache beginning with the processleast recently used, but also giving some consideration toward which processesare most memory intensive. To keep your process cached as long as possible,follow the advice in the following sections about when to release yourreferences.

如果你的app有一个缓存的进程,并且维持当前并不需要的内存,那么你的app——及时用户没有正在使用它——正限制系统的整体性能。所以,当系统内存不足的时候,它会从最近最少使用的进程开始,在LRU缓存中杀掉进程,但是也会对内存最敏感的进程进行更多考虑。为了尽可能长的保持你的进程被缓存,遵循下面章节中关于释放应用的时间的建议。

 

More information about how processes are cached while notrunning in the foreground and how Android decides which ones can be killed isavailable in the Processes andThreads guide.

为获取不在前台运行的进程是如何缓存的,以及Android设备确定哪些可以被杀掉,请参阅Processes andThreads

 

How Your App Should ManageMemory  app应该怎样管理内存

You should consider RAM constraints throughout all phasesof development, including during app design (before you begin development).There are many ways you can design and write code that lead to more efficientresults, through aggregation of the same techniques applied over and over.

你应该在开发周期的整个过程中考虑RAM限制,包括app设计的时候(在开发之前)。有许多可以导致更高效结果的设计和编码方法,这是一些可以叠加使用的、反复使用过得相同技术。

 

You should apply the following techniques while designingand implementing your app to make it more memory efficient.

你在设计和实现app的时候,应该应用下面的技术,从而使其更高效。

 

Use servicessparingly  保守地使用服务

If your app needs a service toperform work in the background, do not keep it running unless it's activelyperforming a job. Also be careful to never leak your service by failing to stopit when its work is done.

如果你的app需要service在后台执行工作,不要让它一直运行,除非它正执行一项任务。还要注意,当一个服务的工作完成的时候,不要因停止失败而导致泄露。

【译注】也就是说,服务要随用随起,用完就要停止。

 

When you start a service, the system prefers to alwayskeep the process for that service running. This makes the process veryexpensive because the RAM used by the service can’t be used by anything else orpaged out. This reduces the number of cached processes that the system can keepin the LRU cache, making app switching less efficient. It can even lead tothrashing in the system when memory is tight and the system can’t maintainenough processes to host all the services currently running.

当你启动一个服务的时候,系统宁愿总是保持服务的进程运行。这使得进程非常昂贵,因为服务使用的RAM不能被任何东西使用,也不能被置换出。这减少了系统可以在LRU缓存中维护的缓存进程的数量,使得app切换较为低效。当内存紧张,系统不能维持足够进程来管理当前正在运行的服务的时候,它甚至会导致系统“抖动”(thrashing)。

 

The best way to limit the lifespan of your service is touse an IntentService, which finishes itself as soon as it's done handling theintent that started it. For more information, read Running in aBackground Service .

限制服务生命周期长度的最好方法是使用IntentService,它一旦处理完启动它的Intent后,会结束自己。为获取更多信息,参阅Running in aBackground Service

 

Leaving a service running when it’s not needed is oneof the worst memory-management mistakes an Android app can make. Sodon’t be greedy by keeping a service for your app running. Not only will itincrease the risk of your app performing poorly due to RAM constraints, butusers will discover such misbehaving apps and uninstall them.

当不需要的时候让一个服务保持运行是Android应用会造成的最糟的内存管理错误之一。所以,不要贪婪到为你app的运行而一直维护一个服务。这不但会增加由于RAM限制而导致的app低效执行的风险,还会使得用户发现这种行为不端的app而卸载掉。

 

 

Release memorywhen your user interface becomes hidden  UI不可见的时候释放内存

When the user navigates to a different app and your UI isno longer visible, you should release any resources that are used by only yourUI. Releasing UI resources at this time can significantly increase the system'scapacity for cached processes, which has a direct impact on the quality of theuser experience.

当用户导航到其它app而你的UI不在可见的时候,你应该释放仅由你的UI使用的任何资源。在这个时候释放UI资源能够显著增减系统可供缓存进程使用的容量,这对用户体验质量有直接影响。

 

To be notified when the user exits your UI, implementthe onTrimMemory() callback in your Activity classes. You should use this method to listen forthe TRIM_MEMORY_UI_HIDDEN level, which indicates your UI is now hidden fromview and you should free resources that only your UI uses.

为收到用户离开UI的通知,在Activity类中实现 onTrimMemory() 回调。你应该使用这个方法来监听 TRIM_MEMORY_UI_HIDDEN 级别,这个级别意味着你的UI已经从视图层隐藏,你应该释放仅由你的UI使用的资源。

 

Notice that your app receives the onTrimMemory() callback with TRIM_MEMORY_UI_HIDDEN only when all the UI components ofyour app process become hidden from the user. This is distinct from the onStop() callback, which is called when an Activity instance becomes hidden, which occurs even when theuser moves to another activity in your app. So although you shouldimplement onStop() to release activity resources such as a networkconnection or to unregister broadcast receivers, you usually should not releaseyour UI resources until you receiveonTrimMemory(TRIM_MEMORY_UI_HIDDEN). This ensures that if the user navigates back fromanother activity in your app, your UI resources are still available to resumethe activity quickly.

注意,只有当app进程的所有UI组件都变得对用户不可见的时候,app才会收到TRIM_MEMORY_UI_HIDDEN级别的onTrimMemory()回调。这个与onStop()回调是不同的,onStop()回调是在一个Activity实例变得不可见的时候被调用的,发生在用户移动到app的其它Activity的时候。所以,尽管你应该实现onStop()方法来释放诸如网络连接之类的资源,或者取消注册广播接收者,但你通常并不能在接收到onTrimMemory(TRIM_MEMORY_UI_HIDDEN)之前释放UI资源。这确保了当用户从你应用的其它activity返回的时候,你的UI资源仍旧可以用于快速重建Activity。

 

【补充】http://www.open-open.com/lib/view/open1437404498474.html

OnTrimMemory回调中可以释放哪些资源?

通常在架构阶段就要考虑清楚,我们有哪些东西是要常驻内存的,有哪些是伴随界面存在的.一般情况下,有下面几种资源需要进行释放:

·         缓存 缓存包括一些文件缓存,图片缓存等,在用户正常使用的时候这些缓存很有作用,但当你的应用程序UI不可见的时候,这些缓存就可以被清除以减少内存的使用.比如第三方图片库的缓存.

·         一些动态生成动态添加的View. 这些动态生成和添加的View且少数情况下才使用到的View,这时候可以被释放,下次使用的时候再进行动态生成即可.比如原生桌面中,会在 OnTrimMemory的TRIM_MEMORY_MODERATE等级中,释放所有AppsCustomizePagedView的资源,来保证在低内存的时候,桌面不会轻易被杀掉.

 

 

Release memoryas memory becomes tight  当内存紧张的时候要释放

During any stage of your app's lifecycle, the onTrimMemory() callback also tells you when the overall device memoryis getting low. You should respond by further releasing resources based on thefollowing memory levels delivered by onTrimMemory():

在app生命周期的任何一个阶段, onTrimMemory() 回调也能告诉你整体设备内存变低的时间。你应该基于下面描述的在 onTrimMemory() 中发布的内存级别进一步释放资源以进行响应。

 

·        TRIM_MEMORY_RUNNING_MODERATE

Your app is running and not considered killable, but the device is runninglow on memory and the system is actively killing processes in the LRU cache.

你的app正在运行,并且没有被认为是可以杀死的,但是设备内存已经变低,并且系统正积极地杀死LRU缓存中的进程。

 

·        TRIM_MEMORY_RUNNING_LOW

Your app is running and not considered killable, but the device is runningmuch lower on memory so you should release unused resources to improve systemperformance (which directly impacts your app's performance).

你的app正在运行,并且没有被认为是可以杀死的,但是设备内存已经更低,并且你应该释放没有使用的资源以提高系统性能(这会直接影响你的app性能)。

 

·        TRIM_MEMORY_RUNNING_CRITICAL

Your app is still running, but the system has already killed most of theprocesses in the LRU cache, so you should release all non-critical resourcesnow. If the system cannot reclaim sufficient amounts of RAM, it will clear allof the LRU cache and begin killing processes that the system prefers to keepalive, such as those hosting a running service.

你的app正在运行,但是系统已经杀死LRU缓存中的大部分进程,所以你现在应该释放所有非关键资源。如果系统不能回收足够的内存,它将清楚LRU缓存中的所有内容,并且开始杀死系统想要保持存活的进程,例如持有正在运行的服务的那种进程。

 

Also, when your app process is currently cached, you mayreceive one of the following levels fromonTrimMemory():

此外,当你的app进程被缓存的时候,你可能受到下面的来自onTrimMemory()的内存级别之一:

 

·        TRIM_MEMORY_BACKGROUND

The system is running low on memory and your process is near the beginningof the LRU list. Although your app process is not at a high risk of beingkilled, the system may already be killing processes in the LRU cache. Youshould release resources that are easy to recover so your process will remainin the list and resume quickly when the user returns to your app.

系统内存正在降低,你的进程靠近LRU列表的开始。尽管你的app进程被杀死的风险并不是很高,但是系统可能已经开始杀死LRU缓存中的进程。你应该释放哪些容易恢复的资源,从而你的进程会保持在哪个列表中,以便在用户返回的时候可以快速重建。

 

·        TRIM_MEMORY_MODERATE

The system is running low on memory and your process is near the middle ofthe LRU list. If the system becomes further constrained for memory, there's achance your process will be killed.

系统内存正在降低,你的进程靠近LRU列表的中部。如果系统对内存更加严格,你的进程就有被杀死的可能性。

 

·        TRIM_MEMORY_COMPLETE

The system is running low on memory and your process is one of the firstto be killed if the system does not recover memory now. You should releaseeverything that's not critical to resuming your app state.

系统的内存正在降低,如果系统不能重新获得内存的话,你的进程是最先被杀掉的之一。你应该释放任何对重建app状态的非关键资源。

 

Because the onTrimMemory() callback was added in API level 14, you can usethe onLowMemory() callback as a fallback for older versions, which isroughly equivalent to the TRIM_MEMORY_COMPLETE event.

因为 onTrimMemory() 回调实在API 14中添加的,你可以在之前的版本中使用onLowMemory()回调。onLowMemory()大致相当于TRIM_MEMORY_COMPLETE事件。

 

Note: When thesystem begins killing processes in the LRU cache, although it primarily worksbottom-up, it does give some consideration to which processes are consumingmore memory and will thus provide the system more memory gain if killed. So theless memory you consume while in the LRU list overall, the better your chancesare to remain in the list and be able to quickly resume.

注意:当系统开始杀死LRU缓存中的进程的时候,尽管它首先从底部开始,但会考虑哪些进程正消耗更多的内存,因此杀死后系统会获取更多内存。所以,当在LRU列表的时候,你消耗的内存越少,你保持在列表中并能够快速重建的机会就越好。

 

Check how muchmemory you should use  检查你应该使用多少内存

As mentioned earlier, each Android-powered device has adifferent amount of RAM available to the system and thus provides a differentheap limit for each app. You can call getMemoryClass() to get an estimate of your app's available heap inmegabytes. If your app tries to allocate more memory than is available here, itwill receive anOutOfMemoryError.

就如上面所提到的,每一个Android设备都有可供系统使用的不同数量的内存,因此会为每一个app提供不同的堆限制。你可以调用getMemoryClass() 来估计可供app使用的堆的兆字节数。如果你的app尝试分配比可用量更多的内存,将会收到一个OutOfMemoryError

 

【补充】 http://blog.csdn.net/tiantangrenjian/article/details/39182293

ActivityManager mActivityManager = (ActivityManager)getSystemService(Context.ACTIVITY_SERVICE);
int memoryClass = am. getMemoryClass();              //96MB [Mi2S,Android 4.1]

可以通过在AndroidManifest.xml文件中设置来扩大内存限制:(不过不建议使用此参数,建议节省内存)

 

 

In very special situations, you can request a larger heapsize by setting the largeHeap attributeto "true" in the manifest <application> tag.If you do so, you can call getLargeMemoryClass() to get an estimate of the large heap size.

在特殊情况下,你可以通过在manifest文件 <application>的标签中设置largeHeap属性为“true”以请求更大的堆。如果你这么做了,可以调用getLargeMemoryClass()方法来估计这个大堆的大小。

 

However, the ability to request a large heap is intendedonly for a small set of apps that can justify the need to consume more RAM(such as a large photo editing app). Never request a large heap simplybecause you've run out of memory and you need a quick fix—you shoulduse it only when you know exactly where all your memory is being allocated andwhy it must be retained. Yet, even when you're confident your app can justifythe large heap, you should avoid requesting it to whatever extent possible.Using the extra memory will increasingly be to the detriment of the overalluser experience because garbage collection will take longer and systemperformance may be slower when task switching or performing other commonoperations.

然而,请求更大堆的能力是专门为一小部分能够证明有消耗更多RAM的需要的app设计的(例如大的照片编辑app)。绝对不要只是因为内存耗尽而申请更大的堆,并且你需要尽快修复——只有当你精确地知道你的内存的分配位置和必须保留的原因时,你才能够用它。然而,即使当你有信心认为app取得大的堆是合理的,你也应该避免申请任何可能的扩展。使用额外的空间会急剧的损害用户的整体体验,因为垃圾收集会执行更长时间,并且在任务切换或者执行其它常见操作的时候,系统性能会变慢。

 

Additionally, the large heap size is not the same on alldevices and, when running on devices that have limited RAM, the large heap sizemay be exactly the same as the regular heap size. So even if you do request thelarge heap size, you should call getMemoryClass() to check the regular heap size and strive to alwaysstay below that limit.

此外,大堆的大小在不同的设备上是不同的,并且当运行在RAM有限的设备上的时候,大堆的大小可能跟常规堆的大小完全一样。所以,即使你请求大堆的大小,你也应该调用getMemoryClass()来检查常规堆大小,并努力保持在那个限制之下。

 

Avoid wastingmemory with bitmaps  避免位图浪费内存

When you load a bitmap, keep it in RAM only at theresolution you need for the current device's screen, scaling it down if theoriginal bitmap is a higher resolution. Keep in mind that an increase in bitmapresolution results in a corresponding (increase2) in memory needed, because both the X and Y dimensionsincrease.

当你装载位图的时候,以当前设备屏幕需要的分辨率保持在RAM中,如果原始位图的分辨率较高,就要进行压缩。牢记一点,位图分辨率增加一点,会导致与之相对应(平方)的内存需要,因为XY两个维度都会增加。

 

Note: On Android2.3.x (API level 10) and below, bitmap objects always appear as the same sizein your app heap regardless of the image resolution (the actual pixel data isstored separately in native memory). This makes it more difficult to debug thebitmap memory allocation because most heap analysis tools do not see the nativeallocation. However, beginning in Android 3.0 (API level 11), the bitmap pixeldata is allocated in your app's Dalvik heap, improving garbage collection anddebuggability. So if your app uses bitmaps and you're having troublediscovering why your app is using some memory on an older device, switch to adevice running Android 3.0 or higher to debug it.

注意:在Android 2.3.x(API 10)及其更低版本上,位图对象总是以你app堆中的同样大小出现,而不管图像的分辨率是多少(实际的像素数据被单独存放在本地内存中)。这使得调试位图内存分配更加困难,因为大多数堆分析工具不能分析本地分配。然而,从Android 3.0(API 11)开始,位图像素数据被分配在app的Dalvik堆中,改善了垃圾收集和可调试性。所以,如果你的app使用位图,并且你在寻找app在较老的设备上使用更多的内存的原因时遇到麻烦,那么可以到Android 3.0或更新的版本上运行和调试。

 

For more tips about working with bitmaps, read Managing BitmapMemory.

为获取关于位图使用的更多要点,参阅Managing BitmapMemory

 

Use optimizeddata containers  使用优化的数据容器

Take advantage of optimizedcontainers in the Android framework, such as SparseArraySparseBooleanArray, and LongSparseArray. The generic HashMap implementation can be quite memory inefficient because itneeds a separate entry object for every mapping. Additionally, the SparseArray classes are more efficient because they avoid the system'sneed to autobox the key and sometimes value (whichcreates yet another object or two per entry). And don't be afraid of droppingdown to raw arrays when that makes sense.

利用Android框架中优化的容器,例如SparseArraySparseBooleanArray, 和 LongSparseArray。泛型的HashMap实现内存效率很低,因为对每一个映射,它都需要一个单独的条目对象。除此之外,SparseArray类效率更高,因为他们避免系统自动打包键,以及有时自动打包值(这会给每一个条目创建另外一个或者两个对象)。当这些变得有意义的时候,不要害怕丢弃哪些粗糙的数组。

 

【补充】https://liuzhichao.com/p/832.html

SparseArray是android里为<Interger,Object> 这样的Hashmap而专门写的类,目的是提高效率,其核心是折半查找函数(binarySearch)。在Android中,当我们需要定义

HashMap <Integer, E> hashMap = new HashMap <Integer, E> ();

时,我们可以使用如下的方式来取得更好的性能.

SparseArray <E> sparseArray = new SparseArray <E> ();

 

更详细的内容见官方文档:http://developer.android.com/reference/android/util/package-summary.html

 

 

 

Be aware ofmemory overhead 对内存开销保持清醒

Be knowledgeable about the cost and overhead of thelanguage and libraries you are using, and keep this information in mind whenyou design your app, from start to finish. Often, things on the surface thatlook innocuous may in fact have a large amount of overhead. Examples include:

要对你使用的语言和库的成本及开销有足够的认识,当在设计app的时候,要把这些从始至终都要牢记在心。通常,表面看起来没有害处的东西事实上会有大量开销。例子包括:

 

·        Enums often require morethan twice as much memory as static constants. You should strictly avoid usingenums on Android.

枚举通常需要比静态常量多两倍的内存。你在Android设备上应该严格避免使用枚举。

 

·        Every class in Java(including anonymous inner classes) uses about 500 bytes of code.

Java中的每个类(包括匿名内部类)使用大约500 bytes的代码。

 

·        Every class instance has12-16 bytes of RAM overhead.

每个类实例有12-16字节的内存开销。

 

·        Putting a single entryinto a HashMap requires the allocation of an additional entryobject that takes 32 bytes (see the previous section about optimized datacontainers).

向一个HashMap中放一条单一的记录需要分配一个占据32 bytes的额外记录对象。

 

A few bytes here and there quickly add up—app designsthat are class- or object-heavy will suffer from this overhead. That can leaveyou in the difficult position of looking at a heap analysis and realizing yourproblem is a lot of small objects using up your RAM.

一些零星分散的字节快速堆积——app设计(重型类或对象)会受这种开销的不良影响。当查看堆分析的时候,这将使你不容易意识到你的问题是大量小对象耗尽了内存。

 

Be careful withcode abstractions  对代码抽象保持小心

Often, developers useabstractions simply as a "good programming practice," becauseabstractions can improve code flexibility and maintenance. However,abstractions come at a significant cost: generally they require a fair amountmore code that needs to be executed, requiring more time and more RAM for thatcode to be mapped into memory. So if your abstractions aren't supplying asignificant benefit, you should avoid them.

通常,开发者简单地将抽象作为“好的编程实践”使用,因为抽象能够提高代码的灵活性和可维护性。然而,抽象是有显著代价的:一般而言,它们需要增加显著量的代码来执行,需要更多的时间和RAM来映射到存储器。所以,如果你的抽象没有显著的好处,你就应该避免使用。

 

Use nanoprotobufs for serialized data 使用nano protobufs序列化数据

Protocol buffers area language-neutral, platform-neutral, extensible mechanism designed by Googlefor serializing structured data—think XML, but smaller, faster, and simpler. Ifyou decide to use protobufs for your data, you should always use nano protobufsin your client-side code. Regular protobufs generate extremely verbose code,which will cause many kinds of problems in your app: increased RAM use,significant APK size increase, slower execution, and quickly hitting the DEXsymbol limit.

Protocol buffers是一种语言中立、平台中立,由Google为了序列化结构化数据(例如XML)设计的可扩展机制,但是小巧、快速、简单。如果你决定使用protobufs,你应该在客户端代码中使用nano protobufs。常规protobufs生成极度冗长的代码,将会在app中导致许多问题:增加RAM耗用、显著增加APK大小、执行慢,并且快速达到DEX符号(DEX symbol)的限度。

 

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

为获取更多信息,请参阅protobuf readme的“Nano version”部分。

 

Avoid dependencyinjection frameworks  避免使用依赖注入框架

Using a dependency injectionframework such as Guice or RoboGuice may be attractive because they can simplify the code youwrite and provide an adaptive environment that's useful for testing and otherconfiguration changes. However, these frameworks tend to perform a lot ofprocess initialization by scanning your code for annotations, which can requiresignificant amounts of your code to be mapped into RAM even though you don'tneed it. These mapped pages are allocated into clean memory so Android can dropthem, but that won't happen until the pages have been left in memory for a longperiod of time.

使用诸如GuiceRoboGuice的依赖注入框架可能会很吸引人,因为它们会简化你写的代码,并提供一个可适配的环境,这对测试和其它配置的变更是有用的。然而,这些框架往往需要浏览你代码中的注解,以执行进程的初始化,而这需要大量的代码映射到RAM,即使你不需要。这些映射页面被分配到干净内存(cleanmemory),所以Android虽然可以清除它们,但会在驻留内存很长一段时间后才会进行。

 

Be careful aboutusing external libraries  使用外部库要小心

External library code is often not written for mobileenvironments and can be inefficient when used for work on a mobile client. Atthe very least, when you decide to use an external library, you should assumeyou are taking on a significant porting and maintenance burden to optimize thelibrary for mobile. Plan for that work up-front and analyze the library interms of code size and RAM footprint before deciding to use it at all.

外部库通常不是为移动环境开发,所以用于移动客户端时会没有效率。最起码,当你决定使用外部库的时候,你应该承担入门与维护的大量负担,以为移动设备进行库的优化。

 

Even libraries supposedly designed for use on Android arepotentially dangerous because each library may do things differently. Forexample, one library may use nano protobufs while another uses micro protobufs.Now you have two different protobuf implementations in your app. This can andwill also happen with different implementations of logging, analytics, imageloading frameworks, caching, and all kinds of other things you don't expect. ProGuard won'tsave you here because these will all be lower-level dependencies that arerequired by the features for which you want the library. This becomesespecially problematic when you use an Activitysubclass from a library (which will tend to have wideswaths of dependencies), when libraries use reflection (which is common andmeans you need to spend a lot of time manually tweaking ProGuard to get it towork), and so on.

甚至,原本觉得是为专门在Android平台上使用而设计的库都是有潜在危险的,因为每个库的实现的方式可能不同。例如,有的库可能用nano protobufs,而有的会用micro protobufs。结果就是,你的app中有两种不同的protobuf实现。这种情况能够并且可能在日志、分析、图片加载框架、缓存和其它你意想不到的东西的不同实现上也会发生。ProGuard 这里并不会帮到你,因为这是你引入这些库而取得的特性所需要的低层次依赖。当你使用来自一个库的Activity的子类(这个库往往还有大量依赖)的时候,当库使用了反射(这很常见,意味着你需要花费大量时间手动调整ProGuard使其工作)的时候,以及其它很多情况下,这个问题会变得尤其突出。

 

Also be careful not to fall into the trap of using ashared library for one or two features out of dozens of other things it does;you don't want to pull in a large amount of code and overhead that you don'teven use. At the end of the day, if there isn't an existing implementation thatis a strong match for what you need to do, it may be best if you create yourown implementation.

还要小心,不要陷入这样的困境——为一两个功能而引入一个共享库,而这个库还有许多其它功能;你不想引入你不需要的大量代码和开销。归根结底,如果没有与你需要的功能非常匹配的已有实现,你自己去实现恐怕是最好的。

 

Optimize overallperformance  优化总体性能

A variety of information about optimizing your app'soverall performance is available in other documents listed in Best Practicesfor Performance. Many of these documents include optimizations tipsfor CPU performance, but many of these tips also help optimize your app'smemory use, such as by reducing the number of layout objects required by yourUI.

可以在另一个文档Best Practicesfor Performance访问关于优化app整体性能的大量知识。这些文档中,很多都包含CPU性能的优化要点,但是这些要点在优化app内存方面也有帮助,例如减少UI所需要的布局对象的数量。

 

You should also read about optimizing yourUI with the layout debugging tools and take advantage of theoptimization suggestions provided by the lint tool.

你也应该参阅optimizing yourUI,里面有布局调试工具,并且利用好lint tool提供的优化建议。

 

Use ProGuard tostrip out any unneeded code 使用ProGuard去除任何没有用的代码

The ProGuard tool shrinks,optimizes, and obfuscates your code by removing unused code and renamingclasses, fields, and methods with semantically obscure names. Using ProGuardcan make your code more compact, requiring fewer RAM pages to be mapped.

ProGuard 工具通过移除没有用的代码、使用语义模糊的名称来重命名类、变量和方法,来压缩、优化并混淆你的代码。使用ProGuard工具可以使你的代码更紧凑,使用更少的RAM页来实现映射。

 

Use zipalign onyour final APK  在最终APK上使用zipalign

If you do any post-processing of an APK generated by abuild system (including signing it with your final production certificate),then you must run zipalign onit to have it re-aligned. Failing to do so can cause your app to requiresignificantly more RAM, because things like resources can no longer be mmappedfrom the APK.

如果你对一个由编译系统生成的APK(包括用你最终的产品证书进行签名)做任何的后处理,你必须在它上面运行zipalign ,来使其重新排列。不这么做会导致你的app需要明显多的RAM,因为资源之类的东西不能再从APK中映射。

 

Note: Google PlayStore does not accept APK files that are not zipaligned.

注意:Google Play Store不接受没有经过zipaligned处理的APK文件。

 

Analyze your RAMusage 分析RAM使用

Once you achieve arelatively stable build, begin analyzing how much RAM your app is usingthroughout all stages of its lifecycle. For information about how to analyzeyour app, read Investigating Your RAM Usage.

一旦你得到了一个相对稳定的构建,就可以开始分析你的app在整个生命周期中使用了多少RAM。为获取app分析的更多知识,请阅读Investigating Your RAM Usage

 

Use multipleprocesses 使用多进程

If it's appropriate for your app, an advanced techniquethat may help you manage your app's memory is dividing components of your appinto multiple processes. This technique must always be used carefully and mostapps should not run multiple processes, as it can easily increase—ratherthan decrease—your RAM footprint if done incorrectly. It is primarily useful toapps that may run significant work in the background as well as the foregroundand can manage those operations separately.

在对你的app合适的情况下,一项可以帮助你管理内存的高级技术是将你app的组件分配到多个进程中。这项技术需要小心使用,并且大多数app不应运行在多进程中,因为在使用不当的情况下,它可能容易增加——而不是减少——内存占用。有些app需要在后台和前台运行大量的工作,并且能够单独管理这些操作,这项技术对这类app非常有用。

 

An example of when multiple processes may be appropriateis when building a music player that plays music from a service for long periodof time. If the entire app runs in one process, then many of the allocationsperformed for its activity UI must be kept around as long as it is playingmusic, even if the user is currently in another app and the service iscontrolling the playback. An app like this may be split into two process: onefor its UI, and the other for the work that continues running in the backgroundservice.

一个适合选择多进程的例子是,开发一个需要长时间在后台服务播放音乐的音乐播放器。如果整个app运行在一个进程中,那么许多为activity UI而进行的内存分配必须在播放音乐期间一直处在就绪状态,即使用户当前在操作其它app,service处在重复播放状态。像这样的一个app可以拆分为连个进程:一个管理UI,一个管理在后台服务中持续运行的工作。

 

You can specify a separate process for each app componentby declaring the android:process attributefor each component in the manifest file. For example, you can specify that yourservice should run in a process separate from your app's main process bydeclaring a new process named "background" (but you can name theprocess anything you like):

你可以通过为manifest文件中的每个组件声明android:process属性来为每个app组件制定一个单独的进程。例如,你可以通过声明一个名称为“background”(但是你将新进程命名为任何你喜欢的名称)的新进程,来指定service运行在独立于app主进程的进程中:

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

 

Your process name should begin with a colon (':') toensure that the process remains private to your app.

你的新进程的名称应该以冒号(”:”)以确保新进程为你的app私有。

 

Before you decide to create a new process, you need tounderstand the memory implications. To illustrate the consequences of eachprocess, consider that an empty process doing basically nothing has an extramemory footprint of about 1.4MB, as shown by the memory information dump below.

在决定创建新进程之前,你需要理解内存含义。为了说明每个进程的重要地位,认为一个几乎不做工作的空进程有1.4MB的内存占用,像下面通过内存信息导出显示的那样:

 

adb shell dumpsys meminfocom.example.android.apis:empty

【译注】“:”后面的emptymanifest文件中配置的process属性对应。

** MEMINFO inpid 10172 [com.example.android.apis:empty] **

                Pss     Pss Shared Private  SharedPrivate    Heap    Heap   Heap

              Total   Clean  Dirty   Dirty   Clean  Clean    Size   Alloc   Free

             ------ ------  ------  ------ ------  ------  ------ ------  ------

  Native Heap     0      0       0       0      0       0    1864   1800      63

  Dalvik Heap  764       0    5228    316       0       0   5584    5499      85

 Dalvik Other  619       0    3784    448       0       0

        Stack   28       0       8     28       0       0

    Other 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     148

   Other mmap     8      0       8       8     20       0

      Unknown  403       0     600    380       0       0

        TOTAL 2417     148   12480   1392    4832     152    7448    7299    148

Note: Moreinformation about how to read this output is provided in InvestigatingYour RAM Usage. The key data here is the Private Dirty and PrivateClean memory, which shows that this process is using almost 1.4MB ofnon-pageable RAM (distributed across the Dalvik heap, native allocations,book-keeping, and library-loading), and another 150K of RAM for code that hasbeen mapped in to execute.

注意:关于怎样阅读这个输出的更多知识在InvestigatingYour RAM Usage中提供。这里的关键数据是Private Dirty 和 PrivateClean memory,这表明这个进程使用了接近1.4MB的非分页RAM(在Dalvik堆、本地分配、记账和库装载)和另外150K的RAM,供已经映射并即将执行的代码使用。

 

This memory footprint for an empty process is fairlysignificant and it can quickly grow as you start doing work in that process.For example, here is the memory use of a process that is created only to showan activity with some text in it:

这个空进程的内存占用是相当显著的,并且当你在那个进程中开始工作的时候会迅速增长。例如,下面是一个进程的内存耗用,而创建这个进程仅仅是为在其中显示一个包含文字的activity:

 

** MEMINFO inpid 10226 [com.example.android.helloactivity] **

                Pss     Pss Shared Private  SharedPrivate    Heap   Heap    Heap

              Total   Clean  Dirty   Dirty   Clean  Clean    Size   Alloc   Free

             ------  ------ ------  ------  ------ ------  ------  ------ ------

  Native Heap     0      0       0       0      0       0    3000   2951      48

  Dalvik Heap 1074       0    4928    776       0       0   5744    5658      86

 Dalvik Other  802       0    3612    664       0       0

        Stack   28       0       8     28       0       0

       Ashmem     6      0      16       0      0       0

    Other 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       4

   Other mmap   10       0       8      8      68       0

      Unknown  632       0     412    624       0       0

        TOTAL 5169       4   11832   4032   10152      8    8744   8609     134

The process has now almost tripled in size, to 4MB,simply by showing some text in the UI. This leads to an important conclusion:If you are going to split your app into multiple processes, only one processshould be responsible for UI. Other processes should avoid any UI, as this willquickly increase the RAM required by the process (especially once you startloading bitmap assets and other resources). It may then be hard or impossibleto reduce the memory usage once the UI is drawn.

这个进程大小上已经接近三倍了,达到4MB,原因仅仅是在UI中显示一些文本。这得出一个重要的结论:如果你将要把app分到多个进程中,应当只有一个进程负责UI。其它进程应该避免任何UI,因为这将快速增加进程所需要的内存(尤其是一旦你开始加载位图和其它资源)。一旦UI绘制出来,那将很难或者不可能减少内存耗用。

 

Additionally, when running more than one process, it'smore important than ever that you keep your code as lean as possible, becauseany unnecessary RAM overhead for common implementations are now replicated ineach process. For example, if you are using enums (though you should notuse enums), all of the RAM needed to create and initialize thoseconstants is duplicated in each process, and any abstractions you have withadapters and temporaries or other overhead will likewise be replicated.

除此之外,当运行多于一个进程的时候,比任何事情都重要的是,尽量保持你的代码简洁,因为任何不必要的通常意义上的内存耗用现在都会在每个进程中重复发生。例如,如果你使用了枚举(尽管你不应该使用枚举you should notuse enums),用于创建和初始化那些常量的RAM在每个进程中会重复发生,并且任何你使用适配器(adapters)和临时变量(temporaries)或其它内存耗用而进行的抽象都会重复发生。

 

Another concern with multiple processes is thedependencies that exist between them. For example, if your app has a contentprovider that you have running in the default process which also hosts your UI,then code in a background process that uses that content provider will alsorequire that your UI process remain in RAM. If your goal is to have abackground process that can run independently of a heavy-weight UI process, itcan't have dependencies on content providers or services that execute in the UIprocess.

另一个在使用多进程时需要关注的问题是进程间的依赖。例如,如果你的app在主进程中有一个content provider,那么使用这个content provider的后台进程中的代码也会需要你的UI进程驻留RAM。如果你的目标是拥有一个独立于高权重UI进程的后台进程,那么这个后台进程不能依赖在UI进程中执行的content provider或者service。

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值