最近忙了2个月的项目,挺累的,但是通过自己的努力,学到不少知识,今天先讲讲AccessibilityService的用法(应用自动升级)。
AccessibilityService,android辅助功能,类似钩子。具体信息自己百度哈,我只用到了自动升级的功能,其实也就是模拟用户点击事件。
1、首先,我们需要继承AccessibilityService类,其中有一个比较重要的方法需要我们重写,那就是onAccessibilityEvent(AccessibilityEvent event),该方法在每当窗口有活动是,就会回调到onAccessibilityEvent()方法,具体代码如下:
@Override
public void onAccessibilityEvent(AccessibilityEvent event) {
AccessibilityNodeInfo nodeInfo = event.getSource();// 得到的是被点击的单体对象
// AccessibilityNodeInfo nodeInfo =getRootInActiveWindow();//获得是整个窗口的对象
System.out.println("******Accessibility Event******");
System.out.println("nodeInfo被点击的单体对象是否为空" + nodeInfo);
if (nodeInfo != null) {
int eventType = event.getEventType();
if (eventType == AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED
|| eventType == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
// if (handledMap.get(event.getWindowId()) == null) {
System.out.println("***---***");
// boolean handled = iterateNodesAndHandle1(nodeInfo);
installAPK(event);
}
}
}
/*
* 方法1
*/
private void installAPK(AccessibilityEvent event) {
AccessibilityNodeInfo source = getRootInActiveWindow();
List<AccessibilityNodeInfo> nextInfos = source.findAccessibilityNodeInfosByText("下一步");
nextClick(nextInfos);
List<AccessibilityNodeInfo> installInfos = source.findAccessibilityNodeInfosByText("安装");
nextClick(installInfos);
List<AccessibilityNodeInfo> wcInfos = source.findAccessibilityNodeInfosByText("打开");
nextClick(wcInfos);
runInBack(event);
}
private void runInBack(AccessibilityEvent event) {
System.out.println("global_action_back");
event.getSource().performAction(AccessibilityService.GLOBAL_ACTION_BACK);
}
private void nextClick(List<AccessibilityNodeInfo> infos) {
System.out.println("infos是否为空:" + infos);
if (infos != null)
for (AccessibilityNodeInfo info : infos) {
System.out.println("视图类型:" + info.getClassName() + "包含" + info.getChildCount() + "子视图");
if (info.isEnabled() && info.isClickable())
System.out.println("执行点击");
info.performAction(AccessibilityNodeInfo.ACTION_CLICK);
}
}
在installAPK方法中,我们模拟下一步,安装,打开方法,当使用系统uri方法进行安装,升级应用时会跳出安装弹窗,步骤就是下一步,安装,安装完之后会有打开按钮,该方法就是模拟用户点击这些按钮进行安装
2、继承AccessibilityService之后怎么用呢
在xml文件中声明service,和一般的一样,如下:
<!-- 自动更新服务 -->
<service
android:name=".service.MyAccessibilityService"
android:label="门禁助手(自动安装)"
android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE" >
<intent-filter>
<action android:name="android.accessibilityservice.AccessibilityService" />
</intent-filter>
<meta-data
android:name="android.accessibilityservice"
android:resource="@xml/accessibility_service_config" />
</service>
android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE" >
<intent-filter>
<action android:name="android.accessibilityservice.AccessibilityService" />
</intent-filter>
<meta-data
android:name="android.accessibilityservice"
android:resource="@xml/accessibility_service_config" />
这几个都不能少,否则将无法启动service meta-data中的resource如下,在xml文件夹(工程默认没有xml文件夹,需要手动在res文件夹中建立)中建立一个accessibility_service_config.xml文件,内容如下:
<!--
给AccessibilityService 设置的属性,也可以在代码里的connnect函数里手动设置,可以向下兼容。
accessibilityEventTypes:响应时间的类型,事件分很多种:单击、长按、滑动等,需要指定,我设置了所有事件都响应:TYPES_ALL_MASK
accessibilityFeedbackType:设置回馈给用户的方式,是语音播出还是振动。这个属性如果不设置的话 我们那个onAccessibilityEvent 这个回调函数 根本回调不了 所以这里要注意
notificationTimeout:响应时间的设置
packageNames:这个属性 就是捕获什么app的行为的,比如我这里写的包名是packageinstaller 那就肯定只能捕获安装器的 事件了
有的rom 安装器可能不是这个包名 那你就要进行特殊设置了,此外这个属性你如果什么都不写 就意味着 你可以捕获所有手机的动作
如果你要做流氓软件的话 可以packageNames 里面什么都不写。。。甚至可以操作支付宝 给你打钱。。。如果你知道用户密码的话。
当然你如果真这么做了 相信捕获一次用户输入密码的行为 也是很容易的
description:这个就是对你那个申请服务的时候说明了,可以写的煽情一点 让用户打开这个服务的可能性更高一点。。。
-->
<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
android:accessibilityEventTypes="typeAllMask"
android:accessibilityFeedbackType="feedbackGeneric"
android:accessibilityFlags="flagDefault"
android:canRetrieveWindowContent="true"
android:description="@string/app_name"
android:notificationTimeout="100"
android:packageNames="com.android.packageinstaller" />
各个字段的用意也有说明了
3、service建立,声明之后就可以启动应用了,启动之后怎么看有没有启动该service呢,第一次启动肯定没有启动的,需要在设置中手动开启智能服务。进入手机设置,看到有一个辅助功能选项(不同手机可能名字不一样,有的叫无障碍等),进去之后能看到自己的应用xml文件中声明的名字
就是在声明service中的label
android:label="门禁助手(自动安装)"
点击该项,打开既可以了,看一下log,
@Override
protected void onServiceConnected() {
System.out.println("Accessibility 服务连接");
打开之后出现了上面的打印,说明服务以及开启。
4、自己发送一个安装apk intent,进行测试
String apkPath = Environment.getExternalStorageDirectory().getAbsolutePath()+"/test.apk";
File file = new File(apkPath);
Uri uri = Uri.fromFile(file);
Intent localIntent = new Intent(Intent.ACTION_VIEW);
localIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
localIntent.setDataAndType(uri, "application/vnd.android.package-archive");
startActivity(localIntent);
发送之后会出现下一步,然后会自动跳过,进入安装,然后自动进入安装,安装完成之后出现打开界面,到这里之后,如果是对本应用升级,因为在升级过程中,本应用会停止掉,后面的完成,打开界面将无法进行模拟。这个时候,我们可以在我们的应用中监听PACKAGE_REPLACED广播,在安装完成之后会收到该广播,然后启动应用即可。
第二种方法,不接收广播,在安装完成之后application也会启动,AccessibilityService会重新连接,在onServiceConnected方法中模拟打开按钮即可,方法如下:
@Override
protected void onServiceConnected() {
System.out.println("Accessibility 服务连接");
AccessibilityNodeInfo source = getRootInActiveWindow();
if(source != null) {
List<AccessibilityNodeInfo> wcInfos = source.findAccessibilityNodeInfosByText("打开");
if (wcInfos != null)
nextClick(wcInfos);
}
super.onServiceConnected();
}
模拟打开按钮,activity会启动,而且完成,打开按钮由于模拟了点击打开按钮,系统安装界面会退出去,体验较好。
以上方法是我在实际项目中使用的方法,应该是没有问题的,当然,也发现有部分系统兼容问题,比如说AccessibilityService没有再次连接,这个问题就没办法了