参考:
http://www.infoq.com/cn/articles/android-accessibility-installing
https://developer.android.com/guide/topics/ui/accessibility/services
https://developer.android.com/training/accessibility/service

一.Android Accessibility 简介

对于那些失明或低视力,色盲,耳聋或听力受损,以及运动技能受限的用户,
Android提供了Accessibility(辅助功能/无障碍)更加简单地操作设备,
包括文字转语音、触觉反馈、手势操作、轨迹球和手柄操作等。 在Android 4.0以前,Accessibility功能单一,仅能过单向获取窗口信息(获取输入框内容);
在Android 4.1以后,Accessibility增加了与窗口元素的双向交互,可以操作窗口元素(点击按钮)。 近期项目需要在小米/华为手机上自动安装/升级APP(不能root),市面上大部分的应用市场APP都通过辅助功能实现免root自动安装,
于是想借鉴一下方案,试用了5个APP:豌豆荚,360手机助手,百度手机助手,腾讯应用宝,应用汇。
腾讯应用宝竟然没有实现免root自动安装,必须要root才能自动安装,难道是我没发现设置按钮,找到的麻烦通知一声。。。
在华为上这几个自动安装都是失效的,在小米上只有豌豆荚(要单独下载插件APP,估计是对小米单独适配了)。 自动安装原理(Accessibility):
启动"x.x.packageinstaller"系统安装界面,获取"安装"按钮,然后模拟用户点击,直到安装结束。
技术实现看起来非常简单,麻烦在于国内千奇百怪Android系统安装界面,现在只能自己动手适配项目需要的几台手机。。。

二.自动安装的基本步骤

完整源码:https://github.com/lifegh/AutoInstall

1.manifest添加辅助服务, res/xml配置辅助功能

在AndroidManifest.xml中
<application ...>
<!--
android:label(可选) 在辅助功能(无障碍)的系统设置会使用该名称,若不设置,则会使用<application android:label
android:process(可选) 把该服务设在单独进程中,进程名以[冒号:]开头,是本应用的私有进程,其它应用无法访问
android:permission(必需) 添加权限以确保只有系统可以绑定到该服务
-->
<service
android:name=".AutoInstallService"
android:label="@string/aby_label"
android:process=":install"
android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
<intent-filter>
<action android:name="android.accessibilityservice.AccessibilityService" />
</intent-filter> <!--在xml文件配置辅助功能,也可在onServiceConnected()中使用setServiceInfo()动态配置-->
<meta-data
android:name="android.accessibilityservice"
android:resource="@xml/accessibility_config" />
</service>
</application> 在res/xml/accessibility_config中
<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/aby_desc"
android:notificationTimeout="100"/>
<!--android:packageNames="com.android.packageinstaller" 国内有不少手机都是自定义安装界面,packageNames不固定--> <!--
android:description 辅助功能描述
android:packageNames 指定辅助功能监听的包名,不指定表示监听所有应用
android:accessibilityEventTypes 事件类型,typeAllMask表示接收所有事件
android:accessibilityFlags 查找截点方式,一般配置为flagDefault默认方式
android:accessibilityFeedbackType 操作按钮以后给用户的反馈类型,包括声音,震动等
android:notificationTimeout 响应超时
android:canRetrieveWindowContent 是否允许提取窗口的节点信息
--> 注意:[来源豌豆荚 http://www.infoq.com/cn/articles/android-accessibility-installing ]
在一些使用虚拟键盘的APP中,经常会出现这样的逻辑
Button button = (Button) findViewById(R.id.button);
String num = (String) button.getText();
在一般情况下,getText方法的返回值是Java.lang.String类的实例,上面这段代码可以正确运行。
但是在开启Accessibility Service之后,如果没有指定 packageNames, 系统会对所有APP的UI都进行Accessible的处理。
在这个例子中的表现就是getText方法的返回值变成了android.text.SpannableString类的实例
(Java.lang.String和android.text.SpannableString都实现了java.lang.CharSequence接口),进而造成目标APP崩溃。
所以强烈建议在注册Accessibility Service时指定目标APP的packageName,
以减少手机上其他应用的莫名崩溃(代码中有这样的逻辑的各位,也请默默的改为调用toString()方法吧)。

2.继承服务AccessibilityService,实现自动安装

public class AutoInstallService extends AccessibilityService {
private static final String TAG = AutoInstallService.class.getSimpleName();
private static final int DELAY_PAGE = 320; // 页面切换时间
private final Handler mHandler = new Handler(); ...... @Override
public void onAccessibilityEvent(AccessibilityEvent event) {
if (event == null || !event.getPackageName().toString()
.contains("packageinstaller"))//不写完整包名,是因为某些手机(如小米)安装器包名是自定义的
return;
/*
某些手机安装页事件返回节点有可能为null,无法获取安装按钮
例如华为mate10安装页就会出现event.getSource()为null,所以取巧改变当前页面状态,重新获取节点。
该方法在华为mate10上生效,但其它手机没有验证...(目前小米手机没有出现这个问题)
*/
Log.i(TAG, "onAccessibilityEvent: " + event);
AccessibilityNodeInfo eventNode = event.getSource();
if (eventNode == null) {
Log.i(TAG, "eventNode: null, 重新获取eventNode...");
performGlobalAction(GLOBAL_ACTION_RECENTS); // 打开最近页面
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
performGlobalAction(GLOBAL_ACTION_BACK); // 返回安装页面
}
}, DELAY_PAGE);
return;
} /*
模拟点击->自动安装,只验证了小米5s plus(MIUI 9.8.4.26)、小米Redmi 5A(MIUI 9.2)、华为mate 10
其它品牌手机可能还要适配,适配最可恶的就是出现安装广告按钮,误点安装其它垃圾APP(典型就是小米安装后广告推荐按钮,华为安装开始官方安装)
*/
AccessibilityNodeInfo rootNode = getRootInActiveWindow(); //当前窗口根节点
if (rootNode == null)
return;
Log.i(TAG, "rootNode: " + rootNode);
if (isNotAD(rootNode))
findTxtClick(rootNode, "安装"); //一起执行:安装->下一步->打开,以防意外漏掉节点
findTxtClick(rootNode, "继续安装");
findTxtClick(rootNode, "下一步");
findTxtClick(rootNode, "打开");
// 回收节点实例来重用
eventNode.recycle();
rootNode.recycle();
} // 查找安装,并模拟点击(findAccessibilityNodeInfosByText判断逻辑是contains而非equals)
private void findTxtClick(AccessibilityNodeInfo nodeInfo, String txt) {
List<AccessibilityNodeInfo> nodes = nodeInfo.findAccessibilityNodeInfosByText(txt);
if (nodes == null || nodes.isEmpty())
return;
Log.i(TAG, "findTxtClick: " + txt + ", " + nodes.size() + ", " + nodes);
for (AccessibilityNodeInfo node : nodes) {
if (node.isEnabled() && node.isClickable() && (node.getClassName().equals("android.widget.Button")
|| node.getClassName().equals("android.widget.CheckBox") // 兼容华为安装界面的复选框
)) {
node.performAction(AccessibilityNodeInfo.ACTION_CLICK);
}
}
} // 排除广告[安装]按钮
private boolean isNotAD(AccessibilityNodeInfo rootNode) {
return isNotFind(rootNode, "还喜欢") //小米
&& isNotFind(rootNode, "官方安装"); //华为
} private boolean isNotFind(AccessibilityNodeInfo rootNode, String txt) {
List<AccessibilityNodeInfo> nodes = rootNode.findAccessibilityNodeInfosByText(txt);
return nodes == null || nodes.isEmpty();
}
}

3.退出"辅助功能/无障碍"设置

public class AutoInstallService extends AccessibilityService {
private static final String TAG = AutoInstallService.class.getSimpleName();
private static final int DELAY_PAGE = 320; // 页面切换时间
private final Handler mHandler = new Handler(); @Override
protected void onServiceConnected() {
Log.i(TAG, "onServiceConnected: ");
Toast.makeText(this, getString(R.string.aby_label) + "开启了", Toast.LENGTH_LONG).show();
// 服务开启,模拟两次返回键,退出系统设置界面(实际上还应该检查当前UI是否为系统设置界面,但一想到有些厂商可能篡改设置界面,懒得适配了...)
performGlobalAction(GLOBAL_ACTION_BACK);
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
performGlobalAction(GLOBAL_ACTION_BACK);
}
}, DELAY_PAGE);
} @Override
public void onDestroy() {
Log.i(TAG, "onDestroy: ");
Toast.makeText(this, getString(R.string.aby_label) + "停止了,请重新开启", Toast.LENGTH_LONG).show();
// 服务停止,重新进入系统设置界面
AccessibilityUtil.jumpToSetting(this);
} ......
}

4.开启"辅助功能/无障碍"设置

public class AccessibilityUtil {
......
/**
* 检查系统设置:是否开启辅助服务
* @param service 辅助服务
*/
private static boolean isSettingOpen(Class service, Context cxt) {
try {
int enable = Settings.Secure.getInt(cxt.getContentResolver(), Settings.Secure.ACCESSIBILITY_ENABLED, 0);
if (enable != 1)
return false;
String services = Settings.Secure.getString(cxt.getContentResolver(), Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES);
if (!TextUtils.isEmpty(services)) {
TextUtils.SimpleStringSplitter split = new TextUtils.SimpleStringSplitter(':');
split.setString(services);
while (split.hasNext()) { // 遍历所有已开启的辅助服务名
if (split.next().equalsIgnoreCase(cxt.getPackageName() + "/" + service.getName()))
return true;
}
}
} catch (Throwable e) {//若出现异常,则说明该手机设置被厂商篡改了,需要适配
Log.e(TAG, "isSettingOpen: " + e.getMessage());
}
return false;
} /**
* 跳转到系统设置:开启辅助服务
*/
public static void jumpToSetting(final Context cxt) {
try {
cxt.startActivity(new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS));
} catch (Throwable e) {//若出现异常,则说明该手机设置被厂商篡改了,需要适配
try {
Intent intent = new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
cxt.startActivity(intent);
} catch (Throwable e2) {
Log.e(TAG, "jumpToSetting: " + e2.getMessage());
}
}
}
}

5.允许"未知来源"设置

public class InstallUtil {
...... /**
* 检查系统设置:是否允许安装来自未知来源的应用
*/
private static boolean isSettingOpen(Context cxt) {
boolean canInstall;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) // Android 8.0
canInstall = cxt.getPackageManager().canRequestPackageInstalls();
else
canInstall = Settings.Secure.getInt(cxt.getContentResolver(), Settings.Secure.INSTALL_NON_MARKET_APPS, 0) == 1;
return canInstall;
} /**
* 跳转到系统设置:允许安装来自未知来源的应用
*/
private static void jumpToInstallSetting(Context cxt) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) // Android 8.0
cxt.startActivity(new Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES, Uri.parse("package:" + cxt.getPackageName())));
else
cxt.startActivity(new Intent(Settings.ACTION_SECURITY_SETTINGS));
} /**
* 安装APK
*
* @param apkFile APK文件的本地路径
*/
public static void install(Context cxt, File apkFile) {
AccessibilityUtil.wakeUpScreen(cxt); //唤醒屏幕,以便辅助功能模拟用户点击"安装"
try {
Intent intent = new Intent(Intent.ACTION_VIEW);
Uri uri;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
// Android 7.0以上不允许Uri包含File实际路径,需要借助FileProvider生成Uri(或者调低targetSdkVersion小于Android 7.0欺骗系统)
uri = FileProvider.getUriForFile(cxt, cxt.getPackageName() + ".fileProvider", apkFile);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
} else {
uri = Uri.fromFile(apkFile);
}
intent.setDataAndType(uri, "application/vnd.android.package-archive");
cxt.startActivity(intent);
} catch (Throwable e) {
Toast.makeText(cxt, "安装失败:" + e.getMessage(), Toast.LENGTH_LONG).show();
}
}
}

简书: https://www.jianshu.com/p/04ebe2641290
CSDN: https://blog.csdn.net/qq_32115439/article/details/80261568
GitHub博客: http://lioil.win/2018/05/09/Android-Accessibility.html
Coding博客: http://c.lioil.win/2018/05/09/Android-Accessibility.html

from : https://www.jianshu.com/p/04ebe2641290

【转】Android-Accessibility(辅助功能/无障碍,自动安装APP)的更多相关文章

  1. Android 高仿豌豆荚 一键安装app 功能 实现

    以往我们那些应用市场 帮我们安装app的时候  我们都得点确定,当然你如果 root 以后 不用点确定 也能自动安装了,后来豌豆荚 推出了一个功能 非root的手机也能不点确定 直接帮你安装好.(如果 ...

  2. 启动avd Android模拟器缓慢 HAXM自动安装失败

    问题1.更新Android sdk镜像,腾讯镜像地址 android-mirror.bugly.qq.com 使用方法如图 问题2.自动更新HAXM失败解决方法 手动下载地址 http://softw ...

  3. 自动打开Accesibility Service 可以自动安装APP

    package com.venscor.helloworld;import java.io.BufferedReader;import java.io.IOException;import java. ...

  4. Android实例-IdHTTP下载(并实现自动安装)(XE10+小米2)

    相关资料: 1.群号 383675978 2.运行时提示"connection closed gracefully"错误原因与解决 http://www.delphifans.co ...

  5. 检验appium环境是否正常:使用appium自动给手机安装app(注意:如果已存在该app,再执行会将原来的卸载再重装,需谨慎)

    (注意:如果已存在该app,再执行会将原来的卸载再重装.泪的教训,我的微信被卸载重装了o(╥﹏╥)o,自动安装app这个步骤需谨慎操作) hi,前面几篇已经讲了appium环境的搭建.设备的连接, 那 ...

  6. android 版本更新适配8.0,解决8.0手机无法更新自动安装apk

    随着android 7.0的普及android 8.0的也逐渐流行起来,那么google对权限方面又有了新的修改.而且我发现在android8.0中除了一些bug,比如说:在小米6(Android 8 ...

  7. Android 自己实现更新下载自动安装

    1.一些公司开发完一款App之后可能并不会去上架App商店,但事后期也需要定时进行维护更新,所以会选择把打包好的apk 发布到自己的服务器,然后在数据库建一个版本号的表,然后剩下的就交给你androi ...

  8. android完整智能家居、备忘录、蓝牙配对、3D动画库、购物车页面、版本更新自动安装等源码

    Android精选源码 app 版本更新.下载完毕自动自动安装 android指针式分数仪表盘 ANdroid蓝牙设备搜索.配对 Android 图片水印框架,支持隐形数字水印 android3D旋转 ...

  9. Ionic实战 自动升级APP(Android版)

    Ionic 框架介绍 Ionic是一个基于Angularjs.可以使用HTML5构建混合移动应用的用户界面框架,它自称为是"本地与HTML5的结合".该框架提供了很多基本的移动用户 ...

随机推荐

  1. 亲测:LNMP环境下,解决项目缓冲慢、502以及配置https的问题

    在做的项目在nginx下访问缓冲时间过长,明显比apache下访问蛮11倍有余, 解决办法: 1增加nginx的upstream,其中upstream中为php-cgi的地址: 2利用nginx作为反 ...

  2. 13 ,CSS 入门基础,行内排版内嵌式排版和外部排版样式

    1.认识 CSS 2.传统 HTML 设计网页版面的缺点 3.CSS 的特点 4.CSS 的排版样式 13.1 认识CSS CSS的英文全名是 Cascading Style Sheets,中文可翻译 ...

  3. JS中substring与substr的用法

    substring方法用于提取字符串中介于两个指定下标之间的字符 substring(start,end) 开始和结束的位置,从零开始的索引javascript 参数 描述 start 必需.一个非负 ...

  4. ajax知识点

    什么是AJAX? AJAX = Asynchronous JavaScript and XML(异步的 JavaScript 和 XML). AJAX 不是新的编程语言,而是一种使用现有标准的新方法. ...

  5. uni-app (1) 安装与运行。

    安装: 直接到官网安装. 到插件市场下载一个模版. 新建项目的时候有提示链接: 找一个模版用于测试,或者在创建的时候选一个内置模版. 运行:第一次运行的时候有几个地方需要配置,这里因为只是用到了微信小 ...

  6. 详解Vue.js 技术

    本文主要从8个章节详解vue技术揭秘,小编觉得挺有用的,分享给大家. 为了把 Vue.js 的源码讲明白,课程设计成由浅入深,分为核心.编译.扩展.生态四个方面去讲,并拆成了八个章节,如下: 准备工作 ...

  7. 矢量图面层和线层相交得到相交后的线层文件(gis相交)

    目的:将arcgis里的面层和线层相交(重叠)部分的线单独生成一个shp文件,用于道路路网密度计算等. 注意:进行相交运算后生成的是线要素文件,相当于把面线相交部分的线单独拿了出来. 操作例子:将图示 ...

  8. 9.Odoo产品分析 (二) – 商业板块(4) –讨论(1)

    查看Odoo产品分析系列--目录 讨论模块就是一个信息记录和消息提醒的功能,在登录到odoo平台的时候第一个界面就是它:  1. 收件箱 收件箱,登录者的邮箱管理,并显示未读邮件数量:    点击上面 ...

  9. Python对象相关内置函数

    针对一个对象,通过以下几个函数,可以获取到该对象的一些信息. 1.type() ,返回某个值的类型 >>> type() <class 'int'> >>&g ...

  10. 浅谈EditText控件的inputType类型

    android:inputType="none"--默认 android:inputType="text"--输入文本字符 android:inputType= ...