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

williamfzc · November 23, 2018 · Last by 张全蛋 replied at November 26, 2018 · 1659 hits

用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 中提及了此贴 18 Dec 11:47
需要 Sign In 后方可回复, 如果你还没有账号请点击这里 Sign Up