Android基础知识性能优化Android知识

打磨APP

2016-09-09  本文已影响399人  shone

1. 为啥要打磨APP,为啥要性能优化?

为了省电,为了快!
安卓手机作为移动设备.它的电量比标准台式机或笔记本电脑少很多.为啥苹果手机体验好,很重要因素也是速度快,基于这些原因,我们有必要关心内存的消耗!
特别是在Android 5.0以前,你想避免触发垃圾回收器.结果就是Android运行时(runtime)有一个大约200ms的冻结期(freeze).
如果用户正在滚动一个list,那将会有一个很明显的延时.

2. 如何优化?

2.1 避免不必要的对象分配

竟量避免创造不必要的objects对象,尤其是在内存有限的情况下.竟可能的去复用对象objects.
创建不必要的objects,只会引起更为频繁的垃圾回收,于情于理都不应该.
例如在咱们的自定义View中,避免在循环体(loops)或者onDraw()方法里创建对象.

2.2 使用高效的数据结构

安卓提供了很多Sparse*Array的实现类,想一下下面这段代码

Map<Integer, String> map = new HashMap<Integer, String>();
数据结构 描述
SparseArray<E> 映射integers到Objects, 避免Integer objects的创建.
SparseBooleanArray 映射 integers 到 booleans.
SparseIntArray 映射 integers 到 integers

用这段代码的结果是不必要的Integer对象创建.
安卓为咱们提供了更高效的为了映射values到objects这样一种数据结构.
下表给出了SparseArrays的例子

数据结构 描述
SparseArray<E> 映射integers到Objects, 避免Integer objects的创建.
SparseBooleanArray 映射 integers 到 booleans.
SparseIntArray 映射 integers 到 integers

为了改进上面的代码,我更倾向下面的写法

SparseArray<String> map = new SparseArray<String>();
map.put(1, "Hello");

2.3 处理bitmaps

Bitmaps如果全尺寸加载需要分配大量的内存.推荐加载期望值大小的尺寸.假如你有
一个应用需要显示100x100dp的图片,你应当以这个精确的大小加载图片.

常规的方法是首先测量未加载的bitmap,通过传递一个标志给BitmapFactory.

// instruct BitmapFactory to only the bounds and type of the image
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(getResources(), R.id.myimage, options);

// get width and height
int imageHeight = options.outHeight;
int imageWidth = options.outWidth;
// type of the image
String imageType = options.outMimeType;

后面,我们可以加载压缩过的图片.用下面的方法(来自于官方文档)以2为基数去计算缩放比例因子

public static Bitmap decodeBitmapWithGiveSizeFromResource(Resources res, int resId,
        int reqWidth, int reqHeight) {

    // First decode with inJustDecodeBounds=true to check dimensions
    final BitmapFactory.Options options = new BitmapFactory.Options();
    options.inJustDecodeBounds = true;
    BitmapFactory.decodeResource(res, resId, options);

    // Calculate inSampleSize
    options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);

    // Decode bitmap with inSampleSize set
    options.inJustDecodeBounds = false;
    return BitmapFactory.decodeResource(res, resId, options);
}

public static int calculateInSampleSize(
            BitmapFactory.Options options, int reqWidth, int reqHeight) {
    // Raw height and width of image
    final int height = options.outHeight;
    final int width = options.outWidth;
    int inSampleSize = 1;

    if (height > reqHeight || width > reqWidth) {

        final int halfHeight = height / 2;
        final int halfWidth = width / 2;

        // Calculate the largest inSampleSize value that is a power of 2 and keeps both
        // height and width larger than the requested height and width.
        while ((halfHeight / inSampleSize) > reqHeight
                && (halfWidth / inSampleSize) > reqWidth) {
            inSampleSize *= 2;
        }
    }

    return inSampleSize;
}

用下面的方法很方便地将图片显示到ImageView上面了,

viewWidth = imageView.getWidth();
viewHeight = imageView.getHeight();

imageView.setImageBitmap(
    decodeSampledBitmapFromResource(getResources(), R.id.myimage, viewWidth, viewHeight));

2.4 使用缓存

2.4.1 如何用缓存?

缓存允许重用对象,如果我们想把一个对象加载到内存,是否应该考虑把这个对象作一个缓存.例如,咱们从网络下载图片,然后把它显示到list里,那咱们应该保持它在内存里,以避免多次从网络下载.
很多场景,我们需要去回收一些对象,不然,app会OOM.最好的策略是,回收那些我们很长时间没有用过的对象.
Android平台为我们提供了LruCache类,从API-12(或者用support-v4 library),LruCache类提供了最近最少使用策略的实现.LRU记录了每个对象的使用情况,它有一个给定的大小,如果超过了这个大小,它将移除长时间没用的对象,特性图


下面的代码提供了一个LruCache的简单实现,用来缓存图片:
public class ImageCache extends LruCache<String, Bitmap> {
 
  public ImageCache( int maxSize ) {
    super( maxSize );
  }
 
  @Override
  protected int sizeOf( String key, Bitmap value ) {
    return value.getByteCount();
  }
 
  @Override
  protected void entryRemoved( boolean evicted, String key, Bitmap oldValue, Bitmap newValue ) {
    oldValue.recycle();
  }
 
}

用法很简单

LruCache<String, Bitmap> bitmapCache = new LruCache<String, Bitmap>();

为了确定缓存的初始化大小,最好的策略是基于设备的可用内存总大小,MemoryClass这个类
可以获得总大小,看下面的例子

int memClass = ( ( ActivityManager) activity.getSystemService( Context.ACTIVITY_SERVICE ) ).getMemoryClass();
int cacheSize = 1024 * 1024 * memClass / 8;
LruCache cache = new LruCache<String, Bitmap>( cacheSize );

2.4.2 清理缓存

从API-14,我们能重写onTrimMemory()方法,这个方法被调用的时候,说明系统正告诉你
需要清理内存了,安卓系统需要资源来维护前台进程.

@Override
public void onTrimMemory(int level) {
      super.onTrimMemory(level);
      switch (level){
          case TRIM_MEMORY_RUNNING_MODERATE:{//5
              break;
          }
          case TRIM_MEMORY_RUNNING_LOW:{//10
              break;
          }
          case TRIM_MEMORY_RUNNING_CRITICAL:{//15
              break;
          }
          case TRIM_MEMORY_UI_HIDDEN:{//20
              break;
          }
          case TRIM_MEMORY_BACKGROUND:{//40
              break;
          }
          case TRIM_MEMORY_MODERATE:{//60
              break;
          }
          case TRIM_MEMORY_COMPLETE:{//80
              break;
          }
      }
}

Android系统会根据不同等级的内存使用情况,调用这个函数,并传入对应的等级:

    TRIM_MEMORY_UI_HIDDEN 表示应用程序的 所有UI界面 被隐藏了,即用户点击了Home
键或者Back键导致应用的UI界面不可见.这时候应该释放一些资源.

    TRIM_MEMORY_UI_HIDDEN这个等级比较常用,和下面六个的关系不是很强,所以单独说.

下面三个等级是当我们的应用程序真正运行时的回调:

    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缓存列表的最边缘
位置,系统会最优先考虑杀掉我们的应用程序,在这个时候应当尽可能地把一切可以释放的东西都进行释放。

原文:
http://www.vogella.com/tutorials/AndroidApplicationOptimization/article.html

上一篇下一篇

猜你喜欢

热点阅读