安卓性能优化——内存管理技巧

引用了大量API文档的内容,没有对超链接进行删改,有需要的朋友可以直接点超链接访问API文档。

安卓性能优化

Performance and power

Implementing a cool idea is a great start toward an app that delights users, but it's just the beginning. The next step is maximizing your app's performance. For example, users want apps that:

  • Use power sparingly.
  • Start up quickly.
  • Respond quickly to user interaction.

This section provides you with the know-how you need in order to make your apps not only cool, but also performant. Read on to discover how to develop apps that are power-thrifty, responsive, efficient, and well-behaved.

https://developer.android.google.cn/topic/performance/


内存管理技巧

Use more memory-efficient code constructs

Some Android features, Java classes, and code constructs tend to use more memory than others. You can minimize how much memory your app uses by choosing more efficient alternatives in your code.

https://developer.android.google.cn/topic/performance/memory#code


 使用Service的风险

Use services sparingly

Leaving a service running when it’s not needed is one of the worst memory-management mistakesan Android app can make. If your app needs a service to perform work in the background, do not keep it running unless it needs to run a job. Remember to stop your service when it has completed its task. Otherwise, you can inadvertently cause a memory leak.

When you start a service, the system prefers to always keep the process for that service running. This behavior makes services processes very expensive because the RAM used by a service remains unavailable to other processes. This reduces the number of cached processes that the system can keep in the LRU cache, making app switching less efficient. It can even lead to thrashing in the system when memory is tight and the system can’t maintain enough processes to host all the services currently running.

You should generally avoid use of persistent services because of the on-going demands they place on available memory. Instead, we recommend that you use an alternative implementation such as JobScheduler. For more information about how to use JobScheduler to schedule background processes, see Background Optimizations.

If you must use a service, the best way to limit the lifespan of your service is to use an IntentService, which finishes itself as soon as it's done handling the intent that started it. For more information, read Running in a Background Service.


如果应用程序当中需要使用Service来执行后台任务的话,请一定要注意只有当任务正在执行的时候才应该让Service运行起来。另外,当任务执行完之后去停止Service的时候,要小心Service停止失败导致内存泄漏的情况。


当我们启动一个Service时,系统会倾向于将这个Service所依赖的进程进行保留,这样就会导致这个进程变得非常消耗内存。并且,系统可以在LRU cache当中缓存的进程数量也会减少,导致切换应用程序的时候耗费更多性能。严重的话,甚至有可能会导致崩溃,因为系统在内存非常吃紧的时候可能已无法维护所有正在运行的Service所依赖的进程。


为了能够控制Service的生命周期,Android官方推荐的最佳解决方案就是使用IntentService,这种Service的最大特点就是当后台任务执行结束后会自动停止,从而极大程度上避免了Service内存泄漏的可能性。


注意:
让一个Service在后台一直保持运行,即使它并不执行任何工作,这是编写Android程序时最糟糕的做法之一。


响应事件释放内存

As described in Overview of Android Memory Management, Android can reclaim memory from your app in several ways or kill your app entirely if necessary to free up memory for critical tasks. To further help balance the system memory and avoid the system's need to kill your app process, you can implement the ComponentCallbacks2 interface in your Activity classes. The provided onTrimMemory() callback method allows your app to listen for memory related events when your app is in either the foreground or the background, and then release objects in response to app lifecycle or system events that indicate the system needs to reclaim memory.

For example, you can implement the onTrimMemory() callback to respond to different memory-related events as shown here:

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;
        }
    }
}

The onTrimMemory() callback was added in Android 4.0 (API level 14). For earlier versions, you can use the onLowMemory(), which is roughly equivalent to the TRIM_MEMORY_COMPLETE event.


当界面不可见时释放内存

当用户打开了另外一个程序,我们的程序界面已经不再可见的时候,我们应当将所有和界面有关的资源进行释放

只需要在Activity中重写onTrimMemory()方法,然后在这个方法中监听TRIM_MEMORY_UI_HIDDEN这个级别,一旦触发之后,就说明用户已经离开了我们的程序,那么此时就可以进行资源释放操作了:

@Override 
public void onTrimMemory(int level) {
    super.onTrimMemory(level);
    switch (level) {
        case TRIM MEMORY UI HIDDEN:
        //进行资源释放操作
        break;
    }
}

 当内存紧张时释放内存

onTrimMemory()方法还有很多种其它类型的回调,可以在手机内存降低的时候及时通知我们。

我们应该根据回调中传入的级别来去决定如何释放应用程序的资源

  • TRIM_MEMORY_RUNNING_MODERATE:表示程序正常运行,不会被杀掉。但内存已经有点低了,系统可能会开始根据LRU缓存规则来去杀死进程了。
  • TRIM_MEMORY_RUNNING_LOW :表示程序正常运行,不会被杀掉。但内存已经非常低了,我们应该去释放掉一些不必要的资源以提升系统的性能,同时这也会直接影响到我们应用程序的性能。
  • TRIM_MEMORY_RUNNING_CRITICAL:表示程序正常运行,但系统已经根据LRU缓存规则杀掉了大部分缓存的进程。我们应当尽可能地去释放任何不必要的资源,否则系统可能会继续杀掉所有缓存中的进程,并且开始杀掉一些本来应当保持运行的进程,比如说后台运行的服务。

以上是当我们的应用程序正在运行时的回调

如果我们的程序目前是被缓存的,则会收到以下几种类型的回调:

  • TRIM_MEMORY_BACKGROUND:内存已经很低了,系统准备开始根据LRU缓存来清理进程。这个时候我们的程序在LRU缓存列表的最近位置,是不太可能被清理掉的,但这时去释放掉一些比较容易恢复的资源能够让手机的内存变得比较充足,从而让我们的程序更长时间地保留在缓存当中,这样当用户返回我们的程序时会感觉非常顺畅,而不是经历了一次重新启动的过程。
  • TRIM_MEMORY_MODERATE:内存已经很低了,并且我们的程序处于LRU缓存列表的中间位置,如果手机内存还得不到进一步释放的话,那么我们的程序就有被系统杀掉的风险了。
  • TRIM_MEMORY_COMPLETE :内存已经很低了,并且我们的程序处于LRU缓存列表的最边缘位置,系统会最优先考虑杀掉我们的应用程序,在这个时候应当尽可能的把一切可以释放的东西都进行释放。

避免在Bitmap上浪费内存

参考之前写的《Bitmaps与优化——位图采样及OOM异常》

https://blog.csdn.net/nishigesb123/article/details/89474352

主要是缓存位图


使用优化过的数据集合

Some of the classes provided by the programming language are not optimized for use on mobile devices. For example, the generic HashMap implementation can be quite memory inefficient because it needs a separate entry object for every mapping.

The Android framework includes several optimized data containers, including SparseArraySparseBooleanArray, and LongSparseArray. For example, the SparseArray classes are more efficient because they avoid the system's need to autobox the key and sometimes value (which creates yet another object or two per entry).

Android API当中提供了一些优化过后的数据集合工具类,如SparseArray、SparseBooleanArray以及LongSparseArray等,使用这些API可以让我们的程序更加高效。

  • SparseArray指的是稀疏数组(Sparse array)——所谓稀疏数组就是数组中大部分的内容值都未被使用(或都为零),在数组中仅有少部分的空间使用。因此造成内存空间的浪费,为了节省内存空间,并且不影响数组中原有的内容值,我们可以采用一种压缩的方式来表示稀疏数组的内容。
  • SparseArray是android里为<Integer ,Object>这样的HashMap而专门写的类,目的是提高效率,其核心是折半查找函数(binarySearch)。

忘记是离散数学还是数据结构学的东西了,或者两个都学过一次...等哪天专门一篇文章关于这块。

        //性能较低,可能产生内存浪费
        HashMap<Integer,String> map = new HashMap<>();
        map.put(1,"a");
        map.put(2,"b");
        map.put(3,"c");

        //性能较高,优化过后的集合
        SparseArray<String> array = new SparseArray<>();
        array.put(1,"a");
        array.put(2,"b");
        array.put(3,"c");

左为HashMap,虽然只存了5个值,但是开辟了(7*9)63个空间。右图为SparseArray,存储5个数只花了18个空间,分两部分。


了解内存的开支情况

Check how much memory you should use

To allow multiple running processes, Android sets a hard limit on the heap size alloted for each app. The exact heap size limit varies between devices based on how much RAM the device has available overall. If your app has reached the heap capacity and tries to allocate more memory, the system throws an OutOfMemoryError.

To avoid running out of memory, you can to query the system to determine how much heap space you have available on the current device. You can query the system for this figure by callinggetMemoryInfo(). This returns an ActivityManager.MemoryInfo object that provides information about the device's current memory status, including available memory, total memory, and the memory threshold—the memory level at which the system begins to kill processes. TheActivityManager.MemoryInfo object also exposes a simple boolean, lowMemory that tells you whether the device is running low on memory.

The following code snippet shows an example of how you can use the getMemoryInfo() method in your application.

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;
}
  1. 使用枚举通常会比使用静态常量要消耗两倍以上的内存,在Android开发当中我们应当尽可能地不使用枚举
  2. 任何一个Java类,包括内部类、匿名类,都要占用500字节的内存空间
  3. 任何一个类的实例要消耗12-16字节的内存开支,因此频繁创建实例也是会一定程度上影响内存的
  4. 在使用HashMap时,即使你只设置了一个基本数据类型的键,比如说int,但是也会按照对象的大小来分配内存,大概是32字节,而不是4字节。因此最好的办法就想上面说的,使用优化过的数据集合。

反抽象编程理念

Be careful with code abstractions

Developers often use abstractions simply as a good programming practice, because abstractions can improve code flexibility and maintenance. However, abstractions come at a significant cost: generally they require a fair amount more code that needs to be executed, requiring more time and more RAM for that code to be mapped into memory. So if your abstractions aren't supplying a significant benefit, you should avoid them.

在面向对象的世界里,使用抽象编程是一种被崇尚的编程习惯,使用抽象编程在代码的维护和可扩展性方面都会提高很多,但是,在Android上使用抽象会带来额外的内存开支,因为抽象的编程方法需要编写额外的代码,虽然这些代码根本执行不到,但是却也要映射到内存当中,不仅占用了更多的内存,在执行效率方面也会有所降低。

所以,在Android世界里,一切以性能、高效为宗旨,请不要滥用抽象编程


避免使用依赖注入框架

使用一些依赖注入的框架后,代码可能是这样的:

@ContentView(R.layout.main)
class MainActivity extends RoboActivity {
    @InjectView(R.id.name)
    TextView tv name;
    @InjectView(R.id.thumbnail) 
    ImageView iv thumbnail;
    @InjectResource(R.drawable.icon)
    Drawable icon;
    @InjectResource(R.string.app_name) 
    String name;public void onCreate(Bundle savedinstanceState) { 
        super.onCreate(savedInstanceState);
        tv-name.setText( "Hello, " + name );
    }
}

这些框架为了搜寻代码中的注解,通常都需要经历比较长的初始化过程,并且可能将一些你用不到的对象也一并加载到内存中
这些用不到的对象会一直占用着内存空间,可能要过很久才能得到释放


使用ProGuard混淆代码

ProGuard通常大家都会认为是一个混淆代码的工具,但是除了混淆之外,它还具有压缩和优化代码的功能

ProGuard会对我们的代码进行检索,删除一些无用的代码,并且会对类、字段、方法等进行重命名,重命名之后的类、字段和方法名都会比原来简短很多,这样的话也就对内存的占用变得更少了。

——

进入SDK目录,打开tools目录,找到proguard目录

找到proguard-android.txt或者下面那个proguard-android-optimize.txt

复制到我们的项目里

还需要对gradle进行一定的配置

找到buildTypes 将 flase改为true

即完成混淆

————

这里涉及proguard-android-optimize.txt和这里涉及proguard-android.txt的区别,optimize即代表优化

  • optimize过程会在Java字节码层面上进行优化,剔除方法中一些冗余的调用。帮助文档中列出了一些当前Proguard支持的优化。

使用多个进程

想要实现多进程的功能也非常简单,只需要在清单文件的应用程序组件中声明一个android:process属性就可以了。

比如我们说希望播放音乐的Service可以运行在一个单独的进程当中:

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

更多内容可以参考API文档~
 

猜你喜欢

转载自blog.csdn.net/nishigesb123/article/details/89603265