记录一下省的忘记了,遍看源码,边猜测,边实验
目前 Android WebView 支持主要还是靠 ChromeDriver 来实现的。
Chromedriver 貌似一定要 ADB 才行。其中用到了命令有 ps
, grep
, adb forward
, am start
等等
既然还用到了adb forward .. localabstract
, 先打开一个带 webview 的 app(这里用了微信)
查看下有哪些服务监听了 localabstract
$ cat /proc/net/unix | grep @
0000000000000000: 00000002 00000000 00010000 0001 01 32803 @bthcitraffic
0000000000000000: 00000002 00000000 00010000 0001 01 3789231 @chrome_devtools_remote
0000000000000000: 00000002 00000000 00010000 0001 01 32963 @/data/misc/bluedroid/.a2dp_ctrl
0000000000000000: 00000002 00000000 00010000 0001 01 76 @time_genoff
0000000000000000: 00000002 00000000 00010000 0001 01 88 @android:debuggerd
0000000000000000: 00000002 00000000 00010000 0001 01 4640516 @webview_devtools_remote_13680
0000000000000000: 00000002 00000000 00010000 0001 01 17596 @jdwp-control
0000000000000000: 00000003 00000000 00000000 0001 03 4453235 @jdwp-control
0000000000000000: 00000003 00000000 00000000 0001 03 4250761 @jdwp-control
其中@
符号代码监听的本地 Unix socket
这里看到有两个 @chrome_devtools_remote
和 @webview_devtools_remote_13680
比较吸引人。
chrome_devtools_remote
应该对应的是浏览器。
webview_devtools_remote_13680
对应的应该是应用内的 WebView,根据源码感觉这个 13680 应该是个 pid
odin:/ $ ps | grep 13680
USER PID PPID VSIZE RSS WCHAN PC NAME
u0_a280 13680 591 2522104 168072 SyS_epoll_ 0000000000 S com.tencent.mm:tools
com.tencent.mm
这个应该就是微信了。
先把这个 forward 到本地
$ adb forward tcp:5000 localabstract:webview_devtools_remote_13680
看 chromedriver 的代码,里面自带一些 http 接口,具体看下面的代码
https://github.com/bayandin/chromedriver/blob/33218feb63bc972c7175390ee2302fe5a2f25056/chrome/devtools_http_client.cc#L92
有个比较简单的接口获取浏览器的版本号,以此来确定该使用的 chromedriver 版本。
$ curl localhost:5000/json/version
{
"Android-Package": "com.tencent.mm",
"Browser": "Chrome/57.0.2987.132",
"Protocol-Version": "1.2",
"User-Agent": "Mozilla/5.0 (Linux; Android 7.1.1; OD103 Build/NMF26F; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/57.0.2987.132 MQQBrowser/6.2 TBS/044205 Mobile Safari/537.36",
"V8-Version": "5.7.492.72",
"WebKit-Version": "537.36 (@user1128ad40ab2e3d761c06d1)"
}
从这个链接里面可以看到没有 Chrome Version(57) 对应的应该使用的 chromedriver 版本号 (2.29) https://chromedriver.storage.googleapis.com/2.41/notes.txt
去这个地址下载 https://npm.taobao.org/mirrors/chromedriver
猜测:除了微信有个调试的按钮,其他的 H5 的应用应该也是可以的吧。
先试了试 Appium 自带的 demo apk
https://github.com/appium/java-client/blob/master/src/test/java/io/appium/java_client/ApiDemos-debug.apk
安装到手机上之后,进入Views
-> WebView
,然后看到这么一个简单的界面
图层结构显示也有 Hierarchy
Chrome 浏览器打开 chrome://inspect
更多相关的内容参考谷歌的 Chrome-Devtools 文档 https://developers.google.com/web/tools/chrome-devtools/remote-debugging/?hl=zh-cn
在浏览器里面并没有发现当前这个应用的 webview。
重新读了下谷歌文档 - 远程调试 WebView
需要在应用中开启 WebViewDebugable 模式才行,也就是说默认不开启喽。
那有没有强制开启的方法呢? 很快谷歌出来一篇文章
强制开启 android webview debug 模式使用 Chrome inspect
看来手机需要 Root 才行,还用到了高端的 Xposed(不对呀,我明明记得可以不用 root 的呀),继续谷歌了下发现还有个 VirtualXposed 可以不用 root
相关的 VirtualXposed(简称 VXP)简介 https://github.com/android-hacker/VirtualXposed
安装很简单,跟普通的 App 一样,下载 apk 到手机,安装就 ok 了。
接下来用 VXP 自带的安装器,安装 ApiDemos-debug.apk
然后再安装个插件 WebViewDebugHook 同样是个 Apk 下载 (官方地址:https://github.com/feix760/WebViewDebugHook),用 VXP 安装上就好了。
关于那个插件的原理可以在这里看到 https://www.jianshu.com/p/d6699cd4505e
安装上之后,先打开
进入到模块中,勾选
返回,然后向下滑动,进入到这个界面,点击箭头位置
拖到最下面点击 重启
是插件更改生效。
PS: 使用 VXP 安装的应用
pm list packages
是看不到的,只有在运行的时候用ps
才可以查看到
回到应用界面,点击刚才安装的 APK API Demos
, 重新进入到 WebView,然后打开chrome://inspect
这个时候终于看到了,期望的界面。
应用使用的 Chrome 版本是 62.0.3202.84
Appium 的代码里面有个对应的表可以直接查改用哪一个 chromedriver https://github.com/appium/appium-chromedriver/blob/master/lib/chromedriver.js#L20
PS D:\Temp> .\chromedriver.exe -h
Usage: D:\Temp\chromedriver.exe [OPTIONS]
Options
--port=PORT port to listen on
--adb-port=PORT adb server port
--log-path=FILE write server log to file instead of stderr, increases log level to INFO
--log-level=LEVEL set log level: ALL, DEBUG, INFO, WARNING, SEVERE, OFF
--verbose log verbosely (equivalent to --log-level=ALL)
--silent log nothing (equivalent to --log-level=OFF)
--version print the version number and exit
--url-base base URL path prefix for commands, e.g. wd/url
--whitelisted-ips comma-separated whitelist of remote IPv4 addresses which are allowed to connect to Chr
omeDriver
chromedriver 默认监听 9515 端口。
先来段简单的代码试试。获取 WebView 中显示的内容
# coding: utf-8
from selenium import webdriver
options = {'androidPackage': 'com.appium.android.apis', 'androidUseRunningApp': True}
d = webdriver.Remote("http://localhost:9515", {"chromeOptions": options})
# /html/body/a
el = d.find_element_by_xpath("/html/body/a")
print(el.text)
运行,之后就报错了,提示
WebDriverException: Message: unknown error: com.appium.android.apis is not installed on device 3578298f
(Driver info: chromedriver=2.41.578737 (49da6702b16031c40d63e5618de03a32ff6c197e),platform=Windows NT 10.0.16299 x86_6
4)
估计是通过pm path <package)name>
判断的有没有安装,也罢,再安装一遍好了。然后给 options 加了androidProcess
参数
options = {
'androidPackage': 'io.appium.android.apis',
'androidUseRunningApp': True,
'androidProcess': 'io.appium.android.apis'
}
重新再运行代码终于出来了
Hello World! - 1
哎,真是麻烦。。 -~~
chromedriver 有个参数--verbose
可以输出更详细的信息。
./chromedriver --verbose
然后写段 Python 代码请求一下看看
from selenium import webdriver
from contextlib import contextmanager
@contextmanager
def driver(package_name):
capabilities = {
"androidDeviceSerial": "bf755cab",
"androidPackage": package_name,
"androidUseRunningApp": True,
}
dr = webdriver.Remote("http://localhost:9515", {
"chromeOptions": capabilities
})
try:
yield dr
finally:
dr.quit()
def main():
package_name = "io.appium.android.apis"
package_name = "com.xueqiu.android"
with driver(package_name) as dr:
print(dr.current_url)
#dr.save_screenshot("s.png")
if __name__ == "__main__":
main()
运行这段 Python 代码之后,你会看到 chromedriver 有一堆的输出。下面我把主要的代码贴出来
[17.612][DEBUG]: Sending adb command: host:devices
[17.612][DEBUG]: Sending adb command: host:transport:bf755cab|shell:pm path com.xueqiu.android
[17.930][DEBUG]: Received adb response: package:/data/app/com.xueqiu.android-1/base.apk
[17.930][DEBUG]: Sending adb command: host:transport:bf755cab|shell:ps
--- 这里还缺少获取 cat /proc/net/unix的步骤,log中看不到
[17.988][DEBUG]: Sending adb command: host-serial:bf755cab:forward:tcp:12604;localabstract:webview_devtools_remote_8495
--- 验证当前的Browser版本号是否在支持的范围内
[17.989][DEBUG]: DevTools request: http://localhost:12604/json/version
[17.992][DEBUG]: DevTools response: {
"Android-Package": "com.xueqiu.android",
"Browser": "Chrome/62.0.3202.84",
"Protocol-Version": "1.2",
"User-Agent": "Mozilla/5.0 (Linux; Android 6.0.1; SM901 Build/MXB48T; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/62.0.3202.84 Mobile Safari/537.36",
"V8-Version": "6.2.414.37",
"WebKit-Version": "537.36 (@957d898f0f6e46cd9661d91d2bae899f34c1c4b6)",
"webSocketDebuggerUrl": "ws://localhost:12604/devtools/browser"
}
--- 获取所有的tab页
[17.992][DEBUG]: DevTools request: http://localhost:12604/json
[17.995][DEBUG]: DevTools response: [ {
"description": "{\"attached\":true,\"empty\":false,\"height\":1698,\"screenX\":0,\"screenY\":222,\"visible\":true,\"width\":1080}",
"devtoolsFrontendUrl": "http://chrome-devtools-frontend.appspot.com/serve_rev/@957d898f0f6e46cd9661d91d2bae899f34c1c4b6/inspector.html?ws=localhost:12604/devtools/page/82563818-74f8-4ed3-b6af-f294509fa0b6",
"faviconUrl": "https://assets.imedao.com/broker/static/images/favicon.8d2e0522.png",
"id": "82563818-74f8-4ed3-b6af-f294509fa0b6",
"title": "平安证券 极速开户",
"type": "page",
"url": "https://broker.xueqiu.com/open/pazq-l2-v1?snb_from=tab",
"webSocketDebuggerUrl": "ws://localhost:12604/devtools/page/82563818-74f8-4ed3-b6af-f294509fa0b6"
}]
---
[18.000][INFO]: resolved localhost to ["::1","127.0.0.1"] # 这个感觉多此一举
[18.004][DEBUG]: DEVTOOLS COMMAND Log.enable (id=1)
[18.004][DEBUG]: DEVTOOLS COMMAND DOM.getDocument (id=2)
[18.004][DEBUG]: DEVTOOLS COMMAND Runtime.enable (id=3)
[18.005][DEBUG]: DEVTOOLS COMMAND Page.enable (id=4)
[18.005][DEBUG]: DEVTOOLS COMMAND Page.enable (id=5)
[18.011][DEBUG]: DEVTOOLS EVENT Log.entryAdded {
"entry": {
"level": "warning",
"lineNumber": 0,
"source": "rendering",
"text": "The key \"viewport-fit\" is not recognized and ignored.",
"timestamp": 1568087651303.1,
"url": "https://broker.xueqiu.com/open/pazq-l2-v1?snb_from=tab"
}
}
[18.012][DEBUG]: DEVTOOLS RESPONSE DOM.getDocument (id=2) {
"root": {
"backendNodeId": 61,
"baseURL": "https://broker.xueqiu.com/open/pazq-l2-v1?snb_from=tab",
"childNodeCount": 2,
... 太多了,省略不写了
[18.012][DEBUG]: DEVTOOLS EVENT Runtime.executionContextCreated {
"context": {
"auxData": {
"frameId": "8495.4",
"isDefault": true
},
"id": 19,
"name": "",
"origin": "https://broker.xueqiu.com"
}
}
[18.015][DEBUG]: DEVTOOLS EVENT Runtime.consoleAPICalled {
"args": [ {
"type": "string",
"value": "{\"url\":\"/account/bind/show.json\",\"type\":\"GET\",\"data\":{},\"success\":\"cb1\",\"error\":\"cb2\",\"name\":\"request\"}"
} ],
"executionContextId": 19,
"stackTrace": {
"callFrames": [ {
"columnNumber": 2213,
"functionName": "render",
"lineNumber": 11,
"scriptId": "470",
"url": "https://assets.imedao.com/broker/static/js/open/pazq.96a2b2c8.js"
}
... 后面还很长,不写了
[18.031][DEBUG]: DEVTOOLS COMMAND Runtime.evaluate (id=8) {
"expression": "(function() { // Copyright (c) 2012 The Chromium Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\n/**\n * Enum f...",
"returnByValue": true
}
[18.039][DEBUG]: DEVTOOLS RESPONSE Runtime.evaluate (id=8) {
"result": {
"type": "object",
"value": {
"status": 0,
"value": 1
}
}
}
--- 不知道哪里调用的
[18.039][INFO]: RESPONSE InitSession {
"acceptSslCerts": true,
"applicationCacheEnabled": false,
"browserConnectionEnabled": false,
"browserName": "chrome",
"chrome": {
"chromedriverVersion": "2.29.461585 (0be2cd95f834e9ee7c46bcc7cf405b483f5ae83b)"
},
"cssSelectorsEnabled": true,
"databaseEnabled": false,
"handlesAlerts": true,
"hasTouchScreen": true,
"javascriptEnabled": true,
"locationContextEnabled": true,
"mobileEmulationEnabled": false,
"nativeEvents": true,
"pageLoadStrategy": "normal",
"platform": "ANDROID",
"rotatable": false,
"takesHeapSnapshot": true,
"takesScreenshot": true,
"unexpectedAlertBehaviour": "",
"~~~": "..."
}
-- GetURL的逻辑 (很长,看的我头疼)
[18.054][DEBUG]: DEVTOOLS COMMAND Runtime.evaluate (id=11) {
"expression": "var isLoaded = document.readyState == 'complete' || document.readyState == 'interactive';if (isLoaded) { var frame = document.createElement('iframe'); frame.name = 'chromedriver dummy frame'; ..."
}
[18.108][DEBUG]: DEVTOOLS EVENT DOM.childNodeCountUpdated {
"childNodeCount": 4,
"nodeId": 10
}
[18.108][DEBUG]: DEVTOOLS EVENT Page.frameAttached {
"frameId": "8495.18",
"parentFrameId": "8495.4",
"stack": {
"callFrames": [ {
"columnNumber": 240,
"functionName": "",
"lineNumber": 0,
"scriptId": "478",
"url": ""
} ]
}
}
[18.108][DEBUG]: DEVTOOLS EVENT Page.frameStartedLoading {
"frameId": "8495.18"
}
[18.108][DEBUG]: DEVTOOLS EVENT Page.frameNavigated {
"frame": {
"id": "8495.18",
"loaderId": "8495.18",
"mimeType": "text/html",
"name": "chromedriver dummy frame",
"parentId": "8495.4",
"securityOrigin": "://",
"url": "about:blank"
}
}
[18.108][DEBUG]: DEVTOOLS EVENT Runtime.executionContextCreated {
"context": {
"auxData": {
"frameId": "8495.18",
"isDefault": true
},
"id": 26,
"name": "",
"origin": "https://broker.xueqiu.com"
}
}
[18.108][DEBUG]: DEVTOOLS EVENT Page.frameStoppedLoading {
"frameId": "8495.18"
}
[18.108][DEBUG]: DEVTOOLS RESPONSE Runtime.evaluate (id=11) {
"result": {
"description": "176",
"type": "number",
"value": 176
}
}
[18.108][INFO]: Done waiting for pending navigations. Status: ok
[18.108][DEBUG]: DEVTOOLS COMMAND Runtime.evaluate (id=12) {
"expression": "(function() { // Copyright (c) 2012 The Chromium Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\n/**\n * Enum f...",
"returnByValue": true
}
[18.133][DEBUG]: DEVTOOLS EVENT DOM.childNodeCountUpdated {
"childNodeCount": 3,
"nodeId": 10
}
[18.133][DEBUG]: DEVTOOLS EVENT Runtime.executionContextDestroyed {
"executionContextId": 26
}
[18.133][DEBUG]: DEVTOOLS EVENT Page.frameDetached {
"frameId": "8495.18"
}
[18.136][DEBUG]: DEVTOOLS RESPONSE Runtime.evaluate (id=12) {
"result": {
"type": "object",
"value": {
"status": 0,
"value": "https://broker.xueqiu.com/open/pazq-l2-v1?snb_from=tab"
}
}
}
[18.136][INFO]: Waiting for pending navigations...
[18.136][DEBUG]: DEVTOOLS COMMAND Runtime.evaluate (id=13) {
"expression": "1"
}
[18.141][DEBUG]: DEVTOOLS RESPONSE Runtime.evaluate (id=13) {
"result": {
"description": "1",
"type": "number",
"value": 1
}
}
[18.141][INFO]: Done waiting for pending navigations. Status: ok
[18.141][INFO]: RESPONSE GetUrl "https://broker.xueqiu.com/open/pazq-l2-v1?snb_from=tab"