Android系统开发 Android10系统设置默认Launcher
<h1><span style="color: rgba(22, 145, 121, 1)">版本声明</span></h1><p>本文来自博客园,作者:观心静 ,转载请注明原文链接: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> </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> <application>
<activity
android:name=".ui.MainActivity"
android:exported="true"
android:launchMode="singleTask">
<intent-filter>
<!--隐藏图标 -->
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
<!-- 设置为 launcher -->
<category android:name="android.intent.category.HOME" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.rayland.home" />
</intent-filter>
</activity>
</application> </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/ 如下图</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<ResolveInfo> list = new ArrayList<ResolveInfo>();
//queryIntentActivities可以检索针对给定意图可以执行的所有活动。
list = pm.queryIntentActivities(intent, 0);
final int num = list.size();
ComponentName[] set = new ComponentName;
int bestMatch = 0;
for (int i = 0; i < num; i++) {
ResolveInfo r = list.get(i);
set = new ComponentName(r.activityInfo.packageName, r.activityInfo.name);
//match是一个整数,它表示了应用程序与目标IntentFilter的匹配程度。这个值是由系统根据ResolveInfo对象中的各种信息(如包名、类名、动作、类别等)与目标IntentFilter的匹配程度来计算的。
if (r.match > 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方法其实有参考来源的。 只要明白了这部分,以后的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<Intent> payloadIntents, Intent[] initialIntents,
List<ResolveInfo> 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 && !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 && 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 && 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 && (mSupportsAlwaysUseOption || mAdapter.hasFilteredItem())
&& 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<String> categories = filterIntent.getCategories();
if (categories != null) {
for (String cat : categories) {
filter.addCategory(cat);
}
}
filter.addCategory(Intent.CATEGORY_DEFAULT);
int cat = ri.match & 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 && 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())
&& !"content".equals(data.getScheme()))) {
filter.addDataScheme(data.getScheme());
// Look through the resolved filter to determine which part
// of it matched the original Intent.
Iterator<PatternMatcher> pIt = ri.filter.schemeSpecificPartsIterator();
if (pIt != null) {
String ssp = data.getSchemeSpecificPart();
while (ssp != null && pIt.hasNext()) {
PatternMatcher p = pIt.next();
if (p.match(ssp)) {
filter.addDataSchemeSpecificPart(p.getPath(), p.getType());
break;
}
}
}
Iterator<IntentFilter.AuthorityEntry> aIt = ri.filter.authoritiesIterator();
if (aIt != null) {
while (aIt.hasNext()) {
IntentFilter.AuthorityEntry a = aIt.next();
if (a.match(data) >= 0) {
int port = a.getPort();
filter.addDataAuthority(a.getHost(),
port >= 0 ? Integer.toString(port) : null);
break;
}
}
}
pIt = ri.filter.pathsIterator();
if (pIt != null) {
String path = data.getPath();
while (path != null && 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<N; i++) {
ResolveInfo r = mAdapter.mUnfilteredResolveList.get(i).getResolveInfoAt(0);
set = new ComponentName(r.activityInfo.packageName,
r.activityInfo.name);
if (r.match > bestMatch) bestMatch = r.match;
}
if (needToAddBackProfileForwardingComponent) {
set = mAdapter.mOtherProfile.getResolvedComponentName();
final int otherProfileMatch = mAdapter.mOtherProfile.getResolveInfo().match;
if (otherProfileMatch > 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() 这几个参数的来源</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]