公司最近需要在机顶盒上面是实现更新app,但是要做无感更新,让用户无法感知
刚开始想的是使用热更新,但是发现好像实现不了版本更新,最后在博客上发现,都是用无障碍,或者adb得比较多,做一下总结
第一种使用安卓无障碍模式进行自动点击
使用无障碍模式,我用得是两个app,用一个app来执行无障碍,更新本体app进行下载,使用的是
AccessibilityService服务
代码:
public class MyAccessibilityService extends AccessibilityService {
Map<Integer, Boolean> handleMap = new HashMap<>();
private static final String TAG = MyAccessibilityService.class.getSimpleName();
private static final int DELAY_PAGE = 320; // 页面切换时间
private final Handler mHandler = new Handler();
@Override
public void onAccessibilityEvent(AccessibilityEvent event) {
log(event.toString());
checkInstall(event);
}
private void log(String string) {
Log.e("getClass()", "AutoInstallAccessibilityService:"
+ string);
}
/**
* 检查是否是要安装应用的标题
*
* @param source
* @param title
* @return
*/
private boolean checkTitle(AccessibilityNodeInfo source, String title) {
List<AccessibilityNodeInfo> titleList = source
.findAccessibilityNodeInfosByText(title);
for (AccessibilityNodeInfo node : titleList) {
Log.d("test", node.toString());
if (node.getClassName().equals("android.widget.TextView")) {
return true;
}
}
return false;
}
/**
* 判断是否需要安装应用,并执行
* @param event
*/
private void checkInstall(AccessibilityEvent event) {
// source不为空
boolean notNull = event.getSource() != null;
if (notNull) {
// 当前界面为安装界面
boolean installPage = event.getPackageName().equals(
"com.android.packageinstaller");
Log.e(TAG, "installPage:--- "+installPage );
if (installPage) {
// 当前标题为应用标题
boolean thisApk = checkTitle(event.getSource(), "木鹰互动");
Log.e(TAG, "thisApk:--- "+thisApk );
if (thisApk) {
doInstall(event);
}
}
}
}
/**
* 执行 安装 并打开应用
*
* @param event
*/
private void doInstall(AccessibilityEvent event) {
List<AccessibilityNodeInfo> unintall_nodes = event.getSource()
.findAccessibilityNodeInfosByText("更新");
for (int i = 0; i < unintall_nodes.size(); i++) {
Log.e(TAG, "doInstall: "+unintall_nodes.get(i).getClassName() );
}
if (unintall_nodes != null && !unintall_nodes.isEmpty()) {
AccessibilityNodeInfo node;
for (int i = 0; i < unintall_nodes.size(); i++) {
node = unintall_nodes.get(i);
if (node.getClassName().equals("android.widget.Button")
&& node.isEnabled()) {
node.performAction(AccessibilityNodeInfo.ACTION_CLICK);
}
}
}
List<AccessibilityNodeInfo> unintall_nodes1 = event.getSource()
.findAccessibilityNodeInfosByText("安装");
if (unintall_nodes1 != null && !unintall_nodes1.isEmpty()) {
AccessibilityNodeInfo node;
for (int i = 0; i < unintall_nodes1.size(); i++) {
node = unintall_nodes1.get(i);
if (node.getClassName().equals("android.widget.Button")
&& node.isEnabled()) {
node.performAction(AccessibilityNodeInfo.ACTION_CLICK);
}
}
}
List<AccessibilityNodeInfo> next_nodes = event.getSource()
.findAccessibilityNodeInfosByText("下一步");
if (next_nodes != null && !next_nodes.isEmpty()) {
AccessibilityNodeInfo node;
for (int i = 0; i < next_nodes.size(); i++) {
node = next_nodes.get(i);
if (node.getClassName().equals("android.widget.Button")
&& node.isEnabled()) {
node.performAction(AccessibilityNodeInfo.ACTION_CLICK);
}
}
}
List<AccessibilityNodeInfo> ok_nodes = event.getSource()
.findAccessibilityNodeInfosByText("打开");
if (ok_nodes != null && !ok_nodes.isEmpty()) {
AccessibilityNodeInfo node;
for (int i = 0; i < ok_nodes.size(); i++) {
node = ok_nodes.get(i);
if (node.getClassName().equals("android.widget.Button")
&& node.isEnabled()) {
node.performAction(AccessibilityNodeInfo.ACTION_CLICK);
}
}
}
}
@Override
public void onInterrupt() {
// TODO Auto-generated method stub
}
}
activity
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
boolean screenReaderActive = AccessibilityUtil.isScreenReaderActive(MainActivity.this);
if (!screenReaderActive) {
Intent intent = new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS);
startActivity(intent);
}
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (!Settings.System.canWrite(MainActivity.this)) {
Intent intent = new Intent(Settings.ACTION_MANAGE_WRITE_SETTINGS,
Uri.parse("package:" + getPackageName()));
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivityForResult(intent, 200);
} else {
// 如果有权限做些什么
// Toast.makeText(this, "已申请", Toast.LENGTH_SHORT).show();
}
}
}
@Override
protected void onResume() {
boolean screenReaderActive = AccessibilityUtil.isScreenReaderActive(MainActivity.this);
if (screenReaderActive) {
Intent intent = new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS);
startActivity(intent);
}
super.onResume();
}
//自动开启无障碍服务
private void autoOpenAccessibilityService(){
Settings.Secure.putString(
getContentResolver(),
Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
"包名/想要打开的无障碍服务的完整路径"
);
//例子:"com.xxx.xxxx/com.xxx.xxxx.service.MyAccessibilityService"
Settings.Secure.putString(
getContentResolver(),
Settings.Secure.ACCESSIBILITY_ENABLED, "1"
);
}
//自动关闭无障碍服务
private void autoCloseAccessibilityService(){
Settings.Secure.putString(
getContentResolver(),
Settings.Secure.ACCESSIBILITY_ENABLED,
"包名/想要打开的无障碍服务完整路径"
);
//例子:"com.xxx.xxxx/com.xxx.xxxx.service.MyAccessibilityService"
Settings.Secure.putString(
getContentResolver(),
Settings.Secure.ACCESSIBILITY_ENABLED, "0"
);
}
}
工具类
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());
}
}
}
public static boolean isAccessibilityEnabled(Context context) throws RuntimeException{
if (context == null) {
return false;
}
// 检查AccessibilityService是否开启
AccessibilityManager am = (AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE);
boolean isAccessibilityEnabled_flag = am.isEnabled();
boolean isExploreByTouchEnabled_flag = false;
// 检查无障碍服务是否以语音播报的方式开启
isExploreByTouchEnabled_flag = isScreenReaderActive(context);
return (isAccessibilityEnabled_flag && isExploreByTouchEnabled_flag);
}
private final static String SCREEN_READER_INTENT_ACTION = "android.accessibilityservice.AccessibilityService";
private final static String SCREEN_READER_INTENT_CATEGORY = "android.accessibilityservice.category.FEEDBACK_SPOKEN";
public static boolean isScreenReaderActive(Context context) {
// 通过Intent方式判断是否存在以语音播报方式提供服务的Service,还需要判断开启状态
Intent screenReaderIntent = new Intent(SCREEN_READER_INTENT_ACTION);
screenReaderIntent.addCategory(SCREEN_READER_INTENT_CATEGORY);
List<ResolveInfo> screenReaders = context.getPackageManager().queryIntentServices(screenReaderIntent, 0);
// 如果没有,返回false
if (screenReaders == null || screenReaders.size() <= 0) {
return false;
}
boolean hasActiveScreenReader = false;
if (Build.VERSION.SDK_INT <= 15) {
ContentResolver cr = context.getContentResolver();
Cursor cursor = null;
int status = 0;
for (ResolveInfo screenReader : screenReaders) {
cursor = cr.query(Uri.parse("content://" + screenReader.serviceInfo.packageName
+ ".providers.StatusProvider"), null, null, null, null);
if (cursor != null && cursor.moveToFirst()) {
status = cursor.getInt(0);
cursor.close();
// 状态1为开启状态,直接返回true即可
if (status == 1) {
return true;
}
}
}
} else if (Build.VERSION.SDK_INT >= 26) {
// 高版本可以直接判断服务是否处于开启状态
for (ResolveInfo screenReader : screenReaders) {
hasActiveScreenReader |= isAccessibilitySettingsOn(context, screenReader.serviceInfo.packageName + "/" + screenReader.serviceInfo.name);
}
} else {
// 判断正在运行的Service里有没有上述存在的Service
List<String> runningServices = new ArrayList<String>();
android.app.ActivityManager manager = (android.app.ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
for (android.app.ActivityManager.RunningServiceInfo service : manager.getRunningServices(Integer.MAX_VALUE)) {
runningServices.add(service.service.getPackageName());
}
for (ResolveInfo screenReader : screenReaders) {
if (runningServices.contains(screenReader.serviceInfo.packageName)) {
hasActiveScreenReader |= true;
}
}
}
return hasActiveScreenReader;
}
// To check if service is enabled
private static boolean isAccessibilitySettingsOn(Context context, String service) {
TextUtils.SimpleStringSplitter mStringColonSplitter = new TextUtils.SimpleStringSplitter(':');
String settingValue = Settings.Secure.getString(
context.getApplicationContext().getContentResolver(),
Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES);
if (settingValue != null) {
mStringColonSplitter.setString(settingValue);
while (mStringColonSplitter.hasNext()) {
String accessibilityService = mStringColonSplitter.next();
if (accessibilityService.equalsIgnoreCase(service)) {
return true;
}
}
}
return false;
}
}
清单文件
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:sharedUserId="android.uid.system"
>
<uses-permission android:name="android.permission.BIND_ACCESSIBILITY_SERVICE"
tools:ignore="ProtectedPermissions" />
<uses-permission android:name="android.permission.WRITE_SETTINGS"
tools:ignore="ProtectedPermissions" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_PHONE_STATE"/>
<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.MyApplication"
tools:targetApi="31">
<service android:name=".ui.MyAccessibilityService"
android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE"
android:exported="true">
<intent-filter>
<action android:name="android.accessibilityservice.AccessibilityService"/>
</intent-filter>
<meta-data
android:name="android.accessibilityservice"
android:resource="@xml/accessibility_service_config"/>
</service>
<activity
android:name=".ui.MainActivity"
android:exported="true"
android:screenOrientation="landscape">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
accessibility_service_config.xml
<?xml version="1.0" encoding="utf-8"?>
<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_title"
android:notificationTimeout="100"
android:packageNames="com.android.packageinstaller" />
这个使用来监控系统安装的弹窗,检测到之后,会自动点击,完成安装,缺点就是当这个app挂掉之后,无法实现自动安装,无障碍还需要手动开启,调研之后发现,如果要实现无障碍一直开启的话,还需把app打包成系统级别应用(没有实现)
参考博客自动和手动开启无障碍服务的方式_settings.secure.putstring-CSDN博客
代码开启无障碍服务-系统应用_settings.secure.putstring(getcontentresolver(), se-CSDN博客
安卓默认打开指定apk的无障碍权限_android 12.0默认开启无障碍服务权限和打开默认apk无障碍服务-CSDN博客
第二种 使用adb 命令安装apk(root)
root 权限还在申请,实现之后更新