自动化工具 用 100 行 python 写一个 android 插拔监听库

williamfzc · 2018年11月23日 · 最后由 我去催饭 回复于 2018年11月26日 · 2270 次阅读

用 100 行 python 写一个 android 设备监听库

起因

在 Android 设备管理这一块上,以获取当前已连接设备为例,大多数人脑海里浮现出来的第一个念头还是adb devices。用命令行调用adb devices进而对返回值进行分析是一个方便的方法,大多数语言都提供了很方便的与命令行交互的方式,从而使得这种方法被广泛的应用。但即便如此,这确实不是一个好方法。

大多数语言在进行命令行交互时会从自身进程 fork 一个子进程出来执行交互,执行完成之后再将子进程杀死。当交互频率较高时,这种频繁进程增删会影响性能且可能引发其他潜在问题。除此之外,使用这种方法我们还需要花费大量代码来进行字符串操作与处理,将进程执行结果转换成可处理的格式。

比较好的方式是,自建一个 client,通过 socket 与 adb server 建立长时通信。这里借张图表达一下 adb 的运作原理:

通过 socket,我们能够操作 adb server 进而操作设备。这些操作完全不需要subprocessos,也不存在进程的增删。

怎么做

原理很简单:

  • 创建 socket 与 adb server 保持连接
  • 通过 socket 与 server 交互以达到希望的效果

其中,阻塞点是:

  • adb server 需要收到什么样的信息?
  • adb server 会返回什么样的信息?

其实这个部分还是因为 google 对文档的支持实在是太不友好了,导致很多人想到这一步的时候就放弃了,没有往下继续做。

到底怎么做

我们直接通过代码看一下我们需要做什么。

建立 socket 连接

import socket
import threading


HOST = '127.0.0.1'
PORT = 5037
ENCODING = 'utf-8'


def connect():
    """ create socket and connect to adb server """
    client = socket.socket()
    client.connect((HOST, PORT))
    return client


# connection to adb server
adb_connection = connect()

这个没什么好说的,连就完事了。socket 不理解的同学直接百度看看吧,这里不多说了。

adb 接收的信息格式

google 的文档,写在代码里:传送门(自带梯子)

没有梯子的话也可以看这里

# message we sent to server contains 2 parts
# 1. length
# 2. content

def encode_data(data):
    byte_data = data.encode(DEFAULT_ENCODING)
    byte_length = "{0:04X}".format(len(byte_data)).encode(DEFAULT_ENCODING)
    return byte_length + byte_data

server 需要接收的信息包含两个部分,一个是信息长度,另一个是信息内容。我们在这里需要做的是:

  • 将信息内容转换成 byte
  • 获取信息长度,并将其转换成 16 进制,再将其转换成 byte
  • 将两者拼接

发送信息到 adb server

ready_data = encode_data('host:devices')
client.send(ready_data)

好了,那么我们要发送的信息内容,同样也需要看 google 的官方文档。以获取当前设备为例,它对应的命令为host:devices。那么,我们把它包装一下发过去吧。

adb server 会返回什么

# and, message we got also contains 3 parts:
# 1. status (4)
# 2. length (4)
# 3. content (unknown)
def read_all_content(target_socket):
    result = b''
    while True:
        buf = target_socket.recv(1024)
        if not len(buf):
            return result
        result += buf


# get them
status = client.recv(4)
length = client.recv(4)
content = read_all_content(client)

返回值包含三个部分:

  • 状态
  • 信息长度
  • 信息内容

状态代表你发送的命令是否生效,关于状态的详细说明还是得看上面发的 google 官方文档!!

我们把它打出来看看长什么样:

# check your result
final_result = {
    'status': status,
    'length': length,
    'content': content,
}
final_result = {_: v.decode(DEFAULT_ENCODING) for _, v in final_result.items()}
pprint.pprint(final_result)

没有设备时:

{'content': '', 'length': '0000', 'status': 'OKAY'}

有设备时:

{'content': '123456F\tdevice\n', 'length': '0010', 'status': 'OKAY'}

在操作完成后记得把 socket 关掉。

更多

通过查阅官方文档可以发现,除了host:devices以外,其实所有 adb 能支持的方法都是有对应的参数的。只要对这些方法进行支持,理论上就可以写出一个功能齐全的 adb client。不过,现在类似的项目已经很多了,这里就不再展开。

本文的完整代码参见:socket2adb,只有 60 行

利用相同原理实现的设备插拔监听库:ConnectionTracer,只有 100 行

实际应用的例子

利用相同原理重写了之前的 whenconnect 的底层,无痛换掉了 subprocess 与 os。

最后

描述有误之处欢迎随时指正,以免误导他人 :)

欢迎 star 与 fork

共收到 4 条回复 时间 点赞

不错不错

谢谢分享

不错,已 add star

williamfzc 重新定义 ADB 客户端 -- fa 中提及了此贴 12月18日 11:47
需要 登录 后方可回复, 如果你还没有账号请点击这里 注册