打磨APP

作者: shone | 来源:发表于2016-09-09 18:26 被阅读399次

    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

    相关文章

      网友评论

        本文标题:打磨APP

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