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

WebView 封装思路

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

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

mAgentWeb = AgentWeb.with(this)//传入Activity.setAgentWebParent(mLinearLayout, new LinearLayout.LayoutParams(-1, -1))//传入AgentWeb 的父控件 ,如果父控件为 RelativeLayout , 那么第二参数需要传入 RelativeLayout.LayoutParams.useDefaultIndicator()// 使用默认进度条.defaultProgressBarColor() // 使用默认进度条颜色.setReceivedTitleCallback(mCallback) //设置 Web 页面的 title 回调.createAgentWeb()//.ready().go("http://www.jd.com");

效果图

jd.png

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

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

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

BaseActivity 封装使用的 UML 关系图

common_.png

AgentWeb 封装使用的 UML 关系图

agentweb结构.png

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

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

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

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

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

内存泄露

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

                Field field = WebView.class.getDeclaredField("mWebViewCore");field = field.getType().getDeclaredField("mBrowserFrame");field = field.getType().getDeclaredField("sConfigCallback");field.setAccessible(true);field.set(null, null);

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

对于这个问题 ,我相信大家或多或少都有点了解 ,问题是由注入类引起 ,从注入类中找到 Runtime 对象 ,可以通过 Runtime 执行 shell 命令 。 Google 只针对 Android 4.2.2 版本及以上版本给出了解决方法 ,为了解决兼容 4.2.2 以下版本这个问题 AgentWeb 采用 360 大牛给出的方案 , 向 Web 页面注入一段 Js 脚本 ,然后通过脚本弹 Prompt 向 Java 通信 ,解决了 4.2.2 以下版本 addJavascriptInterface 安全通信问题 。 下面给出大致实现

比如下面注入类

public class AndroidInterface {   public void callAndroid(final String msg) {Log.i("Info",""+msg);}
}
 mAgentWebView.addJavascriptInterface("android",new AndroidInterface()); //注意 mAgentWebView 是 WebView 子类 , 并重写了 addJavascriptInterface 方法 。

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

注入的脚本

.....省略N多 Js 代码
android.callAndroid = function() {.....省略N多 Js 代码var m = prompt('AgentWeb: ' + JSON.stringify({method: l, types: e, args: f}));var g = JSON.parse(m);if (g.code != 200) {throw "Android call error, code:" + g.code + ", message:" + g.result}return g.result};

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

Js 执行下面方法

function sendHelloToAndroid() {    window.android.callAndroid("你好,Android! ");}

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

对于同源跨域攻击问题

什么是同源策略吗 ? 同源策略是由 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 ,会打破这种状况 , 比如说以下这种使用

public void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_webview);webView = (WebView) findViewById(R.id.webView1);webView.getSettings().setAllowFileAccess(true);                    webView.getSettings().setJavaScriptEnabled(true);                   webView.getSettings().setAllowFileAccessFromFileURLs(true);       webView.getSettings().setAllowUniversalAccessFromFileURLs(true); Intent i = getIntent();String url = i.getData().toString(); webView.loadUrl(url);}

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

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

         mWebSettings.setAllowFileAccess(true); //允许file 协议 , 加载本地文件  mWebSettings.setAllowFileAccessFromFileURLs(false); //禁止通过 file url 加载的文件执行 Javascript 读取其他的本地文件 .mWebSettings.setAllowUniversalAccessFromFileURLs(false);//禁止通过 file url 加载的文件执行 Javascript 可以访问其他的源 如  http , https 。

Context 引致的 onJsAlert 失效

这个问题根本原因在防止泄露时候创建 new WebView(activity.getApplicationContext()); 导致 , 很正常啊 , 因为你创建 WebView 传的是 Application , Application 本身是无法弹 Dialog 的 。 所以只能无反应 !这个问题解决方案只要你创建 WebView 时候传入 Activity , 或者 自己实现 onJsAlert 方法即可。

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 是无法解析的 。(这个代码跨度有点大 , 就不贴源码了 , 有兴趣可以克隆仓库看下)

WebView 封装后使用

App 下载

AgentWeb 在 Fragment 中使用

    @Overridepublic void onViewCreated(View view, @Nullable Bundle savedInstanceState) {super.onViewCreated(view, savedInstanceState);mAgentWeb = AgentWeb.with(this)// Fragment  传入.setAgentWebParent((LinearLayout) view, new LinearLayout.LayoutParams(-1, -1))// 设置 AgentWeb 的父控件 , 这里的view 是 LinearLayout , 那么需要传入 LinearLayout.LayoutParams.useDefaultIndicator()// 使用默认进度条.setReceivedTitleCallback(mCallback) //标题回调.setSecurityType(AgentWeb.SecurityType.strict) //注意这里开启 strict 模式 , 设备低于 4.2 情况下回把注入的 Js 全部清空掉 , 这里推荐使用 onJsPrompt 通信.createAgentWeb()//.ready()//.go(getUrl());}

跟原先 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

作者:Justson
链接:http://www.jianshu.com/p/d657580ac643
來源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

封装解决WebView的那些坑相关推荐

  1. 微信小程序模板消息(服务通知消息)原始post工具封装(不使用jar包--坑比较多),解决47001(JSON格式)和中文乱码问题

    微信小程序模板消息(服务通知消息)原始post工具封装(不使用jar包--坑比较多),解决47001(JSON格式)和中文乱码问题 参考文章: (1)微信小程序模板消息(服务通知消息)原始post工具 ...

  2. 安卓WebView的那些坑

    1.手机以及电脑浏览器均能打开,而WebView打开该网址却不显示任何东西 项目中,要显示一个URL详情,其实就是使用WebView来打开一个URL而已啦!本来都好好的,某一天,测试的过程发现该Web ...

  3. android webview缺点,Android的WebView有哪些坑?

    今天逛知乎的时候,看到一个有关 WebView 的坑很多,主要的比较让人疑惑的坑有如下几个: 1.WebViewClient.onPageFinished().你永远无法确定当WebView调用这个方 ...

  4. android webview 重定向 多次load问题,解决webview 第二次调用loadUrl页面不刷新的问题...

    一个需求,当点击button按钮时,希望加载另一个url. 以下方法可以成功! @override public void onclick(view view) { webview.loadurl(u ...

  5. 笨办法学python3 pdf 脚本之家_解决python3输入的坑——input()

    如下所示: a,b,c,d = input() 很简单的代码,如果输入为 1 -1 -2 3 结果会报错,原因在于input函数会将你的输入作为python脚本运行,那么输入就变成了 1-1 -2 3 ...

  6. vue项目中将视频链接分享至推特的解决方法及踩坑记录

    vue项目中将视频链接分享至推特的解决方法及踩坑记录 将动态改变的视频链接分享至推特,并希望能直接在推特上播放视频的需求实现方法及踩坑记录 如果只要将文本或链接分享到推特,不需要推特识别图片/视频等媒 ...

  7. 安卓webview的一些坑

    WebView可以很好地帮助我们展示html页面,但是webview使用不当的话还是可能产生一定问题的,下面就以下几个方面说说我的优化技巧 1.展示webview的activity可以另开一个进程,这 ...

  8. 解决webview.loadUrl()会加载系统浏览器

    转载自:http://blog.csdn.net/lieri111/article/details/8213069 第一种解决方法(使用匿名内部类) webView.getSettings().set ...

  9. 解决Webview加载不完全导致部分js无效

    问题出现 有两种情况: 一种情况是打开activity时webview开始加载页面,但是发现加载了一部分后就停止了,余下的一直不再加载.但是当关闭这个activity时发现webview又继续加载了. ...

  10. Android之解决webview加载第三方网页点击弹不出下拉框(html页面里面的select标签)

    1 问题 决webview加载第三方网页点击弹不出下拉框(html页面里面的select标签),我们访问youtube.com官网,点击网站的视频,点击视频右上角三个点设置,然后点击 播放设置 然后点 ...

最新文章

  1. java初学者笔记总结day9
  2. js获取微信验证实现微信自动跳转 检验code操作
  3. linux下的socket通信小程序分享——第三圣子
  4. 【python】画一个爱心
  5. 信源压缩编码 编程c语言,霍夫曼信源编码实验报告.docx
  6. geoserver 报错 HTTP ERROR: 503
  7. 对Spring框架的理解(转)
  8. vue差(插)值表达式
  9. linux android交叉编译,【转】搭建arm-linux-gcc交叉编译工具链环境(Android原生(JNI)开发环境搭建)...
  10. pb9 调用系统语音_语音通知解决方案,VIKI语音通知软件介绍
  11. 如何搜c语言考题答案,C语言考题答案.doc
  12. 一键清空服务器文件,一键清理操作系统垃圾文件的BAT
  13. Linux中arp表的老化机制
  14. 【无标题】How to Programe 2021 GMC Terrain Smart Key Programming by Autel IM508
  15. OpenWRT配置 -- 网络配置network文件
  16. chromecast 协议_如何解决常见的Google Chromecast问题
  17. Spring data jpa + sqlserver + druid, druid连接sqlserver 报错:查询超时值 -1 无效。
  18. 记swagger离线文档乱码解决
  19. java使用redis incr,JFinal Redis plugin 有关数值类型incr操作的bug
  20. SSL协议原理(Secure Socket Layer)【转载】

热门文章

  1. Unity 正则表达式
  2. 记Dorado7学习(3)
  3. 蓝宝石rx580怎么超频_【蓝宝石 RX580 8G D5 超白金 OC 显卡使用总结】游戏|界面|按钮|频率_摘要频道_什么值得买...
  4. java解析json list
  5. 火星坐标转换 php,百度经纬度和火星坐标转换
  6. matlab二重定积分_MATLAB计算二重数值积分(dblquad)
  7. 智慧城市项目在PPP模式中的应用
  8. 腾讯云支付平台配置掌优云音响
  9. mysql随机生成中文姓名_编写mysql函数 随机生成中文姓名
  10. Java代理模式及其应用