Android11.0系统中添加设置开机启动App功能

添加设置开机启动App功能

本文主要描述Android11中通过在自已的系统App中添加设置开机启动App功能,实现在系统每次启动后,根据保存的需要启动的App包名启动对应App。
如何在系统源码中添加开发自己的系统App,可以查看: Android11.0系统中添加开发系统App

创建数据保存

开机启动的App是其它应用通过广播向demo应用设置App的包名实现,需要保存用户设置的此数据,可以使用SharedPreferences或数据库,这里使用SharedPreferences来保存数据。

  1. 添加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);
    }
}

  1. 在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);
    }
}

  1. 通常使用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广播接收器

  1. 添加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;
    }

}

  1. 注册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>

//********省略代码******

添加开机广播接收器

  1. 添加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;
    }

}

  1. 注册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

  1. 其它应用可以通过广播向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;
    } 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值