sdk接入指南

1. mgc_sdk

mgc_sdk是为手机边缘计算而开发的sdk,必须配套针对开发者的小融盒子开放版硬件(以下简称设备)开发环境使用。 IDE环境的使用可以参考《开发工具说明》

1. 1 环境准备

  • Android版本 4.4+
  • Python 3.7+

1. 2 快速开始

  • 把闲置手机插上设备,确保手机开发者权限已经打开并可用,设备会自动初始化闲置手机的环境。需要用户手动开启某些权限,详细步骤可参考《开发者用户指南》

1. 3 API文档

mgc sdk api文档说明,详细的API定义。

sdk api 2.0

@property
def android_address(self):
    """
    get address of connection to phone
    :return: http://ip:port
    """

def android_dump_hierarchy(self, compressed=False, pretty=False):
    """
    phone ui hierarchy
    :param: compressed:
    :param: pretty: if True then auto wrap
    :return: xml
    """

@property
def android_serial(self)
    """
    get phone serial num

    :return: str
    """


@property
def android_alive(self)
    """
    check if phone alive

    :return: True or False
    """

def android_click(self, x, y):
    """
    click x,y
    :param: x:
    :param: y:
    :return: None
    """

def android_double_click(self, x, y, duration=0.1):
    """
    double click x, y
    :param: x:
    :param: y:
    :param: duration:
    :return: None
    """

def android_long_click(self, x, y, duration=None):
    """
    long click x, y
    :param: x:
    :param: y:
    :param: duration:
    :return: None
    """

def android_swipe(self, x1, y1, x2, y2, duration=0.1, steps=None)
    """
    swipe on screen

    :param: x1:
    :param: y1:
    :param: x2:
    :param: y2:
    :param: duration:
    :param: steps:
    :return: None
    """


def android_app_install(self, url, installing_callback=None, server=None)
    """
    call back args {u'message': u'downloading', "progress": {u'totalSize': 407992690, u'copiedSize': 49152}}

    :returns:
        packageName

    :raises:
        RuntimeError
    """


def android_shell(self, cmdargs, stream=False, timeout=60)
    """
    Run adb shell command with arguments and return its output.

    :param: cmdargs: str or list, example: "ls -l" or ["ls", "-l"]
    :param: timeout: seconds of command run, works on when stream is False
    :param: stream: bool used for long running process.

    :returns:
        (output, exit_code) when stream is False
        requests.Response when stream is True, you have to close it after using

    :raises:
        RuntimeError

    When command got something wrong, exit_code is always 1, otherwise exit_code is always 0
    """


def android_app_start(self,
              package_name,
              activity=None,
              extras={},
              wait=False,
              stop=False,
              unlock=False, launch_timeout=None)
    """
    Launch application

    :param: package_name (str): package name
    :param: activity (str): app activity
    :param: stop (bool): Stop app before starting the activity. (require activity)
    :param: wait (bool): wait until app started. default False

    :raises:
        SessionBrokenError
    """


def android_app_current(self)
    """
    get current app info

    :returns:
        dict(package, activity, pid?)
    :raise:
        EnvironementError

    """


def android_wait_activity(self, activity, timeout=10)
    """
    wait activity

    :param: activity (str): name of activity
    :param: timeout (float): max wait time
    :returns:
        bool of activity
    """


def android_app_wait(self, package_name, timeout = 20.0, front=False)
    """
    Wait until app launched

    :param: package_name (str): package name
    :param: timeout (float): maxium wait time, default 20s
    :param: front (bool): wait until app is current app

    :returns:
        pid (int) 0 if launch failed
    """


def android_app_list_running(self)
    """
    :returns:
        list of running apps
    """


def android_app_stop(self, pkg_name)
    """
     Stop one application: am force-stop

    :param: pkg_name:
    :return: None
    """


def android_app_stop_all(self, excludes=[])
    """
    Stop all third party applications

    :param:
        excludes (list): apps that do now want to kill
    :return:
        a list of killed apps
    """


def android_app_clear(self, pkg_name)
    """
    Stop and clear app data: pm clear

    :param: pkg_name:
    :return: None
    """


def android_app_uninstall(self, pkg_name)
    """
    Uninstall an app

    :param: pkg_name (str)
    :returns:
        bool: success
    """


def android_app_uninstall_all(self, excludes=[], verbose=False)
    """
    uninstall all app excludes['com.aaa.bbb','aaa.ccc.bbb']

    :param: excludes:
    :param: verbose:
    :return:
        bool: success
    """

#目前不可用
def android_unlock(self):
    """
    unlock screen
    :return: None
    """

def android_screen_on(self)
    """
    turn on the screen

    :return: None
    """


def android_screen_off(self)
    """
     urn off the screen

    :return: None
    """


def android_push_url(self, url, dst, mode=0o644)
    """
    from a url push file to dst

    :param: url (str): http url address
    :param: st (str): destination
    :param: mode (str): file mode

    :raises:
        FileNotFoundError(py3) OSError(py2)
    :return: None
    """


def android_push(self, src, dst, mode=0644)
    """
    push src file or fileobj to dst

    :param: src (path or fileobj): source file
    :param: dst (str): destination can be folder or file path

    :returns:
        dict object, for example:

            {"mode": "0660", "size": 63, "target": "/sdcard/ABOUT.rst"}

        Since chmod may fail in android, the result "mode" may not same with input args(mode)

    :raises:
        IOError(if push got something wrong)
    """


def android_pull(self, src, dst)
    """
    Pull file from device to local

    :param: src:
    :param: dst:
    :return: FileNotFoundError(py3) OSError(py2)
    """


def android_pull_content(self, src:str)
    """
    Read remote file content

    :return: bytes
    :raises:
        FileNotFoundError
    """



def android_screenshot(self, *args, **kwargs)
    """
    Take screenshot of device

    :returns:
        PIL.Image
    """


@property
def android_device_info(self)
    """
    device detail info

    :return: str
    """


@property
def android_info(self)
    """
    device sample info

    :return: str
    """


def android_click_post_delay(self, value=None)
    """
    set global delay for click post delay

    :param value: float seconds
    :return: None
    """


def android_wait_timeout(self, value=None)
    """
    set global timeout for waitfor resource

    :param value: float seconds
    :return:
    """


def android_press(self, key)
    """
    press a key

    :param key (str): home, back, left, right, up, down, center, menu, search, enter,
        delete(or del), recent(recent apps), volume_up, volume_down,
        volume_mute, camera, power
    :return: 0 success
    """


def android_app_info(self, pkg_name)
    """
    Get app info

    :param: pkg_name (str): package name
    :returns:
        {
            "mainActivity": "xxx.xxx.xxx.MainActivity",
            "label": "MGC",
            "versionName": "1.1.7",
            "versionCode": 1001007,
            "size":1760809
        }

    :raises:
        UiaError
    """


def android_app_icon(self, pkg_name)
    """
    get app icon
    :param: pkg_name : str
    :return:
        PIL.Image
    :raises:
        UiaError
    """


@property
def android_wlan_ip(self)
    """
    get wlan ip

    :return: IPV4
    """


@property
def android_xpath(self)
    """
    get XPath object

    :return:
         XPath
    """


@property
def android_watchers(self)
    """
    get watchers ojb

    :return: watchers
    """


def android_watcher(self, name)
    """
    create a watcher

    :param name: str
    :return: watcher obj
    """


@property
def android_toast(self)
    """
    create a toast

    :return: toast obj
    """

2. API调用示例

详细展示每个API的调用示例。

2. 1 连接设备

进行其他操作之前先连接手机,通过手机的serial名字连接。

  • USB形式

    adb devices看到的usb设备名称`abcd1234`
    import mgc_sdk.api as mgc_api
    dev = mgc_api.android_mgc_connect('abcd1234')
    print(dev.android_info)
    Notes:*如果调用mgc_api.android_mgc_connect()不带参数,那么如果当前只有一个usb 设备,则默认连接当前设备。如果有超出一个设备,那么会提示连接失败。*
    

2. 2 脚本升级

  • 脚本自升级

    #脚本升级前不用链接设备,所以这里可以在android_mgc_connect之前调用,pkg_name为脚本的package名称
    import mgc_sdk.api as mgc_api
    ret = mgc_api.android_update_script(pkg_name='python_script')
    #ret==0 success, or http response code, default timeout 10s
    #请求会同步等待服务端处理完成,如果手机多的话,10s可能会超时。需要加大延时
    ret = mgc_api.android_update_script(pkg_name='python_script',timeout=20)
    

每个脚本需引入mgc_sdk.api包,以下示例不再重复。

2. 3 App管理

  • 安装App 该api只支持从URL安装App

    dev.android_app_install('http://XXX.com/demo.apk')
  • 启动App 根据包名启动app

    dev.android_app_start("com.example.hello_world")
  • 通过指定main activity的方式启动应用,相当于启动 com.aaa.bbb/.MainActivity

    dev.android_app_start("com.aaa.bbb", ".MainActivity")
  • 停止App

    # 相当于 `am force-stop`
    dev.android_app_stop("com.aaa.bbb") 
    # 相当于`pm clear`
    dev.android_app_clear('com.aaa.bbb')
  • 停止所有App

    # 停止所有app
    dev.android_app_stop_all()
    # 停止除'com.aaa.bbb'之外所有的app
    dev.android_app_stop_all(excludes=['com.aaa.bbb'])
  • 获取app信息

    dev.android_app_info("com.aaa.bbb")
    # 输出
    #{
    #    "mainActivity": "com.aaa.bbb.MainActivity",
    #    "label": "ABC",
    #    "versionName": "1.0.0",
    #    "versionCode": 1001007,
    #    "size":3740811
    #}
  • 保存app图标

    img = dev.android_app_icon("com.aaa.bbb")
    img.save("aaabbb_icon.png")
  • 列出所有运行的app

    dev.android_app_list_running()
    # 输出
    # ["com.xxxx.xxxx", "com.aaa.bbb", "yyy"]
  • 等app到前台运行

    等待应用运行, return pid(int)
    pid = dev.android_app_wait("com.aaa.bbb") 
    if not pid:
        print("com.aaa.bbb is not running")
    else:
        print("com.aaa.bbb pid is %d" % pid)
    
    dev.android_app_wait("com.aaa.bbb", front=True) # 等待应用前台运行
    dev.android_app_wait("com.aaa.bbb", timeout=20.0) # 最长等待时间20s(默认)
  • 获取当前前台App

    不同手机输出结果可能不一样

    print(dev.android_app_current())
    # 可能输出1: {'activity': '.Client', 'package': 'com.aaa.bbb', 'pid': 23710}
    # 可能输出2: {'activity': '.Client', 'package': 'com.aaa.bbb'}
    # 可能输出3: {'activity': None, 'package': None}
  • 等待activity

    dev.android_wait_activity(".ApiDemos", timeout=10) # 默认 10.0s
    # 输出: True or False

2. 4 推拉文件

  • 推一个文件

    # 推到目录
    dev.android_push("A.txt", "/sdcard/")
    
    # 重命名
    dev.android_push("A.txt", "/sdcard/A.txt")
    
    # 推文件对象
    with open(".txt", 'rb') as f:
        dev.android_push(f, "/sdcard/")
    
    # 推文件并修改权限,修改权限可能会失败
    dev.android_push("A.sh", "/data/local/tmp/", mode=0o755)
  • 拉取文件

    dev.android_pull("/sdcard/tmp.txt", "tmp.txt")
    
    #如果文件不存在 会抛出FileNotFoundError
    dev.android_pull("/sdcard/some-file-not-exists.txt", "tmp.txt")

2. 5 Shell commands

  • 简单命令(Default timeout 60s)

    output, exit_code = d.android_shell("pwd", timeout=60) # timeout 60s (Default)
    # 输出: "/\n", exit_code: 0
    # 跟adb shell pwd 功能一样
    
    #另一种接受返回值的方式
    output = d.android_shell("pwd").output
    exit_code = d.android_shell("pwd").exit_code
    #第一个参数可以接受 list对象
    output, exit_code = dev.android_shell(["ls", "-l"])
    #输出: "/....", exit_code: 0
    这种调用阻塞直到命令运行完才会返回,或者超时返回。如果命令需要长时间运行,强烈推荐使用stream方式调用
  • 需要长时间运行的命令 stream方式

    #返回requests.models.Response. 详细查看[requests stream](http://docs.python-requests.org/zh_CN/latest/user/quickstart.html#id5)
    
    r = dev.android_shell("logcat", stream=True)
    # 返回: requests.models.Response
    deadline = time.time() + 10 # run maxium 10s
    try:
      for line in r.iter_lines(): # r.iter_lines(chunk_size=512, decode_unicode=None, delimiter=None)
        if time.time() > deadline:
          break
        print("Read:", line.decode('utf-8'))
    finally:
      r.close() # this method must be called

    r.close()调用之后命令会停止运行。

2. 6 设备信息

  • 设备信息摘要

    print(dev.android_info)

    输出:

    { 
      'displayRotation': 0,
      'displaySizeDpY': 640,
      'displaySizeDpX': 360,
      'currentPackageName': 'com.android.launcher',
      'productName': 'oppoR9',
      'displayWidth': 1080,
      'sdkInt': 18,
      'displayHeight': 1920,
      'naturalOrientation': True
    }
  • 获取设备 serial名称

    print(dev.android_serial)
    # 输出: fadb23xxa
  • 获取设备WiFi IP

    print(dev.android_wlan_ip)
    # 输出: 192.168.0.1
  • 获取设备详细信息

    print(dev.android_device_info)

    输出:

    {'udid': '3578298f-b4:0b:44:e6:1f:90-OD103',
    'version': '7.1.1',
    'serial': '3578298f',
    'brand': 'SMARTISAN',
    'model': 'OD103',
    'hwaddr': 'b4:0b:44:e6:1f:90',
    'port': 6631,
    'sdk': 25,
    'agentVersion': 'dev',
    'display': {'width': 1080, 'height': 1920},
    'battery': {'acPowered': False,
    'usbPowered': False,
    'wirelessPowered': False,
    'status': 3,
    'health': 0,
    'present': True,
    'level': 99,
    'scale': 100,
    'voltage': 4316,
    'temperature': 272,
    'technology': 'Li-ion'},
    'memory': {'total': 3690280, 'around': '4 GB'},
    'cpu': {'cores': 8, 'hardware': 'Qualcomm Technologies, Inc MSM8953Pro'},
    'presenceChangedAt': '0001-01-01T00:00:00Z',
    'usingBeganAt': '0001-01-01T00:00:00Z'}

2. 7 按键

  • 模拟按键

    dev.android_press("home") # 按home键
    dev.android_press("back") # 按back键
  • 目前支持的按键:

    • home
    • back
    • left
    • right
    • up
    • down
    • center
    • menu
    • search
    • enter
    • delete ( or del)
    • recent (recent apps)
    • volume_up
    • volume_down
    • volume_mute
    • camera
    • power

2. 8 手势交互

  • 点击

    dev.android_click(x, y)
    dev.android_double_click(x, y, duration=0.2)
    dev.android_long_click(x, y, duration=0.5)
  • 滑动

    dev.android_swipe(sx, sy, ex, ey)
    dev.android_swipe(sx, sy, ex, ey, 0.5) # 持续滑动0.5s
    
    #按照points 坐标数组里面的点滑动
    dev.android_swipe_points([(123,234),(321,234),(567, 678)], duration=0.5)
  • 拖拽

    #from (x1,y1) drag to (x2,y2)
    dev.android_drag( x1, y1, x2, y2, duration=0.5)
    #return True or False

2. 9 屏幕相关

  • 屏幕旋转

    
    dev.android_get_orientation
    #return:left/right or natural/upsidedown
    
    dev.android_set_orientation(value)
    #value: 0,1,2,3
    

    冻结屏幕方向

    dev.android_freeze_rotation(freeze=True)
    #freeze True or False
  • 开关屏

    dev.android_screen_on() # 开屏
    dev.android_screen_off() # 关屏
  • 解锁屏幕

    此函数目前不可用。

    dev.android_unlock()
  • 获取屏幕状态

    dev.android_info.get('screenOn') # 要求 Android >= 4.4
    返回:True or False
  • 截屏

    # 需要android4.2版本以上
    dev.android_screenshot("home.jpg")
    
    # 获取PIL.Image 格式的图片。
    image = dev.android_screenshot() # 默认格式是 "pillow"
    image.save("home.jpg") # 当前只支持png 和 jpg ,保存本地
    
    # 获取 opencv 格式的图片.
    import cv2
    image = dev.android_screenshot(format='opencv')
    cv2.imwrite('home.jpg', image)
    
    # 获取raw jpeg data
    imagebin = dev.android_screenshot(format='raw')
    open("some.jpg", "wb").write(imagebin)

2. 10 文本输入

  • 切换输入法和输入

    
    #mgc输入法开关
    dev.android_set_mgcinput_ime(enable=True)
    
    #输入法切换的时候等待切换完
    dev.android_wait_mgcinput_ime(timeout=5.0)
    
    dev.android_current_ime()
    #return:(method_id(str), shown(bool), example: ('com.control.mgtow/.MgcImeService', True)
    
    #在焦点输入框内输入文本,True代表清除已有内容
    dev.android_send_keys("fdsfd", True)
    
    #模拟输入发上的按钮,确定/搜索/发送等
    dev.android_send_action(code)
    #code:
       "go": 2,
       "search": 3,
       "send": 4,
       "next": 5,
       "done": 6,
       "previous": 7,
    
    #清除焦点输入框内的文本
    dev.android_clear_text()
    

2. 11 UI Selector

  • Selector 是获取UI元素的一种方式

    # 选择text是'Clock' 并且类名是'android.widget.TextView'
    dev(text='Clock', className='android.widget.TextView')
    # 选择text是'Clock'
    dev(text='Clock')
    # 选择类名是'android.widget.TextView'
    dev(className='android.widget.TextView')
    # 选择description是'Clock' 并且类名是'android.widget.TextView'
    dev(description='Clock', className='android.widget.TextView')
    # 选择resourceId是 "android:id/tabs"
    dev(resourceId="android:id/tabs")
  • 目前支持的Selector

    • text, textContains, textMatches, textStartsWith
    • className, classNameMatches
    • description, descriptionContains, descriptionMatches, descriptionStartsWith
    • checkable, checked, clickable, longClickable
    • scrollable, enabled,focusable, focused, selected
    • packageName, packageNameMatches
    • resourceId, resourceIdMatches
    • index, instance

2. 11. 1 查找方式

  • 父子关系

    # get the children or grandchildren
    dev(className="android.widget.ListView").child(text="Bluetooth")
  • 同级关系

    # get siblings
    dev(text="Google").sibling(className="android.widget.ImageView")
  • 级联

    同时匹配父级和子级条件

    # get the child matching the condition className="android.widget.LinearLayout"
    # and also its children or grandchildren with text "Bluetooth"
    dev(className="android.widget.ListView", resourceId="android:id/list") \
     .child_by_text("Bluetooth", className="android.widget.LinearLayout")
    
    # get children by allowing scroll search
    dev(className="android.widget.ListView", resourceId="android:id/list") \
     .child_by_text(
        "Bluetooth",
        allow_scroll_search=True,
        className="android.widget.LinearLayout"
      )
    

    child_by_description 和 child_by_text一样,参数相同

    dev(className="android.widget.ListView", resourceId="android:id/list") \
      .child_by_text("Wi‑Fi", className="android.widget.LinearLayout") \
      .child(className="android.widget.Switch") \
      .click()
  • 相对关系

    使用相对位置查找: left, right, up, down.

    • dev(A).left(B), selects B on the left side of A.
    • dev(A).right(B), selects B on the right side of A.
    • dev(A).up(B), selects B above A.
    • dev(A).down(B), selects B under A.

    例如:

    ## select "switch" on the right side of "Wi‑Fi"
    dev(text="Wi‑Fi").right(className="android.widget.Switch").click()
  • 多实例查找

    有时候同一个selector有多个实例,可以通过instance查找,或者数组索引的方式

    dev(text="Add new", instance=0)  # which means the first instance with text "Add new"
    # 获取符合selector的个数
    dev(text="Add new").count
    
    # same as count property
    len(dev(text="Add new"))
    
    # 通过索引获取
    dev(text="Add new")[0]
    dev(text="Add new")[1]
    ...
    
    # iterator
    for view in d(text="Add new"):
      view.info  # ...
  • 正则表达式

    #上面提到的textMatches classNameMatches descriptionMatches
    # resourceIdMatches packageNameMatches 都可以使用正则表达式匹配 
    
    # 例如description 是 10时30分32秒,时间是动态变化的
    dev(descriptionMatches=".*时.*分.*秒")
    
    # 例如text 是 "开百宝箱得金币"
    dev(textMatches="开百宝箱.*")
    dev(textMatches=".*百宝箱.*")
    dev(textMatches=".*得金币")
    
    # 开头匹配 
    dev(textStartsWith="开百宝箱")
    
    # 多条件查找,匹配 Yes或者OK或者确定或者确认或者好的
    dev(textMatches="Yes|OK|确定|确认|好的")
  • 图片匹配

      敬请期待
  • Notes*:当使用selectors查找元素时,必须保证UI元素不会实时变化,否则可能会导致Element-Not-Found error抛出

2. 11. 2 查询selector状态信息

  • 查找UI元素是否存在

    dev(text="Settings").exists # True if exists, else False
    dev.exists(text="Settings") # alias of above property.
    
    # advanced usage
    dev(text="Settings").exists(timeout=3) # wait Settings appear in 3s, same as .wait(3)
  • UI元素的info信息

    dev(text="Settings").info

    输出:

    { u'contentDescription': u'',
      u'checked': False,
      u'scrollable': False,
      u'text': u'Settings',
      u'packageName': u'com.android.launcher',
      u'selected': False,
      u'enabled': True,
      u'bounds': {u'top': 385,
                  u'right': 360,
                  u'bottom': 585,
                  u'left': 200},
      u'className': u'android.widget.TextView',
      u'focused': False,
      u'focusable': True,
      u'clickable': True,
      u'chileCount': 0,
      u'longClickable': True,
      u'visibleBounds': {u'top': 385,
                          u'right': 360,
                          u'bottom': 585,
                          u'left': 200},
      u'checkable': False
    }
  • UI元素的操作

    dev(text="Settings").get_text()  # get widget text
    dev(text="Settings").set_text("My text...")  # set the text
    dev(text="Settings").clear_text()  # clear the text
  • 获取坐标

    x, y = dev(text="Settings").center()
    # x, y = d(text="Settings").center(offset=(0, 0)) # left-top x, y

2. 11. 3 元素点击

  • 点击的各种方式

    # 默认点击元素的中心
    dev(text="Settings").click()
    
    # 等待元素 超时10s  出现的时候就点击
    dev(text="Settings").click(timeout=10)
    
    # click with offset(x_offset, y_offset)
    # click_x = x_offset * width + x_left_top
    # click_y = y_offset * height + y_left_top
    dev(text="Settings").click(offset=(0.5, 0.5)) # Default center
    dev(text="Settings").click(offset=(0, 0)) # click left-top
    dev(text="Settings").click(offset=(1, 1)) # click right-bottom
    
    # 等待元素出现后点击,超时10s
    clicked = dev(text='Skip').click_exists(timeout=10.0)
    
    # 一直点击 直到元素消失
    is_gone = dev(text="Skip").click_gone(maxretry=10, interval=1.0) # maxretry default 10, interval default 1.0
  • 元素长按

    # 默认长按元素中心
    dev(text="Settings").long_click()
    dev(text="Settings").long_click(duration=5) #default duration is 0.5s
    dev(text="Settings").long_click(duration=5, timeout=10) #wait 10s for element show up
    

2. 11. 4 元素拖拽

  • 拖动元素往某坐标 或者 某元素之上

    # Android版本要求4.3以上
    # 拖动元素到下坐标(x,y)
    dev(text="Settings").drag_to(x, y, duration=0.5)
    # 拖动元素到其他元素上
    dev(text="Settings").drag_to(text="Clock", duration=0.25)
  • 往各方向滑动元素

    支持的四个方向:

    • left
    • right
    • top
    • bottom
    dev(text="Settings").swipe("right")
    dev(text="Settings").swipe("left", steps=10)
    dev(text="Settings").swipe("up", steps=20) # 1 steps is about 5ms, so 20 steps is about 0.1s
    dev(text="Settings").swipe("down", steps=20)
  • 等待UI对象

    # wait until the ui object appears
    dev(text="Settings").wait(timeout=3.0) # return bool
    # wait until the ui object gone
    dev(text="Settings").wait_gone(timeout=1.0)

    默认超时是 20s,参考全局设置

2. 12 Watcher

  • 注册when条件满足的时候触发的动作,比如这里的click

    dev.android_watcher("AUTO_FC_WHEN_ANR").when(text="ANR").when(text="Wait") \
                                 .click(text="Force Close")
    # dev.watcher(name) ## 创建一个watcher.
    #  .when(condition)  ## 侦听的条件.
    #  .click(target)  ## 条件满足的时候触发的动作.
    dev.android_watcher("ALERT").when(text="OK").click()
    # Same as
    dev.android_watcher("ALERT").when(text="OK").click(text="OK")

    还可以触发按键

    dev.android_watcher("AUTO_FC_WHEN_ANR").when(text="ANR").when(text="Wait") \
                                 .press("back", "home")
    
  • 检查watcher是否被触发

    当条件满足的时候watcher被触发

    dev.android_watcher("watcher_name").triggered
    # true in case of the specified watcher triggered, else false
  • 删除watcher

    dev.android_watcher("watcher_name").remove()
  • 列出所有watcher

    dev.android_watchers
    # a list of all registered watchers
  • 检查是否有任何watcher被触发

    dev.android_watchers.triggered
    #  true in case of any watcher triggered
  • 重置watcher

    # 重置所有被触发的watcher, 到时dev.watchers.triggered将为false.
    dev.android_watchers.reset()
  • 删除watcher

    # 删除所有的watcher
    dev.android_watchers.remove()
    # 按名字删除watcher, 跟 dev.watcher("watcher_name").remove()一样
    dev.android_watchers.remove("watcher_name")
  • 前置run所有的watcher

    # force to run all registered watchers
    dev.android_watchers.run()

2. 13 Toast

  • 显示toast

    dev.android_toast.show("Hello world")
    dev.android_toast.show("Hello world", 1.0) # show for 1.0s, default 1.0s
  • 获取Toast

    # [Args]
    # 5.0: 超时时间,单位秒. 默认10.0
    # 10.0: 最近缓存时长,指缓存最近10秒的toast
    # "default message": return if no toast finally get. Default None
    dev.android_toast.get_message(5.0, 10.0, "default message")
    
    # 示例
    assert "Short message" in dev.android_toast.get_message(5.0, default="")
    
    # 清除缓存
    dev.android_toast.reset()
    # Now dev.toast.get_message(0) is None

2. 14 XPath

xpath定位元素比较耗资源,相比UISelector优先选择使用UISelector。

示例:其中一个节点的内容

  <android.widget.TextView
    index="2"
    text="05:19"
    resource-id="com.xxx.yyy:id/qf"
    package="com.netease.cloudmusic"
    content-desc=""
    checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false"
    scrollable="false" long-clickable="false" password="false" selected="false" visible-to-user="true"
    bounds="[957,1602][1020,1636]" />

xpath定位和使用方法

  • 有些属性的名字有修改需要注意

    description -> content-desc
    resourceId -> resource-id
  • 常见用法

    # 等元素出现 超时10s 
    dev.android_xpath("//android.widget.TextView").wait(10.0)
    
    # 点击元素
    dev.android_xpath("//*[@content-desc='分享']").click()
    
    #检查资源是否存在 
    if dev.android_xpath("//android.widget.TextView[contains(@text, 'Se')]").exists:
        print("exists")
    
    # 获取元素文本内容 属性  中心坐标
    for elem in dev.android_xpath("//android.widget.TextView").all():
      print("Text:", elem.text)
        # Dictionary eg: 
        # {'index': '1', 'text': '999+', 'resource-id': 'com.xxx.yyy:id/qb', 'package': 'com.netease.cloudmusic', 'content-desc': '', 'checkable': 'false', 'checked': 'false', 'clickable': 'false', 'enabled': 'true', 'focusable': 'false', 'focused': 'false','scrollable': 'false', 'long-clickable': 'false', 'password': 'false', 'selected': 'false', 'visible-to-user': 'true', 'bounds': '[661,1444][718,1478]'}
        print("Attrib:", elem.attrib)
        # Coordinate eg: (100, 200)
        print("Position:", elem.center())

2. 15 全局设置

  • 对全局有效

    # set delay 1.5s after each UI click and click
    dev.android_click_post_delay = 1.5 # default no delay
    
    # 设置默认的等待元素超时时长
    dev.android_wait_timeout = 30.0 # default 20.0

2. 16 插件例子

# -*- coding: UTF-8 -*-

import mgc_sdk.api as mgc_api

from time import sleep
import random
import sys
import os
import threading
import signal
import time

app_name="com.aaaa.bbbb"

def start_work(name):

    #连接设备
    dev = mgc_api.android_mgc_connect(name) # connect to device
    dev.android_screen_on()
    sleep(1)

    #查看设备简要信息
    print("device sample info: %s" % dev.android_info)

    #查看设备详细信息
    print("device detail info: %s" % dev.android_device_info)

    #查看设备serial
    print("device serial: %s" % dev.android_serial)

    #查看wifi ip
    print("device wlan ip: % s" % dev.android_wlan_ip)

    #停止 app_name
    dev.android_app_stop(app_name)

    #按键 home
    dev.android_press("home") # press the back key, with key name

    #获取所有运行的app
    print(dev.android_app_list_running())

    #启动指定的app
    dev.android_app_start(app_name)#启动app

    # 等待应用前台运行
    dev.android_app_wait(app_name, front=True)

    #do something here
    where True:
        sleep(1)



    dev.android_press("home") # press the home key, with key name

    dev.android_app_stop(app_name)


def main(argv):
    name = ''
    if len(argv) > 1:
        name = sys.argv[1]

    start_work(name)

#当插件部署到平台后,平台拉起插件时默认传一个手机的serial作为入口参数。
#在开发工具中开发调试插件时则需要开发者自行处理手机serial传参问题。
if __name__ == '__main__':
    main(sys.argv)