高效显示图片(一,二)

来源:互联网 发布:linux安装软件命令yum 编辑:程序博客网 时间:2024/05/16 16:23
原文链接: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 = newBitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(getResources(), R.id.myimage, options);
intimageHeight = options.outHeight;
intimageWidth = 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
publicstatic int calculateInSampleSize(
            BitmapFactory.Options options, intreqWidth, intreqHeight) {
   // Raw height and width of image
   finalint height = options.outHeight;
   finalint width = options.outWidth;
   intinSampleSize = 1;
 
   if(height > reqHeight || width > reqWidth) {
 
        // Calculate ratios of height and width to requested height and width
        finalint heightRatio = Math.round((float) height / (float) reqHeight);
        finalint 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;
   }
 
   returninSampleSize;
}

?
代码片段,双击复制
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
publicstatic Bitmap decodeSampledBitmapFromResource(Resources res, intresId,
        intreqWidth, intreqHeight) {
 
   // First decode with inJustDecodeBounds=true to check dimensions
   finalBitmapFactory.Options options = newBitmapFactory.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;
   returnBitmapFactory.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
classBitmapWorkerTask extendsAsyncTask<Integer, Void, Bitmap> {
   privatefinal WeakReference<ImageView> imageViewReference;
   privateint data = 0;
 
   publicBitmapWorkerTask(ImageView imageView) {
        // Use a WeakReference to ensure the ImageView can be garbage collected
        imageViewReference = newWeakReference<ImageView>(imageView);
   }
 
   // Decode image in background.
   @Override
   protectedBitmap doInBackground(Integer... params) {
        data = params[0];
        returndecodeSampledBitmapFromResource(getResources(), data, 100,100));
   }
 
   // Once complete, see if ImageView is still around and set bitmap.
   @Override
   protectedvoid onPostExecute(Bitmap bitmap) {
        if(imageViewReference != null&& bitmap != null) {
            finalImageView imageView = imageViewReference.get();
            if(imageView != null) {
                imageView.setImageBitmap(bitmap);
            }
        }
   }
}


对于ImageView的WeakReference确保了AsyncTask不会阻止垃圾回收机制对ImageView进行
回收。当task完成时,不能保证ImageView仍然有效,因此必须在onPostExecute()方法中
检查引用。
?
代码片段,双击复制
01
02
03
04
publicvoid loadBitmap(intresId, ImageView imageView) {
   BitmapWorkerTask task = newBitmapWorkerTask(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
staticclass AsyncDrawable extendsBitmapDrawable {
   privatefinal WeakReference<BitmapWorkerTask> bitmapWorkerTaskReference;
 
   publicAsyncDrawable(Resources res, Bitmap bitmap,
            BitmapWorkerTask bitmapWorkerTask) {
        super(res, bitmap);
        bitmapWorkerTaskReference =
            newWeakReference<BitmapWorkerTask>(bitmapWorkerTask);
   }
 
   publicBitmapWorkerTask getBitmapWorkerTask() {
        returnbitmapWorkerTaskReference.get();
   }
}

在执行BitmapWorkerTask之前,创建一个AsyncDrawable并将它绑定到目标ImageView:
?
代码片段,双击复制
01
02
03
04
05
06
07
08
09
publicvoid loadBitmap(intresId, ImageView imageView) {
   if(cancelPotentialWork(resId, imageView)) {
        finalBitmapWorkerTask task = newBitmapWorkerTask(imageView);
        finalAsyncDrawable asyncDrawable =
                newAsyncDrawable(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
publicstatic boolean cancelPotentialWork(intdata, ImageView imageView) {
   finalBitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView);
 
   if(bitmapWorkerTask != null) {
        finalint bitmapData = bitmapWorkerTask.data;
        if(bitmapData != data) {
            // Cancel previous task
            bitmapWorkerTask.cancel(true);
        }else{
            // The same work is already in progress
            returnfalse;
        }
   }
   // No task associated with the ImageView, or an existing task was cancelled
   returntrue;
}

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

最后一步就是onPostExecute(),检查task是否被取消,和当前的任务是否与ImageView关
联的task相匹配。
?
代码片段,双击复制
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
classBitmapWorkerTask extendsAsyncTask<Integer, Void, Bitmap> {
   ...
 
   @Override
   protectedvoid onPostExecute(Bitmap bitmap) {
        if(isCancelled()) {
            bitmap = null;
        }
 
        if(imageViewReference != null&& bitmap != null) {
            finalImageView imageView = imageViewReference.get();
            finalBitmapWorkerTask bitmapWorkerTask =
                    getBitmapWorkerTask(imageView);
            if(this== bitmapWorkerTask && imageView != null) {
                imageView.setImageBitmap(bitmap);
            }
        }
   }
}

原创粉丝点击