前言
此博客講解如何在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