暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

Android源码分析之设置默认启动的Launcher

嵌入式软件开发交流 2018-08-24
1321
背景

    在Android启动过程中,系统中如果有多个Launcher时,会弹出一个对话框让我们选择哪个应用作为Launcher。所以我们来分析一下Launcher的启动过程。

    Launcher的启动过程首先是从ActivityManagerService的startHomeActivityLocked方法开始。所以先来看该方法。


源码分析
//Android6.0
   boolean startHomeActivityLocked(int userId, String reason) {
       //工厂测试模式
       if (mFactoryTest == FactoryTest.FACTORY_TEST_LOW_LEVEL
               && mTopAction == null) {
           // We are running in factory test mode, but unable to find
           // the factory test app, so just sit around displaying the
           // error message and don't try to start anything.
           return false;
       }
       //获取Home Intent
       Intent intent = getHomeIntent();
       //获取Activity信息
       ActivityInfo aInfo =
           resolveActivityInfo(intent, STOCK_PM_FLAGS, userId);
       if (aInfo != null) {
           intent.setComponent(new ComponentName(
                   aInfo.applicationInfo.packageName, aInfo.name));
           // Don't do this if the home app is currently being
           // instrumented.
           aInfo = new ActivityInfo(aInfo);
           //根据userId获取应用信息
           aInfo.applicationInfo = getAppInfoForUser(aInfo.applicationInfo, userId);
           //获取应用进程,每个android进程,在AMS中对应有一个ProcessRecord
           ProcessRecord app = getProcessRecordLocked(aInfo.processName,
                   aInfo.applicationInfo.uid, true);
           if (app == null || app.instrumentationClass == null) {
               //如果进程不存在,就要在启动时为该应用新建一个堆栈(FLAG_ACTIVITY_NEW_TASK)
               intent.setFlags(intent.getFlags() | Intent.FLAG_ACTIVITY_NEW_TASK);
               //启动Home Activity(Launcher)
               mStackSupervisor.startHomeActivity(intent, aInfo, reason);
           }
       }

       return true;
   }

接下来看ActivityStackSupervisor类中的startHomeActivity方法

//Android6.0
   void startHomeActivity(Intent intent, ActivityInfo aInfo, String reason) {
       //将Home Activity移动到栈顶
       moveHomeStackTaskToTop(HOME_ACTIVITY_TYPE, reason);
       //启动Home Activity
       startActivityLocked(null /* caller */, intent, null /* resolvedType */, aInfo,
               null /* voiceSession */, null /* voiceInteractor */, null /* resultTo */,
               null /* resultWho */, 0 /* requestCode */, 0 /* callingPid */, 0 /* callingUid */,
               null /* callingPackage */, 0 /* realCallingPid */, 0 /* realCallingUid */,
               0 /* startFlags */, null /* options */, false /* ignoreTargetSecurity */,
               false /* componentSpecified */,
               null /* outActivity */, null /* container */,  null /* inTask */);
       if (inResumeTopActivity) {
           // If we are in resume section already, home activity will be initialized, but not
           // resumed (to avoid recursive resume) and will stay that way until something pokes it
           // again. We need to schedule another resume.
           scheduleResumeTopActivities();
       }
   }

上面的过程如果只有一个Launcher时,会直接进入Launcher,但是如果有多个Launcher,会弹出一个Activity来让我们选择,这个Activity为ResolverActivity。

先看一下这个Activity的介绍:

/**
* This activity is displayed when the system attempts to start an Intent for
* which there is more than one matching activity, allowing the user to decide
* which to go to.  It is not normally used directly by application developers.
*/

上面的意思就是当Intent要启动(隐式)的Activity时,有多个Activity匹配这个Intent,就会显示这个Activity,让用户选择要启动哪个。其实就是一个应用选择器,这个Activity不单单是有多个Launcher时才会显示,其它应用也会。


接下就来看它的onCreate方法

@Override
   protected void onCreate(Bundle savedInstanceState) {
       // Use a specialized prompt when we're handling the 'Home' app startActivity()
       //当启动的是Launcher时会弹出一个专门的提示
       final Intent intent = makeMyIntent();
       final Set<String> categories = intent.getCategories();
       if (Intent.ACTION_MAIN.equals(intent.getAction())
               && categories != null
               && categories.size() == 1
               && categories.contains(Intent.CATEGORY_HOME)) {
           // Note: this field is not set to true in the compatibility version.
           mResolvingHome = true;
       }

       setSafeForwardingMode(true);

       onCreate(savedInstanceState, intent, null, 0, null, null, true);
   }

最后一行又调用另一个onCreate方法,继续来看这个方法

protected void onCreate(Bundle savedInstanceState, Intent intent,
           CharSequence title, int defaultTitleRes, Intent[] initialIntents,
           List<ResolveInfo> rList, boolean alwaysUseOption)
{
       setTheme(R.style.Theme_DeviceDefault_Resolver);
       super.onCreate(savedInstanceState);

       // Determine whether we should show that intent is forwarded
       // from managed profile to owner or other way around.
       setProfileSwitchMessageId(intent.getContentUserHint());

       try {
           //获取UID,Android支持多用户,不同用户可能Launcher不同
           mLaunchedFromUid = ActivityManagerNative.getDefault().getLaunchedFromUid(
                   getActivityToken());
       } catch (RemoteException e) {
           mLaunchedFromUid = -1;
       }

       if (mLaunchedFromUid < 0 || UserHandle.isIsolated(mLaunchedFromUid)) {
           // Gulp!
           finish();
           return;
       }
       //获取PackageManager
       mPm = getPackageManager();

       mPackageMonitor.register(this, getMainLooper(), false);
       mRegistered = true;
       //获取ActivityManager
       final ActivityManager am = (ActivityManager) getSystemService(ACTIVITY_SERVICE);
       //获取Launcher Icon的大小
       mIconDpi = am.getLauncherLargeIconDensity();

       // Add our initial intent as the first item, regardless of what else has already been added.
       mIntents.add(0, new Intent(intent));
       //获取推荐的包名
       final String referrerPackage = getReferrerPackageName();

       mResolverComparator = new ResolverComparator(this, getTargetIntent(), referrerPackage);
       //设置布局,类似于setContentView
       if (configureContentView(mIntents, initialIntents, rList, alwaysUseOption)) {
           return;
       }

       // Prevent the Resolver window from becoming the top fullscreen window and thus from taking
       // control of the system bars.
       //防止选择窗口变成全屏
       getWindow().clearFlags(FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR);
       //接下来就是初始化界面,设置监听器
       final ResolverDrawerLayout rdl = (ResolverDrawerLayout) findViewById(R.id.contentPanel);
       if (rdl != null) {
           rdl.setOnDismissedListener(new ResolverDrawerLayout.OnDismissedListener() {
               @Override
               public void onDismissed() {
                   finish();
               }
           });
           if (isVoiceInteraction()) {
               rdl.setCollapsed(false);
           }
           mResolverDrawerLayout = rdl;
       }

       if (title == null) {
           title = getTitleForAction(intent.getAction(), defaultTitleRes);
       }
       if (!TextUtils.isEmpty(title)) {
           final TextView titleView = (TextView) findViewById(R.id.title);
           if (titleView != null) {
               titleView.setText(title);
           }
           setTitle(title);

           // Try to initialize the title icon if we have a view for it and a title to match
           final ImageView titleIcon = (ImageView) findViewById(R.id.title_icon);
           if (titleIcon != null) {
               ApplicationInfo ai = null;
               try {
                   if (!TextUtils.isEmpty(referrerPackage)) {
                       ai = mPm.getApplicationInfo(referrerPackage, 0);
                   }
               } catch (NameNotFoundException e) {
                   Log.e(TAG, "Could not find referrer package " + referrerPackage);
               }

               if (ai != null) {
                   titleIcon.setImageDrawable(ai.loadIcon(mPm));
               }
           }
       }

       final ImageView iconView = (ImageView) findViewById(R.id.icon);
       final DisplayResolveInfo iconInfo = mAdapter.getFilteredItem();
       if (iconView != null && iconInfo != null) {
           new LoadIconIntoViewTask(iconInfo, iconView).execute();
       }

       if (alwaysUseOption || mAdapter.hasFilteredItem()) {
           final ViewGroup buttonLayout = (ViewGroup) findViewById(R.id.button_bar);
           if (buttonLayout != null) {
               buttonLayout.setVisibility(View.VISIBLE);
               //Always按钮
               mAlwaysButton = (Button) buttonLayout.findViewById(R.id.button_always);
               //Once按钮
               mOnceButton = (Button) buttonLayout.findViewById(R.id.button_once);
           } else {
               mAlwaysUseOption = false;
           }
       }

       if (mAdapter.hasFilteredItem()) {
           setAlwaysButtonEnabled(true, mAdapter.getFilteredPosition(), false);
           mOnceButton.setEnabled(true);
       }

       mProfileView = findViewById(R.id.profile_button);
       if (mProfileView != null) {
           mProfileView.setOnClickListener(new View.OnClickListener() {
               @Override
               public void onClick(View v) {
                   final DisplayResolveInfo dri = mAdapter.getOtherProfile();
                   if (dri == null) {
                       return;
                   }

                   // Do not show the profile switch message anymore.
                   mProfileSwitchMessageId = -1;

                   onTargetSelected(dri, false);
                   finish();
               }
           });
           bindProfileView();
       }

       if (isVoiceInteraction()) {
           onSetupVoiceInteraction();
       }

       getWindow().getDecorView().addOnAttachStateChangeListener(
               new OnAttachStateChangeListener() {
           @Override
           public void onViewAttachedToWindow(View v) {
               v.getViewRootImpl().setDrawDuringWindowsAnimating(true);
           }

           @Override
           public void onViewDetachedFromWindow(View v) {
           }
       });
   }

上面其实主要就是在初始化弹出的选择界面,设置监听器之类的。

接下来来看Always和once按钮的监听器

public void onButtonClick(View v) {
       //获取Button id
       final int id = v.getId();
       startSelected(mAlwaysUseOption ?
                       mAdapterView.getCheckedItemPosition() : mAdapter.getFilteredPosition(),
               id == R.id.button_always,
               mAlwaysUseOption);
   }

上面的监听器是在xml中设置onclick属性,这个方法就是调用了startSelected方法,用来启动所选的Launcher。

void startSelected(int which, boolean always, boolean filtered) {
       if (isFinishing()) {
           return;
       }
       //ResolveInfo是Activity信息的集合
       ResolveInfo ri = mAdapter.resolveInfoForPosition(which, filtered);
       if (mResolvingHome && hasManagedProfile() && !supportsManagedProfiles(ri)) {
           Toast.makeText(this, String.format(getResources().getString(
                   com.android.internal.R.string.activity_resolver_work_profiles_support),
                   ri.activityInfo.loadLabel(getPackageManager()).toString()),
                   Toast.LENGTH_LONG).show();
           return;
       }
       //获取要启动的Activity(Launcher)
       //TargetInfo包含了Activity的信息
       TargetInfo target = mAdapter.targetInfoForPosition(which, filtered);
       if (onTargetSelected(target, always)) {
           finish();
       }
   }

上面最关键的就是最后几行了,调用onTargetSelected方法,然后结束ResolverActivity。

protected boolean onTargetSelected(TargetInfo target, boolean alwaysCheck) {
       //获取Activity信息
       final ResolveInfo ri = target.getResolveInfo();
       final Intent intent = target != null ? target.getResolvedIntent() : null;
           ........

           if (filter != null) {
               final int N = mAdapter.mOrigResolveList.size();
               ComponentName[] set = new ComponentName[N];
               int bestMatch = 0;
               for (int i=0; i<N; i++) {
                   ResolveInfo r = mAdapter.mOrigResolveList.get(i).getResolveInfoAt(0);
                   set[i] = new ComponentName(r.activityInfo.packageName,
                           r.activityInfo.name);
                   if (r.match > bestMatch) bestMatch = r.match;
               }
               //always被选中
               if (alwaysCheck) {
                   final int userId = getUserId();
                   final PackageManager pm = getPackageManager();

                   // Set the preferred Activity
                   //设置首选的Activity
                   pm.addPreferredActivity(filter, bestMatch, set, intent.getComponent());
                   //省略
                   ........
               } else {
                   //省略
                 ........
               }
           }
       }

       if (target != null) {
           safelyStartActivity(target);
       }
       return true;
   }

上面最重要的就是pm.addPreferredActivity(filter, bestMatch, set, intent.getComponent());这行代码,它将某个Launcher设置默认Launcher,这样以后启动后就优先选择这个。


所以假如我们要在Settings中加一个选项来设置默认Launcher,那就是使用PackageManager中的addPreferredActivity方法即可。


Android中的偏好设置会被保存在用户的目录下,比如:data/system/users/0/package-restrictions.xml



上面贴了很多源码,其实我们在看源码时要站在宏观的角度去看,要看到它的一个大概流程不要去抠每一行代码的意思。只要到真正需要去修改某一个东西时,我们才需要去关注它具体的实现。

文章转载自嵌入式软件开发交流,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论