在 Android 设备管理这一块上,以获取当前已连接设备为例,大多数人脑海里浮现出来的第一个念头还是adb devices
。用命令行调用adb devices
进而对返回值进行分析是一个方便的方法,大多数语言都提供了很方便的与命令行交互的方式,从而使得这种方法被广泛的应用。但即便如此,这确实不是一个好方法。
大多数语言在进行命令行交互时会从自身进程 fork 一个子进程出来执行交互,执行完成之后再将子进程杀死。当交互频率较高时,这种频繁进程增删会影响性能且可能引发其他潜在问题。除此之外,使用这种方法我们还需要花费大量代码来进行字符串操作与处理,将进程执行结果转换成可处理的格式。
比较好的方式是,自建一个 client,通过 socket 与 adb server 建立长时通信。这里借张图表达一下 adb 的运作原理:
通过 socket,我们能够操作 adb server 进而操作设备。这些操作完全不需要subprocess
与os
,也不存在进程的增删。
原理很简单:
其中,阻塞点是:
其实这个部分还是因为 google 对文档的支持实在是太不友好了,导致很多人想到这一步的时候就放弃了,没有往下继续做。
我们直接通过代码看一下我们需要做什么。
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 不理解的同学直接百度看看吧,这里不多说了。
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 需要接收的信息包含两个部分,一个是信息长度,另一个是信息内容。我们在这里需要做的是:
ready_data = encode_data('host:devices')
client.send(ready_data)
好了,那么我们要发送的信息内容,同样也需要看 google 的官方文档。以获取当前设备为例,它对应的命令为host:devices
。那么,我们把它包装一下发过去吧。
# 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