序言

对于android学习者,对于网络请求势必都经历这样的一个过程,通过HttpClient或者HttpUrlConnection,来发其请求然后通过Handler进行数据的传递,非常的麻烦,然后后来你知道了有Volley,OKHttp,来让我们尝试动手写个网络请求的小工具吧,来对其进行一个剖析。

图片请求网络框架

对于图片的请求,我们需要设置一个缓存,通过缓存策略来减少网络请求,从而减少电量消耗和流量消耗,缓存策略通过二级缓存策略,内存作为一级缓存,磁盘作为二级缓存,缓存采用LRU的方式来进行管理,到得不到指定的内容之后,向网络发起请求来获得图片。这么一听貌似很简单的呀,来我们一个坑一个坑的踩过去。

现在我要根据URL来找一个图片,那么先从内存中取。

 public Bitmap loadBitmap(String uri,int reqWidth,int reqHeight){        Bitmap bitmap = loadBitmapFromMemCache(uri);        if(bitmap!=null)            return bitmap;        try{            bitmap = loadBitmapFromDiskCache(uri,reqWidth,reqHeight);            if(bitmap!=null)                return bitmap;            bitmap = loadBitmapFromHttp(uri,reqWidth,reqHeight);        }catch (IOException e){            e.printStackTrace();        }        if(bitmap==null&&mIsDiskLruCacheCreated){            bitmap = downloadBitmapFromUrl(uri);        }    }
  • loadBitmapFromMemCache:先是从内存中拉取

private Bitmap loadBitmapFromMemCache(String url){        final String key = hashKeyFormUrl(url);        Bitmap bitmap = getBitmapFromMemCache(key);        return bitmap;    }

通过Url我们向内存中缓存进行查找,我们首先将url进行一个哈希,对其哈希的原因很明显,我们的url中可能还有一个特殊字符,影响我们的使用,所以我们一般采用其MD5值来作为key,这个函数这里我们先不去看,现在知道其作用即可。我们所关心的重点是getBitmapFromMemcache(),如何来实现这个方法。

 private Bitmap getBitmapFromMemCache(String key){        return mMemoryCache.get(key);    }

从一个对象中来拿,这个就是我们用来管理内存的LruCache,如何创建这个对象呢?我们创建的时候,还需要重写它的sizeOf方法。

mMemoryCache = new LruCache(cacheSize) {            @Override            protected int sizeOf(String key, Bitmap value) {                return value.getRowBytes() * value.getHeight() / 1024;            }        };

显然,我们可以通过put方法将我们的图片缓存进去,到此,我们关于从内存缓存中如何取图片已经完成了,然后是当我们的在内存中找不到图片从磁盘的缓存中查找的时候。

  • loadBitmapFromDiskCache
    从磁盘加载

 private Bitmap loadBitmapFromDiskCache(String url,int reqWidth,int reqHeight) throws IOException{        if(Looper.myLooper()==Looper.getMainLooper()){            Log.w(TAG,"load bitmap from the UI Thread is not recommended!");        }        if(mDiskLruCache ==null){            return null;        }        Bitmap bitmap = null;        String key = hashKeyFormUrl(url);        DiskLruCache.Snapshot snapshot = mDiskLruCache.get(key);        if(snapshot!=null){            FileInputStream fileInputStream = (FileInputStream)snapshot.getInputStream(DISK_CACHE_INDEX);            FileDescriptor fileDescriptor = fileInputStream.getFD();            bitmap = mBitmapHelper.decodeBitmapFromFileDescriptor(fileDescriptor,reqWidth,reqHeight);            if(bitmap!=null){                addBitmapToMemoryCache(key,bitmap);            }        }        return bitmap;    }

首先判断是否在是在主线程,从磁盘读取文件,不建议在主线程中对其读取,容易导致ANR。接着我们来看下我们的DisKLruCache的实现,将在下篇文章中进行剖析。从磁盘中查找图片得不到的时候,我们会发起网络请求。

  • loadBitmapFromHttp
    从网络中获取图片

private Bitmap loadBitmapFromHttp(String url,int reqWidth,int reqHeight) throws IOException{        if(Looper.myLooper()==Looper.getMainLooper()){            throw new RuntimeException("can not visit network from UI thread");        }        if(mDiskLruCache == null){            return null;        }        String key = hashKeyFormUrl(url);        DiskLruCache.Editor editor = mDiskLruCache.edit(key);        if(editor!=null){            OutputStream outputStream = editor.newOutputStream(DISK_CACHE_INDEX);            if(downloadUrlToStream(url,outputStream)){                editor.commit();            }else{                editor.abort();            }            mDiskLruCache.flush();        }        return loadBitmapFromDiskCache(url,reqWidth,reqHeight);    }

首先我们要进行线程的检测,判断是否处于主线程,然后调用了downloadUrlToStream()来从网络中获取数据流,然后将该数据流转交给DiskLruCache,也就是将图片文件写进我们的磁盘缓存中,然后调用从磁盘缓存中加载图片。来看下如何实现根据提供的url来获取一个数据流的。

  • downloadUrlToStream

 //将网络流转化为数据流    public boolean downloadUrlToStream(String urlString,OutputStream outputStream){        HttpURLConnection urlConnection = null;        BufferedOutputStream out = null;        BufferedInputStream in = null;        try{            final URL url = new URL(urlString);            urlConnection = (HttpURLConnection)url.openConnection();            in = new BufferedInputStream(urlConnection.getInputStream(),IO_BUFFER_SIZE);            out = new BufferedOutputStream(outputStream,IO_BUFFER_SIZE);            int b;            while((b=in.read())!=-1){                out.write(b);            }            return true;        }catch (IOException e){            Log.e(TAG,"downloadBitmap failed"+e);        }finally {            if(urlConnection!=null){                urlConnection.disconnect();            }            IOUtils.close(out);            IOUtils.close(in);        }        return false;    }

我们向该函数传递了我们所需要的两个参数,一个url,一个是输出流,我们通过UrlConnection来获取了一个和网络的连接,获取了数据流,然后根据流来读取,之后写入到我们的输出流,如果你对Java研究并不是很深入,可能听到流,会有写模糊,对其底层的细节也想了解,别急,后续文章会继续来讲。这样我们实现了将数据写入到我们的磁盘缓存。再回到我们最初,我们对于这次湖区图片后面还做了一个判断,你可能会感觉到疑惑了,为什么还要做这么一次操作。不应该是100%的可以从网络中获取到图片的吗?其实不然,这个时候,我们可能会在网络加载等各方面问题上出现了状况,这个时候,我们选择从网络重新进行加载。

//根据Url下载图片    private Bitmap downloadBitmapFromUrl(String urlString){        Bitmap bitmap = null;        HttpURLConnection urlConnection = null;        BufferedInputStream in = null;        try{            final URL url = new URL(urlString);            urlConnection = (HttpURLConnection) url.openConnection();            in = new BufferedInputStream(urlConnection.getInputStream(),IO_BUFFER_SIZE);            bitmap = BitmapFactory.decodeStream(in);        }catch (final IOException e){        }finally {            if(urlConnection!=null){                urlConnection.disconnect();            }            IOUtils.close(in);        }        if(bitmap!=null)                return bitmap;    }

如此一个图片缓存的框架结束了,当然从网络加载过来的图片我们的不可能是将其全部加载到内存,我饿们需要根据其大小做一个显示的处理,处理方式。
获取图片的宽高,根据我们需要的宽高进行一个缩放比对,修改了其属性之后,然后将其设置该Bitmap的属性。从而减小其体积。

public static Bitmap decodeBitmapFromFileDescriptor(FileDescriptor fd,int reqWidth,int reqHeight){        final BitmapFactory.Options options = new BitmapFactory.Options();        options.inJustDecodeBounds = true;        BitmapFactory.decodeFileDescriptor(fd,null,options);        options.inSampleSize = calculateInSampleSize(options,reqWidth,reqHeight);        options.inJustDecodeBounds = false;        return BitmapFactory.decodeFileDescriptor(fd,null,options);    }    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>reqHeight){            final int halfHeight = height/2;            final int halfWidth = width/2;            while((halfHeight/inSampleSize)>=reqHeight&&(halfWidth/inSampleSize)>=reqWidth){                inSampleSize *=2;            }        }        return inSampleSize;    }

这里我们提供了另一个方式,支持ImageView绑定一个View

 //实现异步加载    public void bindBitmap(final String uri,final ImageView imageView,final int reqWidth,final int reqHeight){        imageView.setTag(uri);        Bitmap bitmap = loadBitmapFromMemCache(uri);        if(bitmap!=null){            imageView.setImageBitmap(bitmap);            return;        }        Runnable loadBitmapTask = new Runnable() {            @Override            public void run() {                Bitmap bitmap = loadBitmap(uri,reqWidth,reqHeight);                if(bitmap!=null){                    LoaderResult result = new LoaderResult(imageView,uri,bitmap);                    //get the message from messge pool avoid to create new message                    mMainHandler.obtainMessage(MESSAGE_POST_RESULT,result).sendToTarget();                }            }        };        THREAD_POOL_EXECUTOR.execute(loadBitmapTask);    }

这个方法,先是从内存中查找,如果找到,则进行设置,如果没有找到,则从网络发起请求,创建了一个Runnable,然后将我们上述的从内存,网络中的请求封装到其中,丢给线程池来处理,这个时候问题就来了,丢给线程池之后,我们和UI线程就不在一个线程了,这个时候,需要我们进行线程的切换,如何来操纵我们view,或者是将我们的结果传递出去,实现方式是。对结果类进行了一个封装,将我们的view和结果封装为一个结果类。

private static class LoaderResult{        public ImageView imageView;        public String uri;        public Bitmap bitmap;        public LoaderResult(ImageView imageView,String uri,Bitmap bitmap){            this.imageView = imageView;            this.uri = uri;            this.bitmap = bitmap;        }    }

得到了结果,将其通过消息发送的方式,传递给我们的主线程,然后在Handler中进行处理。处理方式。

//  handler实现交互    private Handler mMainHandler = new Handler(Looper.getMainLooper()){        @Override        public void handleMessage(Message msg) {            LoaderResult result = (LoaderResult)msg.obj;            ImageView imageView = result.imageView;            //imageView.setImageBitmap(result.bitmap);            String uri = (String) imageView.getTag();            if (uri.equals(result.uri)) {                imageView.setImageBitmap(result.bitmap);            }else{                //set a default background            }        }    };

这里,我们给ImageView设置一个tag用来标记,然后通过对其进行判断,然后将图片设置在上面。至此我们一个图片请求的框架就写好了,当然还是需要在进行一些优化的。接下来我们进行普通网络请求(非图片)库的一个封装封装。然后是对于两个缓存工具类的剖析。

更多相关文章

  1. Nginx系列教程(六)| 手把手教你搭建 LNMP 架构并部署天空网络电影
  2. Android图片加载库:最全面的Picasso讲解
  3. ImageSwitcher的应用
  4. Canvas和Drawable
  5. Android中GridView的使用——使用自带的SimpleAdapter(简单适配
  6. Android(安卓)绘制圆形图片
  7. Android中仿微信选择图片并展示在RecyclerView中
  8. Android(安卓)Glide扩展实现图片加载进度条
  9. android并发网络请求的处理

随机推荐

  1. 如何画褶皱?动漫衣服褶皱画法!
  2. 用户注册表单
  3. 如何设置checkbox复选框某一个选项一直处
  4. PHP字符运算、类型转换和系统函数
  5. 简历表代码
  6. 意派Epub360丨吃了没创意的亏?这款感恩节
  7. 11-10作业
  8. 1109课程作业
  9. html基础表单
  10. 五.Python面向对象