微信聊天数据定时清理

技术方案选择

Android测试支持库有:

  1. Junit3, Junit4:用于方法级别的单元测试,不通过手机运行,在测试一些正则表达式时,非常方便
  2. AndroidJUnitRunner:在手机上运行Junit测试,如一些需要获取Context的方法
  3. Espresso:UI 测试框架;适合应用中的功能性 UI 测试。
  4. UI Automator:UI 测试框架;适合跨系统和已安装应用的跨应用功能性 UI 测试
  5. 无障碍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);

//假设当前时间15s之后,就开始第一次触发;然后每隔20s再次触发。
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, // 1个小时
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开发无障碍指南

总结一些关键点:

  1. 默认情况下,只能看到TextView及其ParentView,基他ImageView等等都看不到,但通过设置flags |= FLAG_INCLUDE_NOT_IMPORTANT_VIEWS后,可以看到其他没有包含TextView的View
  2. 只能看到标准View,即自定View的父类,无法看到自定View的类名
  3. 只能获取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;
}
}

参考

  1. Android开发无障碍指南
感谢您的阅读,本文由 刘阳 版权所有。如若转载,请注明出处:刘阳(https://handsomeliuyang.github.io/2017/12/12/%E7%BB%8F%E9%AA%8C%E6%80%BB%E7%BB%93/%E5%BE%AE%E4%BF%A1%E8%81%8A%E5%A4%A9%E6%95%B0%E6%8D%AE%E5%AE%9A%E6%97%B6%E6%B8%85%E7%90%86/
异或总结
抽屉效果实现三端化(android,ios,web)的历程