韩女团aoa劲薄透,安陆市德安中学,官风宝气
在上一篇学习安卓开发[4] - 使用隐式intent启动短信、联系人、相机应用中了解了在调用其它应用的功能时隐式intent的使用,本次基于一个图片浏览app的开发,记录使用asynctask在后台执行http任务以获取图片url,然后使用handlerthread动态下载和显示图片
这里使用java.net.httpurlconnection来执行http请求,get请求的基本用法如下,默认执行的就是get,所以可以省略connection.setrequestmethod("get"),connection.getinputstream()取得inputstream后,再循环执行read()方法将数据从流中取出、写入bytearrayoutputstream中,然后通过bytearrayoutputstream.tobytearray返回为byte数组格式,最后转换为string。网上还有一种方法是使用bufferedreader.readline()来逐行读取输入缓冲区的数据并写入stringbuilder。对于post方法,可以使用getoutputstream()来写入参数。
public byte[] geturlbytes(string urlspec) throws ioexception { url url = new url(urlspec); httpurlconnection connection = (httpurlconnection) url.openconnection(); try { bytearrayoutputstream out = new bytearrayoutputstream(); inputstream in = connection.getinputstream(); if (connection.getresponsecode() != httpurlconnection.http_ok) { throw new ioexception(connection.getresponsemessage() + "with" + urlspec); } int bytesread = 0; byte[] buffer = new byte[1024]; while ((bytesread = in.read(buffer)) > 0) { out.write(buffer, 0, bytesread); } out.close(); return out.tobytearray(); } finally { connection.disconnect(); } } public string geturlstring(string urlspec) throws ioexception { return new string(geturlbytes(urlspec)); }
url为百度的图片接口,返回json格式数据,所以将api返回的json字符串转换为jsonobject,然后遍历json数组,将其转换为指定的对象。
... string url = "http://image.baidu.com/channel/listjson?pn=0&rn=25&tag1=明星&ie=utf8"; string jsonstring = geturlstring(url); jsonobject jsonbody = new jsonobject(jsonstring); parseitems(items, jsonbody); ... private void parseitems(list<galleryitem> items, jsonobject jsonobject) throws ioexception, jsonexception { jsonarray photojsonarray = jsonobject.getjsonarray("data"); for (int i = 0; i < photojsonarray.length() - 1; i++) { jsonobject photojsonobject = photojsonarray.getjsonobject(i); if (!photojsonobject.has("id")) { continue; } galleryitem item = new galleryitem(); item.setid(photojsonobject.getstring("id")); item.setcaption(photojsonobject.getstring("desc")); item.seturl(photojsonobject.getstring("image_url")); items.add(item); } }
http相关的代码准备好了,但无法在fragment类中被直接调用。因为网络操作通常比较耗时,如果在主线程(ui线程)中直接操作,会导致界面无响应的现象发生。所以android系统禁止任何主线程的网络连接行为,否则会报newworkonmainthreadexception。
主线程不同于普通的线程,后者在完成预定的任务后便会终止,但主线程则处于无限循环的状态,以等待用户或系统的触发事件。
至于网络操作,正确的做法是创建一个后台线程,在这个线程中进行。asynctask提供了使用后台线程的简便方法。代码如下:
private class fetchitemstask extends asynctask<void, void, list<galleryitem>> { @override protected list<galleryitem> doinbackground(void... voids) { list<galleryitem> items = new flickrfetchr().fetchitems(); return items; } @override protected void onpostexecute(list<galleryitem> galleryitems) { mitems = galleryitems; setupadapter(); } }
重写了asynctask的doinbackground方法和onpostexecute方法,另外还有两个方法可重写,它们的作用分别是:
asynctask的三个泛型参数就是对应doinbackground(params...)、onprogressupdate(progress...)、onpostexecute(result)的,这里设置为
asynctask<void, void, list<galleryitem>>
所以线程完成后返回的结果类型为list
@override public void oncreate(@nullable bundle savedinstancestate) { ... new fetchitemstask().execute(); }
前面通过asynctask创建的后台线程获取到了所有图片的url信息,接下来需要下载这些图片并显示到recyclerview。但如果要在doinbackground中直接下载这些图片则是不合理的,这是因为:
public class thumbnaildownloader<t> extends handlerthread
这里t设置为之后thumbnaildownloader的使用者,即photoholder。
在fragment创建时启动线程:
@override public void oncreate(@nullable bundle savedinstancestate) { ... mthumbnaildownloader.start(); mthumbnaildownloader.getlooper(); ... }
在fragment销毁时终止线程:
@override public void ondestroy() { super.ondestroy(); mthumbnaildownloader.quit(); }
这一步是必要的,否则即使fragment已被销毁,线程也会一直运行下去。
先了解一下message和handler
给消息队列发送的就是message类的实例,message类用户需要定义这几个变量:
target, 处理消息的handler
target是一个handler类实例,创建的message会自动与一个handler关联,message待处理时,handler对象负责触发消息事件
handler是处理message的target,也是创建和发布message的接口。而looper拥有message对象的收件箱,所以handler总是引用着looper,在looper上发布或处理消息。handler与looper为多对一关系;looper拥有整个message队列,为一对多关系;多个message可引用同一个handler,为多对一关系。
public void queuethumbnail(t target, string url) { log.i(tag, "got a url: " + url); if (url == null) { mrequestmap.remove(target); } else { mrequestmap.put(target, url); mrequesthandler.obtainmessage(message_download, target) .sendtotarget(); } }
然后在recyclerview的adapter绑定holder的时候,调用queuethumbnail,将图片url发送给后台线程。
public class photoadapter extends recyclerview.adapter<photoholder> { ... @override public void onbindviewholder(photoholder holder, int position) { ... mthumbnaildownloader.queuethumbnail(holder, galleryitem.geturl()); }
但后台线程的消息队列存放的不是url,而是对应的holder,url存放在concurrentmap型的mrequestmap中,concurrentmap是一种线程安全的map结构。存放了holder对对应url的map关系,这样在消息队列中处理某个holder时,可以从mrequestmap拿到它的url。
private concurrentmap<t, string> mrequestmap
具体处理消息的动作通过重写handler.handlemessage方法实现。onlooperprepared在looper首次检查消息队列之前调用,所以在此可以实例化handler并重写handlemessage。下载图片的实现在handlerequest方法中,将请求api拿到的byte[]数据转换成bitmap。
public class thumbnaildownloader<t> extends handlerthread { ... @override protected void onlooperprepared() { mrequesthandler = new handler() { @override public void handlemessage(message msg) { if (msg.what == message_download) { t target = (t) msg.obj; log.i(tag, "get a request for url: " + mrequestmap.get(target)); handlerequest(target); } } }; } private void handlerequest(final t target) { try { final string url = mrequestmap.get(target); if (url == null) { return; } byte[] bitmapbytes = new flickrfetchr().geturlbytes(url); final bitmap bitmap = bitmapfactory.decodebytearray(bitmapbytes, 0, bitmapbytes.length); log.i(tag, "bitmap created"); mresponsehandler.post(new runnable() { @override public void run() { if(mrequestmap.get(target)!=url||mhasquit){ return; } mrequestmap.remove(target); mthumbnaildownloadlistener.onthumbnaildownload(target,bitmap); } }); } catch (ioexception ioe) { log.e(tag, "error downloading image", ioe); } }
下载得到的bitmap需要返回给ui线程的holder以显示到屏幕。如何做呢?ui线程也是一个拥有handler和looper的消息循环。所以要返回结果给ui线程,就可以反过来,从后台线程使用主线程的handler。
那么,后台线程首先需要持有ui线程的handler:
public class photogalleryfragment extends fragment { @override public void oncreate(@nullable bundle savedinstancestate) { ... handler responsehandler = new handler(); mthumbnaildownloader = new thumbnaildownloader<>(responsehandler); ... }
thumbnaildownloader的构造函数中接收ui线程的handler。图片下载完成后就要向ui线程发布message了,可以通过handler.post(runnable)进行,重写runable.run()方法,不让halder处理消息,而是在这里触发thumbnaildownloadlistener。
public class thumbnaildownloader<t> extends handlerthread { ... public interface thumbnaildownloadlistener<t>{ void onthumbnaildownload(t target, bitmap thumbnail); } public void setthumbnaildownloadlistener(thumbnaildownloadlistener<t> listener){ mthumbnaildownloadlistener=listener; } public thumbnaildownloader(handler responsehandler) { super(tag); mresponsehandler=responsehandler; } private void handlerequest(final t target) { ... mresponsehandler.post(new runnable() { @override public void run() { if(mrequestmap.get(target)!=url||mhasquit){ return; } mrequestmap.remove(target); mthumbnaildownloadlistener.onthumbnaildownload(target,bitmap); } }); ... } }
mthumbnaildownloadlistener被触发后,ui线程的注册方法就会将后台返回的图片绑定到其holder。
public class photogalleryfragment extends fragment { @override public void oncreate(@nullable bundle savedinstancestate) { ... mthumbnaildownloader.setthumbnaildownloadlistener( new thumbnaildownloader.thumbnaildownloadlistener<photoholder>() { @override public void onthumbnaildownload(photoholder target, bitmap thumbnail) { drawable drawable = new bitmapdrawable(getresources(), thumbnail); target.binddrawable(drawable); } } ); ... }
如此,后台任务的执行与返回就完成了。
如对本文有疑问,请在下面进行留言讨论,广大热心网友会与你互动!! 点击进行留言回复
Android apk 项目一键打包并上传到蒲公英的实现方法
Android 自定义LineLayout实现满屏任意拖动功能的示例代码
android 限制某个操作每天只能操作指定的次数(示例代码详解)
Android 集成 google 登录并获取性别等隐私信息的实现代码
网友评论