Android 图像与动画
一.Bitmap
Bitmap文件会迅速消耗内存从而导致程序崩溃,出现OutOfMemoryError。所以在使用Bitmap时,需要注意。
读取尺寸与类型
为了能在构造Bitmap之前优先读取图片的尺寸与类型,这里先将BitmapFactory.Options的
inJustDecodeBounds属性设置为true,以至于可以在解码的时候避免内存的分配(返回一个null的bitmap)。
BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; BitmapFactory.decodeResource(getResources(), R.id.myimage, options); int imageHeight = options.outHeight; int imageWidth = options.outWidth; String imageType = options.outMimeType;
加载一个按比例缩小的版本到内存中
如果想要把一个大小为1024x768像素的图片显示到大小为128x96像素的ImageView上,我们完全没有必要把整张原图都加载到内存中。这时我们设置BitmapFactory.Options的inSampleSize值来加载一个缩小版的图片,inSampleSize的值都是2的幂。
//计算inSampleSize代码 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; }
设置好inSampleSize值后,只需将options.inJustDecodeBounds设置为false,然后指点decode资源即加载缩小版的图片资源。
//reqWidth 和 reqHeight是希望获取的图片宽高。
public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId, int reqWidth, int reqHeight) { // 首次获取到原图片的大小 final BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; BitmapFactory.decodeResource(res, resId, options); //计算inSampleSize options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight); // 获得缩小版的图片资源 options.inJustDecodeBounds = false; return BitmapFactory.decodeResource(res, resId, options); }
异步加载图片
当图片来自网络或者内存卡时,我们应该使用子线程来加载数据,可以使用AsyncTask等方式处理这种情况。
并发问题
当类似Listview等视图控件在使用上面演示的AsyncTask 方法时,会同时带来并发的问题。首先为了更高的效率,ListView与GridView的子Item视图会在用户滑动屏幕时被循环使用。如果每一个子视图都触发一个AsyncTask,那么就无法确保关联的视图在结束任务时,分配的视图已经进入循环队列中,给另外一个子视图进行重用。而且, 无法确保所有的异步任务的完成顺序和他们本身的启动顺序保持一致。
解决方法:
ImageView保存最近使用的AsyncTask的引用,这个引用可以在任务完成的时候再次读取检查。使用这种方式, 就可以对前面提到的AsyncTask进行扩展。
参考:安卓官方培训课程,Multithreading for Performance