`
phenom
  • 浏览: 409348 次
  • 性别: Icon_minigender_1
  • 来自: 福州
社区版块
存档分类
最新评论

android 下载图片

 
阅读更多
一个网络程序下载图片通常是一个大麻烦,如何处理好下载,才是关键的问题,这关系到程序的性能,甚至崩溃,出现oome.
如果你还在使用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.
分享到:
评论
2 楼 u012911704 2013-12-26  
没大看懂 程序依然OOM=.=
1 楼 yun2223 2013-01-24  
我也福州啊呵呵 楼主

相关推荐

    Android 下载图片保存到相册

    总结一下,Android下载图片到相册涉及的主要步骤包括:请求权限、发起网络请求下载图片、保存图片到公共外部存储目录、以及通知媒体库更新。在实现这些功能时,可以利用Volley、OkHttp等网络库,以及Android的文件...

    Android 下载图片简单例子

    Android 下载图片 简单 例子 Android Image DownLoader

    android 下载图片到本地Sdcard

    至此,你已经学会了如何在Android应用中下载图片并保存到SDcard的特定位置。这个过程包括了网络请求、数据流处理、文件操作和权限管理等关键环节。请确保在实际项目中根据具体需求进行调整,如错误处理、进度显示、...

    android 下载图片并缓存

    - **异步下载**:Android中通常使用异步方式下载图片,避免阻塞主线程,影响用户界面。可以使用`AsyncTask`、`IntentService`或者第三方库如`Volley`、`OkHttp`来实现。 - **HTTP请求**:发送HTTP GET请求获取图片...

    android下载图片、音频实例

    本实例,"android下载图片、音频实例",提供了一个可运行的解决方案,帮助开发者实现这一功能。下面我们将深入探讨实现这一功能所涉及的关键知识点。 1. **Android权限管理**: 在Android中,自6.0(API级别23)...

    android下载图片保存SD卡

    总结来说,实现“android下载图片保存SD卡”涉及的关键知识点包括: 1. 网络请求:使用`HttpURLConnection`或第三方库(如OkHttp)发起HTTP GET请求。 2. 文件操作:创建输出流,将网络数据写入SD卡指定路径的文件。...

    便捷下载 for Android 支持多平台一键批量下载图片、音频、视频的懒人工具.rar

    "便捷下载 for Android" 是一款专为Android用户设计的应用程序,旨在简化图片、音频和视频内容的下载过程。它提供了一键式批量下载功能,让用户能够高效地从多个平台获取媒体资源,尤其适合那些希望节省时间和精力的...

    android下载图片到相册

    在Android开发中,将网络上...总之,Android异步下载图片到相册是一个涉及网络请求、文件操作、权限管理和系统交互的过程,需要对Android基础知识有扎实的理解。通过不断实践和学习,开发者可以更熟练地处理这类问题。

    Android 图片浏览全屏缩放

    在Android开发中,实现图片浏览的全屏缩放效果是一项常见的需求,特别是在社交应用中,如QQ好友动态和微信朋友圈。这种功能不仅需要提供良好的用户体验,还需要考虑性能和内存优化,因为图片通常较大,处理不当可能...

    Android批量下载图片并缓存,非常流畅

    本教程将详细讲解如何在Android应用中实现批量下载图片并进行高效缓存,以实现非常流畅的用户体验。我们将主要关注LruCache技术,这是一种内存管理策略,有助于优化内存使用。 首先,我们需要理解Android中的图片...

    Android Studio —— 下载网络图片显示

    2. **创建网络请求**: 使用Volley的`ImageLoader`类来下载图片。首先,创建一个自定义的`RequestQueue`实例,并初始化`ImageLoader`。在`Application`类或者某个初始化的地方: ```java public class MyApplication ...

    android 上传和下载图片

    本文将详细讲解如何在Android中实现图片的选取、上传至服务器以及从服务器下载图片,并将其存储到MySQL数据库的过程。 首先,我们需要处理图片的选取。Android提供了多种方式让用户选择图片,如使用系统的相册或...

    Android图片选择,裁剪,预览,下载

    在Android应用开发中,图片处理是一项常见的需求,无论是社交应用中的个人头像设置,还是电商应用的商品图片上传,都需要对图片进行选择、裁剪、预览以及下载等操作。本项目"Android图片选择,裁剪,预览,下载"正是...

    android下载网络图片的方法

    在Android开发中,从网络下载图片是一项常见的任务,特别是在构建应用程序时,比如社交应用、电商应用等。本文将深入探讨三种不同的方法来实现这一功能:Handler、AsyncTask以及线程池。 1. Handler机制下载图片: ...

    android多线程下载图片例子

    本示例将围绕"android多线程下载图片例子"这一主题,深入探讨Android中异步加载图片的机制,并结合"AndroidUploadImages"这一文件名,推测这是关于图片上传或下载的实践案例。 1. **AsyncTask** Android提供了一个...

    android从服务器端下载图片并保存在本地sdcard里并在界面滚动显示出来

    在Android开发中,从服务器端下载图片并保存到本地SD卡是常见的需求,尤其是在构建一个包含大量图片的应用,如新闻阅读、社交应用或者电商应用。这个过程涉及到网络请求、文件操作以及UI显示等多个方面。以下将详细...

    Android例子源码异步批量下载图片并缓存

    在Android开发中,异步批量下载图片并缓存是一个常见的需求,特别是在开发涉及大量图片展示的应用时,如社交应用、电商应用等。本教程将基于提供的Android例子源码,深入探讨如何实现这一功能。 首先,我们需要理解...

    Android图片加载框架

    在Android应用开发中,图片加载框架扮演着至关重要的角色,它们负责高效、流畅地加载和显示图片,同时处理内存和CPU资源的优化。本篇文章将深入探讨“Android图片加载框架”,特别是与Glide相关的知识。 Glide是一...

    android网络下载大量图片

    在Android开发中,处理网络下载大量图片是一项常见的任务,尤其在构建内容丰富的应用程序时,如社交媒体应用或电商平台。本文将详细解析如何实现异步加载大量图片,并探讨关键知识点,包括缓存策略、硬盘缓存、容错...

Global site tag (gtag.js) - Google Analytics