实战 | 封装解决WebView的那些坑

WebView 是 Android 最复杂以及最强大的一个控件(最多坑) , 一大堆的 setting 让人摸不着头脑 , 很多时候压根不知道这个设置有什么用 ,加上 WebViewClient 和 WebChromeClient 做为内部类 , 一堆业务逻辑 , 使得 Activity 变得乱糟糟的 ,代码可读性更是糟糕透了 , 最后被逼上梁山 , 走上了封装的道路 。

 

1

WebView 封装思路

 

对于 WebView 的封装 , 相信很多人都是抽象在一个基类里面 , 封装成一个 BaseWebActivity , 或者 BaseWebFragment , 对于这种封装还是不能满足像我这种有洁癖有程序员的 , 因为复用性不高 , 而且容易导致 Activity 或者 Fragment 基类膨胀 。 

 

下面向大家分享我的封装思路。

 

首先让大家看下我封装的效果

 

 

效果图

 

 

上面已经封装成一个 Web 库了 , 叫 AgentWeb , 欢迎大家使用 。

可以看到里面没有一句 WebSettings , 甚至 WebChromeClient 和 WebViewClient 都不用配置 , 使用的是简洁链式调用 。

 

AgentWeb 封装思路是通过代理 , 将 WebView 从 Activity 或者 Fragment 中代理出来 , 不再需要 Activity 或者 Fragment 内部创建和管理 ,Activity 管理 WebView 需要通过 AgentWeb , 下面通过 UML 图来简单说明下 .

 

BaseActivity 封装使用的 UML 关系图

AgentWeb 封装使用的 UML 关系图

BaseWebActivity 直接组合 WebView , 这样做为什么说复用性不高呢 ? 主要还是因为 WebView 依附在 BaseWebActivity 身上 ,要别人直接继承你的 Activity 是很不好的 ,因为 Java 的单继承关系 , 使得使用基类的灵活性受到很大的约束 , 这也是 Effective Java 里面提到的组合优先于继承 。

 

AgentWeb 则不同 , AgentWeb 是一个独立的库 , 可以让你很方便一句话就引入 , 不需要依赖 BaseWebActivity , 就像上面一样简简单单一句话引入即可。

 

AgentWeb 把 WebView 代理出来 , 将功能细分成一个类去管理 , 比如说的 WebCreator 负责创建 WebView 以及 进度条 、WebSettings 则是对 WebView 进行统一设置 , JsEntraceAccess 是对 Javascript 方法访问进行统一入口 , 这样做使得每一个功能独立 , 相互不影响 , 也使得 AgentWeb 的结构更清晰 , 符合单一职责原则 。

 

 源码太多就不贴了 ,下面分享下封装 WebView 遇到的一些问题 。

 

2

WebView 封装的一些问题与解决思路

 

WebView 的封装可谓真是一波三折啊 , WebView 实在太多坑了 , 比如说 常见的泄露 , Js 安全 ,低版本跨源问题 , Context 引致的 onJsAlert 失效 ,Android 4.4 不支持文件选择问题等等。

 

(1)内存泄露

 

这个问题在低版本不好解决就算类似下面代码通过反射制空 sConfigCallback 该字段, 还是有些手机会出现泄露 ,对于该问题 ,唯一有效方案解决在 AndroidManifest 里面为 Web Activity 添加 android:process=":web" 属性 , 然后在 该 Activity onDestroy 里面 执行 System.exit(0); 

 

下面可以解决一部分泄露

 

 

(2)addJavascriptInterface API 引起的远程代码执行漏洞

 

对于这个问题 ,我相信大家或多或少都有点了解 ,问题是由注入类引起 ,从注入类中找到 Runtime 对象 ,可以通过 Runtime 执行 shell 命令 。 

 

Google 只针对 Android 4.2.2 版本及以上版本给出了解决方法 ,为了解决兼容 4.2.2 以下版本这个问题 AgentWeb 采用 360 大牛给出的方案 , 向 Web 页面注入一段 Js 脚本 ,然后通过脚本弹 Prompt 向 Java 通信 ,解决了 4.2.2 以下版本 addJavascriptInterface 安全通信问题 。 

 

下面给出大致实现

 

比如下面注入类

 

 

低于 Android 4.2.2 的版本上面的注入对象会经过包装拼接成如下脚本 。

 

注入的脚本

 

 

 

通过 webView.loadUrl(脚本) 注入上面的脚本 。

 

Js 执行下面方法

 

 

就会执行上面的 function 里面的方法体 ,Android 端回调onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) message 参数里面取出 js 要调的目标方法 , 然后通过反射调用该目标方法 。

 

(3)对于同源跨域攻击问题

 

什么是同源策略吗 ? 同源策略是由 Netscape 提出来的 ,现在主流的浏览器都遵循这种策略 ,同源是一般指

http://(协议)www.google.com(主机):8080 (端口) 

 

三要素都相同 ,但实际上并不是那么严格 , 比如 IE 就会忽略对端口的判断 。

 

同源有什么作用吗 ?
 

同源的数据默认为可以安全访问的 。 比如说url http://www.google.com:xxxx/login 登录后浏览器就会把返回来的 cookies 保存起来 , 对于 http://www.google.com:xxxx/index.html 对于这个 url 浏览器会默认为跟前者同源 , 那么这个 url 可以无缝的访问到 login 保存下来的 cookies 。

 

大家都知道 Android 应用之间的文件和数据一般情况下是不能相互访问的 , 但是不正确的使用 WebView ,会打破这种状况 , 比如说以下这种使用

将该 Activity 设置 exported="true" , 其他应用就可以通过隐式启动 ,将 data 作为 url ,启动的该应用 , 让该应用加载脚本 ,遍历该应用内的文件或者私密文件 , 上传服务器 。

 

这个问题一直存在 Android 手机中 ,这个问题 Google 并没有修复它 ,只是在 4.2 后的版本把 setAllowFileAccessFromFileURLs 以及 mWebSettings.setAllowUniversalAccessFromFileURLs 设置为 false , 用户没有刻意去开启它 , 高于 Android 4.2 版本默认是安全的 。 

 

对于该问题 AgentWeb 使用的是以下设置 。

 

(4)Context 引致的 onJsAlert 失效

 

这个问题根本原因在防止泄露时候创建 new  WebView(activity.getApplicationContext()); 导致 , 很正常啊 , 因为你创建 WebView 传的是 Application , Application 本身是无法弹 Dialog 的 。

 

 所以只能无反应 !这个问题解决方案只要你创建 WebView 时候传入 Activity , 或者 自己实现 onJsAlert 方法即可。

 

(5)Android 4.4 文件访问

 

Android 4.4 WebView 内核正式有 WebKit 替换 为 Chromium 使得很多 Api 都废弃掉了,这是 Google 正式宣告抛弃 Webkit 的一个句号 。 

 

所以要兼容 Android 4.4 以下的 WebView 的应用特别难受 。回到正题 ,4.4 文件访问 ,你会发现 4.4 点击 input 标签没反应 ,瞬间一万只曹尼玛在崩腾 , 幸庆的是还有 Js 通信 ,解决方案:可以通能过 Js 访问 Java 然后打开文件选择器 ,拿到文件后 ,将文件转成 Base64 字符串回传给 Js ,因为拿到的文件路径是 Content:// 开头 JS 是无法解析的 。(这个代码跨度有点大 , 就不贴源码了 ,有兴趣可以克隆仓库看下)

 

3

WebView 封装后使用

 

App 下载

https://github.com/Justson/AgentWeb/blob/master/agentweb.apk

 

AgentWeb 在 Fragment 中使用

 

 

跟原先 WebFragment 比简洁多了 。

 

Js 调用 :

 

function callByAndroid(msg1,msg2){
  console.log("callByAndroid")
}

 

没有经过封装的

 

mWebView.loadUrl("javascript:callByAndroid("+"\"hello\""+","+"\" js\""+")");

 

封装后

 

mAgentWeb.getJsEntraceAccess().quickCallJs("callByAndroid","Hello","js");

 

Github:

https://github.com/Justson/AgentWeb

  • 3
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值