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

Diycode开源项目 磁盘图片缓存+自定义webViewClient+图片点击js方法的更多相关文章

  1. Diycode开源项目 ImageActivity分析

    1.首先看一下效果 1.1做成了一个GIF 1.2.我用格式工厂有点问题,大小无法调到手机这样的大小,目前还没有解决方案. 1.3.网上有免费的MP4->GIF,参考一下这个网站吧. 1.4.讲 ...

  2. Diycode开源项目 搭建可以具有下拉刷新和上拉加载的Fragment

    1.效果预览 1.1.这个首页就是一个Fragment碎片,本文讲述的就是这个碎片的搭建方式. 下拉会有一个旋转的刷新圈,上拉会刷新数据. 1.2.整体结构 首先底层的是BaseFragment 然后 ...

  3. Diycode开源项目 BaseApplication分析+LeakCanary第三方+CrashHandler自定义异常处理

    1.BaseApplication整个应用的开始 1.1.看一下代码 /* * Copyright 2017 GcsSloop * * Licensed under the Apache Licens ...

  4. Diycode开源项目 TopicContentActivity分析

    1.效果预览以及布局分析 1.1.实际效果预览 左侧话题列表的布局是通过TopicProvider来实现的,所以当初分析话题列表就没有看到布局. 这里的话题内容不是一个ListView,故要自己布局. ...

  5. DiyCode开源项目 BaseActivity 分析

    1.首先将这个项目的BaseActivity源码拷贝过来. /* * Copyright 2017 GcsSloop * * Licensed under the Apache License, Ve ...

  6. Diycode开源项目 Glide图片加载分析

    1.使用Glide前的准备 1.1.首先要build.gradle中添加   github原地址点击我. 参考博客:Glide-开始! 参考博客:android图片加载库Glide的使用介绍. 参考博 ...

  7. Diycode开源项目 MainActivity分析

    1.分析MainActivity整体结构 1.1.首先看一下这个界面的整体效果. 1.2.活动源代码如下 /* * Copyright 2017 GcsSloop * * Licensed under ...

  8. DiyCode开源项目 TopicActivity 分析

    1.首先看看TopActivity效果.    2.TopicActivity是一个继承BaseActivity的.前面分析过BaseActivity了.主要有一个标题栏,有返回的图标. 3.贴一下T ...

  9. Diycode开源项目 SitesListFragment分析

    1.效果预览 1.1.网站列表实际界面 1.2.注意这个界面没有继承SimpleRefreshRecycleFragment 前面的话题和新闻继承了SimpleRefreshRecyclerFragm ...

随机推荐

  1. 10.&与&&以及位运算符。

    这是单独的一块,因为一条讲不清楚(虽然内容也不够一篇),而且我之前也没好好弄清楚,所以有必要写出来. 说位运算符也是从&与&&(|与||类似)之间的区别讲起的.事实上,对于两个 ...

  2. CSS文档优化

    首先了解下CSS的渲染逻辑,它是从标记的最后一位开始搜索的,例如:.myclass li a,首选它会遍历所有的<a>,然后看哪些<a>之前有<li>,然后再看哪些 ...

  3. nsight 中出现method could not be resolved 报错

    解决的方法就是现在编译选项中取消该报错. 项目右键->属性->c/c++常规->Code Analysis,选择"Use project settings"  中 ...

  4. 不该被忽视的CoreJava细节(四)

    令人纳闷的数组初始化细节 这个细节问题我很久以前就想深入研究一下,但是一直没有能够抽出时间,借这系列文章的东风,尽量解决掉这个"心头病". 下面以一维int数组为例,对数组初始化方 ...

  5. windows10下git报错warning: LF will be replaced by CRLF in readme.txt. The file will have its original line endings in your working directory.

    window10下使用git时 报错如下: $ git add readme.txtwarning: LF will be replaced by CRLF in readme.txt.The fil ...

  6. Git 推送和删除标签

    事实上Git 的推送和删除远程标签命令是相同的,删除操作实际上就是推送空的源标签refs:git push origin 标签名相当于git push origin refs/tags/源标签名:re ...

  7. 碰到一个微软的bug:CWinAppEx::GetString

    在调试公司项目代码的时候,有一个系统设置的功能,里面需要从注册表中去读取数据,因为使用了MFC框架所以,为了简化代码直接使用了CWinAppEx::GetString .CWinAppEx::SetS ...

  8. [VC]strcpy memcpy memset区别与简介

    strcpy 原型:extern char *strcpy(char *dest,char *src); 用法:#include <string.h> 功能:把src所指由NULL结束的字 ...

  9. apache的安全增强配置(使用mod_chroot,mod_security)

    apache的安全增强配置(使用mod_chroot,mod_security) 作者:windydays      2010/8/17 LAMP环境的一般入侵,大致经过sql注入,上传webshel ...

  10. 在PHP中读取二进制文件

    很多时候,数据并不是用文本的方式保存的,这就需要将二进制数据读取出来,还原成我们需要的格式.PHP在二进制处理方面也提供了强大的支持. 任务 下面以读取并分析一个PNG图像的文件头为例,讲解如何使用P ...