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