广东老表 發表於 2023-11-30 16:26:00

Android系统开发 Android10系统设置默认Launcher

<h1><span style="color: rgba(22, 145, 121, 1)">版本声明</span></h1>
<p>本文来自博客园,作者:观心静&nbsp;,转载请注明原文链接:https://www.cnblogs.com/guanxinjing/p/17867429.html</p>
<div>本文版权归作者和博客园共有,欢迎转载,但必须给出原文链接,并保留此段声明,否则保留追究法律责任的权利。</div>
<h1><span style="color: rgba(22, 145, 121, 1)">前言</span></h1>
<p><span style="color: rgba(0, 0, 0, 1); background-color: rgba(255, 255, 255, 1)">  此博客讲解如何在Android10系统上,将自己的应用设置成默认Launcher。</span></p>
<p>&nbsp;</p>
<h1><span style="color: rgba(22, 145, 121, 1); background-color: rgba(255, 255, 255, 1)">第一步添加需要设置成Launcher的应用</span></h1>
<h2><span style="color: rgba(35, 111, 161, 1)">首先在需要成为Launcher的清单文件里添加如下关键</span></h2>
<p><strong>注意,</strong>要添加singleTask,否则会出现home键多次创建launchar 应用</p>
<pre class="language-xml highlighter-hljs" data-dark-theme="true"><code>   &lt;application&gt;
      &lt;activity
            android:name=".ui.MainActivity"
            android:exported="true"
            android:launchMode="singleTask"&gt;
            &lt;intent-filter&gt;
                &lt;!--隐藏图标   --&gt;
                &lt;action android:name="android.intent.action.MAIN" /&gt;
                &lt;category android:name="android.intent.category.LAUNCHER" /&gt;
                &lt;!-- 设置为 launcher --&gt;
                &lt;category android:name="android.intent.category.HOME" /&gt;
                &lt;category android:name="android.intent.category.DEFAULT" /&gt;
                &lt;category android:name="android.intent.category.rayland.home" /&gt;
            &lt;/intent-filter&gt;
      &lt;/activity&gt;
    &lt;/application&gt; </code></pre>
<h2><span style="color: rgba(35, 111, 161, 1)">将应用导入系统中</span></h2>
<p><span style="color: rgba(0, 0, 0, 1)">路径::~/aosp/packages/apps/&nbsp; &nbsp;如下图</span></p>
<p><span style="color: rgba(35, 111, 161, 1)"><img src="https://img2023.cnblogs.com/blog/1497956/202311/1497956-20231130153710152-1725984002.png"></span></p>
<p><span style="color: rgba(0, 0, 0, 1)">Android.mk 内容如下</span></p>
<pre class="language-bash highlighter-hljs" data-dark-theme="true"><code>
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
####
LOCAL_MODULE := Calligraphy
LOCAL_MODULE_CLASS := APPS
LOCAL_MODULE_TAGS := optional
LOCAL_MODULE_SUFFIX := $(COMMON_ANDROID_PACKAGE_SUFFIX)
LOCAL_PROPRIETARY_MODULE := true
LOCAL_CERTIFICATE := PRESIGNED
LOCAL_SRC_FILES := $(LOCAL_MODULE).apk
include $(BUILD_PREBUILT)</code></pre>
<h2><span style="color: rgba(35, 111, 161, 1); background-color: rgba(255, 255, 255, 1)">到这一步,我们可以先选择编译系统,然后看看效果</span></h2>
<p><span style="color: rgba(0, 0, 0, 1); background-color: rgba(255, 255, 255, 1)">这里可以看到这边启动了一个弹窗让你选择launcher。到这一步,我们就已经达到了一半的目的了</span></p>
<p><span style="color: rgba(35, 111, 161, 1); background-color: rgba(255, 255, 255, 1)"><img src="https://img2023.cnblogs.com/blog/1497956/202311/1497956-20231130154439755-939627017.png"></span></p>
<p><span style="color: rgba(0, 0, 0, 1); background-color: rgba(255, 255, 255, 1)">我们可以通过adb命令查看下这个弹窗,可以发现这个弹窗是叫一个ResolverActivity</span></p>
<p><span style="color: rgba(0, 0, 0, 1); background-color: rgba(255, 255, 255, 1)"><img src="https://img2023.cnblogs.com/blog/1497956/202311/1497956-20231130154537495-1203635799.png"></span></p>
<h1><span style="color: rgba(22, 145, 121, 1); background-color: rgba(255, 255, 255, 1)">第二步设置成默认Launcher</span></h1>
<p>上面我们已经获知launcher的选择弹窗是<span style="color: rgba(0, 0, 0, 1); background-color: rgba(255, 255, 255, 1)">ResolverActivity,现在目标很明确,就是修改<span style="color: rgba(0, 0, 0, 1); background-color: rgba(255, 255, 255, 1)">ResolverActivity这个类,让它自动就选择我们需要的launcher应用,然后快速finish掉自己,不在显示。</span></span></p>
<p><span style="background-color: rgba(255, 255, 255, 1)"><span style="background-color: rgba(255, 255, 255, 1)">ResolverActivity的</span>路径:/aosp/frameworks/base/core/java/com/android/internal/app/ResolverActivity.java</span></p>
<p><span style="color: rgba(0, 0, 0, 1); background-color: rgba(255, 255, 255, 1)"><span style="color: rgba(0, 0, 0, 1); background-color: rgba(255, 255, 255, 1)">在ResolverActivity添加如下代码(<strong>记得将中文注释删除,<span style="color: rgba(0, 0, 0, 1); background-color: rgba(255, 255, 255, 1)"><span style="color: rgba(0, 0, 0, 1); background-color: rgba(255, 255, 255, 1)">怕编译的时候不支持中文注释,</span></span>这里只是说明一下简单的理解</strong>):</span></span></p>
<pre class="language-java highlighter-hljs" data-dark-theme="true"><code>    //add:Setdefaultluncher
    private void setDefaultLauncher(String defPackageName, String defClassName) {
      try {
            final PackageManager pm = getPackageManager();
            Log.i("deflauncherxxz", "deflauncher : PackageName = " +
                  defPackageName + " ClassName = " + defClassName);
            //这里组装一个我们需要启动的launcher的IntentFilter,以提供PackageManager直接启动   
            IntentFilter filter = new IntentFilter();
            filter.addAction("android.intent.action.MAIN");
            filter.addCategory("android.intent.category.HOME");
            filter.addCategory("android.intent.category.DEFAULT");
            //这里创建一个intent并且添加Intent.ACTION_MAIN与Intent.CATEGORY_HOME,调用PackageManager的queryIntentActivities搜索符合的ResolveInfo列表数据
            //简单的来说就是希望搜索下系统中有CATEGORY_HOME(设备启动显示的第一个活动)的数据
            Intent intent = new Intent(Intent.ACTION_MAIN);
            intent.addCategory(Intent.CATEGORY_HOME);
            List&lt;ResolveInfo&gt; list = new ArrayList&lt;ResolveInfo&gt;();
            //queryIntentActivities可以检索针对给定意图可以执行的所有活动。
            list = pm.queryIntentActivities(intent, 0);
            final int num = list.size();
            ComponentName[] set = new ComponentName;
            int bestMatch = 0;
            for (int i = 0; i &lt; num; i++) {
                ResolveInfo r = list.get(i);
                set = new ComponentName(r.activityInfo.packageName, r.activityInfo.name);
                //match是一个整数,它表示了应用程序与目标IntentFilter的匹配程度。这个值是由系统根据ResolveInfo对象中的各种信息(如包名、类名、动作、类别等)与目标IntentFilter的匹配程度来计算的。
                if (r.match &gt; bestMatch) bestMatch = r.match;
            }
            //选择首选活动
            ComponentName preferredActivity = new ComponentName(defPackageName, defClassName);
            //将组合好的数据添加到首选活动中
            pm.addPreferredActivity(filter, bestMatch, set, preferredActivity);

      } catch (Exception e) {
            e.printStackTrace();
      }
    }</code></pre>
<p><span style="background-color: rgba(255, 255, 255, 1)">然后在</span>onCreate方法里以下图方式调用setDefaultLauncher</p>
<p><span style="color: rgba(0, 0, 0, 1); background-color: rgba(255, 255, 255, 1)"><span style="color: rgba(0, 0, 0, 1); background-color: rgba(255, 255, 255, 1)"><img src="https://img2023.cnblogs.com/blog/1497956/202311/1497956-20231130162548091-1287655660.png"></span></span></p>
<p><span style="color: rgba(0, 0, 0, 1); background-color: rgba(255, 255, 255, 1)"><span style="color: rgba(0, 0, 0, 1); background-color: rgba(255, 255, 255, 1)">然后编译,在make之前记得执行一下 make update-api, 因为我们更新了<span style="color: rgba(0, 0, 0, 1); background-color: rgba(255, 255, 255, 1)">ResolverActivity类中的</span>api</span></span></p>
<h1><span style="color: rgba(22, 145, 121, 1); background-color: rgba(255, 255, 255, 1)"><span style="background-color: rgba(255, 255, 255, 1)">分析一波</span></span></h1>
<p>这里分析一波,<span style="color: rgba(0, 0, 0, 1); background-color: rgba(255, 255, 255, 1)"><span style="color: rgba(0, 0, 0, 1); background-color: rgba(255, 255, 255, 1)"><span style="color: rgba(0, 0, 0, 1); background-color: rgba(255, 255, 255, 1)">ResolverActivity是如何执行的并且上面的setDefaultLauncher方法其实有参考来源的。&nbsp; 只要明白了这部分,以后的Android其他版本我们也能大致明白如何修改与实现。</span></span></span></p>
<p><span style="color: rgba(0, 0, 0, 1); background-color: rgba(255, 255, 255, 1)"><span style="color: rgba(0, 0, 0, 1); background-color: rgba(255, 255, 255, 1)"><span style="color: rgba(0, 0, 0, 1); background-color: rgba(255, 255, 255, 1)">首先<span style="color: rgba(0, 0, 0, 1); background-color: rgba(255, 255, 255, 1)"><span style="color: rgba(0, 0, 0, 1); background-color: rgba(255, 255, 255, 1)"><span style="color: rgba(0, 0, 0, 1); background-color: rgba(255, 255, 255, 1)">ResolverActivity方法是通过下面这个configureContentView方法配置需要显示的Launcher选择对话框内容的。</span></span></span></span></span></span></p>
<pre class="language-java highlighter-hljs" data-dark-theme="true"><code>    /**
   * Returns true if the activity is finishing and creation should halt
   */
    public boolean configureContentView(List&lt;Intent&gt; payloadIntents, Intent[] initialIntents,
            List&lt;ResolveInfo&gt; rList) {
      // The last argument of createAdapter is whether to do special handling
      // of the last used choice to highlight it in the list.We need to always
      // turn this off when running under voice interaction, since it results in
      // a more complicated UI that the current voice interaction flow is not able
      // to handle.
      mAdapter = createAdapter(this, payloadIntents, initialIntents, rList,
                mLaunchedFromUid, mSupportsAlwaysUseOption &amp;&amp; !isVoiceInteraction());
      boolean rebuildCompleted = mAdapter.rebuildList();

      if (useLayoutWithDefault()) {
            mLayoutId = R.layout.resolver_list_with_default;
      } else {
            mLayoutId = getLayoutResource();
      }
      setContentView(mLayoutId);

      int count = mAdapter.getUnfilteredCount();

      // We only rebuild asynchronously when we have multiple elements to sort. In the case where
      // we're already done, we can check if we should auto-launch immediately.
      if (rebuildCompleted) {
            if (count == 1 &amp;&amp; mAdapter.getOtherProfile() == null) {
                // Only one target, so we're a candidate to auto-launch!
                final TargetInfo target = mAdapter.targetInfoForPosition(0, false);
                if (shouldAutoLaunchSingleChoice(target)) {
                  safelyStartActivity(target);
                  mPackageMonitor.unregister();
                  mRegistered = false;
                  finish();
                  return true;
                }
            }
      }


      mAdapterView = findViewById(R.id.resolver_list);

      if (count == 0 &amp;&amp; mAdapter.mPlaceholderCount == 0) {
            final TextView emptyView = findViewById(R.id.empty);
            emptyView.setVisibility(View.VISIBLE);
            mAdapterView.setVisibility(View.GONE);
      } else {
            mAdapterView.setVisibility(View.VISIBLE);
            onPrepareAdapterView(mAdapterView, mAdapter);
      }
      return false;
    }</code></pre>
<p>在上面的代码中onPrepareAdapterView组织适配器方法下一步的关键</p>
<p><img src="https://img2023.cnblogs.com/blog/1497956/202311/1497956-20231130173732254-2001545534.png"></p>
<p>看看ItemClickListener点击监听</p>
<p><img src="https://img2023.cnblogs.com/blog/1497956/202311/1497956-20231130174011261-1992511280.png"></p>
<p>看看startSelected方法</p>
<p><img src="https://img2023.cnblogs.com/blog/1497956/202311/1497956-20231130174309333-655113895.png"></p>
<p>在下面的onTargetSelected方法中,就可以找到我们自己实现的<span style="color: rgba(0, 0, 0, 1); background-color: rgba(255, 255, 255, 1)"><span style="color: rgba(0, 0, 0, 1); background-color: rgba(255, 255, 255, 1)"><span style="color: rgba(0, 0, 0, 1); background-color: rgba(255, 255, 255, 1)">setDefaultLauncher<span style="color: rgba(0, 0, 0, 1); background-color: rgba(255, 255, 255, 1)"><span style="color: rgba(0, 0, 0, 1); background-color: rgba(255, 255, 255, 1)"><span style="color: rgba(0, 0, 0, 1); background-color: rgba(255, 255, 255, 1)">参考来源部分代码的。</span></span></span></span></span></span></p>
<pre class="language-java highlighter-hljs" data-dark-theme="true"><code>
    protected boolean onTargetSelected(TargetInfo target, boolean alwaysCheck) {
      final ResolveInfo ri = target.getResolveInfo();
      final Intent intent = target != null ? target.getResolvedIntent() : null;

      if (intent != null &amp;&amp; (mSupportsAlwaysUseOption || mAdapter.hasFilteredItem())
                &amp;&amp; mAdapter.mUnfilteredResolveList != null) {
            // Build a reasonable intent filter, based on what matched.
            IntentFilter filter = new IntentFilter();
            Intent filterIntent;

            if (intent.getSelector() != null) {
                filterIntent = intent.getSelector();
            } else {
                filterIntent = intent;
            }

            String action = filterIntent.getAction();
            if (action != null) {
                filter.addAction(action);
            }
            Set&lt;String&gt; categories = filterIntent.getCategories();
            if (categories != null) {
                for (String cat : categories) {
                  filter.addCategory(cat);
                }
            }
            filter.addCategory(Intent.CATEGORY_DEFAULT);

            int cat = ri.match &amp; IntentFilter.MATCH_CATEGORY_MASK;
            Uri data = filterIntent.getData();
            if (cat == IntentFilter.MATCH_CATEGORY_TYPE) {
                String mimeType = filterIntent.resolveType(this);
                if (mimeType != null) {
                  try {
                        filter.addDataType(mimeType);
                  } catch (IntentFilter.MalformedMimeTypeException e) {
                        Log.w("ResolverActivity", e);
                        filter = null;
                  }
                }
            }
            if (data != null &amp;&amp; data.getScheme() != null) {
                // We need the data specification if there was no type,
                // OR if the scheme is not one of our magical "file:"
                // or "content:" schemes (see IntentFilter for the reason).
                if (cat != IntentFilter.MATCH_CATEGORY_TYPE
                        || (!"file".equals(data.getScheme())
                              &amp;&amp; !"content".equals(data.getScheme()))) {
                  filter.addDataScheme(data.getScheme());

                  // Look through the resolved filter to determine which part
                  // of it matched the original Intent.
                  Iterator&lt;PatternMatcher&gt; pIt = ri.filter.schemeSpecificPartsIterator();
                  if (pIt != null) {
                        String ssp = data.getSchemeSpecificPart();
                        while (ssp != null &amp;&amp; pIt.hasNext()) {
                            PatternMatcher p = pIt.next();
                            if (p.match(ssp)) {
                              filter.addDataSchemeSpecificPart(p.getPath(), p.getType());
                              break;
                            }
                        }
                  }
                  Iterator&lt;IntentFilter.AuthorityEntry&gt; aIt = ri.filter.authoritiesIterator();
                  if (aIt != null) {
                        while (aIt.hasNext()) {
                            IntentFilter.AuthorityEntry a = aIt.next();
                            if (a.match(data) &gt;= 0) {
                              int port = a.getPort();
                              filter.addDataAuthority(a.getHost(),
                                        port &gt;= 0 ? Integer.toString(port) : null);
                              break;
                            }
                        }
                  }
                  pIt = ri.filter.pathsIterator();
                  if (pIt != null) {
                        String path = data.getPath();
                        while (path != null &amp;&amp; pIt.hasNext()) {
                            PatternMatcher p = pIt.next();
                            if (p.match(path)) {
                              filter.addDataPath(p.getPath(), p.getType());
                              break;
                            }
                        }
                  }
                }
            }

            if (filter != null) {
                final int N = mAdapter.mUnfilteredResolveList.size();
                ComponentName[] set;
                // If we don't add back in the component for forwarding the intent to a managed
                // profile, the preferred activity may not be updated correctly (as the set of
                // components we tell it we knew about will have changed).
                final boolean needToAddBackProfileForwardingComponent
                        = mAdapter.mOtherProfile != null;
                if (!needToAddBackProfileForwardingComponent) {
                  set = new ComponentName;
                } else {
                  set = new ComponentName;
                }

                int bestMatch = 0;
                for (int i=0; i&lt;N; i++) {
                  ResolveInfo r = mAdapter.mUnfilteredResolveList.get(i).getResolveInfoAt(0);
                  set = new ComponentName(r.activityInfo.packageName,
                            r.activityInfo.name);
                  if (r.match &gt; bestMatch) bestMatch = r.match;
                }

                if (needToAddBackProfileForwardingComponent) {
                  set = mAdapter.mOtherProfile.getResolvedComponentName();
                  final int otherProfileMatch = mAdapter.mOtherProfile.getResolveInfo().match;
                  if (otherProfileMatch &gt; bestMatch) bestMatch = otherProfileMatch;
                }

                if (alwaysCheck) {
                  final int userId = getUserId();
                  final PackageManager pm = getPackageManager();

                  // Set the preferred Activity
                  pm.addPreferredActivity(filter, bestMatch, set, intent.getComponent());

                  if (ri.handleAllWebDataURI) {
                        // Set default Browser if needed
                        final String packageName = pm.getDefaultBrowserPackageNameAsUser(userId);
                        if (TextUtils.isEmpty(packageName)) {
                            pm.setDefaultBrowserPackageNameAsUser(ri.activityInfo.packageName, userId);
                        }
                  }
                } else {
                  try {
                        mAdapter.mResolverListController.setLastChosen(intent, filter, bestMatch);
                  } catch (RemoteException re) {
                        Log.d(TAG, "Error calling setLastChosenActivity\n" + re);
                  }
                }
            }
      }

      if (target != null) {
            safelyStartActivity(target);

            // Rely on the ActivityManager to pop up a dialog regarding app suspension
            // and return false
            if (target.isSuspended()) {
                return false;
            }
      }

      return true;
    }</code></pre>
<p><span style="color: rgba(0, 0, 0, 1); background-color: rgba(255, 255, 255, 1)">上面的关键点是addPreferredActivity,然后可以看看 filter / bestMatch / set / intent.getComponent()&nbsp; 这几个参数的来源</span></p>
<p><span style="color: rgba(22, 145, 121, 1); background-color: rgba(255, 255, 255, 1)"><img src="https://img2023.cnblogs.com/blog/1497956/202311/1497956-20231130175501847-805668682.png"></span></p>
<p><span style="color: rgba(22, 145, 121, 1); background-color: rgba(255, 255, 255, 1)">end</span></p>

</div>
<div id="MySignature" role="contentinfo">
    <div style="text-align: center">
    <p style="color:orange;font-size:16px;" >本文来自博客园,作者:观心静 ,转载请注明原文链接:https://www.cnblogs.com/guanxinjing/p/17867429.html </p>
    <div style="color:orange;font-size:16px;">本文版权归作者和博客园共有,欢迎转载,但必须给出原文链接,并保留此段声明,否则保留追究法律责任的权利。 </div>
</div><br><br>
来源:https://www.cnblogs.com/guanxinjing/p/17867429.html
頁: [1]
查看完整版本: Android系统开发 Android10系统设置默认Launcher