uiautomator2 源码阅读(三):主应用 - Activity

453 阅读4分钟

本节过一下几个注册的 Activity、关联的 View 以及一些关键的功能和实现。

其实这几个 Activity 更多的是辅助作用,核心的链路并不在这里,简要过一下就好,并不是整个架构的核心。

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

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
        └── [...]

MainActivity.java

这个是主 Activity,我们点击小黄车图标打开的这个界面就是这个 Activity,界面布局可以对照一下 res/layout/activity_main.xml

放个截图,大家对号入座。

image.png

所以我们看一下这几个按钮到底干了些啥就行了。

关闭所有服务:btnFinish (@+id/btn_finish)

btnFinish.setOnClickListener(new OnClickListener() {
    @Override
    public void onClick(View view) {
        stopService(new Intent(MainActivity.this, Service.class));
        finish();
    }
});

就是关闭 Service.java 这个主服务,很好理解。

识别本机:btnIdentify (@+id/btn_identify)

btnIdentify.setOnClickListener(new OnClickListener() {
    @Override
    public void onClick(View v) {
        Intent intent = new Intent(MainActivity.this, IdentifyActivity.class);
        Bundle bundle = new Bundle();
        bundle.putString("theme", "RED");
        intent.putExtras(bundle);
        startActivity(intent);
    }
});

就是简单用 intent 打开 IndentifyActivity,指定 theme 为红色,把 bundle 对象也塞进去,很好理解,至于 IdentifyActivity 干嘛用的后面再看。

无障碍服务:(@+id/accessibility)

findViewById(R.id.accessibility).setOnClickListener(new OnClickListener() {
    @Override
    public void onClick(View v) {
        startActivity(new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS));
    }
});

简单跳到指定的设置页面。

开发者选项:(@+id/development_settings)

findViewById(R.id.development_settings).setOnClickListener(new OnClickListener() {
    @Override
    public void onClick(View v) {
        startActivity(new Intent(Settings.ACTION_APPLICATION_DEVELOPMENT_SETTINGS));
    }
});

同上。

开启悬浮窗:showFloatWindow

public void showFloatWindow(View view) {
    boolean floatEnabled = FloatWindowManager.getInstance().checkFloatPermission(MainActivity.this);
    if (!floatEnabled) {
        Log.i(TAG, "float permission not checked");
        return;
    }
    if (floatView == null) {
        floatView = new FloatView(MainActivity.this);
    }
    floatView.show();
}

就是把 FloatView 显示出来,这个 FloatView 是一个半透明的小黄车悬浮 icon,用来保活,具体后面再展开看一下 FloatView.java 的实现(两百多行呢)。

关闭悬浮窗:dismissFloatWindow

就是 hide 一下,无需解释。

启动 UIAUTOMATOR:startUiautomator

public void startUiautomator(View view) {
    Request request = new Request.Builder()
            .url(ATX_AGENT_URL + "/uiautomator")
            .post(RequestBody.create(null, new byte[0]))
            .build();
    okhttpManager.newCall(request, new Callback() {
        @Override
        public void onFailure(Call call, IOException e) {
            e.printStackTrace();
            uiToaster("UIAutomator not starting");
            checkUiautomatorStatus(null);
        }

        @Override
        public void onResponse(Call call, Response response) throws IOException {
            uiToaster("UIAutomator started");
            checkUiautomatorStatus(null);
        }
    });
}

发了一个没有 body 的 POST 请求到写死的 http://127.0.0.1:7912/uiautomator,如果没有启动,调一下 checkUiautomatorStatus 这个函数,这个才是关键:

这里会再发一下上次那个请求探活,要是不通就在标签文本框里面写一下结果,重点在于 POST http://127.0.0.1:7912/uiautomator 这个接口的处理,其余不重要。

停止 UIAUTOMATOR:stopUiautomator

此处也无需细说,关闭的开关在:DELETE http://127.0.0.1:7912/uiautomator,发一个 DELETE 请求即可关闭服务。

停止 ATXATENT:stopAtxAgent

只是这样:GET http://127.0.0.1:7912/stop,这里警告了一下启动的话提示:“AtxAgent下次必须通过adb启动”。

到底这套 API 做了什么才是核心,这个界面只是调一下。

刷新服务状态(ATXAGENT):checkAtxAgentStatus

GET http://127.0.0.1:7912/ping

刷新服务状态(UIAUTOMATOR):checkUiautomatorStatus

GET http://127.0.0.1:7912/uiautomator

测试 UIAUTOMATOR:testUiautomator

POST http://127.0.0.1:7912/jsonrpc/0

{
  "jsonrpc": "2.0",
  "id": "14d3bbb25360373624ea5b343c5abb1f",
  "method": "dumpWindowHierarchy",
  "params": false
}

其他操作

启动 MainActivity.onCreate 的时候指定隐藏到后台
Intent intent = getIntent();
boolean isHide = intent.getBooleanExtra("hide", false);
if (isHide) {
    Log.i(TAG, "launch args hide:true, move to background");
    moveTaskToBack(true);
}

这段可以看出,启动的时候指定 --ez hide 1 可以让主界面启动时切到后台(实际上并没有什么用)。

adb shell am start -n com.github.uiautomator/.MainActivity --ez hide 1
启动 MainActivity.onCreate 的时候指定隐藏到后台
String[] permissions = new String[]{
        Manifest.permission.ACCESS_COARSE_LOCATION,
        Manifest.permission.READ_PHONE_STATE,
        Manifest.permission.READ_PHONE_NUMBERS,
        Manifest.permission.READ_SMS,
        Manifest.permission.RECEIVE_SMS};
Permissons4App.initPermissions(this, permissions);

也就是启动的时候确认一下权限,这个 Permissons4App 是在 com.github.uiautomator.util 这里简单包扎了一下权限类,没什么营养,而且还拼错了o(╥﹏╥)o,强迫症暴发,那就直接“已阅”好了。

FloatView.java

这个的关键是保活是怎么实现的,为什么这个浮窗可以保活。实际上如果没有权限这个浮窗开不了,也不影响使用,但是这里代码挺多的,但是都是车轱辘话,主要是如何显示、控制这个小图标,保持屏幕常亮云云,已阅算了。

ToastActivity.java、ToastHelper

这个不重要了,就是弹消息的 Toast 的一个简单包扎而已,不展开了,已阅。

IdentifyActivity.java

简单的展示本机信息而已,没什么关键内容,已阅。

image.png

总结

这里看了一下几个主要的 Activity,大都是一个壳子,但是可以看得出来一些简单的 jsonrpc 调用来控制 Service 的行为,以及通过 悬浮 FloatView 保活的一些手段,核心还是在 Service 以及 androidTest 测试服务上面。