Android系統開發 Android10系統設置默認Launcher

前言

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章