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类型,则弹出选择框,让用户选择是用调用哪个类的组件
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,440评论 5 467
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,814评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,427评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,710评论 1 270
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,625评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,014评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,511评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,162评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,311评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,262评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,278评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,989评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,583评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,664评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,904评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,274评论 2 345
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,856评论 2 339

推荐阅读更多精彩内容