uiautomator2 源码阅读(二):主应用 - Receiver

573 阅读7分钟

本节涉及部分:([○] 本节覆盖,[√] 为前面已覆盖,[×] 为确认废弃)

app/src/
├── androidTest
│   └── java
│       └── com
│           └── github
│               └── uiautomator
│                   ├── ApplicationTest.java
│                   └── stub
│                       ├── AccessibilityEventListener.java
│                       ├── AccessibilityNodeInfoDumper.java
│                       ├── AutomatorHttpServer.java
│                       ├── AutomatorServiceImpl.java
│                       ├── AutomatorService.java
│                       ├── ConfiguratorInfo.java
│                       ├── DeviceInfo.java
│                       ├── Helper.java
│                       ├── Log.java
│                       ├── NotImplementedException.java
│                       ├── ObjInfo.java
│                       ├── Point.java
│                       ├── Rect.java
│                       ├── Selector.java
│                       ├── Stub.java
│                       ├── TouchController.java
│                       └── watcher
│                           ├── ClickUiObjectWatcher.java
│                           ├── PressKeysWatcher.java
│                           └── SelectorWatcher.java
└── main
    ├── aidl
    │   └── android
    │       └── view
    │           └── IRotationWatcher.aidl
    ├── AndroidManifest.xml [√]
    ├── java
    │   └── com
    │       └── github
    │           └── uiautomator
    │               ├── AdbBroadcastReceiver.java [○]
    │               ├── compat
    │               │   ├── InputManagerWrapper.java
    │               │   └── WindowManagerWrapper.java
    │               ├── Console.java
    │               ├── FastInputIME.java
    │               ├── FloatView.java
    │               ├── IdentifyActivity.java
    │               ├── MainActivity.java
    │               ├── MinicapAgent.java
    │               ├── MinitouchAgent.java
    │               ├── MockLocationProvider.java
    │               ├── monitor
    │               │   ├── AbstractMonitor.java
    │               │   ├── BatteryMonitor.java [○]
    │               │   ├── HttpPostNotifier.java 
    │               │   ├── RotationMonitor.java [○]
    │               │   └── WifiMonitor.java [○]
    │               ├── RotationAgent.java
    │               ├── ScreenClient.java
    │               ├── ScreenHttpServer.java
    │               ├── Service.java
    │               ├── ToastActivity.java
    │               ├── ToastHelper.java
    │               └── util
    │                   ├── InternalApi.java
    │                   ├── MemoryManager.java
    │                   ├── OkhttpManager.java
    │                   └── Permissons4App.java
    └── res
        └── [...]

入口声明

AndroidManifest.xml,这个 receiver 声明如下:

<receiver
    android:name=".AdbBroadcastReceiver"
    android:enabled="true"
    android:exported="true">
    <intent-filter>
        <action android:name="send.mock" />
        <action android:name="stop.mock" />
    </intent-filter>
</receiver>

可以看出,这是一个 exported 的 receiver,包含了两个动作 send.mock 和 stop.mock。

什么是 Receiver?

关于 Receiver,有些原理不是很懂,问了一下 ChatGPT:

问:在 android 里面的 receiver 是什么概念?

在 Android 应用程序中,Receiver 是一种广播接收器组件,它用于接收系统或其他应用程序发送的广播 Intent。当某个事件发生时,Android 系统会发送一条广播 Intent,经由 Receiver 接收并处理这个 Intent,例如:连接电源、拔出耳机、网络状态变化、时间和日期变化等。

Receiver 组件通常需要在 AndroidManifest.xml 文件中进行声明,这样才能够注册为一个接收器并响应相应的广播 Intent。在 Receiver 的声明中需要指定一个 Intent Filter,它用于过滤可以接收的广播 Intent。例如:

<receiver android:name=".MyReceiver">
   <intent-filter>
      <action android:name="android.intent.action.BATTERY_LOW"/>
      <action android:name="android.intent.action.BATTERY_OKAY"/>
   </intent-filter>
</receiver>

在上面的代码中,我们声明了一个名为 MyReceiver 的类作为 Receiver,并通过 intent-filter 指定了可以接收 BATTERY_LOWBATTERY_OKAY 两种广播 Intent。当电池电量低于20%时,系统会发送一个 BATTERY_LOW 的广播 Intent,MyReceiver 就会接收到这个 Intent 并执行相应的操作。

Receiver 的回调方法中,我们可以获取到 Intent 中包含的数据,例如:Extra 数据、Action、Category、Data 等,并据此实现对特定事件的响应。比如,在上面的代码中,我们可以在 MyReceiver 类中实现 onReceive() 方法并对电池电量低于20%进行提醒或关闭一些不必要的服务等。

总之,Receiver 是 Android 应用程序中的一种重要组件,它可以监听系统事件或其他应用程序的事件,并进行响应和处理。开发者可以根据自己的需要定义一个或多个 Receiver 组件来实现特定功能。

(回答结束)

如何调用

查了一下,在 python 的 uiautomator2 的 API 中,貌似并没有调用这个 mock 方法的调用入口,在这个 Android 项目内部也没有相关的调用,因此应该是可以通过外部直接拉起这个 receiver 的功能的,我们来问一下 ChatGPT 如何调用,然后待会测试一下。

问:如何向 receiver 发送 intent?可以通过 adb 来发送吗?

Receiver 发送 Intent 所需的步骤取决于您想要发送的 Intent 的类型。如果您想要发送一个系统广播 Intent,则可以使用 context.sendBroadcast() 方法实现。如果您想要向自定义的 Receiver 发送 Intent,则需要创建一个 Intent 对象,并使用 context.sendBroadcast(intent) 或者 context.sendOrderedBroadcast(intent, permission) 来发送 Intent。其中,第二个方法可以指定安全权限限制,只有符合条件的应用程序才能接收该 Intent。

在 Android 应用程序中,可以通过以下代码来发送一个指定 Action 的广播 Intent:

Intent intent = new Intent("com.example.MyReceiver.MY_ACTION");
intent.putExtra("message", "Hello Receiver!");
sendBroadcast(intent);

这里我们创建了一个名为 com.example.MyReceiver.MY_ACTIONIntent 对象,并向其中添加了一个 Extra 数据。然后调用 sendBroadcast(intent) 方法将 Intent 广播出去。如果我们的应用程序中有一个 Receiver 组件注册了对应的 Action,并且已经被激活,则该组件将会接收到这个 Intent,执行相关的操作。

至于是否可以使用 ADB 命令来发送 Intent,则是可以的。通过以下命令可以发送一个指定 Action 的广播 Intent:

adb shell am broadcast -a <Action>

例如,要发送上述示例中的 com.example.MyReceiver.MY_ACTION Action,可以执行以下命令:

adb shell am broadcast -a com.example.MyReceiver.MY_ACTION --es message "Hello Receiver!"

这里使用了 --es 参数来添加 Extra 数据。当然,要确保您的应用程序已经安装并且包含指定 Action 的 Receiver 组件。

(回答结束)

尝试调用

阅读源码 AdbBroadcastReceiver,接受的 Intent 应该有两种情况:

  • action=stop.mock
    • 没有其他参数
    • 如果已经开启了 mock,关闭掉
  • action=send.mock
    • 通过 intent.getStringExtra(xxx) 获取一下几个参数
      • lat: 解析成 Double,纬度,默认为 0
      • lon: 解析成 Double,经度,默认为 0
      • alt: 解析成 Double,海拔,默认为 0
      • accurate: 解析成 Float,精度范围,默认为 0

因此我们尝试用 adb 命令向其推送一下北京天安门的虚拟坐标试试:

adb shell am broadcast -a send.mock --es lat "39.416" --es lon "116.514" --es accurate "5"

控制台返回:

Broadcasting: Intent { act=send.mock flg=0x400000 (has extras) }
Broadcast completed: result=0

但是貌似位置并没有发生变化,需要调试一下了(我的手机是 M1 10 Ultra / MIUI14.0.1 / Android 13)

在代码里面有一句,应该是能够输出结果的日志,但是日志并没有输出:

Log.i(TAG, String.format("setting mock to Latitude=%f, Longitude=%f Altitude=%f Accuracy=%f", lat, lon, alt, accurate));

然后找了一下在 GitHub 上有两条相关的 Issue:

提出的说法跟这个 Receiver 的静态注册跟动态注册有关系,这个有待考证。

其他的 Receiver

顺带在代码里面查了一下有没有 registerReceiver 关键字,发现还有几个地方存在动态注册 receiver 的位置,这些理论上都是一些放出的 API 可以让我们进行调用:

FastInputIME.java

这个 Service 的注册入口是 exported 的,因此更可能通过 adb 主动拉起,是用于处理文本输入的。

这个 Service 继承自 android.inputmethodservice.InputMethodService,因此会注册为系统的输入法,具体内容及机制后续有必要再展开,本节只分析其相关的 Receiver。

支持的 Intent 格式如下:

  • -a ADB_INPUT_TEXT --es text "base64:xxx"
  • -a ADB_INPUT_KEYCODE --es code 123
  • -a ADB_CLEAR_TEXT
  • -a ADB_SET_TEXT --es text "base64:xxx"
  • -a ADB_EDITOR_CODE --es code 123
  • -a ADB_GET_CLIPBOARD - Activity.RESULT_CANCEL / Activity.RESULT_OK: "base64:xxx"

BatteryMonitor.java

这个 registerReceiverBatteryMonitor 的构造函数中注册,这个会在主 Service.java 里面的 onCreate 钩子上触发。触发之后,这个 monitor 实例会存放在 Service 类的 private monitors 字段中。

这个会捕获系统 USB 插拔以及电量变化的时候向 notifier 的 /info/battery 接口抛出消息,notifier 是实例化 BatteryMonitor 时传入的一个 HttpPostNotifier 服务回调。

支持的 Intent 格式如下:

  • -a Intent.ACTION_BATTERY_CHANGED -es BatteryManager.EXTRA_LEVEL 0
  • -a android.hardware.usb.action.USB_STATE -es BatteryManager.EXTRA_LEVEL 0

RotationMonitor.java

与 BatteryMonitor 一样,这个也是一个 AbstractMonitor 的子类,但是貌似没有默认注册,也没有任何其他代码的引用,因此很可能是无效的。

事实上代码里面有注释:Deprecated, use RotationAgent to watch rotation. 因此后面看 RotationAgent 的时候可以再看看实现,这个直接无视更好。

触发的话,会将结果上报到 reporter 的 /info/rotation 接口抛出消息,内容为 windowService.getDefaultDisplay().getRotaion()

支持的 Intent 格式如下:

  • -a Intent.ACTION_CONFIGUATION_CHANGED

WifiMonitor.java

与 BatteryMonitor 一样,这个也是 Monitor,会在 Service 启动时注册。

在网络状态变化以及连接状态变化时会向 notifier 的 /info/wifi 接口上报消息。

一、监听wifi的打开与关闭,与wifi的连接无关,上报的内容可能是如下几种:

  • wifi 关闭中,上报两条:
    • "wifi"
    • new WifiInfos(wifiStatus: false, ssid: "").toString()
  • 其余情况不会上报

二、监听网络连接,包括wifi和移动数据的打开和关闭,以及连接上可用的连接都会接到监听

  • 连接成功并且连接可用时上报
    • new WifiInfos(true, wInfo.getSSID()).toString()
  • 其余情况不会上报

总之,效果就是断网和连上网的时候,notifier 会收到通知。

支持的 Intent 格式如下:

  • -a WifiManager.NETWORK_STATE_CHANGED_ACTION
  • -a ConnectivityManager.CONNECTIVITY_ACTION

总结

这里分析了几个 Receiver 的定义,理论上通过这些 Receiver 起码可以向外暴露接口,通过一些 Intent 的传入实现一下这些功能,但事实上并没有测试通过:

  • 位置模拟
  • 输入法
  • 剪贴板获取
  • 网络状态监听
  • 电量监听
  • 横竖屏切换(已废弃)

然后这里面还引出一个在 Service 启动的时候拉起的 monitor 机制,这个后面再专题看一下。