知乎客户端跨平台 Hybrid 调试实战
背景
知乎客户端中有一个自己维护的 Hybrid 框架,在此基础上开发了一些 Hybrid 页面,当需要前端或者客户端开发接口的时候,就涉及到联调的问题。
和一般的 前端 <=> 服务端,或者 客户端 <=> 服务端 类似,前端 <=> 客户端也会出现联调的各种问题,但是往往 Hybrid 开发相关的调试工具并不是那么完备。
由于在 Hybrid 里面,前端和客户端联系是很紧密的,假如一个人既熟悉前端开发又熟悉客户端开发,调试是很容易的,但是现实一般是可能客户端开发并不是那么熟悉前端开发,前端开发也不熟悉客户端开发,出现问题的时候需要两个人一起排查。
对于大多数公司来说,开发一整套的调试工具是不现实的,因为没有这么多的资源去做,短时间内,我们如何提供出可靠便利的调试工具呢?
通信日志
对于前端来说,客户端开发是一个黑盒,当遇到调试问题的时候,前端会比较迷茫,无法确定是自己调用的有问题,还是客户端的代码写的有问题。最简单的方式我们在通信发生的时候,使用约定的格式,在两端都打出日志
客户端通过执行一段 js
webview.evaluateJavascript("console.log('" + msg + "')", valueCallback);
使得在 ChromeDevTools 的 console 里面能看到打出的日志,全面描述这次通信的详细过程,将黑盒在一定程度上变成白盒。
全局日志
有时候前端除了需要通信日志之外,可能还需要其他客户端日志,简单的方式是前端可以通过 adb 获取日志,这里是官方使用文档:logcat 命令行工具
这里推荐一个工具:flipper..官网,也能比较简单方便的查看日志,具体用法将在后面 flipper 中详细介绍
接口测试
客户端开发了一个接口,要给前端使用,在交付之前,需要测试一下,但是客户端又不会写前端代码,可能就直接会交给前端自己去测,假如有问题,来来回回很麻烦并且耗费时间,我们需要提供一种方式让客户端能够较好的测试,并且不依赖前端。在交付给前端之前,达到一定的质量标准,而不是说等到前端介入开发,才能去测试这个接口。
客户端接口一般分两种,
1.一种是所有所有页面都可以调用的,并不依赖特定的环境,举个例子:一个获取用户信息的接口,在任意页面发起这个调用,都会有同样的结果。
2.一种是在特定页面才可以调用的,比如获取当前回答的状态,必须要在回答页访问这个接口才有效。
本地模拟
针对第一类接口,我们开发一个普通的网页,上面列有所有接口的示例参数,和模拟调用的按钮,例如
此刻,在手机上打开上面的网页,点击 dispatch 按钮即可。
但是这种方式无法针对第二种接口做测试。
并且还有一个很大的问题,当接口参数稍微复杂一点,在手机上编辑这些参数是非常不方便的,变更参数是一件很不愉快的体验。
于是我们提供了一个在 PC 网页上调用客户端接口的方式。
远程调用
最初的时候,我给上面的网页加了一个扩展的功能,增加了一个二维码
PC 端打开网页,用客户端扫码进入一个 Hybrid 页面后,点击 dispatch 会在客户端上的当前 Hybrid 页面调用相应的 api。
又或者,你打开了一个其他 Hybrid 页面,点击 dispatch,就会在这个页面调用接口。
这么做的原理主要是让 PC 端和 客户端能发生通信,然后在客户端的每一个 Hybrid 网页注入一段模拟调用的 js.
做这样的功能,需要维护一个 socket 服务,PC端需要和这个服务器通信,客户端也需要,开发起来依旧是比较麻烦。但也算较好的解决了 本地模拟 的一些问题。
在开发上述功能的过程中,发现了 flipper 这个工具
flipper 提供了一个桌面客户端,然后这个桌面客户端提供了一个和手机客户端通信的机制,免去了 socket 服务的开销,依靠这个通信机制,我们可以把上述的功能复制过来
基于这个通信机制把 api 名称和 api 参数传到手机客户端上,然后再在客户端里注入的 js 调用相应的 api,无需再维护 socket 服务
使用 flipper
希望全面了解 flipper 的可以上 https://fbflipper.com/
具体文档查看:flipper 文档
工程中就基于 flipper 开发了一个 api 调试的功能,以及获取当前 Hybrid 页面的客户端数据,下面有一个简单的示例,简单介绍一下接入流程和开发一个插件
桌面插件
需要有简单 React 组件开发经验,不会的话,照葫芦画瓢 10 分钟也能搞定。
桌面插件是一个 React 组件,用来描述界面,接受发送数据
0.前置条件
下载 Node,Yarn
Node.js
Yarn
1.创建工程
创建目录,执行 yarn init,目录下会出现一个文件 package.json 注意 name得以 flipper-plugin- 开头
{
"name": "flipper-plugin-myplugin",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
"dependencies": {}
}
2.开始编程
创建 index.js,在 index.js 里面可以编写自己的插件,一个简单的组件可能是这样。
这个插件有三个要注意的点
id 为 "myplugin",id 很重要,桌面插件和客户端插件 id 一样的话才能发生通信
订阅了客户端 "clientMessage" 的消息
给客户端发送了 "getData" 的消息(稍后会在客户端中接收这个消息)
import {FlipperPlugin, Button, FlexCenter} from 'flipper';
export default class extends FlipperPlugin {
static title = 'My Plugin';
static id = 'myplugin';
static icon = 'internet';
constructor(props) {
super(props);
this.init();
}
init(){
this.state = {
data: 'null',
message: 'null'
};
//订阅客户端发送的数据
this.client.subscribe('clientMessage', res => {
if(res){
this.setState({message:res.message});
}
});
}
sendData(){
//给客户端发消息
this.client.call('getData', {request:""}).then(res => {
this.setState({data:res.data});
});
}
render() {
const {data, message} = this.state;
return (
<FlexCenter style={mainStyle}>
<Button style={commonStyle} onClick={this.sendData.bind(this)}>点我调用 getData</Button>
<p style={commonStyle}>{'getData 的内容是:' + data}</p>
<p style={commonStyle}>{'clientMessage 的内容是:' + message}</p>
</FlexCenter>
)
}
}
const mainStyle = {
"margin-top":60,
"flex-direction":"column"
}
const commonStyle = {
margin: 20,
};
3.加载插件
我编写完桌面插件之后,按照 flipper 提示的方式进行配置,还是无法动态加载我写的插件,最后我是把 flipper 的源码拉下来,将插件工程放到了
flipper/src/plugins/ 文件夹下面,然后执行 yarn build --version --mac 在 dist 文件夹下直接打出包,再使用的。
我不确实这是我的问题还是 flipper 的问题。
这是成功加载起插件的图例,假如没有写对应手机客户端插件的话,是不会显示我们写好的 MyPlugin,所以接下来介绍怎么写一个对应的手机客户端插件
客户端插件
0.依赖
当大家想接入一个 debug 相关的库的时候,可能会考虑说,不想把 debug 的功能带到线上去,有几种做法,大家自行选用
a.在代码中去判断一下,在恰当的时候,把 debug 相关的功能给关掉
好处是做起来很简单轻松
坏处是会在线上引入一个线上根本不需要的库,增大了包体积
b.在非线上的时候引入正式库,在线上包中引入 'no-op' 库,'no-op' 库可以理解为正式库的一个空实现, 这里有一个讨论 关于 no-op 的讨论
好处是不会增大太多包体积,虽然还是要引入一个不必要的包
不好的地方可能就是如果官方没有实现 no-op 的库,就需要你自己写
例如:
debugImplementation 'com.facebook.stetho:stetho-okhttp3:1.5.0'
releaseImplementation 'com.zhihu.android.library:stetho-no-op:1.0.0'
这种方式似乎也是很多类似库通用的处理方式
c.第三种,新建一个文件夹,比如 flipper,在里面写相关初始化代码,通过某种方式执行这些代码,然后再在 sourceSets 加上 srcDirs 即可 下面的意思是在 mr 或者 debug 包中才加上 srcDirs
dependencies {
...
debugImplementation 'com.facebook.flipper:flipper:0.11.1'
mrImplementation 'com.facebook.flipper:flipper:0.11.1'
}
android {
sourceSets {
...
debug {
java.srcDirs += "src/flipper/java";
}
mr {//
java.srcDirs += "src/flipper/java";
}
}
1.初始化
/** 官方示例 */
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
SoLoader.init(this, false);
/** 这里是 flipper 官方的示例代码,实际上在我们的工程中不是这么做的,实际工程中使用了上面依赖方式的第三种 */
if (BuildConfig.DEBUG && FlipperUtils.shouldEnableFlipper(this)) {
final FlipperClient client = AndroidFlipperClient.getInstance(this);
client.addPlugin(new InspectorFlipperPlugin(this, DescriptorMapping.withDefaults()));
client.addPlugin(new MyFlipperPlugin());
client.start();
}
}
}
/**
* 我们的方式,示例代码,下面精简了一点
*(我们的工程里有一个 Task,在编译 app 时 gradle 插件会自动定位到所有的 Task,并运行时生成依赖图,按依赖顺序启动 Task)
*/
public class FlipperTask extends Task{
public FlipperTask(String name) {
super(name);
}
@Override
public void afterSetup() {
setScheduler(AndroidSchedulers.mainThread());
beFinalizedBy("Task1");
}
@Override
public void onRun() {
final Application app = (Application) getInput("app");
SoLoader.init(app, false);
FlipperClient client = AndroidFlipperClient.getInstance(app);
if (client != null){
client.addPlugin(new InspectorFlipperPlugin(app, DescriptorMapping.withDefaults()));
client.addPlugin(new MyFlipperPlugin());
client.start();
}
}
}
2.编写插件
注意
id 要和桌面插件一致
同时实现了 "getData" 的接收,和 "clientMessage" 的发送
public class MyFlipperPlugin implements FlipperPlugin { ¨K7K }
这样一个简单的插件就写完了,赶紧跑起来试用一下吧!
手机客户端:
桌面客户端:
最后,通过这一个简单的 demo,我们把传递的参数改成 ' Hybrid 接口' 和 ' Hybrid 接口的参数',指示客户端进行的接口调用,复制出远程调用的功能就可以了。
总结
最终,基于方便的日志查看,基于 flipper 开发的各类插件,比如远程调用 Hybrid 接口,获取当前 Hybrid 数据等等,可以在短时间之内提供一个较好的调试和测试体验。
让测试不需要了解开发细节,客户端开发能独立测试,前端能快速自己确定问题,降低开发中联调的耦合程度。
现在很多公司都会有许多 Hybrid 页面,如何低成本的调试一直是一个问题,大家好什么好的想法也可以一起交流。
最后
针对Android程序员,我这边给大家整理了一些资料,包括不限于高级UI、性能优化、架构师课程、NDK、混合式开发(ReactNative+Weex)微信小程序、Flutter等全方面的Android进阶实践技术;希望能帮助到大家,也节省大家在网上搜索资料的时间来学习,也可以分享动态给身边好友一起学习!
资料领取:点赞+加群免费获取 Android IOC架构设计
加群 Android IOC架构设计领取获取往期Android高级架构资料、源码、笔记、视频。高级UI、性能优化、架构师课程、混合式开发(ReactNative+Weex)全方面的Android进阶实践技术,群内还有技术大牛一起讨论交流解决问题。