资讯专栏INFORMATION COLUMN

Android网络图片请求+二级缓存实现

Hegel_Gu / 3151人阅读

摘要:从磁盘加载首先判断是否在是在主线程,从磁盘读取文件,不建议在主线程中对其读取,容易导致。得到了结果,将其通过消息发送的方式,传递给我们的主线程,然后在中进行处理。

序言

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

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/12730.html

相关文章

  • Android常用图片加载库介绍及对比

    摘要:前言图片加载在开发项目中是必不可少的,为了降低开发周期和难度,我们经常会选用一些图片加载的开源库,而发展到现在图片加载开源库也越来越多了,下面介绍几种开发中主流的图片加载框架以及他们之间的对比优缺点。 前言 图片加载在 Android开发项目中是必不可少的,为了降低开发周期和难度,我们经常会选用一些图片加载的开源库,而Android发展到现在图片加载开源库也越来越多了,下面介绍几种开发...

    Yangyang 评论0 收藏0
  • CDN高级技术专家周哲:深度剖析短视频分发过程中的用户体验优化技术点

    摘要:讲解从三个部分展开短视频应用场景阿里云短视频解决方案阿里云对短视频用户体验的相关优化。同时,为了面对业务的突发流量,阿里云提供了超过的带宽储备,为持续增长的业务保驾护航。二播放卡顿是指在播放过程中的不流畅情况,会严重影响用户体验。 深圳云栖大会已经圆满落幕,在3月29日飞天技术汇-弹性计算、网络和CDN专场中,阿里云CDN高级技术专家周哲为我们带来了《海量短视频极速分发》的主题分享,带...

    alphahans 评论0 收藏0
  • Android网络框架

    摘要:无网读缓存,有网根据过期时间重新请求使用和实现网络缓存。浅析的你了解过吗为什么每次请求都用了长连接完成一次网络请求都经历了什么感兴趣的不妨可以看下。 Android OKHttp3.0 以上使用方法 Android OKHttp3.0 以上使用方法详解 Retrofit 之日志拦截 Retrofit 日志拦截相关介绍 Retrofit源码解析 Retrofit的源码分析将从基本的使用方...

    smallStone 评论0 收藏0
  • Android Picasso最详细的使用指南

    摘要:比如高斯模糊,圆角圆形等处理。可以在一个请求上应用多个比如我想先做个度灰处理然后在做一个高斯模糊图第一步定义一个度灰的第二步如果是多个操作,有两种方式应用方式一直接调用多次方法,不会覆盖的。Picasso 是Square 公司开源的Android 端的图片加载和缓存框架。Square 真是一家良心公司啊,为我们Android开发者贡献了很多优秀的开源项目有木有!像什么Rerefoit 、Ok...

    pepperwang 评论0 收藏0
  • 网络相关 - 收藏集 - 掘金

    摘要:关于三请求流程,源码必知掘金前两两片我们介绍了,基本使用和如何查看源码,今天我们正式进入源码分析流程。掘金是一款上的组件。 一篇文章带你走通 OkHttp+Retrofit+Rxjava - Android - 掘金一篇文章带你走通 OkHttp+Retrofit+Rxjava @(Android)[android] ... 升级 Https 的那些事 - Android - 掘金一、...

    haobowd 评论0 收藏0

发表评论

0条评论

Hegel_Gu

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<