美文网首页
android异步下载照片墙

android异步下载照片墙

作者: ShengFQ | 来源:发表于2016-06-28 19:44 被阅读315次

    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开发编程权威指南

    相关文章

      网友评论

          本文标题:android异步下载照片墙

          本文链接:https://www.haomeiwen.com/subject/spvtjttx.html