- 浏览: 406123 次
- 性别:
- 来自: 福州
最新评论
-
野牛精:
感谢楼主分享,帮大忙了。
android ftp 客户端编写(ftp4j) -
happytianqiu:
你好,我最近也在搞这个,能发个demo吗,邮箱是:624951 ...
开发android机顶盒应用 事件,焦点处理 -
zhunanfengfeimeng:
http://www.iteye.comhttp://www. ...
android ftp 客户端编写(ftp4j) -
icyttea:
好棒!感谢楼主分享
vlc for android录制视频与截图 -
clwwlc:
有demo吗
开发android机顶盒应用 事件,焦点处理
一个网络程序下载图片通常是一个大麻烦,如何处理好下载,才是关键的问题,这关系到程序的性能,甚至崩溃,出现oome.
如果你还在使用ui线程下载图片,赶紧看看如何在另一个线程下载图片的相关文章吧,ui线程要做的事只是显示.
看上去使用AsyncTask是个好办法,方便操作,一般不会有非ui线程处理ui的问题.虽然它有线程池的概念,但是我也发现,还是会发起上千次甚至w次的线程请求,在一个ListView滚动过程中,然而需要下载的图片却只有不到二十张,这显然是内存的浪费了.出现oome也是必然的,一方面可能是图片本身占用内存较多,一方面是线程占用的内存资源.
所以在AsyncTask里做好缓存检测是很有必要的,检测到已经下载过的文件 就不需要下载.但是这样的问题在于,doInBackground里面检查的话,已经是新建了一个线程了.虽然这样可以减少下载量,但是新建一个线程也是要消耗资源的.
现在介绍一种自定义线程池的办法来处理下载的问题.
图片下载一直是比较麻烦的问题,如何缓存,如何处理图片都需要小心操作,因为图片的可用内存有限,太多时oome容易出现.
我的微博程序用上面的线程池处理后普通下载,gif图片另外处理,即使同时下载与解析gif动态图2m左右,帧数大约150的也没有出现oome.
线程池下载图片只是通用的一个方法.如果你需要下载一张大图,而下载不是同时进行的,建议还是单独写一个下载的方法,容易控制.因为下载8m或更大的图片也是个问题,还有下载后的存储,不宜用这种方法.
更多时候不是抱怨系统为何只提供这么少的内存供图片使用,先反省下自己的处理方式.
希望可以帮助别更多的人.
2011.12.11.
如果你还在使用ui线程下载图片,赶紧看看如何在另一个线程下载图片的相关文章吧,ui线程要做的事只是显示.
看上去使用AsyncTask是个好办法,方便操作,一般不会有非ui线程处理ui的问题.虽然它有线程池的概念,但是我也发现,还是会发起上千次甚至w次的线程请求,在一个ListView滚动过程中,然而需要下载的图片却只有不到二十张,这显然是内存的浪费了.出现oome也是必然的,一方面可能是图片本身占用内存较多,一方面是线程占用的内存资源.
所以在AsyncTask里做好缓存检测是很有必要的,检测到已经下载过的文件 就不需要下载.但是这样的问题在于,doInBackground里面检查的话,已经是新建了一个线程了.虽然这样可以减少下载量,但是新建一个线程也是要消耗资源的.
现在介绍一种自定义线程池的办法来处理下载的问题.
/** * 图片下载线程池,暂时可允许最多三个线程同时下载。 * * @author archko */ public class DownloadPool extends Thread{ public static final int MAX_THREAD_COUNT=3;定义最大同时下载线程 private int mActiveThread=0;当前活动的线程数 private App mApp;继承了Application,在manifest文件里配置的. private List<DownloadPiece> mQuery;下载队列. } 先看看App里如何处理的:public DownloadPool mDownloadPool = null; onCreate()方法里对线程池初始化. if (this.mDownloadPool != null) { return; } Log.d(TAG, "initDownloadPool."); DownloadPool downloadPool = new DownloadPool(this); this.mDownloadPool = downloadPool; this.mDownloadPool.setPriority(Thread.MIN_PRIORITY); this.mDownloadPool.setName("DownloadPool"); this.mDownloadPool.start(); 需要注意的是mDownloadPool是在程序启动后一直在运行的,然后就是它的构造方法了: public DownloadPool(App app) { this.mQuery=new ArrayList<DownloadPiece>(); this.mApp=app; } 关于DownloadPiece内容:一个内容类: public class DownloadPiece { Handler handler; public String name; //md5加密过的名字, public String uri;//图片的url public int type;// public String dir;//存储目录 } 这样一个线程池构造一半了.它如何处理下载事宜呢?接着就是定义它的run方法了. while (true) {//正常状态下一直运行. synchronized (this) { notifyAll(); if ((GetCount()!=0)&&(GetThreadCount()!=MAX_THREAD_COUNT)) {如果队列不为空,且当前下载线程数量不等于最大线程数量就新建下载线程下载图片 ,如果条件不满足,就等待,其它线程notify后它就可以下载了,这避免了一次性建太多的线程. DownloadPiece piece=Pop(); Handler handler=piece.handler; FrechImg_Impl(handler, piece.name, piece.type, piece.uri, piece.dir); } else { Log.d(TAG, "wait."+GetThreadCount()); try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } } 它处理下载的方法是FrechImg_Impl(),这个方法会建一个线程来下载图片的.而线程池只是管理下载线程用的. if (uri==null||name==null) { Log.d(TAG, "名字不存在。"); return; } String filename=dir+name; File file=new File(filename); if (file.exists()) { Log.d(TAG, "文件已经存在了不需要下载:"+uri); Bundle bundle=new Bundle(); bundle.putString("name", filename); FetchImage.SendMessage(handler, type, bundle, uri); return; } synchronized (this) { mApp.mDownloadPool.ActiveThread_Push(); String str3=Uri.encode(uri, ":/"); HttpGet httpGet=new HttpGet(str3); httpGet.setHeader("User-Agent", Twitter.USERAGENT); FetchImage fetchImage=new FetchImage(mApp, handler, httpClient, httpGet, type, name, uri, dir); fetchImage.setPriority(2); fetchImage.start(); } FetchImage是一个下载图片的线程.FrechImg_Impl做的事从代码来看,先检查下载的url是否合法,然后检查下载的文件是否存在,不满足下再下载,但先push,把当前的线程数量加1,然后启动线程下载. public void ActiveThread_Push() { synchronized (this) { mActiveThread++; } } 你也许会问,下载完了如何处理.这里有一个handler,这个是在你将url放入下载队列时带来的回调方法,这样也可以避免了非ui线程的问题,下载完成后就可以在handler中处理你的ui了. 其它的同步方法: public DownloadPiece Get(int paramInt) { synchronized (this) { int size=this.mQuery.size(); if (paramInt<=size) { return mQuery.get(paramInt); } } return null; } public int GetCount() { synchronized (this) { return mQuery.size(); } } public int GetThreadCount() { synchronized (this) { return mActiveThread; } } public DownloadPiece Pop() { synchronized (this) { DownloadPiece downloadPiece=(DownloadPiece) this.mQuery.get(0); this.mQuery.remove(0); return downloadPiece; } } public void ActiveThread_Pop() { synchronized (this) { int i=this.mActiveThread-1; this.mActiveThread=i; notifyAll(); } } 看到这,已经比较明朗了,你可以不关心队列中的数据哪里来的,因为上面只处理了如何下载. 下载需要一个url队列,所以需要提供一个public方法: public void Push(Handler handler, String uri, int type, String dir) { } String name=Util().getMd5(uri);//文件存储的名字自定义. synchronized (this) { for(DownloadPiece piece:mQuery) { if(piece.uri.equals(uri)) { notifyAll(); Log.d(TAG, "已经存在url:"+uri); return; } } //在这里检查了一次下载队列中的url,如果存在,就不需要再下载了,前面提到会检查一次文件是否已经下载的问题,如果你有存储,但我觉得在这里检查一次会比检查文件快一些. DownloadPiece piece=new DownloadPiece(handler, uri, name, type, dir); mQuery.add(piece);把数据放入队列. notifyAll(); } } 以上就是线程池的全部内容了.至于 下载线程,你可以自定义处理了. 需要FetchImage构造方法.把一些参数传过去, 继承extends Thread. public void run() { App app=(App) this.mContext.getApplicationContext(); HttpResponse response; /*synchronized (app.mDownloadPool) { if(app.mDownloadPool.GetThreadCount()==DownloadPool.MAX_THREAD_COUNT &&app.mDownloadPool.GetCount()>8) { Log.d(TAG, "当前的线程数为3,且等待下载的数量大于8,清除数据."); app.mDownloadPool.PopPiece(); } }*/如果这段没有,也可以,因为我觉得,当ListView滚动时,下载线程中可能有不再可见的内容,这时优先想看到的应该是当前显示的内容,所以把队列中的其它内容清除了,保留一小部分,可以再快地看到当前的ListView可见部分的图片内容.因为多线程,所以时刻记着同步处理操作. try { HttpParams httpParameters=new BasicHttpParams(); HttpConnectionParams.setConnectionTimeout(httpParameters, MicroBlog.CONNECT_TIMEOUT); HttpConnectionParams.setSoTimeout(httpParameters, MicroBlog.READ_TIMEOUT); DefaultHttpClient httpClient=new DefaultHttpClient(httpParameters); response=httpClient.execute(httpget); int code=response.getStatusLine().getStatusCode(); if (code==200) { byte[] bytes=EntityUtils.toByteArray(response.getEntity()); String filePath=SaveIconToFile(mName, bytes); Bundle bundle=new Bundle(); bundle.putString("name", filePath); FetchImage.SendMessage(mHandler, mType, bundle, uri); } else { Log.d(TAG, "下载图片失败:"+uri); } } catch (IOException e) { Log.d(TAG, "uri:"+uri+" exception:"+e.toString()); //e.printStackTrace(); } finally { // 默认把它移出,下载失败后不再下载。 app.mDownloadPool.ActiveThread_Pop(); } } SendMessage发送消息下载完成 .这里需要handler,就是构造时传来的参数了. DownloadPool: public void PopPiece() { synchronized (this) { int size=mQuery.size(); mQuery=mQuery.subList(size-5, size); } }为什么选择8和这里的保留5个url,因为我的一个ListView显示的图片可能一般情况下可见区会有5张图片,所以保留5个,不至于第一张不下载.如果上限数量变大,就是需要等待更多的图片下载完成后才会下载当前的图片,8这个上限没有太多 的根据,暂时定义的. SaveIconToFile()就是保存图片了. public String SaveIconToFile(String name, byte[] data) { String str2=dir+"/"+name; //Log.d(TAG, "str2:"+str2); FileOutputStream outputStream=null; try { outputStream=new FileOutputStream(str2); /*Options options=new Options(); options.inJustDecodeBounds=true; BitmapFactory.decodeByteArray(data, 0, data.length, options); int heightRatio=(int) Math.ceil(options.outHeight/(float) 800); int widthRatio=(int) Math.ceil(options.outWidth/(float) 480); if (heightRatio>1&&widthRatio>1) { if (heightRatio>widthRatio) { // Height ratio is larger, scale according to it options.inSampleSize=heightRatio; } else { // Width ratio is larger, scale according to it options.inSampleSize=widthRatio; } } options.inDither=true; options.inJustDecodeBounds=false; options.inPreferredConfig=Config.RGB_565; Bitmap bitmap=BitmapFactory.decodeByteArray(data, 0, data.length, options);*/ 这段注释了,我不需要下载的图片压缩存储,因为压缩了图片的质量就少了许多,也可以压缩处理,但你控制好你的图片质量,如果你需要显示高清的原图,就不要压缩,上面的代码是宽高大约是480*800以上时会压缩,现在主流手机分辨率就是这个,我觉得只有超过了才需要压缩.当然,你可以通过传来更多的参数来处理是否压缩,压缩的质量等. Bitmap bitmap=BitmapFactory.decodeByteArray(data, 0, data.length); bitmap.compress(CompressFormat.PNG, 100, outputStream); outputStream.flush(); bitmap.recycle(); } catch (Exception e) { }finally { if(outputStream!=null){ try { outputStream.close(); } catch (IOException e) { e.printStackTrace(); } } } return str2; } 外部调用:Activity中 ((App) mContext.getApplicationContext()).mDownloadPool.Push( mHandler, url, mCacheDir+"/picture/");至于其它参数可以自己看着办吧,处理的事务不同需要的东西不同.不必太在意我传的是什么.但是mHandler回调还是必须的. Handler mHandler=new Handler() { @Override public void handleMessage(Message msg) { int what=msg.what; Bundle bundle=msg.getData(); String imgUrl=(String) msg.obj; Bitmap bitmap=BitmapFactory.decodeFile(bundle.getString("name")); if (bitmap!=null&&!bitmap.isRecycled()) { 这样就可以处理了.如果你的图片不需要存储在文件系统中,你可以直接把下载的流解析成Bitmap,然后通过Handler传过来. } } 这里的bitmap可以存储在一个map中,这样你就有了一个内存缓存了,然后在调用((App) mContext.getApplicationContext()).mDownloadPool.Push( mHandler, url, mCacheDir+"/picture/");前可以先检查下内存缓存中是否已经存在了图片. BmpCache bmpCache=BmpCache.getInstance(); bmpCache.save(imgUrl, bitmap);这样保存,缓存就不列出了,网上搜索下到处是,可以用一个简单点的.
图片下载一直是比较麻烦的问题,如何缓存,如何处理图片都需要小心操作,因为图片的可用内存有限,太多时oome容易出现.
我的微博程序用上面的线程池处理后普通下载,gif图片另外处理,即使同时下载与解析gif动态图2m左右,帧数大约150的也没有出现oome.
线程池下载图片只是通用的一个方法.如果你需要下载一张大图,而下载不是同时进行的,建议还是单独写一个下载的方法,容易控制.因为下载8m或更大的图片也是个问题,还有下载后的存储,不宜用这种方法.
更多时候不是抱怨系统为何只提供这么少的内存供图片使用,先反省下自己的处理方式.
希望可以帮助别更多的人.
2011.12.11.
发表评论
-
android 批量打渠道包
2014-09-16 17:27 5818打包,是一个经常会遇到的问题,写个脚本就可以解决了.不同的脚本 ... -
vlc for android录制视频与截图
2014-09-08 18:31 7976首先说明,这不算原 ... -
android百度地图转为高德地图
2014-08-11 11:09 2910使用百度地图也不少时间了,但是一直出现无法解决的问题,在官 ... -
编译 i9000的cm系统
2014-08-04 13:10 1540[color=red][b]此文 并不 ... -
mp4v2 保存h264流
2014-05-01 21:15 11767大侠已经完成了很多操作了,唯一不足的是,工程完整性差一些,而且 ... -
baidumap的缩放到看到所有点
2014-04-09 21:03 1401之前使用1.3.5版本的sdk,通过调用mapviewcont ... -
ViewPager 查看图片
2014-02-13 12:59 4473一个图片查看器 , app到处都是 , 但那是别人的. 现在的 ... -
机顶盒 页面 选中后的 动画
2013-12-05 09:39 2133前面已经说过了,对于机顶盒的焦点的处理. 相信有不少人看 ... -
引爆你的图片浏览, ListView 大图片
2013-11-05 07:58 1265之前发现,ListView里面的图片资源越占越大,特别是当 ... -
微博开放源码
2013-10-23 12:09 987微博程序已经发布不少时间了,但一直也没有很多用户使用,主要在用 ... -
Mupdf 缩小apk包,减少字体
2013-10-20 15:53 3666在以前的apv中,字体占了很大的一部分,如果去除cjk字体 ... -
ActionBar appcompat 解决碎片化问题
2013-09-15 16:58 2122actionbarsherlock 这个在api11以下的系统 ... -
android机顶盒获取有线mac
2013-09-02 16:09 5440直接上代码: public static String ... -
android竖着的seekbar
2013-08-22 20:33 5432以前网上有位虾士发过一篇文章是关于竖着的seekbar,但是也 ... -
新浪微博oauth2.0 自动认证
2013-08-13 09:01 0oauth2.0作者认为它不先进,都放弃了.但是oauth2. ... -
android 颜表情.
2013-07-20 21:16 1780在使用TagsViewGroup 流布局后,我替换了原来的Gr ... -
flow 流布局.
2013-07-13 20:06 1260在git上看到一个FlowingViewGroup,代码有点旧 ... -
开发android机顶盒应用 事件,焦点处理
2013-07-13 19:58 19753机顶盒应用不同于手机 ... -
android 磁盘缓存.
2013-07-13 19:30 5235开发一个app,特别是图片的app,免不了要存储图片,内存缓存 ... -
TextView 文字淡入效果
2013-04-15 13:34 3454一个文本渐渐地从左到右的显示。 几步就可以了实现了。利用的是V ...
相关推荐
DownLoadImageToGallery: Android 下载图片保存到相册
Android 下载图片 简单 例子 Android Image DownLoader
android 下载图片到sdcard 并保存到指定的文件中
android 下载图片并缓存的demo
便捷下载 for Android 支持多平台一键批量下载图片、音频、视频的懒人工具
android实现音频、图片下载功能的代码实例,可以运行的,对于需要实现该功能的人很实用
android异步下载图片到相册,异步下载,可以自行更改,适合新手学习
android 图片选择图片上传到服务端并保存到mysql数据库,同时能支持从服务端下载图片资源
通过url获取网上的图片,然后保存在sd卡上面
Android批量下载图片并进行缓存,本例包含内存和文件二重缓存,极大的提高流畅度。
高仿现在主流的图片浏览的全屏缩放效果。如QQ好友动态、微信朋友圈。
android 图片异步下载,很实用。可以同时下载多张照片
android异步下载图片从网络上 分别用handler Asyntask 和线程池
Android 从网上下载图片并显示到ListView上
Android图片下载显示(相册效果)
Android图片加载框架类似Glide
android从服务器端下载图片,并将图片保存在本地sdcard里,需要显示图片时,先从本地查找,查询不到时再从服务器端下载,并在界面实现滚动显示图片
android io 下载图片 android io 下载图片
实现异步加载大量图片的源码和例子,包括缓存、硬盘缓存、容错机制等技术。这是一个开源的项目,在github上下载的,实现的很好。