这篇主要是记录一下在完全没学过Android的情况下硬拗完的这个APK,拖了很久查了很多资料才勉强写完,比较垃圾但还是实现功能了。记录的过程我也尽量把知识点贴出来。

一开始是看了一个大佬的分享贴(testerhome的帖子,但是现在论坛封了,等过后再贴大佬的链接),决定改写这个apk。大佬原先写的代码不适用于MIUI系统,稍微改了一下,再增加了跳转获取Accessibility权限部分。(这部分代码也参考了网上的大佬,我有空再找找记得哪个链接就贴哪个,毕竟查了太多资料了)

这个版本现在还很稚嫩,过后有空还会完善。


贴上GitHub地址:https://github.com/congyingHHZ/AutoInstall

有需要的朋友可以自己下载源码


测试设备:小米10

工具:Android Studio 11.0.10

环境:window10

软件介绍

这个软件主要是为了解决MIUI系统(todo:尝试兼容各家定制Android)在APK安装过程中总是需要手动允许才能继续安装流程这个比较烦人的事情。并且也为了以后接入自动化测试,遇到安装APK可以自动完成。

软件大致分成2个部分

  • 判断是否有AccessibilityService权限,如果没有权限则引导跳转到AccessibilityService权限设置页面
  • 利用AccessibilityService对安装过程的弹窗进行自动点击,已完成自动化安装

代码

一、利用AccessibilityService对权限弹窗的“允许”等按钮进行点击(从核心内容开始说起, AccessibilityUtil.java)

1. 创建一个类继承AccessibilityService

无障碍服务AccessibilityService可以主动接收到系统的事件,通过对事件的判断,知道是否有进行安装操作,并且可以对控件进行点击等操作。

public class AutoInstallService extends AccessibilityService{
// do something
}

2. 当接收到系统事件后,调用onAccessibilityEvent方法

在这个方法里对收到的系统事件进行判断,如果判断为系统正在安装软件那么就调用自己写performInstallation去完成自动点击操作。

public void onAccessibilityEvent(AccessibilityEvent event) {
/*
* 回调方法,当事件发生时会从这里进入,在这里判断需要捕获的内容,
* 可通过下面这句log将所有事件详情打印出来,分析决定怎么过滤。
*/ log("!!onAccessibilityEvent!!");
//log(event.toString());
AccessibilityNodeInfo noteInfo = event.getSource();
log("===noteInfo!===");
if (event.getSource() == null) {
log("<null> event source");
return;
} AccessibilityNodeInfo rowNode = getRootInActiveWindow();
log("===rowNode!===");
//log(rowNode.toString());
int eventType = event.getEventType();
log(eventType+"");
log(event.getPackageName().toString()); if (eventType == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED
&& (event.getPackageName().equals(PACKAGE_INSTALLER_MIUI) | event.getPackageName().equals(PACKAGE_INSTALLER_MIUI_adb))) {
boolean r = performInstallation(event);
log("Action Perform: " + r);
}else if(eventType == AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED &&
event.getPackageName().equals(PACKAGE_INSTALLER_MIUI)){
log("!!input TYPE_WINDOW_CONTENT_CHANGED !!");
boolean r = performInstallation(event);
log("Action Perform: " + r);
}
}

相关知识点:

  1. event.getSource()

    接收的系统事件类型是AccessibilityEvent,通过getSource()方法可以获取到事件信息

    AccessibilityNodeInfo noteInfo = event.getSource();
  2. getRootInActiveWindow()

    获取节点信息

    AccessibilityNodeInfo rowNode = getRootInActiveWindow();

    之后通过节点信息判断页面有没有我们想要的内容,如“允许安装”这些

    3.event.getEventType()

    获取事件的类型,返回值是INT, TYPE_VIEW_CLICKED, TYPE_VIEW_LONG_CLICKED, TYPE_VIEW_SELECTED……

    event.getPackageName()

    获取事件产生的应用,也就是这个事件是哪个应用产生的。

    int eventType = event.getEventType();
if (eventType == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED
&& (event.getPackageName().equals(PACKAGE_INSTALLER_MIUI) | event.getPackageName().equals(PACKAGE_INSTALLER_MIUI_adb))){
...
}else if(eventType == AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED &&
event.getPackageName().equals(PACKAGE_INSTALLER_MIUI)){
}

传进来的AccessibilityEvent,可能是发生了点击事件、长按事件等等,但是如果安装软件弹出来权限框肯定是页面变化,所以这里判断even是不是页面变化的类型(TYPE_WINDOW_STATE_CHANGED,TYPE_WINDOW_CONTENT_CHANGED )才执行接下来的操作。

同时因为系统可能还会有其他软件导致的页面变化事件,因此这里还判断了是不是安装软件产生的页面变化。MIUI中安装应用的包名是PACKAGE_INSTALLER_MIUI,通过adb安装应用的包名是PACKAGE_INSTALLER_MIUI_adb。

3. 确定是进入安装流程后开始执行点击获取权限操作,跳转到performInstallation方法

private boolean performInstallation(AccessibilityEvent event) {
List<AccessibilityNodeInfo> nodeInfoList;
/*
* 有的手机会弹2次,有的只弹一次,在替换安装时会出现确定按钮,
* 为了大而全,下面定义了比较多的内容,可按需增减。
*/
log("!!performInstallation!!");
String[] labels = new String[]{"本次允许","允许", "确定", "继续安装", "下一步", "完成","安装"};
for (String label : labels) {
log(label);
nodeInfoList = event.getSource().findAccessibilityNodeInfosByText(label);
if (nodeInfoList != null && !nodeInfoList.isEmpty()) {
boolean performed = performClick(nodeInfoList);
if (performed) return true;
}
}
return false;
}

这部分没有什么可说的,就是获取页面内容,然后循环判断有没有存在“允许”之类的字符,这些都要要点击的节点。

4. 如果页面有需要点击节点,也就是弹出的权限框,则跳转到performClick()执行点击操作。

 @RequiresApi(api = Build.VERSION_CODES.N)
private boolean performClick(List<AccessibilityNodeInfo> nodeInfoList) {
for (AccessibilityNodeInfo node : nodeInfoList) {
/*
* 这里还可以根据node的类名来过滤,大多数是button类,这里也是为了大而全,
* 判断只要是可点击的是可用的就点。
*/ if (node.isClickable() && node.isEnabled()) {
return node.performAction(AccessibilityNodeInfo.ACTION_CLICK);
}
else if(node.getClassName() == "android.widget.Button"){
Log.d(TAG,"clickByNode");
return clickByNode(node);
}
}
return false;
}

这一段代码逻辑也很简单就是传进来上一步在当前页面发现的所有node,然后遍历这个list,如果是按钮可以点击则点击该node。

相关知识点:

  1. node.isClickable() && node.isEnabled()

    AccessibilityNode如果是按钮就具有isClickable()和isEnabled()属性
  2. node.getClassName() == "android.widget.Button"

    这里比原版代码多加了一个判断,因为小米安装过程弹出权限框的“允许安装”按钮(还是“继续安装”?不记得了,反正就是有个按钮点不了)不是isClickable的,导致无法继续往下走执行点击。但是这个node的类名是"android.widget.Button",所以补充判断再进行点击操作。当然原来isClickable属性的按钮点击方法也用不了了,这里也添加的这种类型node的点击方法。

5. 执行点击操作。这部分是新增的,针对node.performAction()无法实现点击的情况。

public final boolean clickByNode(AccessibilityNodeInfo nodeInfo){
if (nodeInfo == null){
return false;
}
// if (nodeInfo.getClassName() != "android.widget.Button"){
// return false;
// }
Rect rect = new Rect();
nodeInfo.getBoundsInScreen(rect);
int x = (rect.left + rect.right)/2;
int y = (rect.top + rect.bottom)/2; Point point = new Point(x,y); GestureDescription.Builder builder = new GestureDescription.Builder();
Path path = new Path();
path.moveTo(point.x,point.y); builder.addStroke(new GestureDescription.StrokeDescription(path,100,50));
//path:路径 startTime:从手势开始到开始笔画的时间
final GestureDescription gesture = builder.build(); return dispatchGesture(gesture,
new GestureResultCallback(){
@Override
public void onCompleted(GestureDescription gestureDescription){
super.onCompleted(gestureDescription);
}
@Override
public void onCancelled(GestureDescription gestureDescription){
super.onCancelled(gestureDescription);
} },null); }

这一段主要是利用了GestureDescription,这个api是Android7.0之后引入的,所以必须在这个方法前增加 @RequiresApi(api = Build.VERSION_CODES.N)。

利用GestureDescription可以实现在不root手机的情况下进行模拟手势操作。

相关知识点:

  1. GestureDescription.dispatchGesture()

小米手机MIUI安装APK时自动获取安装权限(自动点击权限框)的更多相关文章

  1. 安装APK时SO库的选择策略

    此文已由作者尹彬彬授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. 0X0 前言 在Android系统中,当我们安装apk文件的时候,lib目录下的so文件会被解压到app的原 ...

  2. 安装apk时出现错误Failure [INSTALL_FAILED_DEXOPT]问题解决的方法

    在android4.0源码里面编译出来apk后,用adb install (或adb install -r 重装)安装时,报错[INSTALL_FAILED_DEXOPT]. xu@xu-PC:~$  ...

  3. 安装APK时引发INSTALL_PARSE_FAILED_MANIFEST_MALFORMED错误的几种可能(申明:来源于网络)

    安装APK时引发INSTALL_PARSE_FAILED_MANIFEST_MALFORMED错误的几种可能(申明:来源于网络) 地址:https://my.oschina.net/freestyle ...

  4. Android系统移植与调试之------->安装apk时出现错误Failure [INSTALL_FAILED_DEXOPT]问题解决的方法

    在android4.0源码里面编译出来apk后,用adb install (或adb install -r 重装)安装时,报错[INSTALL_FAILED_DEXOPT]. xu@xu-PC:~$ ...

  5. 解决小米手机USB安装apk时AS报错:INSTALL_FAILED_USER_RESTRICTED

    今天,直接用AS在小米手机上运行安装的时候总是报错:INSTALL_FAILED_USER_RESTRICTED,于是乎,通过以下方式解决: 在开发者选项将USB安装打开,然后,哈,解决了.记录一下.

  6. 关于heritrix安装配置时出现”必须限制口令文件读取访问权限”的解决方法

    转载:http://www.floatinglife.cn/关于heritrix安装配置时出现必须限制口令文件读取访问 最近开始写一个RSS聚合程序,需要爬虫支持,于是就整来heritrix,没想到, ...

  7. Windows下安装程序时提示未安装Microsoft Net FrameWork 2.0

    问题描述 安装程序时碰到如下: 现在基本都是用win7.win10系统,缺少环境大多数都是因为系统没有启用. 解决方法 控制面板 - 程序 - 启用或关闭Windows功能 - 把第一项'NET Fr ...

  8. android手机上安装apk时出现解析包错误的一个解决办法

    今天下午在学习安卓开发时,学习开发文档中的gridview时,在模拟器上调试程序一切正常,如下图所示: 但当将bin目录下的HelloGridView.apk拷贝到M8安卓系统后进行安装时,出现了“解 ...

  9. 关于部分Android手机安装apk,无法获取正常的logo

    最近出现过类似的问题,主要出现是在,MediaPad X1 7.0和MediaPad M1 8.0. 发布应用的时候明明配置好了图标的,但是始终找不到原因,郁闷了好几个小时,也浪费了好几个小时. 如果 ...

  10. 【原创】RPM安装软件时解决依赖性问题(自动解决依赖型)

    满足以下3个条件才能自动解决依赖性: 1.使用rpmdb -redhat(在安装时会自动弹出依赖性错误) 2.所有互相依赖的软件都必须在同一个目录下面. 3.调用-aid参数.

随机推荐

  1. 发现一个API接口自动化测试平台

    gitee地址:https://gitee.com/season-fan/autometer-api 记录一下: 1.API测试平台的5个需求: ①支持不同的项目,不同的角色,技术人员多人协作 ②支持 ...

  2. Mac OS X 下安装Tableau Desktop Pro for Mac 10.2.0

    ​ 安装步骤 1.断网安装tableau 2.双击Tableau Desktop.pkg ​编辑 3.安装后到应用程序里把tableau这个单独文件从tableau文件夹里直接放到应用程序根目录 4. ...

  3. 【MySQL】MySQL时区问题、数据库时间相差8小时问题解决

    解决:修改MySQL系统时区,改为东8区. 在命令行界面或者可视化工具下(如:Navicat)依次运行以下命令. 1.查询当前系统时间 select now(); 2.检查MySQL系统时区 show ...

  4. mysql生成随机数的函数

    例:update [tablename] set [columnname] = FLOOR( 6546541 + RAND() * (987987989 - 6546541)) where ? FLO ...

  5. 浅谈Java线程池的概念、创建与执行

    转': 浅谈Java线程池的概念.创建与执行 如果使用 newCachedThreadPool   线程池的实例: ExecutorService executor = Executors.newCa ...

  6. 编译报错Could NoT find Threads (missing: Threads FOUND)

    解决方法1: 原来的命令行加上-lpthread 解决方法2: -DCMAKE_THREAD_LIBS_INIT=-lpthread

  7. QT--QMainWindow窗口的状态栏设置

    QMainWindow窗口状态栏 实时显示时间: 1.获取实时时间使用定时器QTimer, QTimer *timer = new QTimer(); connect(timer, &QTim ...

  8. Keil5 STM32 C++开发 ARM V6编译器的使用教程

    Keil5更新之后,开始支持ARM V6编译器,新版本的编译器对C++有了更多的支持,在编译方面也做了很多的改善,具体的没有详细了解,本文只是对STM32 开发下,使用V6版本的编译器进行STM32的 ...

  9. git介绍和常用操作

  10. How to setup a Chia Harvester on Ubuntu

    How to setup a Chia Harvester on Ubuntu Posted on May 4, 2021 A Chia Harvest is a computer that farm ...