前端 WebView 指南之调试篇

WebView 是一个客户端浏览器控件,可以实现加载并渲染网页的逻辑。但是这个控件并不能完全同等于浏览器,而且我们页面的一些行为会依赖客户端的交互所以我们需要在 WebView 环境中进行调试。下面我就来说一说简单的 WebView 调试方法。

抓包

抓包即我们查看下 WebView 中的所有网络请求,在很多无法获取到机器的时候非常有用。通常页面行为不正常往往是接口请求的参数不正确,或者是接口返回的数据有异常。甚至于页面行为存在打点,根据打点请求来判断行为是否正常。通过这种“望闻问切”的方式,有些问题能够浮出水面。市面上抓包软件很多,Windows 上大家一般都是用 Fiddler, Mac 上则使用 Charles,还有其它的这里就不一一列举了。不过软件很多原理确实一样,简单的说来则是使用 HTTP 代理将所有的请求转发到软件记录本次 HTTP 请求的相关数据。

首先我们需要保证手机和电脑在同一个局域网中,软件中开启 Proxy 配置,这里以 Charles 为例,如图勾选"Enable transparent HTTP Proxying"即可。然后手机 WiFi 上配置 HTTP 代理 IP 为你的电脑 IP,端口为刚才软件中配置的端口(默认8888)。配置完后如无问题就可以在软件中看到请求流了。我们可以类似于 Chrome 开发者工具中的 Network 一样详细的查看请求的请求报文和响应报文。

Charles Enable Proxy

阅读全文

前端 WebView 指南之 iOS 交互篇

前文我们介绍了 Android 的 WebView 交互方式,iOS 从原理上来说和 Android 还是非常类似的。在 iOS 中 WebView 需要分为UIWebView 和 iOS8 中新增的 WKWebView 两种类型。其中 WKWebView 相较于 UIWebView 优势在于能够直接使用系统 Safari 渲染引擎去渲染页面,支持更多的 HTML5 特性,渲染性能也会更好点。由于对 iOS 开发了解不太多,以下的代码大多是网络整理,没有 swift 的实现,如果有任何错误还请及时联系。

客户端调用 JS

两个 WebView 类型提供了不同的调用方式,但是基本上可以归类成以下两种:

evaluateScript

在 UIWebView 中,iOS7+ 提供了 JavascriptCore 让我们能够直接在 WebView 中获取到 JSContext,也就是当前执行环境的 JS 上下文。在这里我们就可以获取到对应的 JS 方法并执行,是非常高效的执行方式。同时这种方式的好处是能够拿到 JS 执行的结果,并转换成对应的 JS 类型。定义好 jsContext 之后就可以调用 evaluateScript 方法来执行 JS 了。

- (void)webViewDidFinishLoad:(UIWebView *)webView
{
    JSContext *jsContext = [self.webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
    //设置JS执行报错捕获
    [self.jsContext setExceptionHandler:^(JSContext *context, JSValue *exception){
        NSLog(@"%@", exception);
    }];

    JSValue *value = [self.jsContext evaluateScript:@"document.title"];
    self.navigationItem.title = value.toString;
}
Objective-C 数据类型 对应 JavaScript 数据类型
nil undefined
NSNull null
NSString string
NSNumber number, boolean
NSDictionary Object object
NSArray Array object
NSDate Date object
NSBlock Function object
id Wrapper object
Class Constructor object

阅读全文

前端 WebView 指南之 Android 交互篇

WebView 是移动端应用中的一个控件,提供了类似浏览器可以在 App 中加载网页的功能。现在市面上很多应用都会使用这种方式内嵌一些 h5 页面用来实现产品功能。使用这种方式带来的好处就是支持快速迭代更新,并且页面的功能是全网升级。当然目前 RN 和 Codorva 给我们带来的热更新方案也是可以的,只是目前 Apple 的态度很拒绝,这里我们略过不表。在 WebView 中的网页势必存在和客户端进行交互的动作,进行数据的共享。下面我们就来说说 Android WebView 中 JS 和 Native 的交互方式。

客户端调用 JS

loadUrl()

我们明白 WebView 其实就是在加载网页,所以客户端可以直接访问 javascript:console.log('hello') 这样的伪 URL 即可实现在页面注入需要执行的 JS 代码。调用方法如下:

WebView webview = (WebView) findViewById(R.id.webView);

webview.loadUrl("javascript:console.log('hello')");

这样我们就实现了调用 JS 的目的了。loadUrl() 的方案从另外一个角度来看可以算是 hack 方案了,对客户端来说,他们的 JS 交互本质上其实就是一个拼接 JS 字符串的过程。

evaluateJavascript()

刚才我们也说了 loadUrl() 不是 Android 的正经解决方法。好在官方也想到了这点,在 Android 4.4+ 之后,官方给提供了原生的方法支持调用,那就是 evaluateJavascript()。这个方法最大的好处就是能够直接在一次执行的时候获取到 JS 返回的结果。如果是使用 loadUrl() 的方式的话,执行完后对客户端来说这句话就结束了,如果想要拿到返回的结果的话另外需要 JS 调用客户端的方法返回。

阅读全文

我的第一款Drone插件

Drone 是一款基于Docker的CI工具,不仅运行环境是容器化的,甚至于 CI 流中的每一个插件也是 docker 容器。基于容器的 Drone 插件一大特色就是允许我们使用任何语言去开发插件,Python, PHP, Nodejs, bash... 这些都是可以的。每个开发者都可以使用自己熟悉的语言去开发自己想要的插件。Drone 的官方插件市场罗列了很多插件,并且在文档提供了 GolangBash 版本的插件开发文档,并开源了一个插件脚手架

官方插件里有一个 drone-telegram 插件,不过插件目前是不支持自定义 telegrame 请求地址的,对于我这种机器部署在国内需要使用反代的方式访问 telegram 的用户来说不是很方便。搜索到 node-telegram-api 这个模块可以配置,下面我就简单说说基于 Nodejs 写一个 telegram 消息通知的插件。

阅读全文

反代访问 Telegram

最近有使用 Telegram 机器人相关的服务,之前是部署在境外服务器上,运行良好。某天脑子抽风把服务迁移到国内机器了,发现一直 Timeout,后来才反应过来 Telegram 被墙了。由于我使用的是 Telegram bot 相关服务,请求的都是 https://api.telegram.org 这个域名的地址,于是我就想到了能不能借助国外的机器设置反向代理解决这个问题。

中间人劫持

首先我想到的是不修改服务代码,直接将我本地的 api.telegram.org 解析到我的境外服务器,然后在服务器上做反向代理。这个方案是可行的,唯一需要解决的是服务器上 api.telegram.org 证书信任的问题。解决方法也很简单,服务器上自建证书后在本地设置强制信任即可。

这里比较坑的一点在于网上给的教程一般都是说如何在 Linux 上创建自建证书然后在客户端例如 Windows 上信任。很少有教程会说如何在 Linux 上信任的,所以当时搜的资料很少。使用如下命令可为 Linux 添加证书:

sudo mkdir /usr/share/ca-certificates/extra
sudo cp foo.crt /usr/share/ca-certificates/extra/foo.crt
sudo dpkg-reconfigure ca-certificates

阅读全文

使用 Caddy 代替 Nginx

Caddy 是一款由 Go 编写的 Web Server 工具,在折腾 Drone 的时候,我最终就是使用的它提供 Web 服务。回到标题,为什么要用 Caddy 替换掉 Nginx 呢?最主要的原因是 Caddy 能让网站自动支持 HTTPS。同样是使用 Let's Encrypt,换成 Nginx 我们就必须手工操作,并且还需要设置三个月更新证书的计划任务。而且默认还支持 http/2,很多事情都不需要我们再配置了。另外它的配置文件也比 Nginx 的要简单很多,几十行的 Nginx 配置文件 Caddy 仅需要几行就可以搞定了。

安装 Caddy

Caddy 提供了一键安装命令生成功能,访问 https://caddyserver.com/download 选择对应的平台,插件可默认为空,最后选择对应的协议后下方即会生成一键安装命令,例如:

$ curl https://getcaddy.com | bash -s personal

阅读全文

Drone安装指南

在上文《基于Docker的CI工具—Drone》中我们介绍了 Drone 以及它的优缺点,下面我将来介绍下如何配置安装 Drone。官方推荐使用 docker-compose 来启动服务,所以首先我们要准备好 docker 和 docker-compose。

安装 docker

docker 服务提供了桌面版,服务器版和云服务版不同操作系统的多种支持,可以在官方文档中找到对应的安装方法。这里以 Debain 服务器安装为例。Docker 支持 Debian 7.7+ 以上的系统版本, 需要 3.10 版本以上的 Linux 内核环境。

首先我们添加 docker 源的密钥:

$ sudo apt-get update
$ sudo apt-get install \
     apt-transport-https \
     ca-certificates \
     curl \
     gnupg2 \
     software-properties-common
$ curl -fsSL https://download.docker.com/linux/$(. /etc/os-release; echo "$ID")/gpg | sudo apt-key add -

然后我们添加 docker 源并使用 apt 安装 docker:

$ sudo add-apt-repository \
   "deb [arch=amd64] https://download.docker.com/linux/$(. /etc/os-release; echo "$ID") \
   $(lsb_release -cs) \
   stable"
$ sudo apt-get update
$ sudo apt-get install docker-ce

执行以下命令确保我们已经安装正确:

$ sudo docker run hello-world

相比 docker 的安装,docker-compose 的安装就比较简单了。它是用 python 写的一个 docker 多镜像编排启动的工具,我们可以直接使用 pip 来安装它:

$ pip install docker-compose

阅读全文

基于Docker的CI工具—Drone

drone

我在很多 Github 项目中都会使用 travis-ci 来做自动化构建任务,其中的一项主要内容就是代码提交后自动执行单元测试计算测试覆盖率。但是 travis-ci 对于其它平台(例如 Gitlab)以及公司内网仓库来说都是不支持的,所以萌生了想要找另外一款 CI 工具代替 travis-ci 的想法。对比了几款 CI 产品之后,我最终选择了 Drone。它是一款使用 Go 开发的开源的 CI 自动构建平台,能够单独部署,支持常见的 Git 仓库,例如 Github, Gitlab, Bitbucket 以及 Gogs 等。Drone 主要有以下几个方面吸引我:

阅读全文

webview中viewport初始化不生效

最近做的项目有用户反馈问题,在 WebView 中页面的宽度不对了,显示了 PC 站的大小。我使用 chrome://inspect 调试了一下,发现 WebView 中任何一个自适应网站都有问题了,并且将页面 DOM 删除到最少也还是有问题,确认是客户端 WebView 的问题。

查了下资料后,最开始怀疑是不是客户端没有加上 setUseWideViewPort(true) 让 WebView 支持 viewport 属性。客户端查了下代码告知是有的,只能想想是否是其它问题了。

后来想了一下出问题的页面都比较特别,是需要经过一个中间页的跳转的。而这个中间页是没有设置 viewport 属性的。根据规范,如果在设置了 setUseWideViewPort(true) 的 WebView 中不写 viewport 的话默认是使用屏幕宽度来进行初始化,这里的屏幕宽度是真实的屏幕宽度,所以就看起来不正常了。

由于之前已经了解过 Webview 会有一些初始化的行为,这些行为是在这个 Webview 初始化之后无法改变的,只要这个 WebView 还没有被销毁,不管你切换什么页面都是无法修改的。所以我就在怀疑是不是 Webview 最开始使用了跳转页来初始化 WebView,然后跳转页上没有设置 viewport 属性初始化行为就不正常了。后来跳转后的页面虽然有设置 viewport,但是初始化已经完成所以已经不生效了。

解决的办法也非常简单,直接在跳转页上增加 viewport 设置即可。添加之后测试了下问题真的解决了。