Android Intent 源碼學習

來源:互聯網
上載者:User

標籤:

前言    這篇文章主要是介紹一下Android Intent,並且從Android源碼的角度對Intent查詢匹配過程進行分析。
Intent介紹    Intent的中文是“意圖”的意思,而意圖是一個非常抽象的概念,那麼在Android的編碼設計中,如何執行個體化意圖呢?因此Android系統明確指定一個Intent可由兩方面屬性來衡量。
  • 主要屬性:包括Action和Data。其中Action用於表示該Intent所表達的動作意圖,Data用於表示該Action所操作的資料。
  • 次要屬性:包括Category、Type、Component和Extras。其中Category表示類別,Type表示資料的MIME類型,Component可用於指定特定的Intent的響應者(例如指定intent為某個包下的某個class類),Extras用於承載其他的資訊。
    Android系統中主要有兩種類型的Intent,顯示Intent(Explicit Intent)和隱式Intent(Implicit Intent)。
  • Explicit Intent:這類Intent明確指明了要找哪個Component。在代碼中可以通過setClassName或者setComponent來鎖定目標對象。
  • Implicit Intent:這類Intent不明確指明要啟動哪個Component,而是設定Action、Data、Category讓系統來篩選出合適的Component。
    接下來,寫兩個程式碼範例,來介紹一下Explicit Intent和Implict Inent。首先是Explicit Intent:
private void startExplicitIntentWithComponent() {Intent intent = new Intent();ComponentName component = new ComponentName("com.example.photocrop", "com.example.photocrop.MainActivity");intent.setComponent(component);startActivity(intent);}private void startExplicitIntentWithClassName() {Intent intent = new Intent();intent.setClassName("com.example.photocrop", "com.example.photocrop.MainActivity");startActivity(intent);}
    但是,從源碼裡面去看,發現setClassName也是藉助了ComponentName實現了Explicit Intent。源碼如下:
    public Intent setClassName(String packageName, String className) {        mComponent = new ComponentName(packageName, className);        return this;    }
    然後,在給出一個Implict Intent的程式碼範例。我這裡用一個Activity標註一些Intent Filter為例,然後在寫一個Intent用於啟動它。
        <activity             android:name=".SendIntentType">            <intent-filter >                <action android:name="justtest"/>                <category android:name="justcategory"/>            </intent-filter>        </activity>
    在當前應用的AndroidManifest.xml中,給SendIntentType類增加了intent-filter,action的名字為“justtest”,category的名字為“justcategory”。啟動該Activity的代碼如下:
private void startImplictIntent() {Intent intent = new Intent();intent.setAction("justaction");intent.addCategory("justcategory");startActivity(intent);}
    系統在匹配Implict Intent的過程中,將以Intent Filter列出的3項內容為參考標準,具體步驟如下:
  1. 首先匹配IntentFilter的Action,如果Intent設定的action不滿足IntentFilter的Action,則匹配失敗。如果IntentFilter未設定Action或者設定的Action相同,則匹配成功。
  2. 然後檢查IntentFilter的Category,匹配方法同Action的匹配相同,唯一例外的是當Category為CATEGORY_DEFAULT的情況。
  3. 最後檢查Data。

Activityi資訊的管理    從上面的分析可以看出,系統的匹配Intent的過程中,首先需要管理當前系統中所有Activity資訊。Activity的資訊是PackageManagerService在掃描APK的時候進行收集和管理的。相關源碼如下:
// 處理該package的activity資訊N = pkg.activities.size();r = null;for (i = 0; i < N; i++) {PackageParser.Activity a = pkg.activities.get(i);a.info.processName = fixProcessName(pkg.applicationInfo.processName, a.info.processName,pkg.applicationInfo.uid);mActivities.addActivity(a, "activity");}
    上面代碼中,有兩個比較重要的資料結構,如所示。    結合代碼和的資料結構,可知:
  • mAcitivitys為ActivityIntentResolver類型,是PKMS的成員變數,用於儲存系統中所有與Activity相關的資訊。此資料結構內部也有一個mActivities變數,它以ComponentName為key,儲存PackageParser.Activity對象。
  • 從APK中解析得到的所有和Acitivity相關的資訊(包括XML中聲明的IntentFilter標籤)都由PackageParser.Activity來儲存。
    前面代碼中調用addActivity函數完成了私人資訊的公有化。addActivity函數的代碼如下:
public final void addActivity(PackageParser.Activity a, String type) {final boolean systemApp = isSystemApp(a.info.applicationInfo);mActivities.put(a.getComponentName(), a);final int NI = a.intents.size();for (int j = 0; j < NI; j++) {PackageParser.ActivityIntentInfo intent = a.intents.get(j);if (!systemApp && intent.getPriority() > 0 && "activity".equals(type)) {// 非系統APK的priority必須為0intent.setPriority(0);}addFilter(intent);}}
    接下來看一下addFilter函數。函數源碼如下:
    public void addFilter(F f) {    // mFilters儲存所有IntentFilter資訊        mFilters.add(f);        int numS = register_intent_filter(f, f.schemesIterator(),                mSchemeToFilter, "      Scheme: ");        int numT = register_mime_types(f, "      Type: ");        if (numS == 0 && numT == 0) {            register_intent_filter(f, f.actionsIterator(),                    mActionToFilter, "      Action: ");        }        if (numT != 0) {            register_intent_filter(f, f.actionsIterator(),                    mTypedActionToFilter, "      TypedAction: ");        }    }
    這裡又出現了幾種資料結構,它們的類似都是ArrayMap<String, F[ ]>,其中F為模板參數。
  • mSchemeToFilter:用於儲存uri中與scheme相關的IntentFilter資訊。
  • mActionToFilter:用於儲存僅設定Action條件的IntentFilter資訊。
  • mTypedActionToFilter:用於儲存既設定了Action又設定了Data的MIME類型的IntentFilter資訊。
    瞭解了大概的資料結構之後,我們來看一下register_intent_filter的函數實現:
    private final int register_intent_filter(F filter, Iterator<String> i,            ArrayMap<String, F[]> dest, String prefix) {        if (i == null) {            return 0;        }        int num = 0;        while (i.hasNext()) {            String name = i.next();            num++;            addFilter(dest, name, filter);        }        return num;    }
    然後又是一個addFilter函數,明顯是一個函數重載,我們來看一下這個addFilter的實現:
    private final void addFilter(ArrayMap<String, F[]> map, String name, F filter) {        F[] array = map.get(name);        if (array == null) {            array = newArray(2);            map.put(name,  array);            array[0] = filter;        } else {            final int N = array.length;            int i = N;            while (i > 0 && array[i-1] == null) {                i--;            }            if (i < N) {                array[i] = filter;            } else {                F[] newa = newArray((N*3)/2);                System.arraycopy(array, 0, newa, 0, N);                newa[N] = filter;                map.put(name, newa);            }        }    }
    其實代碼還是很簡單的,如果F數組存在,則判斷容量,不夠則擴容,夠的話就找到位置插入。如果F數組不存在,則建立一個容量為2的數組,將0號元素賦值為該filter。
Intent匹配查詢分析    用戶端通過ApplicationPackageManager輸出的queryIntentActivities函數向PackageManagerService發起一次查詢請求,代碼如下:
    @Override    public List<ResolveInfo> queryIntentActivities(Intent intent,                                                   int flags) {        return queryIntentActivitiesAsUser(intent, flags, mContext.getUserId());    }    /** @hide Same as above but for a specific user */    @Override    public List<ResolveInfo> queryIntentActivitiesAsUser(Intent intent,                                                   int flags, int userId) {        try {            return mPM.queryIntentActivities(                intent,                intent.resolveTypeIfNeeded(mContext.getContentResolver()),                flags,                userId);        } catch (RemoteException e) {            throw new RuntimeException("Package manager has died", e);        }    }
    可以看到,queryIntentActivities的真正實現是在PackageManagerService.java中,函數代碼如下:
public List<ResolveInfo> queryIntentActivities(Intent intent, String resolvedType, int flags, int userId) {if (!sUserManager.exists(userId))return Collections.emptyList();enforceCrossUserPermission(Binder.getCallingUid(), userId, false, "query intent activities");ComponentName comp = intent.getComponent();if (comp == null) {if (intent.getSelector() != null) {intent = intent.getSelector();comp = intent.getComponent();}}if (comp != null) {// Explicit的Intent,直接根據component得到對應的ActivityInfofinal List<ResolveInfo> list = new ArrayList<ResolveInfo>(1);final ActivityInfo ai = getActivityInfo(comp, flags, userId);if (ai != null) {final ResolveInfo ri = new ResolveInfo();ri.activityInfo = ai;list.add(ri);}return list;}// readersynchronized (mPackages) {final String pkgName = intent.getPackage();if (pkgName == null) {// Implicit Intentreturn mActivities.queryIntent(intent, resolvedType, flags, userId);}final PackageParser.Package pkg = mPackages.get(pkgName);if (pkg != null) {// 指定了包名的Intentreturn mActivities.queryIntentForPackage(intent, resolvedType, flags, pkg.activities, userId);}return new ArrayList<ResolveInfo>();}}
    可以看到,Explicit Intent的實現較為簡單,我們重點來看一下Implict Intent實現。Implicit Intent調用了queryIntent方法,我們來看一下queryIntent的實現代碼:
public List<ResolveInfo> queryIntent(Intent intent, String resolvedType, int flags, int userId) {if (!sUserManager.exists(userId))return null;mFlags = flags;return super.queryIntent(intent, resolvedType, (flags & PackageManager.MATCH_DEFAULT_ONLY) != 0, userId);}
    繼續跟蹤到IntentResolver.java的queryIntent方法,源碼如下:
    public List<R> queryIntent(Intent intent, String resolvedType, boolean defaultOnly,            int userId) {        String scheme = intent.getScheme();        ArrayList<R> finalList = new ArrayList<R>();        // 最多有4輪匹配操作        F[] firstTypeCut = null;        F[] secondTypeCut = null;        F[] thirdTypeCut = null;        F[] schemeCut = null;        // If the intent includes a MIME type, then we want to collect all of        // the filters that match that MIME type.        if (resolvedType != null) {            int slashpos = resolvedType.indexOf('/');            if (slashpos > 0) {                final String baseType = resolvedType.substring(0, slashpos);                if (!baseType.equals("*")) {                    if (resolvedType.length() != slashpos+2                            || resolvedType.charAt(slashpos+1) != '*') {                        // Not a wild card, so we can just look for all filters that                        // completely match or wildcards whose base type matches.                        firstTypeCut = mTypeToFilter.get(resolvedType);                        secondTypeCut = mWildTypeToFilter.get(baseType);                    } else {                        // We can match anything with our base type.                        firstTypeCut = mBaseTypeToFilter.get(baseType);                        secondTypeCut = mWildTypeToFilter.get(baseType);                    }                    // Any */* types always apply, but we only need to do this                    // if the intent type was not already */*.                    thirdTypeCut = mWildTypeToFilter.get("*");                } else if (intent.getAction() != null) {                    // The intent specified any type ({@literal *}/*).  This                    // can be a whole heck of a lot of things, so as a first                    // cut let's use the action instead.                    firstTypeCut = mTypedActionToFilter.get(intent.getAction());                }            }        }        // If the intent includes a data URI, then we want to collect all of        // the filters that match its scheme (we will further refine matches        // on the authority and path by directly matching each resulting filter).        if (scheme != null) {            schemeCut = mSchemeToFilter.get(scheme);        }        // If the intent does not specify any data -- either a MIME type or        // a URI -- then we will only be looking for matches against empty        // data.        if (resolvedType == null && scheme == null && intent.getAction() != null) {            firstTypeCut = mActionToFilter.get(intent.getAction());        }        FastImmutableArraySet<String> categories = getFastIntentCategories(intent);        if (firstTypeCut != null) {            buildResolveList(intent, categories, debug, defaultOnly,                    resolvedType, scheme, firstTypeCut, finalList, userId);        }        if (secondTypeCut != null) {            buildResolveList(intent, categories, debug, defaultOnly,                    resolvedType, scheme, secondTypeCut, finalList, userId);        }        if (thirdTypeCut != null) {            buildResolveList(intent, categories, debug, defaultOnly,                    resolvedType, scheme, thirdTypeCut, finalList, userId);        }        if (schemeCut != null) {            buildResolveList(intent, categories, debug, defaultOnly,                    resolvedType, scheme, schemeCut, finalList, userId);        }        sortResults(finalList);        return finalList;    }
    具體的查詢匹配過程是由buildResolveList函數完成了。查詢的匹配實現我就不貼代碼了,大家自己去查詢看就好了。

參考文獻[1] 深入理解Android:卷二

Android Intent 源碼學習

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.