打磨APP

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,948评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,371评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,490评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,521评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,627评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,842评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,997评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,741评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,203评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,534评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,673评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,339评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,955评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,770评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,000评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,394评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,562评论 2 349

推荐阅读更多精彩内容