美文网首页
管理App内存【译】

管理App内存【译】

作者: 一线游骑兵 | 来源:发表于2019-01-01 21:02 被阅读0次

    原文:https://developer.android.google.cn/topic/performance/memory#kotlin
    随机存取器(RAM)在任何软件开发环境中都是非常宝贵的资源,特别是在无力内存受限的移动操作系统上更为珍贵。尽管在ART和Dalvik虚拟机上都会执行常规的垃圾回收操作,但并不意味着你可以忽略你的app分配和释放内存的时机和位置。你仍然需要避免内存泄漏(通常是由被静态成员变量持有对象的引用导致),并在生命周期定义的回调的适当的时间释放这些引用。

    监测可用的内存和内存的使用情况

    在修复app内存使用问题之前,你首先要找到这些问题。Android Studio 中的 Memory Profiler可以帮助你发现和分析内存的问题,步骤如下:

    1. 观察你的app随着时间分配的内存的情况。Memory Profiler展示了一个实时的图标用来描述你的app当前内存的使用情况,比如分配给java对象的数量,以及何时触发了GC。
    2. 在你的app运行的时候发起GC事件,并获取java堆的快照。
    3. 记录你的app内存分配,然后检查所有已分配的对象,查看每个对象分配的堆栈跟踪,并跳转到对应的代码中。
    在对应的事件中释放内存

    Android系统可以通过多种方式从你的app中回收内存,或者在必要的时候杀死你的app来释放内存去执行关键的任务。为了进一步平衡系统内存并避免被系统杀死你的app进程,你可以在Activity类中实现ComponentCallbacks2 接口。 该接口中提供的onTrimMemory()的回调方法允许你的app无论在前台还是后台,都可以监听内存相关的事件,然后释放对象来响应app的声明周期,或者在收到系统需要回收内存的指示时释放对象。
    例如,你可以实现 onTrimMemory() 回调来响应不同内存相关事件,示例如下:

    import android.content.ComponentCallbacks2
    // Other import statements ...
    
    class MainActivity : AppCompatActivity(), 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.
         */
        override fun onTrimMemory(level: Int) {
    
            // Determine which lifecycle or system event was raised.
            when (level) {
    
                ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN -> {
                    /*
                       Release any UI objects that currently hold memory.
    
                       The user interface has moved to the background.
                    */
                }
    
                ComponentCallbacks2.TRIM_MEMORY_RUNNING_MODERATE,
                ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW,
                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.
                    */
                }
    
                ComponentCallbacks2.TRIM_MEMORY_BACKGROUND,
                ComponentCallbacks2.TRIM_MEMORY_MODERATE,
                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.
                    */
                }
    
                else -> {
                    /*
                      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.
                    */
                }
            }
        }
    }
    

    该回调方法是在Android4.0添加的,为了兼容更低版本,可以使用onLowMemory(),它大致相当于onTrimMemory()方法中的TRIM_MEMORY_COMPLETE事件。

    检查你应该使用多少内存

    为了允许运行多个进程,Android为每个app设置了一个硬限制的heap大小。具体的heap的大小。具体的heap大小限制会根据不同设备可用的RAM的多少而不同。如果你的app已经达到了heap大小的边界值并尝试申请分配更多内存,则系统会抛出OutOfMemoryError异常。
    为避免内存不足,您可以通过调用 getMemoryInfo()来查询系统以确定当前设备上可用的堆空间大小。这将返回一个ActivityManager.MemoryInfo对象,该对象提供有关设备当前内存状态的信息,包括可用内存,总内存和内存阈值 - 系统开始终止进程的内存级别。
    The following code snippet shows an example of how you can use the [getMemoryInfo()](https://developer.android.google.cn/reference/android/app/ActivityManager.html#getMemoryInfo(android.app.ActivityManager.MemoryInfo)) method in your application.
    下边的代码片段展示了getMemoryInfo()的示例:

    fun doSomethingMemoryIntensive() {
    
        // Before doing something that requires a lot of memory,
        // check to see whether the device is in a low memory state.
        if (!getAvailableMemory().lowMemory) {
            // Do memory intensive work ...
        }
    }
    
    // Get a MemoryInfo object for the device's current memory status.
    private fun getAvailableMemory(): ActivityManager.MemoryInfo {
        val activityManager = getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
        return ActivityManager.MemoryInfo().also { memoryInfo ->
            activityManager.getMemoryInfo(memoryInfo)
        }
    }
    
    慎重的使用service

    让一个服务在不需要的时候也运行时安卓app中可以犯的最大的错误之一。如果你的app需要一个服务来执行后台工作,不要保持它的运行状态除非它需要运行一个工作。当任务完成后要记得停止你的服务,否则,你可能会在无意中造成内存泄漏。
    当你启动一个服务,系统为了保证服务的运行更倾向于保留该服务所在的进程。此行为使得服务进程非常昂贵,因为该服务使用的RAM对其他进程是不可用的。这减少了系统可以保留在LRU高速缓存中的高速缓存进程数,从而降低了应用程序切换的效率。当内存紧张并且系统无法保留足够的进程来托管当前服务的运行时,会造成系统的不稳定。
    你应该尽量避免使用持久性的服务,因为他们可能会不断的对可用内存提出要求。相反的,我们推荐你使用可选择的实现,例如JobScheduler.
    如果你必须使用一个服务,限制你的服务的生命周期的最好方法就是使用IntentService,它会在完成工作尽快的结束自己。更多信息请阅读 Background Service一文。

    使用优化过的数据容器

    一些编程语言提供的类是没有为移动设备优化的,例如泛型HashMap的实现可能会非常低效,因为每一个映射都需要一个单独的入口对象。
    Android framework包含了一系列的优化过的数据容器,包括SparseArraySparseBooleanArray以及LongSparseArray。例如,SparseArray类因为避免了系统为key和一些value所需要的自动装箱
    如有必要,您始终可以切换到原始阵列以获得非常精简的数据结构。

    注意抽象编码

    开发者通常使用抽象来作为一个好的编码习惯,因为抽象可以提高代码的灵活性和可维护性。然而,抽象的成本也异常高:通常需要更多的代码被执行,需要更多的时间和空间来讲代码映射到内存。所以如果抽象实现对你没有明显的好处,你应该避免使用它们。

    避免内存抖动

    就像前边提到的,GC事件通常不会影响你的app的性能。然而,短时间内大量的GC事件会占用你的帧画面刷新显示时间。系统花费在GC上的时间越多,则处理渲染或者音频流上的时间就会越少。
    通常,内存抖动可以造成大量的GC事件。实践中,内存抖动描述了在给定事件分配的临时对象的数量。
    例如,您可能在for循环中分配了多个临时对象。 或者可以在View的onDraw()函数内新建了Paint或Bitmap对象。 在这两种情况下,应用程序都会以高容量快速创建大量对象。 这些可以快速消耗年轻代中的所有可用内存,从而迫使垃圾收集事件发生。
    当然,你需要在代码中找到内存流失率较高的位置,然后才能修复它们。 为此,您应该在Android Studio中使用Memory Profiler。

    移除不必要的资源和库

    代码中的某些资源和库可以在不知情的情况下吞噬内存。 APK的总体大小(包括第三方库或嵌入式资源)可能会影响应用消耗的内存量。 您可以通过从代码中删除任何冗余,不必要或臃肿的组件,资源或库来提高应用程序的内存消耗。

    减小apk的大小

    您可以通过减少应用的总体大小来显着降低应用的内存使用量。 位图大小,资源,动画帧和第三方库都可以影响APK的大小。 Android Studio和Android SDK提供了多种工具来帮助您减少资源和外部依赖项的大小。
    有关如何降低整体APK大小的详细信息,请参阅 缩小APK大小

    使用Dagger2来进行依赖注入

    依赖注入框架可以简化您编写的代码,并提供对测试和其他配置更改有用的自适应环境。
    如果您打算在应用程序中使用依赖注入框架,请考虑使用Dagger 2. Dagger不使用反射来扫描应用程序的代码。 Dagger的静态编译时实现意味着它可以在Android应用程序中使用,而无需不必要的运行时成本或内存使用。
    使用反射的其他依赖注入框架倾向于通过扫描代码注释来初始化进程。 此过程可能需要更多的CPU周期和RAM,并且可能会在应用程序启动时导致明显的延迟。

    小心的使用外部依赖库

    外部库代码通常不是为移动环境编写的,并且在用于移动客户端时可能效率低下。当您决定使用外部库时,可能需要针对移动设备优化该库。在决定使用它之前,预先计划该工作并在代码大小和RAM占用空间方面分析库。
    即使是一些针对移动端优化过的库也会因实现不同而导致问题。例如,一个库可能使用nano protobufs,而另一个库使用微型protobuf,导致您的应用程序中有两个不同的protobuf实现。这可能发生在日志记录,分析,图像加载框架,缓存以及许多其他您不期望的事情的不同实现中。
    尽管ProGuard可以帮助使用正确的标志删除API和资源,但它无法删除库的大型内部依赖项。您在这些库中需要的功能可能需要较低级别的依赖项。当你使用库中的Activity子类(它往往会有大量的依赖关系)时,问题会变得很棘手,当库使用反射时(这是常见的并且意味着你需要花费大量时间手动调整ProGuard来实现它工作),等等。
    另外,请避免使用共享库中的一个或两个功能。您不希望引入大量您甚至不使用的代码和开销。在考虑是否使用库时,请查找与您需要的实现非常匹配的实现。否则,您可以决定创建自己的实现。

    相关文章

      网友评论

          本文标题:管理App内存【译】

          本文链接:https://www.haomeiwen.com/subject/guwocqtx.html