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>();
用这段代码的结果是不必要的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