Android照片墙应用技巧
android照片墙应用技巧。
照片墙应用介绍
应用中需要显示大量的图片,图片从网络获取。
应用有两个问题:
流畅地显示
省流量
流畅性是所有应用都必须要有的,图片要从网上下载,下载过程必须放到工作线程当中来解决。另外图片也有可以有一部分保存在内存当中,直接从内存当中获取并显示,这才是最快的方式。
图片有很多,用户理论上只用下载一遍图片就行了,不能重复去下载,缓存可以帮助用户节省流量。
缓存使用思路
android 缓存原理一文中说明了缓存的原理以及使用方式,如有不明白可参考此文。
其实缓存思路特别简单,在需要使用图片的时候,先查内存缓存,如果没有再查硬盘缓存,还没有则去网络下载,下载完毕后加入硬盘缓存以及内存缓存,以备下次再用。缓存是不是已满,该如何删除交给缓存工具类决定,但大体思路一定是这样。
本文中也是这么做的,而且硬盘缓存读取相当于io过程,较缓慢,也可以放到工作线程当中,于是就可以把查内存缓存以外的所有操作都放到工作线程当中完成。
照片墙的核心代码如下:
privatevoidloadbitmaps(imageviewimageview,stringurl){
try{
bitmapbitmap=getbitmapfrommemorycache(url);
if(bitmap==null){
tasktask=newtask(imageview,url);
mpools.submit(task);
}else{
if(imageview!=null&&bitmap!=null){
imageview.setimagebitmap(bitmap);
}
}
}catch(exceptione){
//todo:handleexception
}
}
如果从内存缓存中无法获取到图片,则向线程池中提交一个任务,在线程池中完成图片的获取。
classtaskimplementsrunnable{
imageviewiv;
stringimageurl;
task(imageviewview,stringurl){
iv=view;
imageurl=url;
}
@override
publicvoidrun(){
filedescriptorfiledescriptor=null;
fileinputstreamfileinputstream=null;
snapshotsnapshot=null;
try{
finalstringkey=hashkeyfordisk(imageurl);
snapshot=mdisklrucache.get(key);
if(snapshot==null){
editoreditor=mdisklrucache.edit(key);
if(editor!=null){
outputstreamoutputstream=editor.newoutputstream(0);
if(downloadurltostream(imageurl,outputstream)){
editor.commit();
}else{
editor.abort();
}
}
snapshot=mdisklrucache.get(key);
}
if(snapshot!=null){
fileinputstream=(fileinputstream)snapshot
.getinputstream(0);
filedescriptor=fileinputstream.getfd();
}
bitmapbitmap=null;
if(filedescriptor!=null){
bitmap=bitmapfactory.decodefiledescriptor(filedescriptor);
}
if(bitmap!=null){
addbitmaptomemorycache(key,bitmap);
resultresult=newresult(iv,bitmap,imageurl);
messagemsg=message.obtain(mhandler,msg_show_bitmap,result);
msg.sendtotarget();
}
}catch(exceptione){
log.e("okunu","run",e);
}
}
}
和前文所说思路一样,先从硬盘缓存中读取,如果没有再从网络中下载图片。一定不能忘记将图片添加进入硬盘缓存和内存缓存中来,这一步非常重要。
由于硬盘缓存的使用方法,在从网上下载图片的时候,是直接下载到缓存文件当中的,而不是先下载再复制一份到硬盘缓存当中,因为这样可以节省一次io过程。
另外还有一些小细节,比如最后如何刷新界面,首先在工作线程中是无法刷新ui的,所以在此处用handler,将结果返回主线程中处理。
if(msg.what==msg_show_bitmap){
objectobj=msg.obj;
if(obj!=null){
resultresult=(result)obj;
if(result.iv.gettag().equals(result.url)){
result.iv.setimagebitmap(result.bitmap);
}
}
}
结果中包含图片、imageview以及url,为了防止图片显示错乱,还加以判定,只有imageview的tag等于此url的时候,才更新图片,如此则不会更新错乱。
多线程使用
在这种有大量耗时操作的时候,开启工作线程是非常必要的,但如何优雅地使用线程,其实仍然有门道。
android中有很多种开启工作线程的方式。
asynctask,封装地非常好,不同的回调函数还处于不同的线程当中,方便用户拿结果更新ui,但如果有多个asynctask实例在执行,它们是顺序执行,并不是想象中的多线程在多核cpu上同时执行。
thread,原始的线程使用方式,如果构造太多,不优雅,线程不能得到复用,浪费资源,开启一个线程也是有开销的
handlerthread,能够与handler结合,一种非常优雅的工作线程开启方式,不过不太适合大量任务的情况,这种只相当于线程池中只有一个线程在跑
线程池,重量级武器,适合大量任务的情况
android中开启工作线程包括但不限于以上4种,它们的优劣大致如上所述,需要我们根据不同的情形选用不同的方式,写出优雅的代码。
在照片墙应用中,明显是有大量任务需要工作线程来执行的,以上四种情况中,最适合的就是线程池了,它的速度效率是最高的,有兴趣的同学可以去做做实验,以四种不同方式来完成任务,看看哪个效率最高
杂谈
在文章一开始的时候,就聊到一个话题,oom的问题,如果图片太大,如果防止oom呢
这个问题相信很多人都知道答案:
/**
*ifsettotrue,thedecoderwillreturnnull(nobitmap),but
*theout...fieldswillstillbeset,allowingthecallerto
*querythebitmapwithouthavingtoallocatethememoryforitspixels.
*/
publicbooleaninjustdecodebounds;
/**
*ifsettoavalue>1,requeststhedecodertosubsampletheoriginal
*image,returningasmallerimagetosavememory.thesamplesizeis
*thenumberofpixelsineitherdimensionthatcorrespondtoasingle
*pixelinthedecodedbitmap.forexample,insamplesize==4returns
*animagethatis1/4thewidth/heightoftheoriginal,and1/16the
*numberofpixels.anyvalue<=1istreatedthesameas1.note:the
*decoderusesafinalvaluebasedonpowersof2,anyothervaluewill
*beroundeddowntothenearestpowerof2.
*/
publicintinsamplesize;
利用injustdecodebounds,计算出insamplesize,相信这样的逻辑网上一找一大堆,本人在此不再复述。如果你的图片应用,图片都是大于5m以上的高清大图,那么一定要考虑这个方法了。
另外由url转化成hash key的时候,怎么这么费劲呢
publicstringhashkeyfordisk(stringkey){
stringcachekey;
try{
finalmessagedigestmdigest=messagedigest.getinstance("md5");
mdigest.update(key.getbytes());
cachekey=bytestohexstring(mdigest.digest());
log.i("okunu","cachekey="+cachekey);
}catch(nosuchalgorithmexceptione){
cachekey=string.valueof(key.hashcode());
}
returncachekey;
}
privatestringbytestohexstring(byte[]bytes){
stringbuildersb=newstringbuilder();
for(inti=0;istringhex=integer.tohexstring(0xff&bytes[i]);
if(hex.length()==1){
sb.append('0');
}
sb.append(hex);
}
returnsb.tostring();
}
第一步,我们通常能理解,获取url的md5值,因为url中可能含有各种奇异字符,不适合作为key来使用,但bytestohexstring方法的作用是什么
大家可以仔细地看看硬盘缓存中文件的名字的长度,就是为了防止名字长度不一致,当长度短一位的时候,补0。
代码已经上传到github当中,有需要的可以取用
https://github.com/okunu
照片处理
android 选择图片上传功能【支持多选拍照预览等】