转载关于android高效显示图片的文章---From 移动微技
原本自己写了一份关于android显示图片的文章,但是觉得写的很多点不到位,刚好在网上看到那么一份觉得不错,就转载过来。我是直接看android-develper的官方手册。英文理解能力不是很好,很多不明白的点,这里都提到了。以下是连接地址:
Android高效显示图片详解(一)
http://blog.csdn.net/zhiying201039/article/details/8653786
Android高效显示图片详解(二)
http://blog.csdn.net/zhiying201039/article/details/8665598
Android高效显示图片详解(三)
http://blog.csdn.net/zhiying201039/article/details/8682419
----------------------------------分割线------------------------------------------------
Android高效显示图片详解(一)
说明:
本讲义分为三部分,较为详细的介绍了Android平台下图片显示,加载等操作的处理原则与办法,以供大家共同学习,转载请注明出处 “From 移动微技”。
前提与解释:
安卓平台作为一款移动端的应用操作平台,其内存容量是十分有限的,内存资源是十分珍贵的,是无法与传统的桌面平台相比的,因此,在安卓平台下同样的图片操作与处理都要十分谨慎,否则你的程序可以迅速地消耗可用内存的预算,最终由于OutOfMemory导致程序崩溃掉。以下有三个原因说明了我们为什么要谨慎:
(1)安卓平台下对应用可使用的系统资源都做出了限制,标准安卓系统下,一个应用程序可用的最大内存为16M,一些第三方ROM
可能会上调这一限制,但是作为应用来说一定要控制自己的内存用量,这并不是可以无限制使用的。
(2)一张高分辨图片的内容耗用量是惊人的,例如,Galaxy Nexus的摄像头在拍摄2592X1936像素(5百万像素)。如果位图使用
的是配置ARGB_8888
(默认的Android 2.3开始),那么此图像加载到内存占用约19MB的内存(2592 * 1936 * 4字节),直接就耗
尽了在某些设备上的每个应用程序的内存上限。
(3)安卓应用程序的一些控件经常需要几个位图一起加载。例如ListView,GridView,ViewPager等控件,并且在使用中还要快速
的滑动,要及时对图片进行更新与回收,更加增加了图片处理的难度。
解决办法:
一,如何去加载与显示大图:
其实,在安卓这样内存有限的平台上,是没有必要按照原始尺寸把一张大图完全加载进来的,只需要加载与我们显示控件相匹配的尺寸就行,多了只会浪费我们宝贵的内存。因此在加载图片时,我们按照我们需要显示的大小对原始图片再采样就OK了。同时我们也可以根据我们所能够使用的内存大小来对图片进行解码,按照我们能够承受的尺寸与分辨率来处理,保证图片所占用的内存在我们可支配的范围之内,也就避免了OOM的问题。
第一步:我们需要获取原始图片的相关尺寸,分辨率等数据
可以利用BitmapFactory的Options来达到这一目的,解码图片时可以先把inJustDecodeBounds的值设为true,这样并没有真正的去解码图片,不占用内存,但是我们却可以在这个过程中获取图片的宽,高以及类型,代码如下:
[html]view plaincopy
BitmapFactory.Optionsoptions=newBitmapFactory.Options();
options.inJustDecodeBounds=true;
BitmapFactory.decodeResource(getResources(),R.id.myimage,options);
intimageHeight=options.outHeight;
intimageWidth=options.outWidth;
StringimageType=options.outMimeType;
第二步:获取原始的图片尺寸后,根据目标计算缩放比例系数,代码如下:
[html]view plaincopy
publicstaticintcalculateInSampleSize(
BitmapFactory.Optionsoptions,intreqWidth,intreqHeight){
//Rawheightandwidthofimage
finalintheight=options.outHeight;
finalintwidth=options.outWidth;
intinSampleSize=1;
if(height>reqHeight||width>reqWidth){
//Calculateratiosofheightandwidthtorequestedheightandwidth
finalintheightRatio=Math.round((float)height/(float)reqHeight);
finalintwidthRatio=Math.round((float)width/(float)reqWidth);
//ChoosethesmallestratioasinSampleSizevalue,thiswillguarantee
//afinalimagewithbothdimensionslargerthanorequaltothe
//requestedheightandwidth.
inSampleSize=heightRatio<widthRatio?heightRatio:widthRatio;
}
returninSampleSize;
}
官方文档中说,inSampleSize这个属性最好是2的倍数,这样处理更快,效率更高。。。
第三步:开始对图片进行解码,代码如下:
[html]view plaincopy
publicstaticBitmapdecodeSampledBitmapFromResource(Resourcesres,intresId,
intreqWidth,intreqHeight){
//FirstdecodewithinJustDecodeBounds=truetocheckdimensions
finalBitmapFactory.Optionsoptions=newBitmapFactory.Options();
options.inJustDecodeBounds=true;
BitmapFactory.decodeResource(res,resId,options);
//CalculateinSampleSize
options.inSampleSize=calculateInSampleSize(options,reqWidth,reqHeight);
//DecodebitmapwithinSampleSizeset
options.inJustDecodeBounds=false;
returnBitmapFactory.decodeResource(res,resId,options);
}
注意,真正解码时需要把inJustDecodeBounds属性重置为false,这样就可以把一张十分巨大的图轻松显示在一个100x100的ImageView中了
[html]view plaincopy
mImageView.setImageBitmap(decodeSampledBitmapFromResource(getResources(),R.id.myimage,100,100));
当然,你也可以用来加载显示其他来源的图片,而不是例子中资源文件中的,下一讲我们研究ListView,GridView中的多图片并发显示问题。
Android高效显示图片详解(二)
上节课我们介绍了如何加载和显示大图,这节课我们就要把这个技巧与实际开发联系起来,在实际的开发过程中,最常见的场景就是用ListView,GridView等集合显示控件
来呈现图片,这节课,我们就要用这些控件来高效的显示图片。
实际的使用环境中,如果图片来源是SD卡或者网络,那那么加载图片的过程一定不要放在UI线程中,这样会严重的阻塞UI线程,出现ANR,程序就废了。因此我们首先要实现异步加载。
第一步:利用AsyncTask实现图片的异步加载
将decodeSampledBitmapFromResource方法放入Task的doInBackground中后台执行。不熟悉AsyncTask的同学可以学习AsyncTask的相关知识,这里不再过多介绍。
代码:
[html]view plaincopy
classBitmapWorkerTaskextendsAsyncTask<Integer,Void,Bitmap>{
privatefinalWeakReference<ImageView>imageViewReference;
privateintdata=0;
publicBitmapWorkerTask(ImageViewimageView){
//UseaWeakReferencetoensuretheImageViewcanbegarbagecollected
imageViewReference=newWeakReference<ImageView>(imageView);
}
//Decodeimageinbackground.
@Override
protectedBitmapdoInBackground(Integer...params){
data=params[0];
returndecodeSampledBitmapFromResource(getResources(),data,100,100));
}
//Oncecomplete,seeifImageViewisstillaroundandsetbitmap.
@Override
protectedvoidonPostExecute(Bitmapbitmap){
if(imageViewReference!=null&&bitmap!=null){
finalImageViewimageView=imageViewReference.get();
if(imageView!=null){
imageView.setImageBitmap(bitmap);
}
}
}
}
注意,这里对ImageView使用 WeakReference弱引用的目的是确保 AsyncTask不会妨碍系统对ImageView必要时候的垃圾回收。否则可能会出现内存泄露,同时,我们一定要在Task执行完毕后对ImageView的存在性进行判断,因为不能保证Task执行完毕后,ImageView还会存在。
下来我们按照下面的代码就可以使用这个Task了:
[html]view plaincopy
publicvoidloadBitmap(intresId,ImageViewimageView){
BitmapWorkerTasktask=newBitmapWorkerTask(imageView);
task.execute(resId);
}
第二步:处理并发情况
ListView与GridView这种多子视图的控件会出现两个问题,
第一,一个ListView会有众多的ChildView,为了更高效的利用内存,控件会自动回收掉被用户滑动过去,不在当前有显示的ChildView,如果每一个ChildView都开启一个Task去加载图片,这样就不能保证开启Task的ChildView在Task执行完毕后没有被回收掉(很有可能用户滑动到其他地方去了)。
第二,因为每张图片的处理时间是不同的,因此同样不能保证加载完成的次序与开始的次序一致。
下来我们开始着手解决这些问题,我们要让ImageView与Task形成一种绑定的关系。
我们先来创建一个特殊的Drawable,这个Drawable有两个功能,一个是与Task形成一种绑定的关系,另外也充当了ImageView的临时占位图像,该Drawable的代码如下:
[html]view plaincopy
staticclassAsyncDrawableextendsBitmapDrawable{
privatefinalWeakReference<BitmapWorkerTask>bitmapWorkerTaskReference;
publicAsyncDrawable(Resourcesres,Bitmapbitmap,
BitmapWorkerTaskbitmapWorkerTask){
super(res,bitmap);
bitmapWorkerTaskReference=
newWeakReference<BitmapWorkerTask>(bitmapWorkerTask);
}
publicBitmapWorkerTaskgetBitmapWorkerTask(){
returnbitmapWorkerTaskReference.get();
}
}
在该Drawable中通过弱引用能与对应的Task形成一种一一对应的捆绑关系。
我们可以这样使用它,在执行Task之前,先创建一个对应的Drawable,并把它当成将要呈现实际图片的ImageView占位图片,同时也与ImageView形成了绑定关系。
[html]view plaincopy
publicvoidloadBitmap(intresId,ImageViewimageView){
if(cancelPotentialWork(resId,imageView)){
finalBitmapWorkerTasktask=newBitmapWorkerTask(imageView);
finalAsyncDrawableasyncDrawable=
newAsyncDrawable(getResources(),mPlaceHolderBitmap,task);
imageView.setImageDrawable(asyncDrawable);
task.execute(resId);
}
}
当然,我们需要判断下ImageView之前是否已经绑定了,如果之前绑定过但与本次的图片不同,那我们就要按最新的需要从新绑定下,如果之前与现在的一致,则保持原状,不再从新绑定,代码中的cancelPotentialWork就是做这个工作的,其代码如下:
[html]view plaincopy
publicstaticbooleancancelPotentialWork(intdata,ImageViewimageView){
finalBitmapWorkerTaskbitmapWorkerTask=getBitmapWorkerTask(imageView);
if(bitmapWorkerTask!=null){
finalintbitmapData=bitmapWorkerTask.data;
if(bitmapData!=data){
//Cancelprevioustask
bitmapWorkerTask.cancel(true);
}else{
//Thesameworkisalreadyinprogress
returnfalse;
}
}
//NotaskassociatedwiththeImageView,oranexistingtaskwascancelled
returntrue;
}
[html]view plaincopy
privatestaticBitmapWorkerTaskgetBitmapWorkerTask(ImageViewimageView){
if(imageView!=null){
finalDrawabledrawable=imageView.getDrawable();
if(drawableinstanceofAsyncDrawable){
finalAsyncDrawableasyncDrawable=(AsyncDrawable)drawable;
returnasyncDrawable.getBitmapWorkerTask();
}
}
returnnull;
}
最后,我们在Task的onPostExecute函数中,把加载的图片更新到视图中去,在更新前我们需要检查下Task是否被取消,并且当前的Task是否是那个与ImageView关联的Task,一致则我们把图片更新到ImageView上去,代码如下:
[html]view plaincopy
classBitmapWorkerTaskextendsAsyncTask<Integer,Void,Bitmap>{
...
@Override
protectedvoidonPostExecute(Bitmapbitmap){
if(isCancelled()){
bitmap=null;
}
if(imageViewReference!=null&&bitmap!=null){
finalImageViewimageView=imageViewReference.get();
finalBitmapWorkerTaskbitmapWorkerTask=
getBitmapWorkerTask(imageView);
if(this==bitmapWorkerTask&&imageView!=null){
imageView.setImageBitmap(bitmap);
}
}
}
}
最后,实际的使用也相当简单,只需要在你的ListView适配器的getView函数中调用上面的loadBitmap函数就OK了~
下一节我们来说说缓存,加入缓存让这个机制更加强大。。
感谢收看! 多多好评,在此谢过!
Android高效显示图片详解(三)地址:http://blog.csdn.net/zhiying201039/article/details/8682419
Android高效显示图片详解(三)
用户在使用ListView或GridView时,控件会自动把用户滑过的已不在当前显示区域的ChildView回收掉,当然也会把该子视图上的bitmap回收掉以释放内存,因此,为了保证一个流畅,快速的操作体验,我们应当避免反复的对同一张图片进行加载,比如说用户在往下看图的过程中又向上滑回去看图,这时对于已经上面已经加载过的图片我们就没有必要让它再加载一遍了,应该能很快的把图片显示出来,这里我们要使用缓存来达到这一目的。
一,使用Memory Cache:
内存缓存速度快,同时为了更加适应实际应用的场景,我们使用LruCache来达到按使用频率缓存的目的,把最近使用的加入缓存,较长时间不用的则会剔除掉释放出空间。
缓存的代码如下:
[html]view plaincopy
privateLruCache<String,Bitmap>mMemoryCache;
@Override
protectedvoidonCreate(BundlesavedInstanceState){
...
//GetmaxavailableVMmemory,exceedingthisamountwillthrowan
//OutOfMemoryexception.StoredinkilobytesasLruCachetakesan
//intinitsconstructor.
finalintmaxMemory=(int)(Runtime.getRuntime().maxMemory()/1024);
//Use1/8thoftheavailablememoryforthismemorycache.
finalintcacheSize=maxMemory/8;
mMemoryCache=newLruCache<String,Bitmap>(cacheSize){
@Override
protectedintsizeOf(Stringkey,Bitmapbitmap){
//Thecachesizewillbemeasuredinkilobytesratherthan
//numberofitems.
returnbitmap.getByteCount()/1024;
}
};
...
}
publicvoidaddBitmapToMemoryCache(Stringkey,Bitmapbitmap){
if(getBitmapFromMemCache(key)==null){
mMemoryCache.put(key,bitmap);
}
}
publicBitmapgetBitmapFromMemCache(Stringkey){
returnmMemoryCache.get(key);
}
那么我们在loadBitmap的时候就可以先检查下缓存中保存的是否有该图片,有则直接取出使用,不再进行加载。
新的代码如下:
[html]view plaincopy
publicvoidloadBitmap(intresId,ImageViewimageView){
finalStringimageKey=String.valueOf(resId);
finalBitmapbitmap=getBitmapFromMemCache(imageKey);
if(bitmap!=null){
mImageView.setImageBitmap(bitmap);
}else{
mImageView.setImageResource(R.drawable.image_placeholder);
BitmapWorkerTasktask=newBitmapWorkerTask(mImageView);
task.execute(resId);
}
}
当然,我们也要在加载图片是及时的维护缓存,把刚使用到的图片add进缓存中去。
新的代码如下:
[html]view plaincopy
classBitmapWorkerTaskextendsAsyncTask<Integer,Void,Bitmap>{
...
//Decodeimageinbackground.
@Override
protectedBitmapdoInBackground(Integer...params){
finalBitmapbitmap=decodeSampledBitmapFromResource(
getResources(),params[0],100,100));
addBitmapToMemoryCache(String.valueOf(params[0]),bitmap);
returnbitmap;
}
...
}
在使用内存做缓存的基础上,我们还可以使用Disk控件做为缓存,构成一种二级缓存的结构,设想这种情况,如果App在使用的过程被突然来电打断,那么此时有可能就会引起系统内存的回收,当用户再次切换到App时,App就要进行次很明显的图片再次加载的过程。这个时候,我们就需要用到Disk了,因为足够持久。
下面是是原来的基础上增加使用Disk Cache 的例子:
[html]view plaincopy
privateDiskLruCachemDiskLruCache;
privatefinalObjectmDiskCacheLock=newObject();
privatebooleanmDiskCacheStarting=true;
privatestaticfinalintDISK_CACHE_SIZE=1024*1024*10;//10MB
privatestaticfinalStringDISK_CACHE_SUBDIR="thumbnails";
@Override
protectedvoidonCreate(BundlesavedInstanceState){
...
//Initializememorycache
...
//Initializediskcacheonbackgroundthread
FilecacheDir=getDiskCacheDir(this,DISK_CACHE_SUBDIR);
newInitDiskCacheTask().execute(cacheDir);
...
}
classInitDiskCacheTaskextendsAsyncTask<File,Void,Void>{
@Override
protectedVoiddoInBackground(File...params){
synchronized(mDiskCacheLock){
FilecacheDir=params[0];
mDiskLruCache=DiskLruCache.open(cacheDir,DISK_CACHE_SIZE);
mDiskCacheStarting=false;//Finishedinitialization
mDiskCacheLock.notifyAll();//Wakeanywaitingthreads
}
returnnull;
}
}
classBitmapWorkerTaskextendsAsyncTask<Integer,Void,Bitmap>{
...
//Decodeimageinbackground.
@Override
protectedBitmapdoInBackground(Integer...params){
finalStringimageKey=String.valueOf(params[0]);
//Checkdiskcacheinbackgroundthread
Bitmapbitmap=getBitmapFromDiskCache(imageKey);
if(bitmap==null){//Notfoundindiskcache
//Processasnormal
finalBitmapbitmap=decodeSampledBitmapFromResource(
getResources(),params[0],100,100));
}
//Addfinalbitmaptocaches
addBitmapToCache(imageKey,bitmap);
returnbitmap;
}
...
}
publicvoidaddBitmapToCache(Stringkey,Bitmapbitmap){
//Addtomemorycacheasbefore
if(getBitmapFromMemCache(key)==null){
mMemoryCache.put(key,bitmap);
}
//Alsoaddtodiskcache
synchronized(mDiskCacheLock){
if(mDiskLruCache!=null&&mDiskLruCache.get(key)==null){
mDiskLruCache.put(key,bitmap);
}
}
}
publicBitmapgetBitmapFromDiskCache(Stringkey){
synchronized(mDiskCacheLock){
//Waitwhilediskcacheisstartedfrombackgroundthread
while(mDiskCacheStarting){
try{
mDiskCacheLock.wait();
}catch(InterruptedExceptione){}
}
if(mDiskLruCache!=null){
returnmDiskLruCache.get(key);
}
}
returnnull;
}
//Createsauniquesubdirectoryofthedesignatedappcachedirectory.Triestouseexternal
//butifnotmounted,fallsbackoninternalstorage.
publicstaticFilegetDiskCacheDir(Contextcontext,StringuniqueName){
//Checkifmediaismountedorstorageisbuilt-in,ifso,tryanduseexternalcachedir
//otherwiseuseinternalcachedir
finalStringcachePath=
Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())||
!isExternalStorageRemovable()?getExternalCacheDir(context).getPath():
context.getCacheDir().getPath();
returnnewFile(cachePath+File.separator+uniqueName);
}
更多相关文章
- afinal的简单应用(一)
- 菜鸟学Android开发系列之:TextView属性补充
- mac上AndroidStudio自带的SDK manager 系统菜单显示问题
- Android简单、灵活、高效的图片裁剪框架 Android-ImageClipper
- Android(安卓)中LayoutInflater(布局加载器)之实战篇
- Android(安卓)Menu中android:showAsAction属性
- 三款Android炫酷Loading动画组件推荐
- Android(安卓)ListView异步加载图片乱序问题,原因分析及解决方案
- Android开发之实现图片自动滚动显示标签的ViewPager