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