本节过一下几个注册的 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。
放个截图,大家对号入座。
所以我们看一下这几个按钮到底干了些啥就行了。
关闭所有服务: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
简单的展示本机信息而已,没什么关键内容,已阅。
总结
这里看了一下几个主要的 Activity,大都是一个壳子,但是可以看得出来一些简单的 jsonrpc 调用来控制 Service 的行为,以及通过 悬浮 FloatView 保活的一些手段,核心还是在 Service 以及 androidTest 测试服务上面。