android异步下载照片墙

android异步下载照片墙


版权声明:本文出自ShengFQ的博客.
转载请注明出处:http://www.jianshu.com/p/1709ea24ebbb

我的问题

我要实现从服务器端下载一个图片地址列表,并异步下载图片展示在imageview,实现缓存和压缩。如果要下载压缩后的图片,应该是由服务器端先压缩后存储,这里只说客户端范围,不做讨论。

需要预备的知识点:
1.AsyncTask异步调用方法下载json数据
2.listview,viewadapter的高性能写法
3.HandlerThread,Hander,Message异步消息机制
4.LurCache缓存
5.BitmapFactory的压缩图片

1.AsyncTask异步调用方法下载json数据
一个AsyncTask和一个Adapter组合,从服务器端拉取包含图片地址的json格式数据。
2.listview,viewadapter的高性能写法


viewadapter.java 部分实现
Object mlock;//异步操作list时,需要对list进行同步锁定
Context mContext;//调用上下文
List<Model> datalist;//list数据源
int itemLayoutId;//adapterview的个性化显示布局id
getDataList();
reload(List models);
add(model model);
addAll(List models);
render(Model model,View view,int position);//重复利用ViewHolder提升getView方法的性能
getCount();
getItem(int position);
getItemId(int position);
class ViewHolder{}
getView(int position,View convertView,ViewGroup parent);

//将一特定的数据格式添加到view中
protected void render(LinkModel linkmodel,View view,int position){
     final LinkModel item=linkmodel;
     ViewHolder viewHolder=(ViewHolder)view.getTag();
     //重复利用视图组件,将视图组件存储到装载对象中
     if(viewHolder==null){
         viewHolder=new ViewHolder();
        viewHolder.app_layout= (FrameLayout)view.findViewById(R.id.iv_app_layout);
        viewHolder.app_icon_layout=(LinearLayout)view.findViewById(R.id.app_icon_layout);
        viewHolder.app_log_iv=(ImageView)view.findViewById(R.id.ic_app_ico);
        viewHolder.app_name_tv=(TextView)view.findViewById(R.id.ic_app_name);
        view.setTag(viewHolder);
     }else{
         viewHolder=(ViewHolder)view.getTag();
     }
     if(item!=null){
         viewHolder.app_log_iv.setImageResource(R.drawable.app_img_app_normal);
         viewHolder.app_name_tv.setText(item.name);
         if (ImageCache.getInstance(mCacheSize).isCache(item.photoUrl)) {
             viewHolder.app_log_iv.setImageBitmap(ImageCache.getInstance(mCacheSize).getBitmapFromMemCache(item.photoUrl));
            }else{
        //后台线程异步下载图片,Token在这里指定了imageview的实例
         mThumbnailThread.queueThumbnail(viewHolder.app_log_iv, item.photoUrl);
            }
    }
}

在AsyncTask中将后台的json数据封装到实体对象,通过上述的adapter.addAll(List models)注入到adapter中.

3.HandlerThread,Hander,Message异步消息机制
定义ThunbnailDownloader类,用来通过发送下载请求消息,从另一个线程去后台下载并通过与前台UI线程交互加载.

public class ThumbnailDownLoader<Token> extends HandlerThread {
    private static final String TAG="ThumbnailDownLoader";
    private static final int MESSAGE_DOWNLOAD=0;
    private Handler mHandler;
    private String requestToken;
    private int mCacheSize;
    Map<Token,String> requestMap= Collections.synchronizedMap(new HashMap<Token,String>());
    /**
     * 主线程中的Handler对象
     * */
    Handler mRespoonseHandler;
    //回调接口变量
    Listener<Token> mListener;
    
    /**
     * 用于通信的监听器接口
     * 当下载完要执行的事情:将图片加载到UI线程的ImageView
     * */
    public interface Listener<Token>{
        /**
         * 后台线程的输出,将下载的图片指定给前台的ImageView
         * @param token 存放图片的容器
         * @param thumbnail 图片格式
         * */
        void onThumbnailDownloaded(Token token,Bitmap thumbnail);
    }

    public void setListener(Listener<Token> listener){
        mListener=listener;
    }

    public ThumbnailDownLoader(String requesttoken){
        super(TAG);
        this.requestToken=requesttoken;
    }

    /**
     * 主线程传递的Handler
     * @param responseHandler 前台UI线程handler
     * @param requesttoken 远程下载请求的token
     * @param cacheSize 缓存大小
     * */
    public ThumbnailDownLoader(Handler responseHandler,String requesttoken,int cacheSize){
        super(TAG);
        mRespoonseHandler=responseHandler;
        this.requestToken=requesttoken;
        this.mCacheSize=cacheSize;
    }
/**
 * 该方法的调用发生在Looper第一次检查消息队列之前
 * */

 @SuppressLint("handlerLeak")
 @Override
 protected void onLooperPrepared(){
    mHandler=new Handler(){
    //looper取得消息队列中的特定消息,回调方法根据消息what属性进行处理
      public void handleMessage(Message msg){
        if(msg.what==MESSAGE_DOWNLOAD){
            @SuppressWarnings("unchecked")
            Token token=(Token)msg.obj;//Handler.obtainMessage(msg,obj);通过Handler发送Message传递了message.obj,这里处理消息时,可以获取obj,属于约定内容。
            Log.i(TAG,"Got a request for url:"+requestMap.get(token));
            handleRequest(token,requestToken);
        }
      }
    };
 }
 /**
  * 发送message请求下载图片,将URL和Token传递到同步hashMap中
  * 在调用getView()的时候请求下载
  * @param token 前台交互的UI控件
  * @param url 前台指定的下载地址
  * */
    public void queueThumbnail(Token token,String url){
        Log.i(TAG,"Got to URL:"+url);
        requestMap.put(token,url);//调用getview()的时候调用
        Message message=mHandler.obtainMessage(MESSAGE_DOWNLOAD,token);//创建信息并传入消息字段,自动完成目标handler的设置
        message.sendToTarget();//将消息压入消息队列
    }

    /**
     * 远程下载图片,将后台下载的图片加载到前台UI的ImageView中
     * @param token 泛型参数,这里指前台的ImageView
     * */
    private void handleRequest(final Token token,final String requestToken){
        try{
            final String url=requestMap.get(token);
            if(url==null)
            return;
            byte[] bitmapBytes=new ImageLoaderUtils().getUrlBytes(url,requestToken);
           // final Bitmap bitmap= BitmapFactory.decodeByteArray(bitmapBytes,0,bitmapBytes.length);
            final Bitmap bitmap=  ImageCompress.decodeSampleBitmapFromBytes(bitmapBytes, 54, 54);//图片压缩
            ImageCache.getInstance(mCacheSize).addBitmapToMemoryCache(url, bitmap);
            Log.i(TAG,"Bitmap created");
            //此处定义了主线程在后台线程交互操作的UI处理
            mRespoonseHandler.post(new Runnable(){
                public void run(){
                    if(requestMap.get(token)!=url) return;
                    requestMap.remove(token);//
                    Bitmap cachebitmap =ImageCache.getInstance(mCacheSize).getBitmapFromMemCache(url);
                    if(cachebitmap!=null)
                        mListener.onThumbnailDownloaded(token,cachebitmap);
                    else{
                          mListener.onThumbnailDownloaded(token,bitmap);
                    }
                }
            });
            
        }catch(IOException ioe){
            Log.e(TAG,"Error downloading image",ioe);
        }
    }

    public void clearQueue(){
        mHandler.removeMessages(MESSAGE_DOWNLOAD);
        requestMap.clear();
    }
}

说明:HandlerThread是一个消息通知处理线程类
onLooperPrepared();//初始化Handler的地方,因为此处在HandlerThread初始化后,进入Loop()之前就会调用的方法,在这里初始化Handler有助于在消息发送之前handler已经初始化.

Handler
handlleMessage(Message msg);//根据消息的类别和消息附带的参数进行异步下载
handler.obtainMessage(String msg,object) //构建一个Message对象
message.sendToTarget();//将消息发送到消息队列
handler.removeMessages(String msg);//清除该消息,释放资源

如何与前台UI线程交互

已经将image下载下来了,要填充到UI线程的Imageview中,这就需要与前台交互,没错,前台UI线程也有Handler,而且只有一个,将前台的handler传递到后台共享

/**
 * 主线程中的Handler对象
 * 
Handler mRespoonseHandler;
/**
     * 主线程传递的Handler
     * @param responseHandler 前台UI线程handler
     * @param requesttoken 远程下载请求的token
     * @param cacheSize 缓存大小
     * */
public ThumbnailDownLoader(Handler responseHandler,String requesttoken,int cacheSize){
    super(TAG);
    mRespoonseHandler=responseHandler;
    this.requestToken=requesttoken;
    this.mCacheSize=cacheSize;
}

在下载完图片后,通过handler.post();方法与前台UI交互


//此处定义了主线程在后台线程交互操作的UI处理
    mRespoonseHandler.post(new Runnable(){
        public void run(){
            if(requestMap.get(token)!=url) return;
            requestMap.remove(token);//
            Bitmap cachebitmap =ImageCache.getInstance(mCacheSize).getBitmapFromMemCache(url);
            if(cachebitmap!=null)
                mListener.onThumbnailDownloaded(token,cachebitmap);//回调接口方法
            else{
                  mListener.onThumbnailDownloaded(token,bitmap);
            }
        }
    });
    

Listener<Token> mListener;//回调接口对象,下载图片后,定义行为将图片设置到imageview

/**
 * 用于通信的监听器接口
 * 当下载完要执行的事情:将图片加载到UI线程的ImageView
 * */
public interface Listener<Token>{
    /**
     * 后台线程的输出,将下载的图片指定给前台的ImageView
     * @param token 存放图片的容器
     * @param thumbnail 图片格式
     * */
    void onThumbnailDownloaded(Token token,Bitmap thumbnail);
}

public void setListener(Listener<Token> listener){
    mListener=listener;
}

回到主线程UI

因此在UI线程中,做两件事,初始化HandlerThread,实现回调接口

  //初始化后台线程下载图片
mThumbnailThread = new ThumbnailDownLoader<ImageView>(new Handler(),token,mCacheSize);//与主线程Looper绑定的Handler
mThumbnailThread.setListener(new ThumbnailDownLoader.Listener<ImageView>() {
    public void onThumbnailDownloaded(ImageView imageView, Bitmap thumbnail) {
        if (true) {
            //给ImageView设置图片Bitmap
            imageView.setImageBitmap(thumbnail);
        }
    }
});
mThumbnailThread.start();
mThumbnailThread.getLooper();

如何触发异步下载

我们要实现的效果是,下拉gridview时,自动下载图片并加载到gridview里的imageview视图,所以我们将焦点回到adapter.getView()方法里,要实现这个功能,需要两个重要参数传递到HandlerThread中
当前的Imageview实例和需要下载的图片URL

mThumbnailThread.queueThumbnail(viewHolder.app_log_iv, item.photoUrl);

4.LurCache缓存
主要使用了LruCache<K,V> 类,该类的使用方法如下
一般缓存大小设置为:

UI线程中指定
int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);  
        // 使用最大可用内存值的1/8作为缓存的大小。  
        mCacheSize = maxMemory / 8;  
/**
 * 图片缓存
 * */
public class ImageCache {
    private static ImageCache instance;
    
    /**
     * 实例化,缓存大小
     */
    public static ImageCache getInstance(int cacheSize) {
        if (instance == null) {
            synchronized (ImageCache.class) {
                if (instance == null) {
                    instance = new ImageCache(cacheSize);
                }
            }
        }
        return instance;
    }
    private LruCache<String,Bitmap> mMemoryCache;
    private ImageCache(int cacheSize){
        mMemoryCache=new LruCache<String,Bitmap>(cacheSize){
            protected int sizeOf(String key,Bitmap bitmap){
                return bitmap.getByteCount()/1024;
            }
        };
    }
    
    /**
     * 加载到缓存
     * */
    public void addBitmapToMemoryCache(String key,Bitmap bitmap){
        if(getBitmapFromMemCache(key) ==null){
            mMemoryCache.put(key, bitmap);
        }
    }
    /**
     * 从缓存中读取
     * */
    public Bitmap getBitmapFromMemCache(String key){
        return mMemoryCache.get(key);
    }
    /**
     * 检查是否缓存
     * */
    public boolean isCache(String key){
        Bitmap map=mMemoryCache.get(key);
        return map!=null;
    }
}

5.BitmapFactory的压缩图片

ImageCompress.java
/**
     * 
     * 加载输入流图片 网络*/
public static Bitmap decodeSampleBitmapFromBytes(byte[] data,int reqWidth,int reqHeight){
    final BitmapFactory.Options options=new BitmapFactory.Options();
    options.inJustDecodeBounds=true;
    //BitmapFactory.decodeResource(resource, resId, options);
    BitmapFactory.decodeByteArray(data, 0, data.length, options);
    options.inSampleSize=calculateInSampleSize(options, reqWidth, reqHeight);
    options.inJustDecodeBounds=false;
    return BitmapFactory.decodeByteArray(data, 0, data.length, options);
}

//怎么压缩呢,按照缩放比例inSamplesize,这个值是可要计算出来的
public static int calculateInSampleSize(BitmapFactory.Options options,int reqWidth,int reqHeight){
    //
    final int height=options.outHeight;
    final int width=options.outWidth;
    int inSampleSize=1;
    if(height>reqHeight || width>reqWidth){
        final int heightRatio=Math.round((float)height/(float)reqHeight);
        final int widthRatio=Math.round((float)width/(float)reqWidth);
        inSampleSize=heightRatio<widthRatio ?heightRatio:widthRatio;
    }
    return inSampleSize;
}


参考资料

1.http://blog.csdn.net/guolin_blog/article/details/9526203 Android照片墙应用实现,再多的图片也不怕崩溃
2 http://blog.csdn.net/guolin_blog/article/details/9316683 Android高效加载大图、多图解决方案,有效避免程序OOM
3.android开发编程权威指南
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,924评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,902评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,716评论 0 239
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,783评论 0 203
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,166评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,510评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,784评论 2 311
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,476评论 0 196
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,196评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,459评论 2 243
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,978评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,321评论 2 252
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,964评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,046评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,803评论 0 193
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,530评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,420评论 2 265

推荐阅读更多精彩内容