高效显示图片(一,二)

原文链接:http://developer.android.com/training/displaying-bitmaps/load-bitmap.html

有效的加载大图

图像存在各种形状和大小。在很多情况下,它们往往比用户界面所需要的图像要大。例如,系
统的Gallery程序显示由Android系统的摄像头拍摄的照片,它们的分辨率往往高于设备的分辨率。

既然应用程序工作在有限的内存下,理想情况下你只想在内存加载低分辨率的图片。低分辨率
的图片大小应该匹配显示它的UI控件大小。一个高分辨率的图片不会提供任何可见的利益,而且
占据了宝贵的内存,还可能由于额外的缩放导致额外的性能开销。

读取位图的尺寸和类型

BitmapFactory类提供了很多解码的方法(decodeByteArray(),decodeFile(),decodeResource())
从不同的来源创建一个Bitmap。基于你的图片数据来源选择最合适的解码方法。这些方法试
图为构建这些图片分配内存,因此很容易就会导致OutOfMemory的exception。每种类型的解
码方法可以通过BitmapFactory.Options类指定解码选项。将inJustDecodeBounds属性设置为
true,可以再解码的时候避免内存的分配。对bitmap对象返回null,但设置了outWidth,outHeight,
和outMimeType。这个技术使得你在构建bitmap之前,读取图片的尺寸和类型。
?
代码片段,双击复制
01
02
03
04
05
06
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;


将缩小的版本加载到内存

既然已经知道了图像的尺寸,他们可以被用来确定完整的图像是否被加载到内存,或者应该
加载一个缩小的版本。下面是一些需要考虑的因素:
1.估计完整的图像加载到内存中内存的使用情况。
2.总计你确认加载这个图片,留给应用程序中其他内存需求的内存大小。
3.图像将要显示的UI控件的大小。
4.当前设备的屏幕大小和分辨率。

例如,要把一个1024*768的图片显示到一个128*96的ImageView中是不划算的。告诉解码器
对图片进行缩放,加载一个缩小的图片到内存中,把BitmapFactory.Options的inSampleSize属
性设置为true。例如,使用inSampleSize为4,将一个2048*1536的图片进行解码,将得到一个
512*384的图片。将这个图片加载到内存中只占用0.75M的内存,而不是完整图片的12M。
?
代码片段,双击复制
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
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) {
 
         // Calculate ratios of height and width to requested height and width
         final int heightRatio = Math.round(( float ) height / ( float ) reqHeight);
         final int widthRatio = Math.round(( float ) width / ( float ) reqWidth);
 
         // Choose the smallest ratio as inSampleSize value, this will guarantee
         // a final image with both dimensions larger than or equal to the
         // requested height and width.
         inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;
    }
 
    return inSampleSize;
}

?
代码片段,双击复制
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
public static Bitmap decodeSampledBitmapFromResource(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);
}

?
代码片段,双击复制
01
02
mImageView.setImageBitmap(
    decodeSampledBitmapFromResource(getResources(), R.id.myimage, 100 , 100 ));


不要再UI线程中处理图片

使用AsyncTask

AsyncTask类提供了一个简单的方式在后台线程中执行一些工作,并将结果返回到UI线程中。
要使用它,创建一个子类,并重写提供的方法。
?
代码片段,双击复制
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {
    private final WeakReference<ImageView> imageViewReference;
    private int data = 0 ;
 
    public BitmapWorkerTask(ImageView imageView) {
         // Use a WeakReference to ensure the ImageView can be garbage collected
         imageViewReference = new WeakReference<ImageView>(imageView);
    }
 
    // Decode image in background.
    @Override
    protected Bitmap doInBackground(Integer... params) {
         data = params[ 0 ];
         return decodeSampledBitmapFromResource(getResources(), data, 100 , 100 ));
    }
 
    // Once complete, see if ImageView is still around and set bitmap.
    @Override
    protected void onPostExecute(Bitmap bitmap) {
         if (imageViewReference != null && bitmap != null ) {
             final ImageView imageView = imageViewReference.get();
             if (imageView != null ) {
                imageView.setImageBitmap(bitmap);
             }
         }
    }
}


对于ImageView的WeakReference确保了AsyncTask不会阻止垃圾回收机制对ImageView进行
回收。当task完成时,不能保证ImageView仍然有效,因此必须在onPostExecute()方法中
检查引用。
?
代码片段,双击复制
01
02
03
04
public void loadBitmap( int resId, ImageView imageView) {
    BitmapWorkerTask task = new BitmapWorkerTask(imageView);
    task.execute(resId);
}


处理并发

普遍的View组件,例如ListView和GridView,在与AsyncTask联合使用时引入了一个新的问题。
为了提供内存的使用效率,当用户滑动的时候这些组件都会回收子视图。如果每一个子视图
都触发一个AsyncTask,不能保证哪一个完成,与其相关联的子视图并没有被回收用于另一
个子视图。此外,异步的任务开始的顺序是不能保证他们完成的顺序。

有文章引出使用多线程来处理并发问题,并提供了一个解决方案,在ImageView存储一个最近
的AsyncTask的引用时,当task完成时可以进行检查。使用相同的方法,可以扩展AsyncTask
达到该目的。

创建一个专用的Drawable类的子类存储一个工作线程的引用。
?
代码片段,双击复制
01
02
03
04
05
06
07
08
09
10
11
12
13
14
static class AsyncDrawable extends BitmapDrawable {
    private final WeakReference<BitmapWorkerTask> bitmapWorkerTaskReference;
 
    public AsyncDrawable(Resources res, Bitmap bitmap,
             BitmapWorkerTask bitmapWorkerTask) {
         super (res, bitmap);
         bitmapWorkerTaskReference =
             new WeakReference<BitmapWorkerTask>(bitmapWorkerTask);
    }
 
    public BitmapWorkerTask getBitmapWorkerTask() {
         return bitmapWorkerTaskReference.get();
    }
}

在执行BitmapWorkerTask之前,创建一个AsyncDrawable并将它绑定到目标ImageView:
?
代码片段,双击复制
01
02
03
04
05
06
07
08
09
public void loadBitmap( int resId, ImageView imageView) {
    if (cancelPotentialWork(resId, imageView)) {
         final BitmapWorkerTask task = new BitmapWorkerTask(imageView);
         final AsyncDrawable asyncDrawable =
                new AsyncDrawable(getResources(), mPlaceHolderBitmap, task);
         imageView.setImageDrawable(asyncDrawable);
         task.execute(resId);
    }
}

cancelPotentialWork方法会检查是否有另一个task已经与ImageView关联。如果有,它将会试
图去通过调用cancel()方法来取消前一个task。在一些情况下,新task的数据与已有task的
数据相同,因此再不需要做什么。
?
代码片段,双击复制
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
public static boolean cancelPotentialWork( int data, ImageView imageView) {
    final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView);
 
    if (bitmapWorkerTask != null ) {
         final int bitmapData = bitmapWorkerTask.data;
         if (bitmapData != data) {
             // Cancel previous task
             bitmapWorkerTask.cancel( true );
         } else {
             // The same work is already in progress
             return false ;
         }
    }
    // No task associated with the ImageView, or an existing task was cancelled
    return true ;
}

getBitmapWorkerTask()是一个辅助方法,被用来检索与一个特定ImageView关联的task。
?
代码片段,双击复制
01
02
03
04
05
06
07
08
09
10
private static BitmapWorkerTask getBitmapWorkerTask(ImageView imageView) {
    if (imageView != null ) {
       final Drawable drawable = imageView.getDrawable();
       if (drawable instanceof AsyncDrawable) {
            final AsyncDrawable asyncDrawable = (AsyncDrawable) drawable;
            return asyncDrawable.getBitmapWorkerTask();
       }
    }
    return null ;
}

最后一步就是onPostExecute(),检查task是否被取消,和当前的任务是否与ImageView关
联的task相匹配。
?
代码片段,双击复制
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {
    ...
 
    @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);
             }
         }
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值