• Diycode开源项目 磁盘图片缓存+自定义webViewClient+图片点击js方法


    1.磁盘图片缓存器DiskImageCache

    1.1.这个类很多情况都可能用的到,耦合性很低,所以分开讲。

      源代码:

    /*
     * Copyright 2017 GcsSloop
     *
     * Licensed under the Apache License, Version 2.0 (the "License");
     * you may not use this file except in compliance with the License.
     * You may obtain a copy of the License at
     *
     *    http://www.apache.org/licenses/LICENSE-2.0
     *
     * Unless required by applicable law or agreed to in writing, software
     * distributed under the License is distributed on an "AS IS" BASIS,
     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     * See the License for the specific language governing permissions and
     * limitations under the License.
     *
     * Last modified 2017-03-12 00:56:52
     *
     * GitHub:  https://github.com/GcsSloop
     * Website: http://www.gcssloop.com
     * Weibo:   http://weibo.com/GcsSloop
     */
    
    package com.gcssloop.diycode.base.webview;
    
    import android.content.Context;
    import android.graphics.Bitmap;
    import android.graphics.BitmapFactory;
    import android.os.Environment;
    import android.os.StatFs;
    import android.support.annotation.NonNull;
    import android.util.Log;
    
    import java.io.File;
    import java.io.FileInputStream;
    import java.io.FileNotFoundException;
    import java.io.FileOutputStream;
    import java.io.IOException;
    import java.io.OutputStream;
    import java.security.MessageDigest;
    import java.security.NoSuchAlgorithmException;
    import java.util.Arrays;
    import java.util.Comparator;
    
    public class DiskImageCache {
        private static final String CACHE_SUFFIX = ".cache";
    
        private static final int MB = 1024 * 1024;
        private static final int CACHE_SIZE = 50;   // 缓存占用空间大小
        private static final int FREE_SD_SPACE_NEEDED_TO_CACHE = 10;    // 为 SD 卡保留多少空间
    
        private File cacheDir;
    
        public DiskImageCache(Context context) {
            cacheDir = getDiskCacheDir(context, "web-image");
            // 整理缓存
            organizeCache(cacheDir);
        }
    
        /**
         * 从缓存中获取图片
         **/
        public Bitmap getBitmap(final String key) {
            final String path = getCachePath(key);
            File file = new File(path);
            if (file.exists()) {
                Bitmap bmp = BitmapFactory.decodeFile(path);
                if (bmp == null) {
                    file.delete();
                } else {
                    updateFileTime(path);
                    return bmp;
                }
            }
            return null;
        }
    
        /**
         * 将图片存入文件缓存
         **/
        public void saveBitmap(String key, Bitmap bm) {
            if (bm == null) {
                return;
            }
            //判断sdcard上的空间
            if (FREE_SD_SPACE_NEEDED_TO_CACHE > freeSpaceOnSd()) {
                return;     //SD空间不足
            }
            File file = new File(getCachePath(key));
            try {
                file.createNewFile();
                OutputStream outStream = new FileOutputStream(file);
                bm.compress(Bitmap.CompressFormat.PNG, 100, outStream);
                outStream.flush();
                outStream.close();
            } catch (FileNotFoundException e) {
                Log.w("ImageFileCache", "FileNotFoundException");
            } catch (IOException e) {
                Log.w("ImageFileCache", "IOException");
            }
        }
    
        /**
         * 保存 bytes 数据
         *
         * @param key   url
         * @param bytes bytes 数据
         */
        public void saveBytes(String key, byte[] bytes) {
            if (bytes == null) {
                return;
            }
            //判断sdcard上的空间
            if (FREE_SD_SPACE_NEEDED_TO_CACHE > freeSpaceOnSd()) {
                return;     //SD空间不足
            }
            File file = new File(getCachePath(key));
            try {
                file.createNewFile();
                OutputStream outStream = new FileOutputStream(file);
                outStream.write(bytes);
                outStream.flush();
                outStream.close();
            } catch (FileNotFoundException e) {
                Log.w("ImageFileCache", "FileNotFoundException");
            } catch (IOException e) {
                Log.w("ImageFileCache", "IOException");
            }
        }
    
        /**
         * 获取一个本地缓存的输入流
         *
         * @param key url
         * @return FileInputStream
         */
        public FileInputStream getStream(String key) {
            File file = new File(getCachePath(key));
            if (!file.exists())
                return null;
            try {
                FileInputStream inputStream = new FileInputStream(file);
                return inputStream;
            } catch (FileNotFoundException e) {
                Log.e("getStream", "FileNotFoundException");
                e.printStackTrace();
            }
            return null;
        }
    
        /**
         * 获取本地缓存路径
         *
         * @param key url
         * @return 路径
         */
        public String getDiskPath(String key) {
            File file = new File(getCachePath(key));
            if (!file.exists())
                return null;
            return file.getAbsolutePath();
        }
    
        /**
         * 是否有缓存
         *
         * @param key url
         * @return 是否有缓存
         */
        public boolean hasCache(String key) {
            File file = new File(getCachePath(key));
            return file.exists();
        }
    
        /**
         * 计算存储目录下的文件大小,
         * 当文件总大小大于规定的CACHE_SIZE或者sdcard剩余空间小于FREE_SD_SPACE_NEEDED_TO_CACHE的规定
         * 那么删除40%最近没有被使用的文件
         */
        private boolean organizeCache(@NonNull File cacheDir) {
            File[] files = cacheDir.listFiles();
            if (files == null) {
                return true;
            }
            if (!Environment.getExternalStorageState().equals(
                    Environment.MEDIA_MOUNTED)) {
                return false;
            }
    
            int dirSize = 0;
            for (int i = 0; i < files.length; i++) {
                if (files[i].getName().contains(CACHE_SUFFIX)) {
                    dirSize += files[i].length();
                }
            }
    
            if (dirSize > CACHE_SIZE * MB || FREE_SD_SPACE_NEEDED_TO_CACHE > freeSpaceOnSd()) {
                int removeFactor = (int) ((0.4 * files.length) + 1);
                Arrays.sort(files, new FileLastModifSort());
                for (int i = 0; i < removeFactor; i++) {
                    if (files[i].getName().contains(CACHE_SUFFIX)) {
                        files[i].delete();
                    }
                }
            }
    
            if (freeSpaceOnSd() <= CACHE_SIZE) {
                return false;
            }
    
            return true;
        }
    
        /**
         * 修改文件的最后修改时间
         **/
        public void updateFileTime(String path) {
            File file = new File(path);
            long newModifiedTime = System.currentTimeMillis();
            file.setLastModified(newModifiedTime);
        }
    
        /**
         * 计算sdcard上的剩余空间
         **/
        private int freeSpaceOnSd() {
            StatFs stat = new StatFs(Environment.getExternalStorageDirectory().getPath());
            double sdFreeMB = ((double) stat.getAvailableBlocks() * (double) stat.getBlockSize()) / MB;
            return (int) sdFreeMB;
        }
    
        /**
         * 根据文件的最后修改时间进行排序
         */
        private class FileLastModifSort implements Comparator<File> {
            public int compare(File arg0, File arg1) {
                if (arg0.lastModified() > arg1.lastModified()) {
                    return 1;
                } else if (arg0.lastModified() == arg1.lastModified()) {
                    return 0;
                } else {
                    return -1;
                }
            }
        }
    
    
        /**
         * 获取缓存文件的绝对路径
         */
        private String getCachePath(String key) {
            return cacheDir.getAbsolutePath() + File.separator + convertKey(key);
        }
    
        /**
         * 获取磁盘缓存文件夹 优先获取外置磁盘
         *
         * @param context    上下文
         * @param uniqueName 自定义名字
         */
        public File getDiskCacheDir(Context context, String uniqueName) {
            String cachePath;
            if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())
                    || !Environment.isExternalStorageRemovable()) {
                cachePath = context.getExternalCacheDir().getPath();
            } else {
                cachePath = context.getCacheDir().getPath();
            }
            File cacheDir = new File(cachePath + File.separator + uniqueName);
            if (!cacheDir.exists())
                cacheDir.mkdir();
            return cacheDir;
        }
    
        /**
         * 哈希编码
         */
        public String convertKey(String key) {
            String cacheKey;
            try {
                final MessageDigest mDigest = MessageDigest.getInstance("MD5");
                mDigest.update(key.getBytes());
                cacheKey = bytesToHexString(mDigest.digest());
            } catch (NoSuchAlgorithmException e) {
                cacheKey = String.valueOf(key.hashCode());
            }
            return cacheKey + CACHE_SUFFIX;
        }
    
        private String bytesToHexString(byte[] bytes) {
            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < bytes.length; i++) {
                String hex = Integer.toHexString(0xFF & bytes[i]);
                if (hex.length() == 1) {
                    sb.append('0');
                }
                sb.append(hex);
            }
            return sb.toString();
        }
    
    }
    View Code

    1.2. 预览成员变量

      

      这里定义了后缀为.cache

      然后定义了数据单位

      然后定义了缓存占用空间最大为50M

      然后定义了为SD卡保留10M

      然后定义了文件缓存的路径

      

    1.3.磁盘图片缓存器的构造函数

      

      传入一个上下文后,先获取名字叫做“web-image”的缓存文件File。

      

      然后整理缓存。

      

      传入一个File参数,返回成功与否。

    1.4.如何从缓存中获取图片

      

      先获取缓存文件的绝对路径,返回一个String

      

      然后修改文件的最后修改时间

      

      file有一个属性lastModified可以记录这个时间

    1.5.如何将图片存入文件缓存文件

      

      参数为一个key,一个Bitmap数据。意思就是将这个图片存入一个可以利用这个key找的的一个文件中。

      这里利用了file的一个函数creaetNewFile,意思就是创建一个新的文件。

      图片的保存,是利用输入流输出流来保存的。

      比如这里bitmap.compress(Bitmap.CompressFormat.PNG,100,outStream)

      这个outStream就是输出流,然后outStream.flush(),outStream.close()来完成存储。

    1.6.如何保存bytes数据?

      

      这里用了一个方法来判断SD卡中还有多少空间。

      

      返回一个MB为单位的数据。记住就好。这里应该是调用了系统函数。

      保存bytes的方法和保存图片的方法基本一样。

    1.7.如何获取一个本地缓存的输入流?

      

      首先获取缓存文件的绝对路径。

      然后生成一个FileInputStream。

      最后返回即可。

    1.8.如何获取本地缓存路径?

      

      通过一个key,获取缓存文件的绝对路径。

      可以看到这个函数和getCachePath的区别了,getCachePath已经是其中的一个调用者,getDiskPath更加细节。

    1.9.根据文件的最后修改时间进行排序

      

      这是一个内部类,里面是一个比较函数,两个参数分别代表两个文件,即可得出谁最后修改的。

    1.10.获取缓存文件的绝对路径中调用了一个哈希编码的函数

      

      传入一个key,利用MessageDigest来MD5加密。

      将bytes数组转换为String

      

      然后将返回的字符串+后缀即得到了哈希编码

      然后加到返回函数getCachePath的结果的尾部。


    2.自定义web Client

    2.1.首先继承WebViewClient没的说

      

      放入一个上下文+一个磁盘图片缓存器

    2.2.实现关键的构造函数

      

      参数为上下文,外部的上下文传进来。

      新建一个图片磁盘缓存器。

    2.3.webView加载完成之际

      

      页面加载完成后回调函数中,给webView添加了一个监听器。

    2.4.加了什么监听器呢?

      

      因为是一个webView,给webView添加监听的方法就是写javascript函数。

      当然凭什么是javascript函数呢?加注解即可解决这个问题。

      这里使用webView.loadUrl来动态加载监听器

      ==>只要有img节点的地方,将链接提取出来,然后注入了图片点击事件。

    2.5.html链接打开方式

      webView中可能会有链接。

      下面自定义打开链接的方式,用手机自带的浏览器打开这个链接即可。

      

      如果你不想用手机自带的浏览器,而是想让它在本页面本WebView直接跳转,用下面的方法:

        view.loadUrl(url);

      

    2.6.加载资源的方式

      

      如果传入的字符串链接是图片就缓存,缓存方式

        Glide.with.load.asBitmap.into==>实现onResourceReady方法==>缓存器保存这个Bitmap。

      如果传入的字符串链接是gif就保存为bytes类型,缓存方式

        Glide.with.load.asGif.into==>实现onResourceReady方法==>缓冲器保存这个bytes。

    2.7.通知主程序webView处理的资源请求

       

      这里两个都写了。

      

    2.8.获取本地资源,看看有没有缓存

      

      上方拦截网络请求的原因:

        先判断本地缓存是否有缓存图片资源,再去决定是否加载。


    3.对应js方法而建立的一个监听器类

    3.1.因为在自定义webViewClient中有一个js方法

      

      所以对应应该有一个处理这个js方法的类。 

      这个类起一个引导作用,将webView和这个js函数发生关联,所以这个类的名字类似监听器。

      

    3.2.怎么关联的呢?

      这里看一下TopicContentActivity中的部分调用代码。

      

      这里调用了webView的一个方法:addJavascriptInterface(object,name)

      所以这个类就成了联系js方法的关键。

    3.3.具体看一下这个WebImageListener的成员变量

      

      这里有一个上下文。

      一个自定义BaseImageActivity。

      一个图片集合,用ArrayList<String>来保存。

    3.4.WebImageListener的构造函数

      

      这里将外部传进来的参数给到自己。

    3.5.收集图片,添加到集合中

      

       判断url如果是gif结尾的就不保存+如果图片不在之间的集合中也不保存

    3.6.图片被点击事件调用该方法

      

      将图片集合序列封装到intent中,然后跳转到图片详细页面。


    4.总结一下

    4.1.本篇博客讲述的是:如何自定义WebViewClient,如何建立一个磁盘图片缓存器,然后如何给webView中添加js

      方法,实现点击webView中的图片,获取到所有的图片,跳转到一个图片浏览页。

    4.2.对于建立一个磁盘图片缓存,这个可能和webView的点击事件没有半毛钱关系,但是真实项目中,肯定会有

      类似这样的功能需要去实现,这个是很科学的,因为既然浏览到了图片,那么下次用户很有可能再次点击图片

      所以最好的办法就是建立一个磁盘图片缓存器。

    4.3.这个磁盘图片缓存也就是通常需要实现的方法。将图片存入文件缓存,从缓存中获取图片,gif的话存放到bytes

      数组里面,获取一个本地缓存的输入流,获取本地缓存路径,整理缓存,修改文件的最后修改时间,计算sd卡

      上剩余空间,根据文件的最后修改时间进行排序,如何获取缓存文件的绝对路径,哈希编码的这些方法都是比

      较常用的。所以这个类可以当做通用类用。

    4.4.然后是自定义WebViewClient,注意添加一个磁盘图片缓存器到里面,然后实现一些复写方法。onPageFinished

      中来添加图片点击事件,就是添加js方法,采用webView.loadUrl(一些js代码)的方式。因为继承了

      WebViewClient,实现shouldOverrideUrlLoading==>html链接打开方式,实现onLoadResource==>

      加载资源的方式,注意将图片存入缓存。复写shouldInterceptRequest==>实现网络拦截,复写

      getWebResourceResponse==>判断是否缓存了。

    4.5.WebImageListener类来沟通webView的addJavascriptInterface,里面有两个参数,第一个参数是Object,就是

      这里定义的WebImageListener,第二个参数是一个字符串,“listener”,它将js函数名和这个Object对应起来,

      将它们联系起来了。因为js函数中要求实现两个函数,window.listener.collectImage和

      window.listener.onImageClicked,然后这两个函数在WebImageListener都有具体的实现,简直完美!

    既然选择了,便不顾风雨兼程。Just follow yourself.
  • 相关阅读:
    学校的破网,你别再掉了
    PhotoShop SDK的获取
    我的C++博客开张了
    一个新的嵌入式门户
    试用Bloglines.com
    PhotoShop的插件体系
    VB6 to VB.NET Migration Guide Community Preview #4
    看看Microsoft都买了些什么
    Borland CTO辞职
    PhotoShop插件的开发
  • 原文地址:https://www.cnblogs.com/Jason-Jan/p/7886400.html
Copyright © 2020-2023  润新知