YorkShen closed pull request #211: * [doc] Update integrate-devtool-to-android.md URL: https://github.com/apache/incubator-weex-site/pull/211
This is a PR merged from a forked repository. As GitHub hides the original diff on merge, it is displayed below for the sake of provenance: As this is a foreign pull request (from a fork), the diff is supplied below (as it won't show otherwise due to GitHub magic): diff --git a/source/cn/guide/integrate-devtool-to-android.md b/source/cn/guide/integrate-devtool-to-android.md index 76a6a289d..e81bf7734 100644 --- a/source/cn/guide/integrate-devtool-to-android.md +++ b/source/cn/guide/integrate-devtool-to-android.md @@ -3,357 +3,130 @@ title: 集成 Devtools 到 Android type: guide group: 开发 order: 5.4 -version: 2.1 +version: 2.2 --- <!-- toc --> Weex Devtools 能够方便调试 Weex 页面,但此功能离不开 Native 的支持。如何让你的 App 也集成 Devtools,在本章将会详细说明 Android 端如何接入 Weex Devtools。 -## Android 应用接入 +#### 版本兼容 -### 添加依赖 +| weex sdk | weex inspector | Debugger Server | +|----------|----------------|-----------------| +| 0.13+ | 0.12+ | 0.2.39+ | +| 0.8.0.1+ | 0.0.8.1+ | 0.2.39+ | +| 0.7.0+ | 0.0.7.13 | 0.2.38 | +| 0.6.0+ | 0.0.2.2 | - | +| 0.16.0+ | 0.12.1 | - | +| 0.17.0+ | 0.13.2 | - | +| 0.18.0+ | 0.13.4-multicontext | - | +| 0.19.0+ | 0.18.68 | - | -可以通过 Gradle 或者 Maven 添加对 devtools aar 的依赖,也可以直接对源码依赖。强烈建议使用最新版本,因为 Weex SDK 和 devtools 都在快速的迭代开发中,新版本会有更多惊喜,同时也修复老版本中一些问题。最新的 release 版本可在[这里](https://github.com/weexteam/weex_devtools_android/releases)查看。所有的 release 版本都会发布到 [jcenter repo](https://bintray.com/alibabaweex/maven/weex_inspector)。 +## Android接入指南 -- *Gradle 依赖* +#### 一、添加依赖 +可以通过Gradle 或者 Maven添加对devtools aar的依赖, 也可以直接对源码依赖. - ```gradle + * *Gradle依赖*. + ``` dependencies { - compile 'com.taobao.android:weex_inspector:${version}' + compile 'com.taobao.android:weex_inspector:0.18.10' } ``` - -- *Maven依赖* - - ```xml + + 或者 + * *Maven依赖*. + ``` <dependency> <groupId>com.taobao.android</groupId> <artifactId>weex_inspector</artifactId> - <version>${version}</version> + <version>0.18.10</version> <type>pom</type> </dependency> ``` - -- *源码依赖* - - 需要复制 [inspector](https://github.com/weexteam/weex_devtools_android/tree/master/inspector) 目录到你的 App 的同级目录,然后在工程的 `settings.gradle` 文件下添加 `include ":inspector"`,此过程可以参考 playground 源码的工程配置及其配置,然后在 App 的 `build.gralde` 中添加依赖。 - - ```gradle + + 或者 + * *源码依赖*. + + 需要复制[inspector](https://github.com/weexteam/weex_devtools_android/tree/master/inspector)目录到你的app的同级目录, 然后在工程的 `settings.gradle` 文件下添加 `include ":inspector"`, 此过程可以参考playground源码的工程配置及其配置, 然后在app的`build.gralde`中添加依赖. + ``` dependencies { - compile project(':inspector') + compile project(':inspector') } - ``` - - 另外 weex_inspector 中有一部分包是以 provided 的方式引入,接入方需要自行解决依赖和版本冲突。 - - - **provided方式引用的包** - - ```gradle - dependencies { - provided 'com.google.code.findbugs:jsr305:2.0.1' - provided 'com.android.support:appcompat-v7:23.1.1' - provided 'com.taobao.android:weex_sdk:0.8.0' - provided 'com.alibaba:fastjson:1.1.45+' - ... - } - ``` - - - **反射引用的包(0.8.0.0以上版本)** - - ```gradle - dependencies { - compile 'com.squareup.okhttp:okhttp:2.3.0' - compile 'com.squareup.okhttp:okhttp-ws:2.3.0' - ... - } - ``` - - 或者 - - ```gradle +``` + + * **需要引入okhttp** + ``` dependencies { - compile 'com.squareup.okhttp:okhttp:3.4.1' - compile 'com.squareup.okhttp:okhttp-ws:3.4.1' + compile 'com.squareup.okhttp:okhttp:2.3.0' + compile 'com.squareup.okhttp:okhttp-ws:2.3.0' ... } - ``` + ``` + +#### 二、调试开关(扫码开启调试/手动开启调试) -#### 版本兼容 +最简单方式就是复用Playground的相关代码,比如扫码和刷新等模块, 但是扫码不是必须的, 它只是与app通信的一种形式, 二维码里的包含DebugServer IP及bundle地址等信息,用于建立App和Debug Server之间的连接及动态加载bundle. 在Playground中给出了两种开启debug模式的范例. -| weex sdk | weex inspector | Debugger Server | -|----------|----------------|-----------------| -| 0.13+ | 0.12+ | 0.2.39+ | -| 0.8.0.1+ | 0.0.8.1+ | 0.2.39+ | -| 0.7.0+ | 0.0.7.13 | 0.2.38 | -| 0.6.0+ | 0.0.2.2 | - | -| 0.16.0+ | 0.12.1 | - | -| 0.17.0+ | 0.13.2 | - | - - - -### 添加 Debug 模式开关 - -控制调试模式的打开和关闭的关键点可以概括为三条规则。 - -**规则一:通过 `sRemoteDebugMode` 和 `sRemoteDebugProxyUrl` 来设置开关和 Debugger Server 地址。** - -Weex SDK 的 `WXEnvironment` 类里有一对静态变量标记了 Weex 当前的调试模式是否开启分别是: - -```java -public static boolean sRemoteDebugMode; // 是否开启 debug 模式,默认关闭 -public static String sRemoteDebugProxyUrl; // DebugServer的websocket地址 +* 范例1: 通过在XXXApplication中设置开关打开调试模式 <br> ``` +public class MyApplication extends Application { + public void onCreate() { + super.onCreate(); + initDebugEnvironment(true, "xxx.xxx.xxx.xxx"/*"DEBUG_SERVER_HOST"*/); + //WXSDKEngine.reload(); + } +} -无论在 App 中无论以何种方式设置 Debug 模式,都必须在恰当的时机调用类似如下的方法来设置 `WXEnvironment.sRemoteDebugMode` 和 `WXEnvironment.sRemoteDebugProxyUrl`。 - -更详细和合适的位置,初始化 inspector 的方法请参考 `Playground`. - -```java private void initDebugEnvironment(boolean enable, String host) { WXEnvironment.sRemoteDebugMode = enable; WXEnvironment.sRemoteDebugProxyUrl = "ws://" + host + ":8088/debugProxy/native"; } ``` - -**规则二:修改 `sRemoteDebugMode` 后一定要调用``WXSDKEngine.reload()`。** - -一般來說,在修改了 `WXEnvironment.sRemoteDebugMode` 以后调用了 `WXSDKEngine.reload()` 方法才能够使 Debug模式生效。`WXSDKEngine.reload()` 用来重置 Weex 的运行环境上下文,在切换调试模式时需要调用此方法来创建新的 Weex 运行时和 DebugBridge 并将所有的 JS 调用桥接到调试服务器执行。在 reload 过程中会调用 launchInspector,这就是 SDK 控制 Debug 模式最核心一个方法,其传入参数即为 `sRemoteDebugMode`,若为 `true` 则该方法中尝试以反射的方式获取 DebugBridge 用来在远端执行 JS,否则在本地运行。 - -```java -private void launchInspector(boolean remoteDebug) { - if (WXEnvironment.isApkDebugable()) { - try { - if (mWxDebugProxy != null) { - mWxDebugProxy.stop(); - } - HackedClass<Object> debugProxyClass = WXHack.into("com.taobao.weex.devtools.debug.DebugServerProxy"); - mWxDebugProxy = (IWXDebugProxy) debugProxyClass.constructor(Context.class, WXBridgeManager.class) - .getInstance(WXEnvironment.getApplication(), WXBridgeManager.this); - if (mWxDebugProxy != null) { - mWxDebugProxy.start(); - if (remoteDebug) { - mWXBridge = mWxDebugProxy.getWXBridge(); - } else { - if (mWXBridge != null && !(mWXBridge instanceof WXBridge)) { - mWXBridge = null; - } - } - } - } catch (HackAssertionException e) { - WXLogUtils.e("launchInspector HackAssertionException ", e); - } +这种方式最直接, 在代码中直接hardcode了开启调试模式, 如果在SDK初始化之前调用甚至连`WXSDKEngine.reload()`都不需要调用, 接入方如果需要更灵活的策略可以将`initDebugEnvironment(boolean enable, String host)`和`WXSDKEngine.reload()`组合在一起在合适的位置和时机调用即可.(如果不是初始化之前调用,n那么每次调用initDebugEnvironment后必须调用WXSDKEngine.reload()刷新Weex引擎) + +* 范例2:通过扫码打开调试模式 <br> +Playground中较多的使用扫描weex debugger生成的调试二维码的方式传递信息, 不仅用这种方式控制Debug模式的开关,而且还通过它来传入bundle的url直接调试. 应当说在开发中这种方式是比较高效的, 省去了修改sdk代码重复编译和安装App的麻烦. + +拦截方式: +```` +if (WXEnvironment.isApkDebugable()) { + String devToolUrl = uri.getQueryParameter("_wx_devtool"); + if (!TextUtils.isEmpty(devToolUrl)) { + WXEnvironment.sRemoteDebugProxyUrl = devToolUrl; + WXEnvironment.sDebugServerConnectable = true; + WXSDKEngine.reload(XXXXX.getApplication(), false); } } -``` - -只要遵循上面的原理,开启 Debug 模式的方式和时机可由接入方灵活实现。从 launchInspector 可以看到,SDK 对 devtools 的 aar 包并无强依赖,我们的 App 只需要在 Debug 包中打包该 aar 即可,这样多少可以缓解包大小问题和安全问题。 - -**例外:** _若修改 `WXEnvironment.sRemoteDebugMode` 的时机在 `WXBridgeManager` 初始化和 restart 和之前则 `WXSDKEngine.reload()` 可忽略._ - -**规则三:通过响应 `ACTION_DEBUG_INSTANCE_REFRESH` 广播及时刷新。** - -广播 `ACTION_DEBUG_INSTANCE_REFRESH` 在调试模式切换和 Chrome 调试页面刷新时发出,主要用来通知当前的 Weex容器以 Debug 模式重新加载当前页。在 playground 中的处理过程如下: +```` -```java -public class RefreshBroadcastReceiver extends BroadcastReceiver { - @Override - public void onReceive(Context context, Intent intent) { - if (IWXDebugProxy.ACTION_DEBUG_INSTANCE_REFRESH.equals(intent.getAction())) { - if (mUri != null) { - if (TextUtils.equals(mUri.getScheme(), "http") || TextUtils.equals(mUri.getScheme(), "https")) { - loadWXfromService(mUri.toString()); - } else { - loadWXfromLocal(true); - } +* 可选:调试刷新协议 <br> +广播 ACTION_DEBUG_INSTANCE_REFRESH 在调试模式切换和 Chrome 调试页面刷新时发出,主要用来通知当前的 Weex容器以 Debug 模式重新加载当前页。在 playground 中的处理过程如下: +``` + public class RefreshBroadcastReceiver extends BroadcastReceiver { + @Override + public void onReceive(Context context, Intent intent) { + if (IWXDebugProxy.ACTION_DEBUG_INSTANCE_REFRESH.equals(intent.getAction())) { + //Do something } } } -} ``` -如果接入方的容器未对该广播做处理,那么将不支持刷新和调试过程中编辑代码时的 watch 功能。 - -## 接入示例 - -最简单方式就是复用 Playground 的相关代码,比如扫码和刷新等模块,但是扫码不是必须的,它只是与 App 通信的一种形式,二维码里的包含 DebugServer IP 及 bundle 地址等信息,用于建立 App 和 Debugger Server 之间的连接及动态加载 bundle。在 Playground 中给出了两种开启 debug 模式的范例。 - -* 范例1:通过在 `XXXApplication` 中设置开关打开调试模式 - -```java -public class MyApplication extends Application { - public void onCreate() { - super.onCreate(); - initDebugEnvironment(true, "xxx.xxx.xxx.xxx"/*"DEBUG_SERVER_HOST"*/); - } -} -``` - -这种方式最直接,在代码中直接 hardcode 了开启调试模式,如果在 SDK 初始化之前调用甚至连 `WXSDKEngine.reload()` 都不需要调用,接入方如果需要更灵活的策略可以将 `initDebugEnvironment(boolean enable, String host)` 和 `WXSDKEngine.reload()` 组合在一起在合适的位置和时机调用即可。 - -* 范例2:通过扫码打开调试模式 - -Playground 中较多的使用扫码的方式传递信息,不仅用这种方式控制 Debug 模式的开关,而且还通过它来传入 bundle 的 url 直接调试。应当说在开发中这种方式是比较高效的,省去了修改 SDK 代码重复编译和安装 App 的麻烦,缺点就是调试工具这种方式接入需要 App 具有扫码和处理特定规则二维码的能力。除了 Playground 中的方式,接入方亦可根据业务场景对 Debugger 和接入方式进行二次开发。 - -Playground 集成的具体代码可参考如下两个文件: +## 科普 -* 开关控制,主要参考对二维码的处理部分,详见 [`WXApplication.java`](https://github.com/weexteam/weex_devtools_android/blob/master/playground/app/src/main/java/com/alibaba/weex/WXApplication.java) - -* 刷新控制 ,主要参考是对容器 `ACTION_DEBUG_INSTANCE_REFRESH`的处理,详见 [`WXPageActivity.java`](https://github.com/weexteam/weex_devtools_android/blob/master/playground/app/src/main/java/com/alibaba/weex/WXPageActivity.java) - -## 牛刀小试 - -### 前置工作 - -如果未安装 Debugger Server,在命令行执行 `npm install -g weex-toolkit` 既可以安装调试服务器,运行命令 `weex debug`,最新的版本我们建议您使用 `weex debugx` 就会启动 DebugServer 并打开一个调试页面(详情请查看 [《Get Started》](../../guide/index.html))。页面下方会展示一个二维码,这个二维码用于向 App 传递 Server 端的地址建立连接。 - -![_](//img.alicdn.com/tps/TB1aKy4NXXXXXacXVXXXXXXXXXX-1019-756.png) - -### 开始调试 - -如果你的 App 客户端完成了以上步骤那么恭喜你已经接入完毕,可以愉快的调试 Weex bundle 了,调试体验和网页调试一致!建议新手首先用官方的 Playground 体验一下调试流程。只需要启动 App 扫描 Chrome 调试页面下方的第一个二维码即可建立与 Debugger Server 的通信,Chorome 的调试页面将会列出连接成功的设备信息。 - -![devtools-main](//img.alicdn.com/tps/TB13fwSKFXXXXXDaXXXXXXXXXXX-887-828.png) - -#### 主要步骤如下 - -1. 如果你要加载服务器上 bundle,第一步就是要让你的 bundle sever 跑起来. 在 Playground 中特别简单,只需要你到 Weex 源码目录下,运行 `./start` 即可。 -2. 命令行运行 `weex debug` 启动 Debugger Server,Chrome 将会打开一个网页,在网页下方有一个二维码和简单的介绍。 -3. 启动 App 并确认打开调试模式。你将在上一步中打开的网页中看到一个设备列表,每个设备项都有两个按钮,分别是 `Debugger` 和 `Inspector`。 -4. 点击 `Inspector` Chrome 将创建 Inspector 网页;点击 `Debugger` Chrome 将创建 Debugger 网页;二者是相互独立的功能,不相互依赖。 - ---- - -## 背景知识 - -### Devtools 组件介绍 -Devtools 扩展了 [Chrome Debugging Protocol](https://developer.chrome.com/devtools/docs/debugger-protocol),在客户端和调试服务器之间的采用 [JSON-RPC](https://en.wikipedia.org/wiki/JSON-RPC) 作为通信机制,本质上调试过程是两个进程间协同,相互交换控制权及运行结果的过程。更多细节还请阅读 [Weex Devtools Debugger 的技术选型实录](http://www.atatech.org/articles/59284)这篇文章。 +#### Devtools组件介绍 +Devtools扩展了[Chrome Debugging Protocol](https://developer.chrome.com/devtools/docs/debugger-protocol), 在客户端和调试服务器之间的采用[JSON-RPC](https://en.wikipedia.org/wiki/JSON-RPC)作为通信机制, 本质上调试过程是两个进程间协同, 相互交换控制权及运行结果的过程. 更多细节还请阅读[Weex Devtools Debugger的技术选型实录](http://www.atatech.org/articles/59284)这篇文章. * **客户端** - - Devtools 客户端作为 aar 被集成 App 中,它通过 webscoket 连接到调试服务器,此处并未做安全检查。出于安全机制及包大小考虑,强烈建议接入方只在 debug 版本中打包此 aar。 +Devtools 客户端作为aar被集成App中, 它通过webscoket连接到调试服务器,此处并未做安全检查. 出于安全机制及包大小考虑, 强烈建议接入方只在debug版本中打包此aar. * **服务器** - - Devtools 服务器端是信息交换的中枢,既连接客户端,又连接 Chrome,大多数情况下扮演一个消息转发服务器和 Runtime Manager 的角色。 +Devtools 服务器端是信息交换的中枢, 既连接客户端, 又连接Chrome, 大多数情况下扮演一个消息转发服务器和Runtime Manager的角色. * **Web端** +Chrome的V8引擎扮演着bundle javascript runtime的角色. 开启debug模式后, 所有的bundle js 代码都在该引擎上运行. 另一方面我们也复用了Chrome前端的调试界面, 例如设置断点, 查看调用栈等, 调试页关闭则runtime将会被清理. - Chrome 的 V8 引擎扮演着 Bundle javascript runtime 的角色。开启 debug 模式后,所有的 bundle js 代码都在该引擎上运行。另一方面我们也复用了 Chrome 前端的调试界面,例如设置断点,查看调用栈等,调试页关闭则 runtime 将会被清理。 - -调试的大致过程请参考如下时序图。 - -![debug sequence diagram](//img.alicdn.com/tps/TB1igLoMVXXXXawapXXXXXXXXXX-786-1610.jpg) - -## FAQ - -在各业务接入过程中,陆续发现一些问题,对高频次的问题解答如下,开发中以 weex debug -V 的方式启动 Debugger Server 可以看到 server 端的 log 信息,对照上文中的时序图对于定位问题还是非常有帮助,建议调试中默认开启 server 端 log。 - -1. **扫码 App 在 DebugServerProxy 中抛出 class not found** - - 已知的原因如下: - - * weex_inspector 以 provided 方式引用的包是否引入成功,如 fastjson 等。 - * weex_inspector 以 compile 方式引用的包是否引入成功,某些 app 重新引入 `com.squareup.okhttp:okhttp:2.3.0` 和 `com.squareup.okhttp:okhttp-ws:2.3.0` 则不再报错。 - * 混淆规则影响反射。 - -2. **playground 扫码调试 crash** - - 已知的原因如下: - - * 系统为 android 6+,崩溃信息提示进程需要 `android.permission.READ_PHONE_STATE` 权限,代码中未做权限检查,在 0.0.2.7 版本以后已修复,不再需要此权限。 - -3. **扫码后设备列表页并没有出现我的设备信息** - - 已知的原因如下: - - * Debugger Server 和手机在不同网段,被防火墙隔离。 - * 手机连接了 PC 端的代理,当前尚不支持。 - * 多进程连接服务器端的同一端口,比如在 Application 的 `onCreate` 中初始化 sdk,若多个进程连接服务器端的同一端口则报错,在 0.0.2.3 版本以后已支持多进程无此问题。 - -4. **调试过程中频繁刷新连接失败,Server 端提示重新启动 App,非必现** - - 已知的原因如下: - - * 多线程操作网络连接引起,在频繁的即断即连时容易触发。在 0.0.7.1 版本已修复。 - -## 注入自定义WebSocket Client -目前Inspector以反射的方式动态调用了okhttp-ws库中的相关代码,可以兼容的okhttp与okhttp-ws版本为: -- okhttp, okhttp-ws 2.7.5版本以下 -- okhttp3, okhttp3-ws 3.5版本以下 - -如果客户端中集成的版本与上述版本不匹配,则可以使用`WeexInspector.overrideWebSocketClient`方法来注入自定义的WebSocket实现,示例: -```java - - public class CustomWebSocketClient implements IWebSocketClient { - - private WebSocket ws; - - @Override - public boolean isOpen() { - return ws != null; - } - - @Override - public void connect(String wsAddress, final WSListener listener) { - OkHttpClient okHttpClient = new OkHttpClient(); - okHttpClient.setConnectTimeout(5, TimeUnit.SECONDS); - okHttpClient.setReadTimeout(5, TimeUnit.SECONDS); - okHttpClient.setWriteTimeout(5, TimeUnit.SECONDS); - - Request request = new Request.Builder().url(wsAddress).build(); - WebSocketCall webSocketCall = WebSocketCall.create(okHttpClient, request); - webSocketCall.enqueue(new WebSocketListener() { - @Override - public void onOpen(WebSocket webSocket, Request request, Response response) throws IOException { - ws = webSocket; - listener.onOpen(); - } - - @Override - public void onMessage(BufferedSource payload, WebSocket.PayloadType type) throws IOException { - if (WebSocket.PayloadType.TEXT == type) { - listener.onMessage(payload.readUtf8()); - } - } - - @Override - public void onPong(Buffer payload) { - //ignore - } - - @Override - public void onClose(int code, String reason) { - listener.onClose(); - } - - @Override - public void onFailure(IOException e) { - listener.onFailure(e); - } - }); - } - - @Override - public void close() { - if (ws != null) { - try { - ws.close(CloseCodes.NORMAL_CLOSURE, "Normal closure"); - } catch (IOException e) { - e.printStackTrace(); - } - } - } - - @Override - public void sendMessage(int requestId, String message) { - if (ws != null) { - try { - ws.sendMessage(WebSocket.PayloadType.TEXT, new Buffer().writeString(message, Charset.defaultCharset())); - } catch (IOException e) { - e.printStackTrace(); - } - } - } - } -``` +调试的大致过程请参考如下时序图. +![debug sequence diagram](https://img.alicdn.com/tps/TB1igLoMVXXXXawapXXXXXXXXXX-786-1610.jpg "debug sequence diagram") ---------------------------------------------------------------- This is an automated message from the Apache Git Service. To respond to the message, please log on GitHub and use the URL above to go to the specific comment. For queries about this service, please contact Infrastructure at: us...@infra.apache.org With regards, Apache Git Services