← 返回
未分类

Hospital Android Adb

Automate hospital appointment booking on the 医联 (Yilian) Android app using uiautomator2. Covers hospital search (with Chinese text input), department selecti...
使用 uiautomator2 自动化医联(Yilian)Android 应用的医院预约,包括医院搜索(支持中文输入)和科室选择等步骤。
openlittlebear openlittlebear 来源
未分类 clawhub v1.0.0 1 版本 100000 Key: 无需
★ 0
Stars
📥 162
下载
💾 0
安装
1
版本
#latest

概述

Hospital Appointment Booking (医联 / Yilian)

Automate doctor appointment booking on the 医联 Android app (com.wonders.mobile.app.yilian). The app uses native Android views (no UC WebView issues). Use uiautomator2 for all interactions — Appium is NOT needed.

Before Any Action

import uiautomator2 as u2
d = u2.connect()

Kill 12306 first if running — both apps can't share the accessibility service:

import subprocess
subprocess.run(["adb", "shell", "am", "force-stop", "com.MobileTicket"])

Launch 医联:

subprocess.run(["adb", "shell", "monkey", "-p", "com.wonders.mobile.app.yilian",
                "-c", "android.intent.category.LAUNCHER", "1"])
import time; time.sleep(5)

App Info

FieldValue
--------------
Packagecom.wonders.mobile.app.yilian
Activity chainMainActivitySearchActivityHospitalHomePageActivityDepartmentActivitySpecialDiseaseActivitySchedulingActivityReserveInfoActivity
UI typeNative Android (RecyclerView, LinearLayout, TextView) — no WebView issues
Input methodel.set_text("华山医院") via AccessibilityService — no IME needed

⚠️ Key Technical Constraints

Chinese Text Input: Use set_text()

d.send_keys("华山医院") fails (clipboard permission denied). adb shell input text fails (NullPointerException on Chinese chars). Use set_text() instead:

# Focus the field, then set text via AccessibilityService ACTION_SET_TEXT
el = d(className='android.widget.EditText')
el.click()         # focus first
time.sleep(0.5)
el.set_text("华山医院")  # bypasses IME/clipboard entirely

This works because set_text() uses the Android AccessibilityService's ACTION_SET_TEXT action, which directly injects text into the focused EditText without needing an IME or clipboard permissions.

Element Clickability

Many department/button TextViews have clickable=false. Their parent container (LinearLayout/FrameLayout) has clickable=true. Always check parent containers by bounds containment:

for p in root.iter('node'):
    if p.get('clickable') == 'true' and parent_bounds_contains(p, child_bounds):
        # This is the actual clickable target

Schedule Data Loading Delay

The SchedulingActivity RecyclerView takes 2-10 seconds to populate with available dates. After tapping a slot, poll the dump for date texts like "2026-06-01"` until they appear.

Screen Reading

Primary: uiautomator2 dump_hierarchy + XML parsing

xml = d.dump_hierarchy()
root = __import__('xml.etree.ElementTree', fromlist=['ElementTree']).fromstring(xml.encode())

Helper: find clickable elements with text

import re
for node in root.iter('node'):
    text = node.get('text', '').strip()
    bounds = node.get('bounds', '')
    clickable = node.get('clickable')
    if text and clickable == 'true':
        nums = re.findall(r'\d+', bounds)
        if len(nums) >= 4:
            h = int(nums[3]) - int(nums[1])
            if h > 20:  # meaningful height
                print(f"'{text}' at {bounds}")

Helper: find clickable parent by bounds

def find_clickable_parent(root, child_bounds):
    child_nums = __import__('re').findall(r'\d+', child_bounds)
    if len(child_nums) < 4: return None
    cx1, cy1, cx2, cy2 = int(child_nums[0]), int(child_nums[1]), int(child_nums[2]), int(child_nums[3])
    for p in root.iter('node'):
        if p.get('clickable') != 'true': continue
        pn = __import__('re').findall(r'\d+', p.get('bounds', ''))
        if len(pn) >= 4:
            px1, py1, px2, py2 = int(pn[0]), int(pn[1]), int(pn[2]), int(pn[3])
            if px1 <= cx1 and py1 <= cy1 and px2 >= cx2 and py2 >= cy2:
                return p
    return None

Fallback: Screenshot + OCR

d.screenshot('/tmp/screen.png')
import pytesseract
from PIL import Image
text = pytesseract.image_to_string(Image.open('/tmp/screen.png'),
    lang='chi_sim', config='--psm 6')

Booking Flow

Step 1: Home Page — Search for Hospital

Home page UI:

  • "搜医院、科室、医生" — search box (clickable) at top
  • "当日挂号" — same-day appointments
  • "预约挂号" — appointment booking label (NOT clickable)
d(text='搜医院、科室、医生').click()
time.sleep(2)

On the search page, type the hospital name:

el = d(className='android.widget.EditText')
el.click()
time.sleep(0.5)
el.set_text("华山医院")  # set text via AccessibilityService
time.sleep(2)            # wait for suggestions

After typing, the search results / suggestions appear. Tap the hospital card:

xml = d.dump_hierarchy()
if '华山医院' in xml:
    # Tap at hospital card position
    d.click(540, 1033)  # center of hospital card

Step 2: Hospital Card → Tap to Enter

On the search result page, tap the hospital card to go to its detail page:

# Look for the hospital card in the results
for node in root.iter('node'):
    if '华山医院' in node.get('text', ''):
        parent = find_clickable_parent(root, node.get('bounds', ''))
        # Tap parent's center
        ...

This navigates to HospitalHomePageActivity.

Step 3: Find "预约挂号" Icon

On the hospital detail page (HospitalHomePageActivity), there are icon buttons:

  • 预约挂号 at column 2 (x: 270-540) in the first row
  • 当日挂号 at column 3 (x: 540-810) in the first row
# The "预约挂号" icon is in the clickable container:
# Row 1: [0,1279][270,1522], [270,1279][540,1522], [540,1279][810,1522], [810,1279][1080,1522]
# Column 2 (270-540) = 预约挂号
d.click(405, 1400)  # center of second icon

This navigates to DepartmentActivity.

Step 4: Select Appointment Type Tab + Department

On DepartmentActivity:

  • Tabs at top: "专家门诊", "专病门诊", "普通门诊" (text, NOT clickable — tap their clickable parent)
  • Department list shows available departments

First select the appointment type tab, then tap the target department:

# Tap "普通门诊" tab
d.click(900, 516)  # center of third tab

# The department list refreshes. Now find "皮肤科":
xml = d.dump_hierarchy()
root = __import__('xml.etree.ElementTree', fromlist=['ElementTree']).fromstring(xml.encode())
for node in root.iter('node'):
    if node.get('text', '').strip() == '皮肤科':
        parent = find_clickable_parent(root, node.get('bounds', ''))
        if parent:
            nums = __import__('re').findall(r'\d+', parent.get('bounds', ''))
            cx = (int(nums[0]) + int(nums[2])) // 2
            cy = (int(nums[1]) + int(nums[3])) // 2
            d.click(cx, cy)

This may navigate to SpecialDiseaseActivity (department detail).

Step 5: Select Appointment Entry

On SpecialDiseaseActivity, find the appointment type entry for the department:

# Look for entries like "普通门诊-[医联平台] ¥25" with "有号" (available)
for node in root.iter('node'):
    t = node.get('text', '').strip()
    if '有号' in t or '普通门诊' in t:
        parent = find_clickable_parent(root, node.get('bounds', ''))
        if parent:
            # Tap to navigate to SchedulingActivity

Step 6: Select Date and Time Slot

On SchedulingActivity (activity == ".patient.ui.hospital.SchedulingActivity"):

The RecyclerView takes 2-10 seconds to populate. Poll for data:

# Wait for schedule data to load
for i in range(15):
    time.sleep(1)
    xml = d.dump_hierarchy()
    if '2026-06-01' in xml:  # target date appeared
        break

# Target date will show as: "2026-06-01  星期一 上午"
# Find its clickable parent and tap
root = __import__('xml.etree.ElementTree', fromlist=['ElementTree']).fromstring(xml.encode())
for node in root.iter('node'):
    if node.get('text', '').strip() == '2026-06-01  星期一 上午':
        parent = find_clickable_parent(root, node.get('bounds', ''))
        ...

This navigates to ReserveInfoActivity.

Step 7: Confirm Appointment

On ReserveInfoActivity, verify all details:

  • 医院: 华山医院
  • 科室: 皮肤科
  • 类型: 普通门诊
  • 就诊日期: 2026-06-01 星期一 上午
  • 就诊时段: 07:45-08:00 (changeable by tapping)

Patient selection: tap the patient name to select/deselect. Captcha section:

  • "获取验证码" — tap to request SMS code
  • "请输入验证码" — tap to enter code
  • "确定预约" at bottom — tap to finalize

SMS verification code is required — inform the user to check their phone.

# Tap 确定预约 after captcha is entered
d.click(540, 2292)

Activity Flow Reference

#ActivityWhat You See
--------------------------
1.patient.ui.MainActivityHome page: search box, 当日挂号
2.patient.ui.hospital.search.SearchActivityHospital search results / suggestions
3.patient.ui.hospital.HospitalHomePageActivityHospital detail: 预约挂号 icon
4.patient.ui.hospital.depart.DepartmentActivityDepartment list with tabs
5.patient.ui.hospital.SpecialDiseaseActivityDepartment detail: appointment type entry
6.patient.ui.hospital.SchedulingActivityDate/time slot picker (RecyclerView)
7.patient.ui.hospital.ReserveInfoActivityConfirmation: details, captcha, 确定预约

Key Elements Reference

ElementWhereNotes
-----------------------
搜医院、科室、医生Home pageClickable search box
预约挂号 (icon)Hospital page2nd icon in row 1
当日挂号Hospital page3rd icon in row 1
普通门诊Department pageTab, 3rd position
皮肤科 / other deptDepartment pageIn scroll list
有号VariousIndicates availability
确定预约Reserve pageSubmit button
获取验证码Reserve pageRequest SMS
请输入验证码Reserve pageCaptcha input

Common Pitfalls

  1. 12306 conflict: Can't run both apps' automation simultaneously. Force-stop 12306 first.
  2. Chinese text input: NOT supported via ADB. Use hot search suggestions or pre-existing text.
  3. Schedule loading delay: RecyclerView populates asynchronously. Poll for date text (up to 15s).
  4. Elements with clickable=false: TextViews may not be clickable — tap their parent container instead.
  5. ADB disconnects: USB can drop. Run adb kill-server && adb start-server to recover.
  6. SMS captcha: The user must enter the verification code manually.
  7. Patient selection: Verify correct patient is checked before confirming.

Decision Tree

d.dump_hierarchy() + check activity:
  ├─ activity == "MainActivity" → HOME
  │   → tap search box → tap hot suggestion
  ├─ activity == "SearchActivity" → SEARCH RESULTS
  │   → tap hospital card
  ├─ activity == "HospitalHomePageActivity" → HOSPITAL PAGE
  │   → tap 预约挂号 icon (2nd icon, 1st row)
  ├─ activity == "DepartmentActivity" → DEPARTMENTS
  │   → select tab (普通门诊) → tap department name
  ├─ activity == "SpecialDiseaseActivity" → DEPT DETAIL
  │   → tap available appointment entry (有号)
  ├─ activity == "SchedulingActivity" → DATE PICKER
  │   → wait for data → tap target date/time slot
  ├─ activity == "ReserveInfoActivity" → CONFIRMATION
  │   → verify details → get SMS captcha → tap 确定预约
  └─ otherwise → unknown state, take screenshot

Files

  • Uses uiautomator2 Python library only (no Appium)
  • No app-specific scripts needed

版本历史

共 1 个版本

  • v1.0.0 当前
    2026-06-01 21:24

安全检测

腾讯云安全 (Keen)

队列中

腾讯云安全 (Sanbu)

队列中

🔗 相关推荐

dev-programming

Github

steipete
使用 `gh` CLI 与 GitHub 交互,通过 `gh issue`、`gh pr`、`gh run` 和 `gh api` 管理议题、PR、CI 运行及高级查询。
★ 683 📥 330,667
life-service

12306 Train Booking

openlittlebear
12306安卓版购票技巧,覆盖UC WebView虚拟列表行为、成熟订票流程及常见坑点。
★ 0 📥 384
dev-programming

Mcporter

steipete
使用 mcporter CLI 直接列出、配置、认证及调用 MCP 服务器/工具(支持 HTTP 或 stdio),涵盖临时服务器、配置编辑及 CLI/类型生成功能。
★ 197 📥 68,145