一、智能安装
(一)什么是智能安装?
最近因为公司需求需要实现智能安装,apk从服务器上下载后,自动打开安装,安装完后自己打开,难了我好长时间才实现的,记录一下。
首先实现有两种方式:
静默安装: 在后台无声无息的安装apk,页面上没有任何显示,这个其实实现起来比智能安装还要简单,但是他必须要有root权限才可以 ,否则实现起来很麻烦。
智能安装: 这种也是自动安装,不需要用户操作,但是页面上会存在安装页面,不需要root权限
在这里主要讲的是智能安装
(二)AccessibilityService
AccessibilityService是Android提供的辅助服务,辅助服务的设计初衷提供给无法和界面进行交互的残疾用户。来协助帮助他们进行一些用户操作,比如点击,返回,长按,获取屏幕信息等能力。后来被开发者另辟蹊径,用于一些插件开发,做一些监听第三方应用的插件。
生命周期:
辅助服务的生命周期由系统专门管理,并遵循Server的生命周期。服务的启动只能用户在设备设置中明确启动服务来触发。当系统绑定到服务后,它会调用AccessibilityService#onServiceConnected()方法。当用户在设置设置中关闭时,辅助服务功能将停止,或者调用AccessibilityService#disableSelf()方法。giant服务会被关闭销毁
有兴趣可以去官方文档去深入学习
在使用的时候必须要打开对应的无障碍权限,如下图,配置对应文件,才会显示这个app对应的辅助权限,上面的文字信息可根据需求去修改
(三)实现
接下来就是如何实现了:
(1)首先在res/xml目录下新建一个accessibility_service_config.xml文件
<?xml version="1.0" encoding="utf-8"?>
<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
android:packageNames="com.android.packageinstaller"
android:description="智能安装服务,无需用户的任何操作就可以自动安装程序"
android:accessibilityEventTypes="typeAllMask"
android:accessibilityFlags="flagDefault"
android:notificationTimeout="100"
android:accessibilityFeedbackType="feedbackGeneric"
android:canRetrieveWindowContent="true"
/>
packageNames: 指的是我们要监听哪个包下的窗口活动,这里指定的时安装页面
description: 描述这个功能,这个文本对应点进上面某个服务页面上显示
accessibilityEventTypes: 指定我们在监听窗口中可以模拟哪些事件,这里写typeAllMask表示所有的事件都能模拟
accessibilityFlags: 可以指定无障碍服务的一些附加参数,这里我们传默认值flagDefault就行
notificationTimeout: 接受事件的时间间隔,通常将其设置为100即可
accessibilityFeedbackType: 指定服务的反馈类型,包括语音反馈,震动反馈,视频反馈等,这里选的是通用反馈
canRetrieveWindowContent: 指定是否允许我们的程序读取窗口中的节点和内容,必须写true。
(2) 安装方法
在这里我只粘贴了打开apk的方法,因为需求的原因,没有粘贴上下文,可在需要的位置使用
mSavePath: 是新apk的保存地址
apkName 文件名字
private void installApk(){
File apkfile = new File(mSavePath, apkName);
Log.i("info","apk保存地址:"+apkfile.getAbsolutePath());
if (!apkfile.exists()){
return;
}
Uri apkUri;// = FileProvider.getUriForFile(mContext, "com.zjs.znstorepack.fileprovider", apkfile);
Intent i = new Intent(Intent.ACTION_VIEW);
// 通过Intent安装APK文件
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
apkUri = FileProvider.getUriForFile(mContext,
"com.installdemo.test.fileprovider",
apkfile);
} else {
apkUri = Uri.fromFile(apkfile);
}
//打开新版本应用的
i.setDataAndType(apkUri, "application/vnd.android.package-archive");
mContext.startActivity(i);
//安装完成 进入完成页面 提示完成、打开,如果需要模拟点击打开,可以加上,如果在模拟点击打开无效,可注掉,通过广播的方式去实现安装后启动
//i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
//彻底杀死进程,通过广播打开app时,必须注释掉
//android.os.Process.killProcess(android.os.Process.myPid());
}
(3)创建一个MyAccessibilityService类并继承自AccessibilityService
public class MyAccessibilityService extends AccessibilityService {
Map<Integer, Boolean> handledMap = new HashMap<>();
public MyAccessibilityService() {
}
@Override
public void onAccessibilityEvent(AccessibilityEvent event) {
AccessibilityNodeInfo nodeInfo = event.getSource();
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) {
boolean handled = iterateNodesAndHandle(nodeInfo);
if (handled) {
handledMap.put(event.getWindowId(), true);
}
}
}
}else{
LogToFile.i("info","此时节点为null");
}
}
private boolean iterateNodesAndHandle(AccessibilityNodeInfo nodeInfo) {
if (nodeInfo != null) {
int childCount = nodeInfo.getChildCount();
if ("android.widget.Button".equals(nodeInfo.getClassName())) {
String nodeContent = nodeInfo.getText().toString();
//这里可以根据需求添加,例如打开,完成,确定等,
//因为设备的原因,我在这里添加打开的判断,有些条件不满足,所已没有判断,打开安装完成后我也没让他弹出安装完成页面
if ("安装".equals(nodeContent)) {
//模拟点击
nodeInfo.performAction(AccessibilityNodeInfo.ACTION_CLICK);
return true;
}
} else if ("android.widget.ScrollView".equals(nodeInfo.getClassName())) {
nodeInfo.performAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD);
}
for (int i = 0; i < childCount; i++) {
AccessibilityNodeInfo childNodeInfo = nodeInfo.getChild(i);
if (iterateNodesAndHandle(childNodeInfo)) {
return true;
}
}
}
return false;
}
@Override
public void onInterrupt() {
}
}
每当窗口有活动时,就会有消息回调到onAccessibilityEvent()方法中,因此所有的逻辑都是从这里开始的。首先我们可以通过传入的AccessibilityEvent参数来获取当前事件的类型,在这里需要监听TYPE_WINDOW_CONTENT_CHANGED和TYPE_WINDOW_STATE_CHANGED这两种事件就可以了,对应窗口文本变化和窗口状态变化,因为在整个安装过程中,这两个事件必定有一个会被触发。当然也有两个同时都被触发的可能,那么为了防止二次处理的情况,这里我们使用了一个Map来过滤掉重复事件。
调用的方法也很好理解,就是进入后,遍历所有子节点,递归调用,如果是按钮,判断上面的文本是不是 “安装” ,如果是模拟点击一下,如果ScrollView 就模拟滑动一下,继续操作
(4) AndroidManifest.xml,在application标签内加上下面的代码
<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.BIND_ACCESSIBILITY_SERVICE的权限, 且必须要有一个值为android.accessibilityservice.AccessibilityService的action,然后我们通过将刚才创建的配置文件指定进去
到此配置完成,打开手机辅助服务后,即可实现
二、安装后自启动
上述操作完成后,一般情况下,可以实现智能安装,打开指定安装包,自动点击安装按钮,点击完后自动点击打开按钮(做了判断的情况下),当然也有可能会遇到和我一样的问题,就是安装完成后,不会自动点打开(就很迷,必须设备接鼠标后移动一下才会点击,没找到原因),这样的话,可以通过广播去实现,因为刚接触安卓不久,对于广播还不是很了解,在这里直接上代码,
(1)自定义receiver,继承BroadcastReceiver
public class MyReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
String localPkgName = context.getPackageName();//取得MyReceiver所在的App的包名
Uri data = intent.getData();
String installedPkgName = data.getSchemeSpecificPart();//取得安装的Apk的包名,只在该app覆盖安装后自启动
if((action.equals(Intent.ACTION_PACKAGE_ADDED)
|| action.equals(Intent.ACTION_PACKAGE_REPLACED)) && installedPkgName.equals(localPkgName)){
Intent launchIntent = new Intent(context, MainActivity.class);//默认打开页面
launchIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(launchIntent);
}
}
}
(2)AndroidManifest.xml,在application标签内加上下面的代码
<receiver android:name=".receiver.MyReceiver">
<intent-filter>
<action android:name="android.intent.action.PACKAGE_ADDED" />
<action android:name="android.intent.action.PACKAGE_REPLACED" />
<action android:name="android.intent.action.PACKAGE_REMOVED" />
<data android:scheme="package"/>
</intent-filter>
</receiver>
注意: 通过广播启动的时候,安装方法中的这两行一定要注释掉,要不然会出现其他bug
i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
android.os.Process.killProcess(android.os.Process.myPid());
到此应该是可以实现智能安装且自启动了,有什么问题或是错误,及时评论指出