博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Android 拦截WebView加载URL,控制其加载CSS、JS资源
阅读量:5898 次
发布时间:2019-06-19

本文共 10549 字,大约阅读时间需要 35 分钟。

版权声明:本文为博主原创文章,转载请标明出处。 https://blog.csdn.net/lyhhj/article/details/49517537

绪论

最近在项目中有了这样一个需求,我们都知道WebView加载网页可以缓存,但是web端想让客服端根据需求来缓存网页,也就是说web端在设置了http响应头,我根据这个头来拦截WebView加载网页,去执行网络加载还是本地缓存加载。这个需求之前一直没听说过,在网上搜了一下,发现有拦截WebView加载网页这个方法,研究了一下,最终实现了,今天小编分享给大家这个开发经验:

WebView缓存机制

1.缓存模式

Android的WebView有五种缓存模式

1.LOAD_CACHE_ONLY //不使用网络,只读取本地缓存数据
2.LOAD_DEFAULT //根据cache-control决定是否从网络上取数据。
3.LOAD_CACHE_NORMAL //API level 17中已经废弃, 从API level 11开始作用同LOAD_DEFAULT模式
4.LOAD_NO_CACHE //不使用缓存,只从网络获取数据
5.LOAD_CACHE_ELSE_NETWORK //只要本地有,无论是否过期,或者no-cache,都使用缓存中的数据

2.缓存路径

/data/data/包名/cache/

/data/data/包名/database/webview.db
/data/data/包名/database/webviewCache.db

3.设置缓存模式

mWebSetting.setLoadWithOverviewMode(true);        mWebSetting.setDomStorageEnabled(true);        mWebSetting.setAppCacheMaxSize(1024 * 1024 * 8);//设置缓存大小        //设置缓存路径        appCacheDir = Environment.getExternalStorageDirectory().getPath() + "/xxx/cache";        File fileSD = new File(appCacheDir);        if (!fileSD.exists()) {            fileSD.mkdir();        }        mWebSetting.setAppCachePath(appCacheDir);        mWebSetting.setAllowContentAccess(true);        mWebSetting.setAppCacheEnabled(true);        if (CheckHasNet.isNetWorkOk(context)) {            //有网络网络加载            mWebSetting.setCacheMode(WebSettings.LOAD_DEFAULT);        } else {            //无网时本地缓存加载            mWebSetting.setCacheMode(WebSettings.LOAD_CACHE_ONLY);        }

4.清除缓存

/**     * 清除WebView缓存     */     public void clearWebViewCache(){         //清理Webview缓存数据库         try {             deleteDatabase("webview.db");              deleteDatabase("webviewCache.db");         } catch (Exception e) {             e.printStackTrace();         }         //WebView 缓存文件         File appCacheDir = new File(getFilesDir().getAbsolutePath()+APP_CACAHE_DIRNAME);         Log.e(TAG, "appCacheDir path="+appCacheDir.getAbsolutePath());         File webviewCacheDir = new File(getCacheDir().getAbsolutePath()+"/webviewCache");         Log.e(TAG, "webviewCacheDir path="+webviewCacheDir.getAbsolutePath());         //删除webview 缓存目录         if(webviewCacheDir.exists()){             deleteFile(webviewCacheDir);         }         //删除webview 缓存 缓存目录         if(appCacheDir.exists()){             deleteFile(appCacheDir);         }     }

好了,我们了解了WebView的缓存缓存机制了之后来看看到底怎么拦截WebView加载网页:

实现原理

1.要想拦截WebView加载网页我们必须重写WebViewClient类,在WebViewClient类中我们重写shouldInterceptRequest()方法,看方法名一目了然,拦截http请求,肯定是这个方法。

2.获取http请求的头,看是否包含所设置的flag,如果包含这个flag说明web端想让我们保存这个html,那么我们改怎么手动保存这个html呢?
1)获取url的connection
2)利用connection.getHeaderField(“flag”)获取http请求头信息
3)得到请求的内容区数据的类型String contentType = connection.getContentType();
4)获取html的编码格式
5)将html的内容写入文件(具体代码下面会介绍)
*3.注意:因为控制WebView加载网页的方法需要三个参数
public WebResourceResponse(String mimeType, String encoding, InputStream data)
mimeType:也就是我们第3步获取的内容区数据的类型
encoding:就是html的编码格式
data:本地写入的html文件*


那么问题来了,我们可以把html代码写到本地缓存文件中,而这个html所对应的mimeType和encoding我们存到哪里呢?因为http的头信息是http请求的属性,我们存到SP中?存到数据库中?好像都不行,无法对应关系啊。这块小编想了好久,因为小编没怎么写过文件读取这一块,最后想到把这两个参数一起存到html文件开始的几个字节,每次加载先读取这两个参数就OK了,不过这样读写比较麻烦,也比较费时,但是却给后台减少了不小的压力。看一下代码具体怎么实现的吧。

class MyWebClient extends WebViewClient {        @Override        public boolean shouldOverrideUrlLoading(WebView view, String url) {            view.loadUrl(url);            return true;        }        @TargetApi(Build.VERSION_CODES.LOLLIPOP)        @Override        public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) {            if (!"get".equals(request.getMethod().toLowerCase())) {                return super.shouldInterceptRequest(view, request);            }            String url = request.getUrl().toString();            //todo:计算url的hash            String md5URL = YUtils.md5(url);            //读取缓存的html页面            File file = new File(appCacheDir + File.separator + md5URL);            if (file.exists()) {                FileInputStream fileInputStream = null;                try {                    fileInputStream = new FileInputStream(file);                    Log.e(">>>>>>>>>", "读缓存");                    return new WebResourceResponse(YUtils.readBlock(fileInputStream), YUtils.readBlock(fileInputStream), fileInputStream);                } catch (FileNotFoundException e) {                    e.printStackTrace();                }                return null;            }            try {                URL uri = new URL(url);                URLConnection connection = uri.openConnection();                InputStream uristream = connection.getInputStream();                String cache = connection.getHeaderField("Ddbuild-Cache");                String contentType = connection.getContentType();                //text/html; charset=utf-8                String mimeType = "";                String encoding = "";                if (contentType != null && !"".equals(contentType)) {                    if (contentType.indexOf(";") != -1) {                        String[] args = contentType.split(";");                        mimeType = args[0];                        String[] args2 = args[1].trim().split("=");                        if (args.length == 2 && args2[0].trim().toLowerCase().equals("charset")) {                            encoding = args2[1].trim();                        } else {                            encoding = "utf-8";                        }                    } else {                        mimeType = contentType;                        encoding = "utf-8";                    }                }                if ("1".equals(cache)) {                    //todo:缓存uristream                    FileOutputStream output = new FileOutputStream(file);                    int read_len;                    byte[] buffer = new byte[1024];                    YUtils.writeBlock(output, mimeType);                    YUtils.writeBlock(output, encoding);                    while ((read_len = uristream.read(buffer)) > 0) {                        output.write(buffer, 0, read_len);                    }                    output.close();                    uristream.close();                    FileInputStream fileInputStream = new FileInputStream(file);                    YUtils.readBlock(fileInputStream);                    YUtils.readBlock(fileInputStream);                    Log.e(">>>>>>>>>", "读缓存");                    return new WebResourceResponse(mimeType, encoding, fileInputStream);                } else {                    Log.e(">>>>>>>>>", "网络加载");                    return new WebResourceResponse(mimeType, encoding, uristream);                }            } catch (MalformedURLException e) {                e.printStackTrace();            } catch (IOException e) {                e.printStackTrace();            }            return null;        }       }  //这里面读写操作比较多,还有截取那两个属性的字符串稍微有点麻烦   /**     * int转byte     * by黄海杰 at:2015-10-29 16:15:06     * @param iSource     * @param iArrayLen     * @return     */    public static byte[] toByteArray(int iSource, int iArrayLen) {        byte[] bLocalArr = new byte[iArrayLen];        for (int i = 0; (i < 4) && (i < iArrayLen); i++) {            bLocalArr[i] = (byte) (iSource >> 8 * i & 0xFF);        }        return bLocalArr;    }    /**     * byte转int     * by黄海杰 at:2015-10-29 16:14:37     * @param bRefArr     * @return     */    // 将byte数组bRefArr转为一个整数,字节数组的低位是整型的低字节位    public static int toInt(byte[] bRefArr) {        int iOutcome = 0;        byte bLoop;        for (int i = 0; i < bRefArr.length; i++) {            bLoop = bRefArr[i];            iOutcome += (bLoop & 0xFF) << (8 * i);        }        return iOutcome;    }    /**     * 写入JS相关文件     * by黄海杰 at:2015-10-29 16:14:01     * @param output     * @param str     */    public static void writeBlock(OutputStream output, String str) {        try {            byte[] buffer = str.getBytes("utf-8");            int len = buffer.length;            byte[] len_buffer = toByteArray(len, 4);            output.write(len_buffer);            output.write(buffer);        } catch (UnsupportedEncodingException e) {            e.printStackTrace();        } catch (IOException e) {            e.printStackTrace();        }    }    /**     * 读取JS相关文件     * by黄海杰 at:2015-10-29 16:14:19     * @param input     * @return     */    public static String readBlock(InputStream input) {        try {            byte[] len_buffer = new byte[4];            input.read(len_buffer);            int len = toInt(len_buffer);            ByteArrayOutputStream output = new ByteArrayOutputStream();            int read_len = 0;            byte[] buffer = new byte[len];            while ((read_len = input.read(buffer)) > 0) {                len -= read_len;                output.write(buffer, 0, read_len);                if (len <= 0) {                    break;                }            }            buffer = output.toByteArray();            output.close();            return new String(buffer,"utf-8");        } catch (UnsupportedEncodingException e) {            e.printStackTrace();        } catch (IOException e) {            e.printStackTrace();        }        return null;    } //为了加密我们的html我们把url转成md5 /**     * 字符串转MD5     * by黄海杰 at:2015-10-29 16:15:32     * @param string     * @return     */    public static String md5(String string) {        byte[] hash;        try {            hash = MessageDigest.getInstance("MD5").digest(string.getBytes("UTF-8"));        } catch (NoSuchAlgorithmException e) {            throw new RuntimeException("Huh, MD5 should be supported?", e);        } catch (UnsupportedEncodingException e) {            throw new RuntimeException("Huh, UTF-8 should be supported?", e);        }        StringBuilder hex = new StringBuilder(hash.length * 2);        for (byte b : hash) {            if ((b & 0xFF) < 0x10) hex.append("0");            hex.append(Integer.toHexString(b & 0xFF));        }        return hex.toString();    }

注意

功能虽然实现了,但是发现一个比较棘手的问题,就是shouldInterceptRequest()方法有两个:

1.public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
return super.shouldInterceptRequest(view, url);
}
2.public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) {}
重载的方法,第一个是已经废弃了的,SDK 20以下的会执行1,SDK20以上的会执行2,那么问题又来了,因为我们在获取http请求的时候要判断是post()请求还是get()请求,如果是post请求我们就网络加载,而get请求才去加载本地缓存,因为post请求需要参数。所以大家可以看到我上面仅仅实现了SDK20以上的新方法,而没有去关SDK20以下废弃的那个函数,因为废弃的那个函数根本获取不到请求方式,不知道是不是因为这个原因才将这个方法废弃的。这一块小编会继续研究的,一定要解决这个问题,小编已经有了思路不知道能不能实现,接下来小编会去研究一下2014年新出的CrossWalk这个浏览器插件,据说重写了底层,比webview能更好的兼容h5新特性,更稳定,屏蔽安卓不同版本的webview的兼容性问题
生命就在于折腾,小编就喜欢折腾,将Android折腾到底O(∩_∩)O~~

你可能感兴趣的文章
(转)yarn 集群部署,遇到的问题小结
查看>>
.NET程序的性能要领和优化建议
查看>>
eclipse failed to create the java virtual machine 问题图文解析
查看>>
POJ 3614 Sunscreen 优先队列
查看>>
STM8S 独立看门狗配置及使用
查看>>
Linux常用命令_(进程管理)
查看>>
[转帖]cocos2d-x 3.0rc开发指南:Windows下Android环境搭建
查看>>
(原创)拨开迷雾见月明-剖析asio中的proactor模式(一)
查看>>
Accounting_权责发生制和收付实现值的区别(概念)
查看>>
数学图形(2.16)三维螺线
查看>>
02 Architecture Overview
查看>>
[Oracle] 11G自己主动收集统计信息
查看>>
分享一个快速的Json(反)序列化开源项目 Jil
查看>>
騰訊RTX的API開發,給RTX開個天窗
查看>>
NGUI Clip Animation (UI动画)
查看>>
纯CSS3编写的面包屑导航收集
查看>>
uva208 - Firetruck
查看>>
关于缺省路由传递问题的探讨(上)[ip default-network、ip default-gateway等]
查看>>
矩阵乘法 --- hdu 4920 : Matrix multiplication
查看>>
FireFly 服务端 Unity3D黑暗世界 客户端 问题
查看>>