技术方案选择
Android测试支持库有:
- Junit3, Junit4:用于方法级别的单元测试,不通过手机运行,在测试一些正则表达式时,非常方便
- AndroidJUnitRunner:在手机上运行Junit测试,如一些需要获取Context的方法
- Espresso:UI 测试框架;适合应用中的功能性 UI 测试。
- UI Automator:UI 测试框架;适合跨系统和已安装应用的跨应用功能性 UI 测试
- 无障碍Api:可用于模拟用户点击,适合跨系统和已安装应用的跨应用功能性UI测试
选择结果:无障碍Api,因为UI Automator只能通过adb shell运行。
注意:Root后的手机,应该可以在App内直接执行UI Automator — 没有经过测试
实现步骤
定时机制
定时机制很容易,使用AlarmManager就行,如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| private void setRepeatingAlarm(){ Intent intent = new Intent(this, ClearWeixinActivity.class); intent.putExtra("msg", "重复的事情多次提醒!!!"); intent.putExtra("type", "repeat"); PendingIntent pendingIntent = PendingIntent.getActivity(this, 101, intent, 0);
Calendar c = Calendar.getInstance(); c.set(Calendar.SECOND, c.get(Calendar.SECOND) + 60*60*1);
AlarmManager alarmManager = (AlarmManager)getSystemService(ALARM_SERVICE); alarmManager.setRepeating(AlarmManager.RTC_WAKEUP, c.getTimeInMillis(), 1*60*60*1000, pendingIntent);
}
|
注意: 当进程被杀后,闹钟无法调起应用,需要开启自启动服务
自动开启无障碍模式
由于无障碍模式的开启后,当应用程序进程被杀后,无障碍模式会被关掉,所以需要自动打开无障碍模式。
通过命令打开障碍模式的命令如下:
1 2 3 4 5 6
| // 打开无障碍模式 adb shell settings put secure enabled_accessibility_services com.ly.robottool/com.ly.robottool.weixin.ClearWeixinService adb shell settings put secure accessibility_enabled 1
// 查看无障碍的配置情况 adb shell content query --uri content://settings/secure
|
App里,通过获取Root权限后,可执行以上命令,如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| try { Process p = Runtime.getRuntime().exec("su"); DataOutputStream dos = new DataOutputStream(p.getOutputStream()); dos.writeBytes("settings put secure enabled_accessibility_services com.ly.robottool/com.ly.robottool.weixin.ClearWeixinService\n"); dos.writeBytes("settings put secure accessibility_enabled 1\n"); dos.writeBytes("mkdir /sdcard/333\n"); dos.writeBytes("exit\n"); dos.flush(); dos.close(); p.waitFor();
mHander.sendEmptyMessageDelayed(MESSAGE_ACCESSIBILITY_SUCCESS, 1000*1); } catch (IOException e) { e.printStackTrace(); } catch (InterruptedException e) { e.printStackTrace(); }
|
注意: 上面的代码,需要在子线程里执行
无障碍服务
无障碍的整体机制
无障碍Api
更加详细的文档,请查看Android开发无障碍指南
总结一些关键点:
- 默认情况下,只能看到TextView及其ParentView,基他ImageView等等都看不到,但通过设置flags |= FLAG_INCLUDE_NOT_IMPORTANT_VIEWS后,可以看到其他没有包含TextView的View
- 只能看到标准View,即自定View的父类,无法看到自定View的类名
- 只能获取View的Parent,children,Text,ClassName,屏幕坐标,大小,viewId,一些状态(checkable,checked,focusable,focused,selected,clickable,longClickable)
注意:微信由于使用了资源id混淆技术,不同版本的微信apk,其viewid会变化
UIAutomatorViewer查看ID
uiautomatorviewer工具所在目录:Android SDK/tools/bin/uiautomatorviewer
与dumpsys比较:
结论:uiautomator,uiautomatorviewer,无障碍Api都只能看到TextView及其ParentView,但dumpsys可以看到全部View
微信自动清理聊天记录
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72
| public void onAccessibilityEvent(AccessibilityEvent event) { if (event == null) { return; }
if (!WECHAT_PACKAGENAME.equals(event.getPackageName())) { return; }
String beginUUID = SharedPreferenceUtils.getBeginUUID(this); String endUUID = SharedPreferenceUtils.getEndUUID(this); if(beginUUID == null) { return ; } if(!beginUUID.equals(endUUID)) { SharedPreferenceUtils.updateEndUUID(this, beginUUID); hasClickMe = false; hasClickSetting = false; hasClickChat = false; hasEnterClearDialog = false; hasClickClear = false; }
log("0000:" + event);
if(!hasClickMe) { enterPerson(event); return ; }
if(!hasClickSetting){ enterSetting(event); return ; }
if(!hasClickChat){ enterChat(event); return ; }
if(!hasEnterClearDialog){ enterClearDialog(event); return ; }
if(!hasClickClear){ clickClear(event); return ; } }
private void enterPerson(AccessibilityEvent event){ if(!"com.tencent.mm.ui.LauncherUI".equals(event.getClassName())){ return ; }
if(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED == event.getEventType()){ List<AccessibilityNodeInfo> nodes = getRootInActiveWindow().findAccessibilityNodeInfosByViewId("com.tencent.mm:id/c3f"); AccessibilityNodeInfo myNode = null; for(AccessibilityNodeInfo node : nodes){ if("我".equals(node.getText())) { myNode = node; } } if(myNode == null) { return ; } myNode.getParent().performAction(AccessibilityNodeInfo.ACTION_CLICK); hasClickMe = true; } }
|
参考
- Android开发无障碍指南