Android 拦截WebView加载URL,控制其加载CSS、JS资源

简介: 版权声明:本文为博主原创文章,转载请标明出处。 https://blog.csdn.net/lyhhj/article/details/49517537 绪论最近在项目中有了这样一个需求,我们都知道WebView加载网页可以缓存,但是web端想让客服端根据需求来缓存网页,也就是说web端在设置了http响应头,我根据这个头来拦截WebView加载网页,去执行网络加载还是本地缓存加载。
版权声明:本文为博主原创文章,转载请标明出处。 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~~

相关文章
|
1月前
|
Java 程序员
JAVA程序员的进阶之路:掌握URL与URLConnection,轻松玩转网络资源!
在Java编程中,网络资源的获取与处理至关重要。本文介绍了如何使用URL与URLConnection高效、准确地获取网络资源。首先,通过`java.net.URL`类定位网络资源;其次,利用`URLConnection`类实现资源的读取与写入。文章还提供了最佳实践,包括异常处理、连接池、超时设置和请求头与响应头的合理配置,帮助Java程序员提升技能,应对复杂网络编程场景。
64 9
|
5月前
|
存储 前端开发 Java
SpringBoot使用云端资源url下载文件的接口写法
在Spring Boot中实现从云端资源URL下载文件的功能可通过定义REST接口完成。示例代码展示了一个`FileDownloadController`,它包含使用`@GetMapping`注解的方法`downloadFile`,此方法接收URL参数,利用`RestTemplate`下载文件,并将文件字节数组封装为`ByteArrayResource`返回给客户端。此外,通过设置HTTP响应头,确保文件以附件形式下载。这种方法适用于从AWS S3或Google Cloud Storage等云服务下载文件。
521 7
|
1月前
|
Java 开发者
JAVA高手必备:URL与URLConnection,解锁网络资源的终极秘籍!
在Java网络编程中,URL和URLConnection是两大关键技术,能够帮助开发者轻松处理网络资源。本文通过两个案例,深入解析了如何使用URL和URLConnection从网站抓取数据和发送POST请求上传数据,助力你成为真正的JAVA高手。
64 11
|
2月前
|
程序员 开发工具 Android开发
Android|WebView 禁止长按,限制非白名单域名的跳转层级
如何限制 WebView 仅域名白名单网址能随意跳转,并禁用长按选择文字。
44 2
|
4月前
|
安全 Java 网络安全
【认知革命】JAVA网络编程新视角:重新定义URL与URLConnection,让网络资源触手可及!
【认知革命】JAVA网络编程新视角:重新定义URL与URLConnection,让网络资源触手可及!
49 2
|
4月前
|
Android开发
【Azure 环境】移动应用 SSO 登录AAD, MSAL的配置为Webview模式时登录页面无法加载
【Azure 环境】移动应用 SSO 登录AAD, MSAL的配置为Webview模式时登录页面无法加载
|
5月前
|
分布式计算 DataWorks 调度
DataWorks产品使用合集之多个业务流程上传同名资源到同一个OSS(对象存储服务)URL,会产生什么问题
DataWorks作为一站式的数据开发与治理平台,提供了从数据采集、清洗、开发、调度、服务化、质量监控到安全管理的全套解决方案,帮助企业构建高效、规范、安全的大数据处理体系。以下是对DataWorks产品使用合集的概述,涵盖数据处理的各个环节。
|
6月前
|
安全 Java 网络安全
【认知革命】JAVA网络编程新视角:重新定义URL与URLConnection,让网络资源触手可及!
【6月更文挑战第22天】JAVA网络编程中,URL代表统一资源定位符,用于表示网络资源地址。通过`new URL(&quot;address&quot;)`创建URL对象,可解析和访问其组件。URLConnection是与URL建立连接的接口,用于定制HTTP请求,如设置GET/POST、超时及交换数据。
46 1
|
5月前
|
缓存 安全 Java
Android深入Binder拦截问题分析
【7月更文挑战第1天】Android Binder 拦截可实现虚拟化、测试、SDK检测、逆向分析及ROM扩展。通过Java层aidl代理,利用IBinder接口规范来拦截通信。拦截步骤包括:替换Binder服务缓存对象,如ActivityManagerService;代理ServiceManager以控制服务获取。此操作需系统权限,可能涉及安全风险和版本差异,非必要时应谨慎。
|
5月前
|
存储 SQL Python
`urllib.parse`模块是Python标准库`urllib`中的一个子模块,它提供了处理URL(统一资源定位符)的实用功能。这些功能包括解析URL、组合URL、转义URL中的特殊字符等。
`urllib.parse`模块是Python标准库`urllib`中的一个子模块,它提供了处理URL(统一资源定位符)的实用功能。这些功能包括解析URL、组合URL、转义URL中的特殊字符等。