一、前置说明
在Appium自动化中,经常需要使用adb命令与设备进行交互,所以有必要把常用的adb操作封装成一个类
二、代码实现
import os
import platform
import re
import subprocess
from common import path
from common.exception import AndroidSDKUninstalledError, AndroidDevicesNotFoundError
from common.logger import logger
class ADBRunner:
def __init__(self):
self._check_adb_is_installed()
@staticmethod
def run_adb(command):
try:
result = subprocess.run(command, capture_output=True, text=True, check=True, shell=True)
logger.debug(f"Execute adb command: {command}")
return result.stdout.strip()
except subprocess.CalledProcessError as e:
logger.error(f"Execute adb command failure: {e}")
return None
def _check_adb_is_installed(self):
result = self.run_adb("adb --version")
if not result:
raise AndroidSDKUninstalledError('Android SDK is not installed or configured.')
return result
def adb_connect_device(self, device):
return self.run_adb(f'adb connect {device}')
def get_connected_device_udids(self):
"""
获取所有连接设备的序列号udid
"""
res = self.run_adb('adb devices')
pattern = r'\b((?!of\b)\S+)\s+device'
devices = re.findall(pattern, res)
if not devices:
raise AndroidDevicesNotFoundError('No connected mobile devices found.')
logger.info(f'Devices found: {devices}')
return devices
@property
def _grep(self):
if platform.system() == 'Windows':
return 'findstr'
else:
return 'grep'
def get_activities(self, udid=None):
"""
获取当前设备的所有top activities, 输出结果示例:
['com.android.settings/.Settings c4e2e17 pid=3637',
'com.mumu.launcher/.Launcher 9392da7 pid=1434',
'com.android.browser/com.android.settings de68b73 pid=3722']
"""
if not udid:
udid = self.get_connected_device_udids()[0]
command = f'adb -s {udid} shell dumpsys activity top | {self._grep} ACTIVITY'
tops = self.run_adb(command).split('ACTIVITY')
tops = [top.strip(' ').strip('\n') for top in tops if top]
logger.debug(f'The top activities on device {udid} are: {tops}')
return tops
def get_app_package_and_activity(self, udid=None):
"""
从 com.android.settings/.Settings c4e2e17 pid=3637,获取包名和活动页面名称
输出:['com.android.settings', '.Settings']
用途:
capabilities = {
"platformName": "Android",
"automationName": "uiautomator2",
"deviceName": "9YS0220306003185",
"appPackage": "com.tencent.mm", # 包名
"appActivity": ".ui.LauncherUI", # 活动页面名称
}
"""
last_activity_info = self.get_activities(udid)[-1]
pattern = r'(\S+) (\S+) pid=(\d+)'
match = re.match(pattern, last_activity_info)
if match:
package, activity = match.group(1).split('/')
return [package, activity]
else:
raise
def get_apk_path(self, package_name, udid=None):
"""
从设备中使用包名,获取应用程序的APK路径。
"""
if not udid:
udid = self.get_connected_device_udids()[0]
# 使用pm path命令获取应用程序的APK路径
command = f'adb -s {udid} shell pm path {package_name}'
result = self.run_adb(command)
if result and result.startswith('package:'):
apk_path = result.replace('package:', '').strip()
return apk_path
else:
logger.error(f"APK path for package '{package_name}' not found on the device {udid}.")
return None
def get_apk_version(self, package_name, udid=None):
"""
获取应用程序的版本号。
"""
if not udid:
udid = self.get_connected_device_udids()[0]
# 使用dumpsys package命令获取应用程序的版本号
command = f'adb -s {udid} shell dumpsys package {package_name} | {self._grep} versionCode'
result = self.run_adb(command)
match = re.search(r'versionCode=(\d+)', result)
if match:
version_code = match.group(1)
return version_code
else:
logger.error(f"Version code not found for package '{package_name}'.")
return None
def pull_apk_from_device(self, package_name, output_dir=None, apk_name=None, udid=None):
"""
根据包名从当前设备中将应用程序的APK文件复制至本地。
用于:
capabilities = {
"platformName": "Android",
"automationName": "uiautomator2",
"deviceName": "9YS0220306003185",
"app": apk_path, # 用在这里
# "appPackage": "com.tencent.mm",
# "appActivity": ".ui.LauncherUI",
}
"""
if not udid:
udid = self.get_connected_device_udids()[0]
if not output_dir:
output_dir = path.get_apk_resources_dir()
# 获取应用程序的APK路径
apk_path = self.get_apk_path(package_name)
apk_name = apk_name if apk_name else package_name
apk_version = self.get_apk_version(package_name)
if apk_path:
# 构建本地输出路径
local_output_path = os.path.join(output_dir, f"{apk_name}_{apk_version}.apk")
# 使用adb pull命令将APK复制到本地
command = f'adb -s {udid} pull {apk_path} {local_output_path}'
result = self.run_adb(command)
if result:
logger.info(f"APK successfully pulled to: {local_output_path}")
return local_output_path
else:
logger.error("Failed to pull APK from device.")
if __name__ == '__main__':
adb = ADBRunner()
print(adb.get_connected_device_udids())
print(adb.get_app_package_and_activity())
print(adb.get_apk_path('cn.com.open.mooc'))
print(adb.get_apk_version('cn.com.open.mooc'))
print(adb.pull_apk_from_device('cn.com.open.mooc'))
欢迎技术交流: