Android11.0系统中添加设置开机启动App功能
添加设置开机启动App功能
本文主要描述Android11中通过在自已的系统App中添加设置开机启动App功能,实现在系统每次启动后,根据保存的需要启动的App包名启动对应App。
如何在系统源码中添加开发自己的系统App,可以查看: Android11.0系统中添加开发系统App
创建数据保存
开机启动的App是其它应用通过广播向demo应用设置App的包名实现,需要保存用户设置的此数据,可以使用SharedPreferences或数据库,这里使用SharedPreferences来保存数据。
- 添加SpHelper,路径为 /vendor/yjz/demo/app/src/main/java/com/yjz/demo/util/SpHelper.java;
package com.yjz.demo.util;
import android.content.Context;
import android.content.SharedPreferences;
public final class SpHelper {
public static final int INVALID_INT = -1;
public static final String INVALID_STRING = "";
private final String NAME = "demo";
private final String STARTUP_APP_KEY = "startup_app";
private final String STARTUP_APP_DELAY_TIME_KEY = "startup_app_delay_time";
private Context mContent;
private static volatile SpHelper INSTANCE;
private SpHelper (Context context) {
mContent = context;
}
public static SpHelper getInstance(Context context) {
if (null == INSTANCE) {
synchronized (SpHelper.class) {
if (null == INSTANCE) {
INSTANCE = new SpHelper(context.getApplicationContext());
}
}
}
return INSTANCE;
}
public void setStartupApp(String packageName) {
putString(STARTUP_APP_KEY, packageName);
}
public String getStartupApp() {
return getSp().getString(STARTUP_APP_KEY, INVALID_STRING);
}
public void setStartupAppDelayTime(int time) {
putInt(STARTUP_APP_DELAY_TIME_KEY, time);
}
public int getStartupAppDelayTime() {
return getSp().getInt(STARTUP_APP_DELAY_TIME_KEY, INVALID_INT);
}
private void putInt(String key, int value) {
getSp().edit().putInt(key, value).commit();
}
private void putString(String key, String value) {
getSp().edit().putString(key, value).commit();
}
private SharedPreferences getSp() {
return mContent.getSharedPreferences(NAME, Context.MODE_PRIVATE);
}
}
- 在DemoApplication中初始化,路径为 /vendor/yjz/demo/app/src/main/java/com/yjz/demo/DemoApplication.java;
package com.yjz.demo;
import android.app.Application;
import android.util.Log;
import com.yjz.demo.util.SpHelper;
public class DemoApplication extends Application {
private static final String TAG = DemoApplication.class.getSimpleName();
@Override
public void onCreate() {
super.onCreate();
Log.d(TAG, "DemoApplication onCreate--->");
SpHelper.getInstance(this);
}
}
-
通常使用SharedPreferences时,数据是保存在 /data/data/com.yjz.demo/shared_prefs/demo.xml下,demo应用是开机自启动的,此时在DemoApplication的onCreate()方法中操作SharedPreferences时,会报错无法访问,这是因为系统开机但用户未解锁时,系统是在安全的“直接启动”模式下运行,无法访问凭据加密存储(这是默认存储位置,仅在用户解锁设备后可用),如果需要访问数据,应用就要设置为支持设备加密存储(该存储位置在“直接启动”模式下和用户解锁设备后均可使用) ,是在AndroidManifest.xml中配置,路径为 /vendor/yjz/demo/app/src/main/AndroidManifest.xml;
android:directBootAware=“true”
android:defaultToDeviceProtectedStorage=“true”
此时SharedPreferences的文件位置:/data/user_de/0/com.yjz.demo/shared_prefs/demo.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.yjz.demo"
android:sharedUserId="android.uid.system">
<application
android:name=".DemoApplication"
android:allowBackup="false"
android:directBootAware="true"
android:defaultToDeviceProtectedStorage="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:persistent="true"
android:supportsRtl="true"
android:theme="@style/DemoNoActionBarTheme"
tools:targetApi="31">
</application>
</manifest>
添加处理设置开机启动App广播接收器
- 添加StartupAppReceive,接收其它应用发送的设置开机启动App的广播,路径为 /vendor/yjz/demo/app/src/main/java/com/yjz/demo/receive/StartupAppReceive.java;
package com.yjz.demo.receive;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.util.Log;
import com.yjz.demo.util.SpHelper;
public class StartupAppReceive extends BroadcastReceiver {
private static final String TAG = "StartupAppReceive";
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if ("com.yjz.demo.ACTION_STARTUP_APP".equals(action)) {
String appPackageName = intent.getStringExtra("startup_app");
int delayTime = intent.getIntExtra("startup_app_delay_time", 0);
Log.d(TAG, "startup app " + appPackageName + " delay time " + delayTime);
if (null == appPackageName || "".equals(appPackageName)) {
SpHelper.getInstance(context).setStartupApp("");
} else {
ApplicationInfo appInfo = getApplicationInfo(context, appPackageName);
if (null != appInfo) {
SpHelper.getInstance(context).setStartupApp(appPackageName);
if (delayTime > 0) {
ChivalrousSpHelper.getInstance(context).setStartupAppDelayTime(delayTime);
}
} else {
Log.w(TAG, "set startup app fail, app not existed " + appPackageName);
}
}
}
}
public ApplicationInfo getApplicationInfo(Context context, String packageName) {
ApplicationInfo info = null;
try {
info = context.getPackageManager().getApplicationInfo(packageName, PackageManager.GET_UNINSTALLED_PACKAGES);
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
return null;
}
return info;
}
}
- 注册StartupAppReceive广播,路径为 /vendor/yjz/demo/app/src/main/AndroidManifest.xml;
//********省略代码******
<receiver
android:name=".receive.StartupAppReceive"
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="com.yjz.demo.ACTION_STARTUP_APP" />
</intent-filter>
</receiver>
//********省略代码******
添加开机广播接收器
- 添加BootCompletedReceive,监听系统开机广播,根据保存的开机启动App信息,启动App,路径为 /vendor/yjz/demo/app/src/main/java/com/yjz/demo/receive/BootCompletedReceive.java;
收到开机广播后就启动设置的App,测试时发现会偶尔会有App无法启动的情况,添加一定的延迟可以解决;同时支持上层Api接口设置延时启动时间,单位秒;
package com.yjz.demo.receive;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
import com.yjz.demo.util.SpHelper;
public class BootCompletedReceive extends BroadcastReceiver {
private static final String TAG = "DemoBootCompletedReceive";
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (Intent.ACTION_BOOT_COMPLETED.equals(action)) {
//如果设置了开机启动app,进行启动
startupApp(context);
}
}
private void startupApp(Context context) {
String appPackageName = SpHelper.getInstance(context).getStartupApp();
if (null == appPackageName || SpHelper.INVALID_STRING.equals(appPackageName)) {
Log.d(TAG, "no startup app");
return;
}
int delayTime = SpHelper.getInstance(context).getStartupAppDelayTime();
if (delayTime <= 0) {
delayTime = 0;
}
final int startDelayTime = delayTime + 3;
new Thread() {
@Override
public void run() {
try {
Log.d(TAG, "startup app delay " + startDelayTime);
Thread.sleep(startDelayTime * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (launchApp(context, appPackageName)) {
Log.d(TAG, "execute startup app success");
} else {
Log.e(TAG, "execute startup app fail");
}
}
}.start();
}
public boolean launchApp(Context context, String packageName) {
if (null == packageName || "".equals(packageName)) {
return false;
}
ApplicationInfo info = getApplicationInfo(context, packageName);
if (null != info) {
Intent mLaunchIntent = context.getPackageManager().getLaunchIntentForPackage(packageName);
if (mLaunchIntent != null) {
List<ResolveInfo> list = context.getPackageManager().queryIntentActivities(mLaunchIntent, 0);
if (list != null && list.size() > 0) {
//非Activity上下文中启动activity,需要添加标识Intent.FLAG_ACTIVITY_NEW_TASK,否则startActivityAsUser相关方法会报错
mLaunchIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivityAsUser(mLaunchIntent, UserHandle.SYSTEM);
return true;
} else {
Log.w(TAG, "not query intent " + packageName);
}
} else {
Log.w(TAG, "not get lanuch intent " + packageName);
}
} else {
Log.w(TAG, "not found app " + packageName);
}
return false;
}
}
- 注册BootCompletedReceive,修改AndroidManifest.xml,路径为 /vendor/yjz/demo/app/src/main/AndroidManifest.xml;
//********省略代码******
//添加权限
<uses-permission android:name="android.permission.BROADCAST_STICKY" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
//********省略代码******
<receiver
android:name=".receive.BootCompletedReceive"
android:enabled="true"
android:exported="true">
<intent-filter android:priority="1000">
<action android:name="android.intent.action.BOOT_COMPLETED" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</receiver>
//********省略代码******
通过广播设置开机启动App
- 其它应用可以通过广播向demo应用中设置要开机启动的应用包名,如果包名为空,是取消之前的保存。
注意:系统禁止后台应用发送广播,想要发送,需要指明处理此广播的应用
public boolean setStartupApp(Context context, String packageName) {
if (null != packageName && !"".equals(packageName)) {
try {
PackageInfo packinfo = context.getPackageManager().getPackageInfo(context.getPackageName(), PackageManager.GET_PERMISSIONS);
String[] list = packinfo.requestedPermissions;
boolean hasPermission = false;
if (null != list) {
for (int i = 0; i < list.length; i++) {
if (Manifest.permission.QUERY_ALL_PACKAGES.equals(list[i])) {
hasPermission = true;
break;
}
}
}
if (!hasPermission) {
throw new RuntimeException("need permission " + Manifest.permission.QUERY_ALL_PACKAGES);
}
} catch (PackageManager.NameNotFoundException e) {
throw new RuntimeException(e);
}
try {
ApplicationInfo info = context.getPackageManager().getApplicationInfo(packageName, PackageManager.MATCH_UNINSTALLED_PACKAGES);
if (null == info) {
return false;
}
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
return false;
}
}
Intent intent = new Intent("com.yjz.demo.ACTION_STARTUP_APP");
intent.putExtra("startup_app", packageName);
intent.putExtra("startup_app_delay_time", 5);
//如果不指定包名,切到后台发送,会报后台无法发送
intent.setPackage("com.yjz.demo");
context.sendBroadcast(intent);
return true;
}