版本声明
本文来自博客园,作者:观心静 ,转载请注明原文链接:https://www.cnblogs.com/guanxinjing/p/17867429.html
前言
此博客讲解如何在Android10系统上,将自己的应用设置成默认Launcher。
第一步添加需要设置成Launcher的应用
首先在需要成为Launcher的清单文件里添加如下关键
注意,要添加singleTask,否则会出现home键多次创建launchar 应用
<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>
将应用导入系统中
路径::~/aosp/packages/apps/ 如下图
Android.mk 内容如下
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)
到这一步,我们可以先选择编译系统,然后看看效果
这里可以看到这边启动了一个弹窗让你选择launcher。到这一步,我们就已经达到了一半的目的了
我们可以通过adb命令查看下这个弹窗,可以发现这个弹窗是叫一个ResolverActivity
第二步设置成默认Launcher
上面我们已经获知launcher的选择弹窗是ResolverActivity,现在目标很明确,就是修改ResolverActivity这个类,让它自动就选择我们需要的launcher应用,然后快速finish掉自己,不在显示。
ResolverActivity的路径:/aosp/frameworks/base/core/java/com/android/internal/app/ResolverActivity.java
在ResolverActivity添加如下代码(记得将中文注释删除,怕编译的时候不支持中文注释,这里只是说明一下简单的理解):
//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[num];
int bestMatch = 0;
for (int i = 0; i < num; i++) {
ResolveInfo r = list.get(i);
set[i] = 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();
}
}
然后在onCreate方法里以下图方式调用setDefaultLauncher
然后编译,在make之前记得执行一下 make update-api, 因为我们更新了ResolverActivity类中的api
分析一波
这里分析一波,ResolverActivity是如何执行的并且上面的setDefaultLauncher方法其实有参考来源的。 只要明白了这部分,以后的Android其他版本我们也能大致明白如何修改与实现。
首先ResolverActivity方法是通过下面这个configureContentView方法配置需要显示的Launcher选择对话框内容的。
/**
* 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;
}
在上面的代码中onPrepareAdapterView组织适配器方法下一步的关键
看看ItemClickListener点击监听
看看startSelected方法
在下面的onTargetSelected方法中,就可以找到我们自己实现的setDefaultLauncher参考来源部分代码的。
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[N];
} else {
set = new ComponentName[N + 1];
}
int bestMatch = 0;
for (int i=0; i<N; i++) {
ResolveInfo r = mAdapter.mUnfilteredResolveList.get(i).getResolveInfoAt(0);
set[i] = new ComponentName(r.activityInfo.packageName,
r.activityInfo.name);
if (r.match > bestMatch) bestMatch = r.match;
}
if (needToAddBackProfileForwardingComponent) {
set[N] = 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;
}
上面的关键点是addPreferredActivity,然后可以看看 filter / bestMatch / set / intent.getComponent() 这几个参数的来源
end
本文来自博客园,作者:观心静 ,转载请注明原文链接:https://www.cnblogs.com/guanxinjing/p/17867429.html