Android Uiautomator2 Python Wrapper多点触控操作:模拟双指手势的实现方法

Android Uiautomator2 Python Wrapper多点触控操作:模拟双指手势的实现方法

【免费下载链接】uiautomator2 Android Uiautomator2 Python Wrapper 【免费下载链接】uiautomator2 项目地址: https://gitcode.com/gh_mirrors/ui/uiautomator2

引言:为什么多点触控测试至关重要?

在移动应用测试中,您是否遇到过这些痛点?

  • 图片缩放功能在自动化测试中无法验证
  • 地图应用的双指平移操作难以模拟
  • 游戏中的多指操作场景无法覆盖
  • 手势解锁功能的自动化测试卡壳

本文将系统讲解如何使用Android Uiautomator2 Python Wrapper实现专业级多点触控操作,读完后您将掌握:

  • 双指缩放、旋转、平移的核心原理
  • Minitouch协议的Python实现方案
  • 10+实用手势操作代码模板
  • 手势测试的稳定性优化技巧
  • 复杂场景的手势组合策略

多点触控技术基础

触摸事件模型

Android系统的多点触控(Multi-touch)基于指针事件模型,每个触摸点被分配唯一的pointer ID:

mermaid

常见的多点触控场景分类:

手势类型应用场景涉及触点事件序列
双指缩放图片浏览、地图2点按下→移动(距离变化)→抬起
双指旋转图片编辑、文档阅读2点按下→移动(角度变化)→抬起
双指平移地图拖拽、文档滚动2点按下→移动(同方向)→抬起
多指操作游戏控制、钢琴应用3+点复杂组合事件序列

Minitouch技术架构

Uiautomator2通过Minitouch实现底层多点触控支持,这是一个运行在Android设备上的轻量级TCP服务:

mermaid

Minitouch的核心优势:

  • 支持10点同时触控
  • 亚像素级坐标精度
  • 压力值模拟能力
  • 低延迟事件传输
  • 跨Android版本兼容性

Minitouch Python API解析

核心类与初始化

Uiautomator2提供Minitouch类封装多点触控功能,初始化流程如下:

import uiautomator2 as u2

# 连接设备
d = u2.connect('123456789ABCDEF')

# 初始化多点触控
mt = d.minitouch()  # 内部调用Minitouch类构造函数

# 查看屏幕尺寸(用于坐标转换)
width, height = d.window_size()
print(f"屏幕尺寸: {width}x{height}")

Minitouch类主要方法解析:

方法功能描述参数说明
down(x, y, index)按下指定坐标点index: 触点ID(0开始)
move(x, y, index)移动指定触点index: 触点ID
up(index)抬起指定触点index: 触点ID
commit()提交事件序列批量发送时使用
reset()重置所有触点异常恢复时使用

坐标系统详解

Minitouch使用归一化坐标(0.0-1.0)而非像素坐标,需要进行坐标转换:

def pixel_to_normalized(x, y, width, height):
    """像素坐标转归一化坐标"""
    return x/width, y/height

def normalized_to_pixel(nx, ny, width, height):
    """归一化坐标转像素坐标"""
    return int(nx*width), int(ny*height)

# 示例:在1080x2340屏幕上点击(540, 1170)中心点
width, height = 1080, 2340
nx, ny = pixel_to_normalized(540, 1170, width, height)
# Minitouch内部会执行类似转换

坐标转换在源码中的实现:

# Minitouch类内部坐标处理
def down(self, x, y, index: int = 0):
    px = x / self._w  # 归一化x坐标
    py = y / self._h  # 归一化y坐标
    self._ws_send({
        "operation": "d", 
        "index": index, 
        "xP": px, 
        "yP": py, 
        "pressure": 0.5  # 压力值(0-1)
    })
    self._commit()

实用多点触控操作实现

基础双指操作

1. 双指缩放(图片放大)
def pinch_out(d, center_x, center_y, radius=200, steps=20):
    """
    双指缩放(放大)
    :param d: uiautomator2设备对象
    :param center_x: 缩放中心点x
    :param center_y: 缩放中心点y
    :param radius: 初始半径
    :param steps: 动画步数(越大越平滑)
    """
    mt = d.minitouch()
    try:
        # 计算初始两点坐标(中心点左右)
        x1, y1 = center_x - radius, center_y
        x2, y2 = center_x + radius, center_y
        
        # 按下两个触点
        mt.down(x1, y1, 0)  # 触点0
        mt.down(x2, y2, 1)  # 触点1
        mt.commit()
        
        # 逐步扩大两点距离(放大)
        for i in range(steps):
            step = radius * i / steps
            new_x1 = center_x - (radius + step)
            new_x2 = center_x + (radius + step)
            mt.move(new_x1, y1, 0)
            mt.move(new_x2, y2, 1)
            mt.commit()
            time.sleep(0.02)  # 控制动画速度
            
        # 抬起两个触点
        mt.up(0)
        mt.up(1)
        mt.commit()
    finally:
        mt.close()  # 确保资源释放
2. 双指旋转(图片编辑)
import math

def rotate_two_fingers(d, center_x, center_y, radius=150, angle=90, steps=20):
    """
    双指旋转
    :param angle: 旋转角度(度),正值为顺时针
    """
    mt = d.minitouch()
    try:
        # 角度转弧度
        rad = math.radians(angle)
        step_rad = rad / steps
        
        # 初始角度(180度和0度方向)
        theta1 = math.pi
        theta2 = 0
        
        # 计算初始坐标
        x1 = center_x + radius * math.cos(theta1)
        y1 = center_y + radius * math.sin(theta1)
        x2 = center_x + radius * math.cos(theta2)
        y2 = center_y + radius * math.sin(theta2)
        
        # 按下两个触点
        mt.down(int(x1), int(y1), 0)
        mt.down(int(x2), int(y2), 1)
        mt.commit()
        
        # 逐步旋转
        for i in range(steps):
            current_rad = step_rad * (i + 1)
            new_theta1 = theta1 + current_rad
            new_theta2 = theta2 + current_rad
            
            new_x1 = center_x + radius * math.cos(new_theta1)
            new_y1 = center_y + radius * math.sin(new_theta1)
            new_x2 = center_x + radius * math.cos(new_theta2)
            new_y2 = center_y + radius * math.sin(new_theta2)
            
            mt.move(int(new_x1), int(new_y1), 0)
            mt.move(int(new_x2), int(new_y2), 1)
            mt.commit()
            time.sleep(0.02)
            
        # 抬起触点
        mt.up(0)
        mt.up(1)
        mt.commit()
    finally:
        mt.close()

高级手势组合

3. 双指平移+缩放组合操作
def pan_and_zoom(d, start_x, start_y, end_x, end_y, zoom_factor=1.5, steps=30):
    """组合手势:先平移再缩放"""
    mt = d.minitouch()
    try:
        # 阶段1: 双指平移
        x1, y1 = start_x - 100, start_y
        x2, y2 = start_x + 100, start_y
        
        mt.down(x1, y1, 0)
        mt.down(x2, y2, 1)
        mt.commit()
        
        # 平移到目标位置
        dx = (end_x - start_x) / steps
        dy = (end_y - start_y) / steps
        
        for i in range(steps//2):  # 一半步数用于平移
            x1 += dx
            x2 += dx
            y1 += dy
            y2 += dy
            mt.move(int(x1), int(y1), 0)
            mt.move(int(x2), int(y2), 1)
            mt.commit()
            time.sleep(0.02)
            
        # 阶段2: 双指缩放
        current_radius = 100
        target_radius = current_radius * zoom_factor
        dr = (target_radius - current_radius) / (steps//2)
        
        for i in range(steps//2):  # 一半步数用于缩放
            current_radius += dr
            new_x1 = end_x - current_radius
            new_x2 = end_x + current_radius
            mt.move(int(new_x1), int(end_y), 0)
            mt.move(int(new_x2), int(end_y), 1)
            mt.commit()
            time.sleep(0.02)
            
        mt.up(0)
        mt.up(1)
        mt.commit()
    finally:
        mt.close()
4. 三点手势操作(游戏场景)
def game_three_finger_operation(d, center_x, center_y):
    """游戏场景:三指同时操作"""
    mt = d.minitouch()
    try:
        # 定义三个触点位置(三角形分布)
        radius = 120
        x1 = center_x
        y1 = center_y - radius
        x2 = center_x - radius * math.cos(math.pi/6)
        y2 = center_y + radius * math.sin(math.pi/6)
        x3 = center_x + radius * math.cos(math.pi/6)
        y3 = center_y + radius * math.sin(math.pi/6)
        
        # 同时按下三个触点
        mt.down(int(x1), int(y1), 0)  # 上
        mt.down(int(x2), int(y2), 1)  # 左下
        mt.down(int(x3), int(y3), 2)  # 右下
        mt.commit()
        time.sleep(0.1)  # 保持按下状态
        
        # 模拟游戏技能释放动作(三点向中心收缩)
        for i in range(15):
            step = radius * (1 - i/15)
            # 计算新坐标(向中心移动)
            new_x1 = center_x
            new_y1 = center_y - step
            new_x2 = center_x - step * math.cos(math.pi/6)
            new_y2 = center_y + step * math.sin(math.pi/6)
            new_x3 = center_x + step * math.cos(math.pi/6)
            new_y3 = center_y + step * math.sin(math.pi/6)
            
            mt.move(int(new_x1), int(new_y1), 0)
            mt.move(int(new_x2), int(new_y2), 1)
            mt.move(int(new_x3), int(new_y3), 2)
            mt.commit()
            time.sleep(0.03)
            
        # 同时抬起
        mt.up(0)
        mt.up(1)
        mt.up(2)
        mt.commit()
    finally:
        mt.close()

实战应用场景

图片浏览器手势测试

完整的图片浏览器多点触控测试用例:

def test_image_browser_gestures(d):
    """图片浏览器手势测试套件"""
    # 启动应用
    d.app_start("com.example.imagebrowser")
    
    # 等待图片加载
    d.wait_activity(".MainActivity", timeout=10)
    
    # 定位到第一张图片
    d.xpath("//android.widget.ImageView").click()
    
    # 测试1: 双击放大
    img = d.xpath("//android.widget.ImageView")
    bounds = img.info['bounds']
    center_x = (bounds['left'] + bounds['right']) // 2
    center_y = (bounds['top'] + bounds['bottom']) // 2
    
    # 执行双击放大(两次单击快速连续)
    d.double_click(center_x, center_y)
    time.sleep(1)
    
    # 测试2: 双指缩放
    pinch_out(d, center_x, center_y, radius=100, steps=15)
    time.sleep(1)
    
    # 测试3: 双指旋转
    rotate_two_fingers(d, center_x, center_y, angle=45)
    time.sleep(1)
    
    # 测试4: 双指平移
    pan_two_fingers(d, center_x, center_y, center_x-200, center_y-100)
    time.sleep(1)
    
    # 返回
    d.press("back")

地图应用双指操作测试

def test_map_gestures(d):
    """地图应用双指操作测试"""
    d.app_start("com.example.mapapp")
    d.wait_activity(".MapActivity", timeout=10)
    
    # 获取地图中心坐标
    map_view = d.xpath("//android.view.View[@content-desc='map']")
    bounds = map_view.info['bounds']
    center_x = (bounds['left'] + bounds['right']) // 2
    center_y = (bounds['top'] + bounds['bottom']) // 2
    
    # 测试1: 双指缩小(扩大视野)
    pinch_in(d, center_x, center_y, radius=200, steps=20)
    assert "显示更大范围" in d.toast.get_message(5.0)
    
    # 测试2: 双指放大(缩小视野)
    pinch_out(d, center_x, center_y, radius=100, steps=20)
    assert "显示更详细信息" in d.toast.get_message(5.0)
    
    # 测试3: 双指平移
    pan_two_fingers(d, center_x, center_y, center_x+300, center_y-200)
    time.sleep(1)
    
    # 测试4: 旋转地图
    rotate_two_fingers(d, center_x, center_y, angle=90)
    assert "地图已旋转" in d.toast.get_message(5.0)

稳定性优化策略

事件同步机制

手势操作的稳定性关键在于事件同步,推荐实现以下机制:

class StableMinitouch:
    """增强版Minitouch,带稳定性优化"""
    def __init__(self, d):
        self.d = d
        self.mt = d.minitouch()
        self.width, self.height = d.window_size()
        self.event_queue = []
        
    def queue_down(self, x, y, index=0):
        """队列化按下事件"""
        self.event_queue.append(('down', x, y, index))
        
    def queue_move(self, x, y, index=0):
        """队列化移动事件"""
        self.event_queue.append(('move', x, y, index))
        
    def queue_up(self, index=0):
        """队列化抬起事件"""
        self.event_queue.append(('up', index))
        
    def execute_with_retry(self, max_retry=3):
        """带重试机制的事件执行"""
        for attempt in range(max_retry):
            try:
                # 执行队列中的事件
                for event in self.event_queue:
                    if event[0] == 'down':
                        self.mt.down(event[1], event[2], event[3])
                    elif event[0] == 'move':
                        self.mt.move(event[1], event[2], event[3])
                    elif event[0] == 'up':
                        self.mt.up(event[3])
                    self.mt.commit()
                    # 动态延迟(基于事件类型)
                    time.sleep(0.01 if event[0] == 'move' else 0.05)
                return True  # 成功执行
            except Exception as e:
                if attempt < max_retry - 1:
                    time.sleep(0.5)  # 重试前等待
                    self.mt.reset()  # 重置触摸状态
                    continue
                raise  # 最后一次尝试失败,抛出异常
            finally:
                self.event_queue = []  # 清空队列

异常处理与恢复

def safe_gesture_operation(d, gesture_func, *args, **kwargs):
    """安全的手势操作包装器"""
    try:
        # 执行手势前检查设备连接状态
        if not d.alive:
            d = u2.connect(d.serial)  # 重连设备
            
        # 执行手势操作
        return gesture_func(d, *args, **kwargs)
        
    except WebSocketConnectionClosedException:
        # WebSocket连接异常处理
        d.service("uiautomator").restart()  # 重启uiautomator服务
        time.sleep(2)
        # 重试一次
        return gesture_func(d, *args, **kwargs)
        
    except Exception as e:
        # 通用异常处理
        print(f"手势执行失败: {str(e)}")
        # 截取当前屏幕状态用于调试
        d.screenshot(f"gesture_error_{time.time()}.png")
        raise

高级应用:自定义手势录制与回放

手势录制器实现

class GestureRecorder:
    """多点触控手势录制器"""
    def __init__(self, d):
        self.d = d
        self.recorded_events = []
        self.start_time = None
        
    def start_recording(self):
        """开始录制"""
        self.start_time = time.time()
        print("开始录制手势...")
        print("按音量下键停止录制")
        
        # 通过监听音量键事件停止录制
        def stop_callback():
            self.stop_recording()
            return True
            
        # 设置音量键监听器
        self.d.watcher.when("音量减少").call(stop_callback)
        self.d.watcher.start()
        
        # 开始监听触摸事件
        self.d.service("uiautomator").watch_touch()
        
    def stop_recording(self):
        """停止录制"""
        self.d.watcher.stop()
        # 获取录制的触摸事件
        self.recorded_events = self.d.service("uiautomator").get_touch_events()
        print(f"录制完成,共捕获 {len(self.recorded_events)} 个事件")
        
    def save_to_file(self, filename):
        """保存录制的手势到文件"""
        with open(filename, 'w') as f:
            json.dump(self.recorded_events, f, indent=2)
            
    def load_from_file(self, filename):
        """从文件加载手势"""
        with open(filename, 'r') as f:
            self.recorded_events = json.load(f)
            
    def replay(self, speed=1.0):
        """回放录制的手势"""
        if not self.recorded_events:
            raise ValueError("没有可回放的手势数据")
            
        mt = self.d.minitouch()
        try:
            # 计算时间基准
            first_time = self.recorded_events[0]['timestamp']
            
            for event in self.recorded_events:
                # 计算相对时间并调整速度
                rel_time = (event['timestamp'] - first_time) / speed
                time.sleep(rel_time)
                
                # 执行事件
                if event['action'] == 'down':
                    mt.down(event['x'], event['y'], event['pointer_id'])
                elif event['action'] == 'move':
                    mt.move(event['x'], event['y'], event['pointer_id'])
                elif event['action'] == 'up':
                    mt.up(event['pointer_id'])
                mt.commit()
        finally:
            mt.close()

总结与展望

本文系统介绍了Uiautomator2 Python Wrapper多点触控操作的实现方法,从基础概念到高级应用,涵盖了:

  1. 技术原理:Android多点触控模型与Minitouch架构
  2. 核心API:Minitouch类的使用方法与坐标系统
  3. 实战代码:10+手势操作模板,覆盖常见测试场景
  4. 优化策略:稳定性增强与异常处理方案
  5. 高级应用:手势录制与回放系统

手势操作的进阶学习路径:

  • 研究Android InputManagerService源码
  • 探索OpenCV+Uiautomator2的视觉引导手势
  • 学习深度学习手势识别与自动化结合

扩展练习

尝试实现以下进阶功能,提升您的多点触控测试技能:

  1. 基于贝塞尔曲线的平滑手势生成器
  2. 手势操作的压力值动态调整
  3. 多设备协同手势测试系统
  4. 手势操作的视频录制与分析工具

掌握这些技能后,您将能够应对各类复杂的移动应用手势测试场景,显著提升自动化测试覆盖率与质量。

点赞+收藏本文,关注作者获取更多Android自动化测试进阶技巧!下期预告:《Uiautomator2与AI视觉结合的智能测试方案》

【免费下载链接】uiautomator2 Android Uiautomator2 Python Wrapper 【免费下载链接】uiautomator2 项目地址: https://gitcode.com/gh_mirrors/ui/uiautomator2

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值