1、图片与内存
① 一张图片占用的内存 = 图片长度(单位像素) *图片宽度(单位像素) *单位像素占用的字节数,这里的单位像素占用的字节数则由BitmapFactory.Options的inPreferredConfig变量来决定的,变量inPreferredConfig是Bitmap.Config类型,取值如下:
ALPHA_8 | 只有透明没有RGB,一个像素占一个字节 |
ARGB_4444 | 透明、R、G、B个占4位,共16位即2个字节 |
ARGB_8888 | 透明、R、G、B个占4位,共32位即4个字节,Bitmap的默认格式 |
RGB_565 | 没有透明即不支持透明和半透明,R占5位,G占6位,B占5位,共16位即2个字节,通过Resources来取得一张图片时就是以该格式来创建Bitmap,Android4.0之后该选项无效,即便设置为该值系统依然用ARGB_8888来构造 |
② Dalvik内存:每一个Android应用在底层都会对应一个独立的Dalvik虚拟机实例;
③ 通过dumpsys meminfo命令可以查看一个进程的内存使用情况,当然也可以通过它来观察我们创建或销毁一张BitMap图片内存的变化,从而推断出图片占用内存的大小;
2、加载大图片
public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId,int reqWidth, int reqHeight) {// 先把inJustDecodeBounds设置为true 取得原始图片的属性
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(res, resId, options);
// 然后算一下我们想要的最终的属性
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
// 在decode的时候 别忘记直接 把这个属性改为false 否则decode出来的是null
options.inJustDecodeBounds = false;
return BitmapFactory.decodeResource(res, resId, options);
}
public static int calculateInSampleSize(
BitmapFactory.Options options, int reqWidth, int reqHeight) {
// 先从options 取原始图片的 宽高
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;
//一直对这个图片进行宽高 缩放,每次都是缩放1倍,然后这么叠加,当发现叠加以后 也就是缩放以后的宽或者高小于我们想要的宽高
//这个缩放就结束 跳出循环 然后就可以得到我们极限的inSampleSize值了。
while ((halfHeight / inSampleSize) > reqHeight
&& (halfWidth / inSampleSize) > reqWidth) {
inSampleSize *= 2;
}
}
return inSampleSize;
}
iv=(ImageView)this.findViewById(R.id.iv);
iv.setImageBitmap(decodeSampledBitmapFromResource(getResources(),R.drawable.gg,20,20));
3、正确加载Bitmap(这里以ListView为案例)
private final WeakReference bitmapWorkerTaskReference;
public AsyncDrawable(Resources res, Bitmap bitmap,
BitmapWorkerTask bitmapWorkerTask) {
super(res, bitmap);
bitmapWorkerTaskReference =
new WeakReference(bitmapWorkerTask);
}
public BitmapWorkerTask getBitmapWorkerTask() {
return bitmapWorkerTaskReference.get();
}
}
private final WeakReference<ImageView> imageViewReference;
private int data = 0;
public BitmapWorkerTask(ImageView imageView) {
// 用弱引用来关联这个imageview。大家一定要记住,弱引用是避免android 在各种callback回调里发生内存泄露的最佳方法!
//而软引用则是做缓存的最佳方法 两者不要搞混了!这里主要是ImageView关联到Activity,如果Activity被销毁了,那么ImageView
imageViewReference = new WeakReference<ImageView>(imageView);
}
// Decode image in background.
@Override
protected Bitmap doInBackground(Integer... params) {
data = params[0];
return decodeSampledBitmapFromResource(getResources(), data, 100, 100);
}
@Override
protected void onPostExecute(Bitmap bitmap) {
if (isCancelled()) {
bitmap = null;
}
if (imageViewReference != null && bitmap != null) {
final ImageView imageView = imageViewReference.get();
final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView);
if (this == bitmapWorkerTask && imageView != null) {
imageView.setImageBitmap(bitmap);
}
}
}
}
public void loadBitmap(int resId, ImageView imageView) {
//如果取得的task为空 就代表这个iv是新的iv 不是从listview回收站里取的 就可以新建一个task 然后
//用这个task 去新建一个drawable。然后用这个新的imageview去set 这个drawable即可
if (cancelPotentialWork(resId, imageView)) {
final BitmapWorkerTask task = new BitmapWorkerTask(imageView);
final AsyncDrawable asyncDrawable = new AsyncDrawable(getResources(), mPlaceHolderBitmap, task);
imageView.setImageDrawable(asyncDrawable);
task.execute(resId);
}
}
//从imageview里取得他的drawable。然后从取得的drawable里取得他的task
//这里实际上就可以看出来imageview-drawable-task是1对1的关系了
private static BitmapWorkerTask getBitmapWorkerTask(ImageView imageView) {
if (imageView != null) {
final Drawable drawable = imageView.getDrawable();
//如果drawable是AsyncDrawable子类,说明里面有任务了,否则说明该imageView是第一次被创建
if (drawable instanceof AsyncDrawable) {
final AsyncDrawable asyncDrawable = (AsyncDrawable) drawable;
return asyncDrawable.getBitmapWorkerTask();
}
}
return null;
}
public static boolean cancelPotentialWork(int data, ImageView imageView) {
//获取imageView对应的任务
final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView);
//如果这个task 不为空 就代表这个iv已经有task了 那这种情况
//任务为空,分2种情况:要么压根就没有,要么执行完毕然后通过WeakReference释放了
if (bitmapWorkerTask != null) {
final int bitmapData = bitmapWorkerTask.data;//这里的data,对应的是其在Adapter中排序序号
// 如果这个task还没有跑完 那就直接cancel这个task。因为没有跑完就肯定是iv 还没有设定值,所以直接cancel
//cancel以后就跳出这个括号 直接返回true了,等同于这个iv是一个新的iv 可以重新绑定新的task
if (bitmapData == 0 || bitmapData != data) {
// Cancel previous task
bitmapWorkerTask.cancel(true);
} else {
// 如果Task已经跑完了 那就别绑定了,否则会错乱的。所以返回false把 这里返回false loadBitmap就什么都不做的。图形就从根本上
//不会错乱了。
return false;
}
}
// task为空的话就返回true了。
return true;
}
4、图片缓存
5、管理内存
我们那会释放bitmap内存的时候 都是调用recyle这个方法的。
经查看,此时占用的内存大概是11mb,然后注释掉options.inBitmap = inBitmap2;发现占用内存暴增到18mb。
6、inBitmap注意事项
简单来说 就是4.4 以前 你要使用这个属性 那图片大小必须一样,但是4.4 以后只要decode的图片 比inBitmap的图片要小 就可以使用这个属性了。
但是这个属性在使用的时候一定要当心:不同的imageview 使用的scaletype 不同,但是你这些不同的imageview的bitmap 在decode时候 如果都是引用的同一个inBitmap的话,这些图片会相互影响,所以大家一定要注意,使用inBitmap这个属性的时候 一定要小心小心再小心。