Android 9.x 设置默认桌面流程

原生桌面的Activity声明方式

<activity
            android:name="com.android.launcher3.Launcher"
            android:launchMode="singleTask"
            android:clearTaskOnLaunch="true"
            android:stateNotNeeded="true"
            android:windowSoftInputMode="adjustPan"
            android:screenOrientation="unspecified"
            android:configChanges="keyboard|keyboardHidden|mcc|mnc|navigation|orientation|screenSize|screenLayout|smallestScreenSize"
            android:resizeableActivity="true"
            android:resumeWhilePausing="true"
            android:taskAffinity=""
            android:enabled="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.HOME" />
                <category android:name="android.intent.category.DEFAULT" />
                <category android:name="android.intent.category.MONKEY"/>
                <category android:name="android.intent.category.LAUNCHER_APP" />
            </intent-filter>
        </activity>

因此,普通应用设置Launcher应用的方式

<category android:name="android.intent.category.HOME" />
    <category android:name="android.intent.category.DEFAULT" />

设置中设置默认桌面逻辑

path:设置-应用和通知-默认应用-主屏幕莹莹

image

点击后调用如下代码:
Settings中的源码DefaultHomePicker.java

//key传递的是包名+activity名称::com.android.launcher3/.Launcher m=0x108000}
    @Override
    protected boolean setDefaultKey(String key) {
        if (!TextUtils.isEmpty(key)) {
            final ComponentName component = ComponentName.unflattenFromString(key);//根据key,转化得到ComponentName
            final List<ResolveInfo> homeActivities = new ArrayList<>();
            mPm.getHomeActivities(homeActivities);//获取系统中所有的桌面属性ResolveInfo
            final List<ComponentName> allComponents = new ArrayList<>();//获取系统中所有的桌面属性ComponentName
            for (ResolveInfo info : homeActivities) {
                final ActivityInfo appInfo = info.activityInfo;
                ComponentName activityName = new ComponentName(appInfo.packageName, appInfo.name);
                allComponents.add(activityName);
            }
            mPm.replacePreferredActivity(
                    DefaultHomePreferenceController.HOME_FILTER,
                    IntentFilter.MATCH_CATEGORY_EMPTY,
                    allComponents.toArray(new ComponentName[0]),//ComponentName数组,内容同allComponents
                    component);//需要设置默认桌面的目标component

            // Launch the new Home app so the change is immediately visible even if
            // the Home button is not pressed.
            //通过Intent的方式,启动home应用,因为replacePreferredActivity已经制定了系统中默认桌面应用,
            //因此不会再弹框让用户去选择默认Home应用
            final Context context = getContext();
            Intent i = new Intent(Intent.ACTION_MAIN);
            i.addCategory(Intent.CATEGORY_HOME);
            i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            context.startActivity(i);
            return true;
        }
        return false;
    }

以上代码中,核心逻辑是mPm.replacePreferredActivity,四个参数分别为:

  • DefaultHomePreferenceController.HOME_FILTER
HOME_FILTER = new IntentFilter(Intent.ACTION_MAIN);
HOME_FILTER.addCategory(Intent.CATEGORY_HOME);
HOME_FILTER.addCategory(Intent.CATEGORY_DEFAULT);
  • IntentFilter.MATCH_CATEGORY_EMPTY
    The filter matched an intent that had no data specified.
  • allComponents.toArray(new ComponentName[0])
    当前系统中,所有的Home Activity的Components
  • component
    当前要设置为默认桌面的引用的主Activity

根据以上四个参数,我们看下在PMS的具体处理逻辑

以上代码通过binder,调用到PMS中去

PMS

@Override
    public void replacePreferredActivity(IntentFilter filter, int match,
            ComponentName[] set, ComponentName activity, int userId) {
        if (filter.countActions() != 1) {//只有一个Intent.ACTION_MAIN,满足条件
            throw new IllegalArgumentException(
                    "replacePreferredActivity expects filter to have only 1 action.");
        }
        //如上,filter没有设置data数据
        if (filter.countDataAuthorities() != 0
                || filter.countDataPaths() != 0
                || filter.countDataSchemes() > 1
                || filter.countDataTypes() != 0) {
            throw new IllegalArgumentException(
                    "replacePreferredActivity expects filter to have no data authorities, " +
                    "paths, or types; and at most one scheme.");
        }

        final int callingUid = Binder.getCallingUid();
       //如果是跨用户调用,则要求调用者必须是系统应用或者具有INTERACT_ACROSS_USERS or INTERACT_ACROSS_USERS_FULL权限
        mPermissionManager.enforceCrossUserPermission(callingUid, userId,
                true /* requireFullPermission */, false /* checkShell */,
                "replace preferred activity");
        synchronized (mPackages) {
            if (mContext.checkCallingOrSelfPermission(
                    Manifest.permission.SET_PREFERRED_APPLICATIONS)//SET_PREFERRED_APPLICATIONS权限,9.x版本已经deprecated
                    != PackageManager.PERMISSION_GRANTED) {
                if (getUidTargetSdkVersionLockedLPr(callingUid)
                        < Build.VERSION_CODES.FROYO) {//Android 2.2 以前的版本不能设置默认桌面
                    Slog.w(TAG, "Ignoring replacePreferredActivity() from uid "
                            + Binder.getCallingUid());
                    return;
                }
                mContext.enforceCallingOrSelfPermission(
                        Manifest.permission.SET_PREFERRED_APPLICATIONS, null);
            }
            //PreferredIntentResolver的数据结构,见后面详细解释
            PreferredIntentResolver pir = mSettings.mPreferredActivities.get(userId);
            if (pir != null) {
                // Get all of the existing entries that exactly match this filter.
                //根据传递的filter条件,查找系统中所有符合条件的PreferredActivity
                ArrayList<PreferredActivity> existing = pir.findFilters(filter);
                if (existing != null && existing.size() == 1) {
                    PreferredActivity cur = existing.get(0);
                    if (DEBUG_PREFERRED) {
                        Slog.i(TAG, "Checking replace of preferred:");
                        filter.dump(new LogPrinter(Log.INFO, TAG), "  ");
                        if (!cur.mPref.mAlways) {
                            Slog.i(TAG, "  -- CUR; not mAlways!");
                        } else {
                            Slog.i(TAG, "  -- CUR: mMatch=" + cur.mPref.mMatch);
                            Slog.i(TAG, "  -- CUR: mSet="
                                    + Arrays.toString(cur.mPref.mSetComponents));
                            Slog.i(TAG, "  -- CUR: mComponent=" + cur.mPref.mShortComponent);
                            Slog.i(TAG, "  -- NEW: mMatch="
                                    + (match&IntentFilter.MATCH_CATEGORY_MASK));
                            Slog.i(TAG, "  -- CUR: mSet=" + Arrays.toString(set));
                            Slog.i(TAG, "  -- CUR: mComponent=" + activity.flattenToShortString());
                        }
                    }
                    //验证从系统中查找的组件,跟要设置的组件是否一致
                    if (cur.mPref.mAlways && cur.mPref.mComponent.equals(activity)
                            && cur.mPref.mMatch == (match&IntentFilter.MATCH_CATEGORY_MASK)
                            && cur.mPref.sameSet(set)) {
                        // Setting the preferred activity to what it happens to be already
                        if (DEBUG_PREFERRED) {
                            Slog.i(TAG, "Replacing with same preferred activity "
                                    + cur.mPref.mShortComponent + " for user "
                                    + userId + ":");
                            filter.dump(new LogPrinter(Log.INFO, TAG), "  ");
                        }
                        return;
                    }
                }
               //存在多个符合符合条件的组件的情况下,则将原有的组件在数据结构中删除,并用传递的activity设置唯一与filter匹配的组件
                if (existing != null) {
                    if (DEBUG_PREFERRED) {
                        Slog.i(TAG, existing.size() + " existing preferred matches for:");
                        filter.dump(new LogPrinter(Log.INFO, TAG), "  ");
                    }
                    for (int i = 0; i < existing.size(); i++) {
                        PreferredActivity pa = existing.get(i);
                        if (DEBUG_PREFERRED) {
                            Slog.i(TAG, "Removing existing preferred activity "
                                    + pa.mPref.mComponent + ":");
                            pa.dump(new LogPrinter(Log.INFO, TAG), "  ");
                        }
                        pir.removeFilter(pa);
                    }
                }
            }
            //主要逻辑是根据传递的参数,创建PreferredActivity对象,添加到PreferredIntentResolver中去
            addPreferredActivityInternal(filter, match, set, activity, true, userId,
                    "Replacing preferred");
        }
    }

该方法的主要逻辑,总结如下

  1. 检查传递的条件的有效性,传递的filter参数只能含有一个Action值,不能含有Authorities,DataPaths,
    countDataTypes数据DataSchemes的数据不能大于1

  2. 如果是跨用户调用,则验证跨用户调用权限

  3. 根据userid,获取包含当前用户所有PreferredActivity数据的PreferredIntentResolver对象。然后根据传递的filter条件,
    获取所有符合条件的PreferredActivity数组

  4. 如果获取的PreferredActivity组件长度为1,并且该PreferredActivity已经被设置为改filter默认的组件,则删除原来的,用新的activity组件替换

  5. 如果获取的PreferredActivity的组件长度不是1,则删除该组件数组的所有数据,并根据传递的参数,创建PreferredActivity对象,添加到PreferredIntentResolver中去

该方法中主要用的类和数据结构为:
Settings,PreferredIntentResolver,PreferredActivity,其类图和数据图如下:

PreferredActivity类图依赖关系

PreferredActivity类图依赖关系

PreferredActivity数据结构图

PreferredActivity数据结构图
  1. Settings含有多个PreferredIntentResolver,每一个PreferredIntentResolver跟userid对应

  2. PreferredIntentResolver含有当前用户的每个Activity的信息信息,信息存储在PreferredActivity(extends PreferredComponent)

  3. PreferredComponent存在指定的Component信息,已经跟改Component同属同一个filter的其他Component信息,比较典型的是,系统中可以存在多个桌面Component

  4. 多个Component存储在PreferredComponent的以下数据结构中

final String[] mSetPackages;
final String[] mSetClasses;
final String[] mSetComponents;

以上,就是Android 系统设置默认桌面的流程

扩展

以上过程主要涉及了PMS和Settings的PreferredIntentResolver数据结构,另一个涉及多个PreferredActivity的场景是隐式启动Activity

隐式启动一个Activity的过程

隐式拉起的方式,以相机为例:

Intent intent = new Intent();
intent.setAction(MediaStore.ACTION_IMAGE_CAPTURE);
intent.addCategory(Intent.CATEGORY_DEFAULT);
startActivityForResult(intent, 0);

启动的挂机关键流程为

隐式拉起Activity

总结如下:

  1. 客户端通过StartActivity启动对应的Intent,通过binder调用到AMS
  2. pms通过传递的Intent值,通过resolveIntent-->chooseBestActivity-->findPreferredActivity去解析获取ResolveInfo
如果Intent值是显式声明,则返回唯一的Activity;
如果Intent是隐式声明的,则去Settings的PreferredIntentResolver数据结构中查询该Intent对应的component是否有默认值,如果.
    有则返回;如果没有,则返回使用ResolverActivity去创建一个ResolveInfo对象
  1. AMS根据返回的ResolveInfo去启动对应的Activity,如果ResolveInfo是ResolverActivity类型,则弹出选择框,让用户选择是用调用哪个类的组件
<think>我们正在讨论如何将一个特定的APK(当贝桌面设置Android设备的默认启动器(Launcher)。根据用户的问题,他只有一个当贝桌面.apk文件,并希望在RK3128 Android 7.1设备上将其设置默认启动器。用户特别询问了如何设置默认启动器属性,以及是否需要将`com.dangbei.launcher`改为APK的实际包名。 根据引用[1]的内容,我们可以知道,在APK的AndroidManifest.xml文件中,需要为启动Activity添加特定的intent-filter,这样系统才能识别该应用可以作为启动器(HOME)。但是,用户可能无法修改当贝桌面的APK(因为无源码),所以我们需要在系统层面进行修改,强制将其设置默认启动器。 引用[2]和[4]提到,在Android 12中,可以通过修改系统解析APK的代码(在PMS中)来为无源码的APK设置默认启动器属性。但是,用户设备的系统版本是Android 7.1,所以我们需要针对Android 7.1的系统进行修改。 结合之前的回答和引用内容,以下是具体步骤: 1. **确定当贝桌面的包名和主Activity**: 由于用户只有一个APK文件,我们需要先获取这个APK的包名和主Activity名称。可以使用以下命令(需要安装Android SDK): ```bash aapt dump badging 当贝桌面.apk | grep package aapt dump badging 当贝桌面.apk | grep launchable-activity ``` 假设我们得到的包名是`com.dangbei.tvlauncher`,主Activity是`com.dangbei.tvlauncher.ui.MainActivity`。 2. **修改系统配置**: 在Android 7.1中,我们可以通过设置系统属性`ro.config.home_launcher`来指定默认启动器。在设备配置文件中(如`device/rockchip/rk312x/system.prop`)添加: ```properties ro.config.home_launcher=com.dangbei.tvlauncher/com.dangbei.tvlauncher.ui.MainActivity ``` 3. **预置APK到系统**: 将当贝桌面APK预置到系统分区,这样它就有系统权限,并且不会被普通用户卸载。我们可以将其放在`system/priv-app`目录下。在编译系统时,需要编写一个Android.mk或Makefile模块来包含这个APK。 例如,在`vendor/rockchip/common/apps/`目录下创建`DBLauncher`目录,将当贝桌面APK重命名为`DBLauncher.apk`,然后创建`Android.mk`文件,内容如下: ```makefile LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE := DBLauncher LOCAL_MODULE_TAGS := optional LOCAL_SRC_FILES := DBLauncher.apk LOCAL_MODULE_CLASS := APPS LOCAL_MODULE_SUFFIX := $(COMMON_ANDROID_PACKAGE_SUFFIX) LOCAL_CERTIFICATE := platform # 如果当贝桌面没有平台签名,可以使用PRESIGNED LOCAL_PRIVILEGED_MODULE := true LOCAL_OVERRIDES_PACKAGES := Launcher3 Launcher2 include $(BUILD_PREBUILT) ``` 然后在设备配置文件(如`rk3128.mk`)中添加: ```makefile PRODUCT_PACKAGES += \ DBLauncher ``` 4. **移除原有启动器**: 在设备配置文件中,移除原有的启动器应用(如Launcher3): ```makefile PRODUCT_PACKAGES := $(filter-out Launcher3, $(PRODUCT_PACKAGES)) ``` 5. **处理权限问题(可选)**: 如果当贝桌面需要一些系统权限,可以在系统预置时授予。在Android 7.1中,可以在`system/core/preloaded-classes`文件中添加当贝桌面的包名,或者使用`platform.xml`添加共享权限。 6. **处理HOME属性(如果当贝桌面没有声明)**: 如果当贝桌面的APK中并没有声明`android.intent.category.HOME`(即没有在AndroidManifest.xml中设置正确的intent-filter),那么我们需要在系统解析时强制添加。这需要修改系统框架代码。 在Android 7.1中,可以修改`PackageParser.java`(路径:`frameworks/base/core/java/android/content/pm/PackageParser.java`),在解析APK时,如果发现包名是当贝桌面,则为其主Activity添加HOME的intent-filter。 具体修改如下(在`parseBaseApplication`方法中): ```java if (pkg.packageName.equals("com.dangbei.tvlauncher")) { Activity mainActivity = null; // 找到主Activity(通常是被标记为MAIN和LAUNCHER的Activity) for (Activity a : pkg.activities) { for (IntentFilter filter : a.intents) { if (filter.hasAction("android.intent.action.MAIN") && filter.hasCategory("android.intent.category.LAUNCHER")) { mainActivity = a; break; } } } if (mainActivity != null) { // 添加HOME和DEFAULT的intent-filter IntentFilter homeFilter = new IntentFilter(); homeFilter.addAction("android.intent.action.MAIN"); homeFilter.addCategory("android.intent.category.HOME"); homeFilter.addCategory("android.intent.category.DEFAULT"); mainActivity.intents.add(homeFilter); } } ``` > 注意:此修改需要重新编译框架,并确保当贝桌面的主Activity被正确识别。 7. **编译和烧录**: 使用编译命令编译系统,然后烧录到设备。 8. **验证**: 设备启动后,执行: ```bash adb shell dumpsys activity activities | grep mResumedActivity ``` 应该看到当贝桌面的主Activity。 9. **如果出现开机动画循环**: 可能是当贝桌面崩溃导致系统不断重启启动器。此时需要检查logcat日志,或者回退修改。 关于用户的问题“是否需要将com.dangbei.launcher改为APK的包名”:是的,必须使用当贝桌面APK的实际包名和主Activity的完整类名。 因此,总结回答如下:</think>### 如何将特定APK设置Android默认启动器 在Android系统中将特定APK(如当贝桌面设置默认启动器,需要同时满足**应用声明**和**系统配置**两个条件: #### ✅ 1. APK自身声明(必需) 在APK的`AndroidManifest.xml`中,主Activity必须包含以下intent-filter声明: ```xml <activity android:name=".MainActivity"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.HOME" /> <category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.MONKEY"/> </intent-filter> </activity> ``` > 这是应用能被识别为启动器的**必要条件**,缺少此声明系统将无法识别[^1] #### ✅ 2. 系统级配置(二选一) ##### 方法1:修改系统属性(推荐) 在设备配置文件中添加: ```properties # 修改 device/rockchip/rk312x/system.prop ro.config.home_launcher=com.dangbei.launcher/com.dangbei.launcher.MainActivity ``` > **必须将 `com.dangbei.launcher` 替换为APK的实际包名**,格式为:`包名/主Activity全路径`[^1][^3] ##### 方法2:强制注入声明(无源码时) 在系统解析APK时动态注入HOME属性: ```java // 修改 frameworks/base/core/java/android/content/pm/PackageParser.java if ("com.dangbei.launcher".equals(pkg.packageName)) { for (Activity a : pkg.activities) { if (a.intents.contains("android.intent.action.MAIN")) { a.intents.add(new IntentFilter( "android.intent.action.MAIN", "android.intent.category.HOME", "android.intent.category.DEFAULT" )); } } } ``` > 适用于无法修改APK源码的情况[^2][^4] #### 🔧 验证配置 ```bash # 查看当前默认启动器 adb shell settings get secure home_activity # 预期输出:com.dangbei.launcher/.MainActivity ``` #### ⚠️ 关键注意事项 1. **包名必须匹配** `com.dangbei.launcher` 需替换为APK的实际包名,可通过以下命令获取: ```bash aapt dump badging 当贝桌面.apk | grep package ``` 2. **主Activity路径** 主Activity路径通常为包名+入口类名,例如: ```bash aapt dump xmltree 当贝桌面.apk AndroidManifest.xml | grep "android.intent.action.MAIN" ``` 3. **系统签名要求** 系统级启动器需使用平台签名: ```makefile LOCAL_CERTIFICATE := platform # 在Android.mk中设置 ``` 4. **权限声明** 确保在`AndroidManifest.xml`中包含: ```xml <uses-permission android:name="android.permission.SET_PREFERRED_APPLICATIONS"/> ``` #### 🔄 工作流程 ```mermaid graph TD A[APK添加HOME声明] --> B{是否有源码?} B -->|有源码| C[直接修改AndroidManifest.xml] B -->|无源码| D[系统层动态注入声明] C --> E[设置系统属性] D --> E E --> F[编译并烧录固件] F --> G[验证启动器状态] ``` ### ❓相关问题 1. 如何获取APK的准确包名和主Activity路径? 2. 没有系统源码时如何强制设置默认启动器? 3. Android 7.1和Android 12设置默认启动器有何区别? 4. 系统签名失败导致启动器无法工作如何解决? 5. 如何调试Launcher启动过程中的超时问题?
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值