作者:古风子 链接:https://www.jianshu.com/p/f8913ce0a004 来源:简书 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
原生桌面的Activity声明方式
<activityandroid: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:设置-应用和通知-默认应用-主屏幕
点击后调用如下代码:
Settings中的源码DefaultHomePicker.java
//key传递的是包名+activity名称::com.android.launcher3/.Launcher m=0x108000}@Overrideprotected boolean setDefaultKey(String key) {if (!TextUtils.isEmpty(key)) {final ComponentName component = ComponentName.unflattenFromString(key);//根据key,转化得到ComponentNamefinal List<ResolveInfo> homeActivities = new ArrayList<>();mPm.getHomeActivities(homeActivities);//获取系统中所有的桌面属性ResolveInfofinal List<ComponentName> allComponents = new ArrayList<>();//获取系统中所有的桌面属性ComponentNamefor (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数组,内容同allComponentscomponent);//需要设置默认桌面的目标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
@Overridepublic 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条件,查找系统中所有符合条件的PreferredActivityArrayList<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 alreadyif (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");}}
该方法的主要逻辑,总结如下
- 检查传递的条件的有效性,传递的filter参数只能含有一个Action值,不能含有Authorities,DataPaths,
countDataTypes数据DataSchemes的数据不能大于1 - 如果是跨用户调用,则验证跨用户调用权限
- 根据userid,获取包含当前用户所有PreferredActivity数据的PreferredIntentResolver对象。然后根据传递的filter条件,
获取所有符合条件的PreferredActivity数组 - 如果获取的PreferredActivity组件长度为1,并且该PreferredActivity已经被设置为改filter默认的组件,则删除原来的,用新的activity组件替换
- 如果获取的PreferredActivity的组件长度不是1,则删除该组件数组的所有数据,并根据传递的参数,创建PreferredActivity对象,添加到PreferredIntentResolver中去
该方法中主要用的类和数据结构为:
Settings,PreferredIntentResolver,PreferredActivity,其类图和数据图如下:
PreferredActivity类图依赖关系
PreferredActivity数据结构图

- Settings含有多个PreferredIntentResolver,每一个PreferredIntentResolver跟userid对应
- PreferredIntentResolver含有当前用户的每个Activity的信息信息,信息存储在PreferredActivity(extends PreferredComponent)
- PreferredComponent存在指定的Component信息,已经跟改Component同属同一个filter的其他Component信息,比较典型的是,系统中可以存在多个桌面Component
- 多个Component存储在PreferredComponent的以下数据结构中
以上,就是Android 系统设置默认桌面的流程final String[] mSetPackages;final String[] mSetClasses;final String[] mSetComponents;
扩展
以上过程主要涉及了PMS和Settings的PreferredIntentResolver数据结构,另一个涉及多个PreferredActivity的场景是隐式启动Activity
隐式启动一个Activity的过程
隐式拉起的方式,以相机为例:
Intent intent = new Intent();intent.setAction(MediaStore.ACTION_IMAGE_CAPTURE);intent.addCategory(Intent.CATEGORY_DEFAULT);startActivityForResult(intent, 0);
启动的挂机关键流程为
总结如下:
- 客户端通过StartActivity启动对应的Intent,通过binder调用到AMS
- pms通过传递的Intent值,通过resolveIntent—>chooseBestActivity—>findPreferredActivity去解析获取ResolveInfo ```xml 如果Intent值是显式声明,则返回唯一的Activity;
如果Intent是隐式声明的,则去Settings的PreferredIntentResolver数据结构中查询该Intent对应的component是否有默认值,如果. 有则返回;如果没有,则返回使用ResolverActivity去创建一个ResolveInfo对象 ```
- AMS根据返回的ResolveInfo去启动对应的Activity,如果ResolveInfo是ResolverActivity类型,则弹出选择框,让用户选择是用调用哪个类的组件
