有时候你不注意位图可以快速地消耗你的可用内存的预算导致应用程序崩溃由于可怕的例外。这就是我们长看到的
java.lang.OutofMemoryError: bitmap size exceeds VM budget.因为android 给application系统资源只能分配16M的内
存。
下面我根据sdk的Displaying Bitmaps Efficiently文章来说下如何让位图变得更有效率。下面看下sdk如何告诉我们解决这个问题的:
Read Bitmap Dimensions and Type
The BitmapFactory
class provides several decoding methods (decodeByteArray()
, decodeFile()
,decodeResource()
, etc.) for creating a Bitmap
from various sources. Choose the most appropriate decode method based on your image data source. These methods attempt to allocate memory for the constructed bitmap and therefore can easily result in an OutOfMemory
exception. Each type of decode method has additional signatures that let you specify decoding options via the BitmapFactory.Options
class. Setting the inJustDecodeBounds
property to true
while decoding avoids memory allocation, returning null
for the bitmap object but setting outWidth
, outHeight
and outMimeType
. This technique allows you to read the dimensions and type of the image data prior to construction (and memory allocation) of the bitmap.
通过设置它为true ,在我们解析的时候避免分配内存这样子我们就得到了一个null的bitmap对象,就不需要损耗内存
并且得到这个位图的宽高。这就为我们接下来设置options.inSampleSize的值做铺垫。那这个inSampleSize到底是有
什么用呢?接下来我们看下sdk的介绍:
public int inSampleSize
Added in API level 1If set to a value > 1, requests the decoder to subsample the original image, returning a smaller image to save memory. The sample size is the number of pixels in either dimension that correspond to a single pixel in the decoded bitmap. For example, inSampleSize == 4 returns an image that is 1/4 the width/height of the original, and 1/16 the number of pixels. Any value <= 1 is treated the same as 1. Note: the decoder uses a final value based on powers of 2, any other value will be rounded down to the nearest power of 2.
看了这里你应该明白,其实它就是一个位图的缩放比率。说了一些基础的知识,下面就让我们用实际的例子来深入
看看下我们的类,很简单就两个类。一个是BietMap的工具类负责解析BItmap,另一个就是我们的主Activity咯!
首先我们看下这个工具类吧!里面我添加了丰富的注释,具体的我就不一一介绍了相信机智的你一定能看懂ovo
public class DecodeBitmapTool {
/**
* 得到位图的缩放的比率 也就是options.sampleSize的值
*
* @param options
* @param reqWidth 我们设定的宽
* @param reqHeight 我们设定的高
* @return
*/
public static int calculateSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
/**
* 默认大小与原图一致
*/
int sampleSize = 1;
/**
* 原图片的宽高
*/
int width = options.outWidth;
int height = options.outHeight;
if (width > reqWidth || height > reqHeight) {
int widthRatio = Math.round(width / reqWidth);
int heightRatio = Math.round(height / reqHeight);
sampleSize = widthRatio < heightRatio ? widthRatio : heightRatio;
}
return sampleSize;
}
/**
* 通过两个解码得到压缩过的bitmap
* @param res
* @param resId
* @param reqWidth
* @param reqHeight
* @return
*/
public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId,
int reqWidth, int reqHeight) {
BitmapFactory.Options options = new BitmapFactory.Options();
/**
* 设置true避免分配内存
*/
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(res, resId, options);
/**
* 这就是压缩的重点得到压缩比率,其实嫌麻烦也可以直接给他赋值
*/
options.inSampleSize = calculateSampleSize(options, reqWidth, reqHeight);
/**
*设置false 开始分配内存得到我们需要的Bitmap对象
*/
options.inJustDecodeBounds = false;
return BitmapFactory.decodeResource(res, resId, options);
}
}
当当当当最后粉墨登场就是我们的mianActivity了,其实里面非常的简单哦!
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ImageView imageView = (ImageView) findViewById(R.id.img_view);
DecodeBitmapTool decodeBitmapTool = new DecodeBitmapTool();
Bitmap bitmap = decodeBitmapTool.decodeSampledBitmapFromResource(getResources(),R.drawable.img_1,400,400);
imageView.setImageBitmap(bitmap);
}
}
你可以自己放一张比较大的图片,然后通过设置他的宽和高得到压缩过的图片。差点忘了,布局其实就是一个imageView代码就不放上去了。
下面我们就说下如果源数据从磁盘读取或网络位置(或真正内存以外的任何来源)。加载数据需要的时间是不可预
测的,取决于多种因素(阅读从磁盘或网络,速度大小的图像,CPU,等等)。如果其中一个任务阻塞UI线程,系统标志应用程
序没有响应,用户选择关闭它。这里我们就通过在后台线程处理位图使用AsyncTask,向您展示如何处理并发问题
这里代码我就只有一个MainActivity还有就是上面的那个压缩位图的工具类 布局和上面的一样也只有一个ImageView
public class MainActivity extends Activity {这里我重点说下WeakReference这个类,和onPostExecute方法里面的判断处理。这里我们先看下sdk里面的解释:
private ImageView mImageView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mImageView = (ImageView) findViewById(R.id.image);
loadBitmap(R.drawable.ic_launcher, mImageView);
}
/**
* 加载图片
*
* @param resId
* @param imageView
*/
public void loadBitmap(int resId, ImageView imageView) {
DecodeBitmapUseAsyncTask decodeBitmapUseAsyncTask = new DecodeBitmapUseAsyncTask(imageView);
decodeBitmapUseAsyncTask.execute(resId);
}
/**
* 异步加载图片
*/
public class DecodeBitmapUseAsyncTask extends AsyncTask<Integer, Void, Bitmap> {
private final WeakReference<ImageView> weakReference;
public DecodeBitmapUseAsyncTask(ImageView imageView) {
/**
* 用这个类来监听ImageView 判断imageView没有被异步任务阻拦
*/
weakReference = new WeakReference<ImageView>(imageView);
}
@Override
protected Bitmap doInBackground(Integer... params) {
int a = params[0];
return new DecodeBitmapTool().decodeSampledBitmapFromResource(getResources(), a, 100, 100);
}
@Override
protected void onPostExecute(Bitmap bitmap) {
super.onPostExecute(bitmap);
if (weakReference != null && bitmap != null) {
ImageView imageView = weakReference.get();
if (imageView != null) {
imageView.setImageBitmap(bitmap);
}
}
}
}
}
The WeakReference
to the ImageView
ensures that the AsyncTask
does not prevent the ImageView
and anything it references from being garbage collected. There’s no guarantee the ImageView
is still around when the task finishes, so you must also check the reference in onPostExecute()
. TheImageView
may no longer exist, if for example, the user navigates away from the activity or if a configuration change happens before the task finishes.
这个的意思就ImageView确保AsyncTask的WeakReference并不阻止ImageView和任何它引用被垃圾收集。没有保证
ImageView仍在当任务完成,所以你也必须检查参考onPostExecute()。ImageView可能不再存在,例如,如果用户导航离
开活动或任务完成之前如果配置发生改变。