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都有具体的实现,简直完美!

 

 

posted @ 2017-11-23 18:35  Jason_Jan  阅读(530)  评论(0编辑  收藏  举报