Intent知識詳解

Intent知識詳解

一、什麼是Intent

貼一個官方解釋:

An intent is an abstract description of an operation to be performed. It can be used with Context#startActivity(Intent) to launch an android.app.Activity , broadcastIntent to send it to any interested BroadcastReceiver components, and android.content.Context#startService or android.content.Context#bindService to communicate with a background android.app.Service .
An Intent provides a facility for performing late runtime binding between the code in different applications. Its most significant use is in the launching of activities, where it can be thought of as the glue between activities. It is basically a passive data structure holding an abstract description of an action to be performed.

Intent 名爲意圖,它是要執行操作的抽象描述,可以在activity、broadcast、service等組件進行請求操作時用來充當消息傳遞對象(intent可傳遞基礎類型數據或者可序列化的對象數據)此時Intent包含要執行的動作的抽象描述。

二、分類

Intent主要分爲兩大類:顯示Intent和隱式Intent。

顯示Intent

定義 :顯示的指定具體類名啓動一個組件 一般用於同應用內的組件啓動 因爲可以方便的知道啓動組件的類名

使用方式:1、通過Intent構造函數傳入要啓動類名
2、直接設置要啓動類名(調用setComponent(), setClass(), setClassName()傳入組件名)

隱式Intent

定義 :不知道要啓動的組件名,通過匹配其他組件中manifest文件的Intent-filter,啓動符合條件的組件,並把Intent中的參數傳過去,一般用於啓動外部應用的組件

使用方式:通過設置action、Category、data等去匹配符合條件的組件(注意:如果有多個intent-filter滿足條件,那麼系統會彈出一個對話框,由用戶決定啓動哪個組件 )

假如我們不希望其他應用啓動我們的組件,只希望在本應用中使用組件,那麼我們就不要在清單中聲明,並且將該組件的 exported 屬性設置爲 false

三、intent工作流程

以Activity A啓動Activity B爲例說明:
1、首先Activity A調用startActivity()並傳入的Intent
2、系統會根據該Intent的條件搜索Android系統中所有匹配的組件
3、若找到了匹配intent的intent-filters所屬的組件(Activity B),則啓動該組件,並回調onCreate()方法,同時將Intent傳遞過去

Intent

四、構建Intent

Intent的構建主要是爲其設置各種屬性包括:actiondatatypecomponentcategoryextrasflags。其中主要屬性是actiondata 。下面我們來詳細解析下每個屬性的意義和作用。

action

action是指要執行的動作。它很大程度上決定了category和data中應傳入的信息;除了官方定義的我們也可以自己定義action,以便讓其他應用程序啓動自己的組件。action可以通過setAction來設置或者在Intent構造函數中設置。

系統提供的常用Action:
public static final String ACTION_MAIN = “android.intent.action.MAIN”; //Android 的程序入口
public static final String ACTION_VIEW = “android.intent.action.VIEW”; //顯示指定數據
public static final String ACTION_WEB_SEARCH = “android.intent.action.WEB_SEARCH”;//網頁搜索關鍵字
public static final String ACTION_CALL = “android.intent.action.CALL”; //直接呼叫 Data 中所帶的號碼

data

data 屬性通常是爲 Action 屬性提供要操作的數據,Data 屬性的值是一個 Uri 對象,格式是:schema://host:port/path 。其各個字段含義如下:
* schema 協議 比如“http”、“https”、“tel”…
* host 主機名如“google.com”,如果定義爲“”則表示任意主機名
port 端口號
* path 路徑

可以通過setData設置data 。

type

Type 屬性用於指定 Data 所制定的 Uri 對應的MIME 類型。MIME 類型是設定某種擴展名的 文件用一種應用程序來打開的方式類型。常見MIME類型 : “image/png”、”image/jpeg”

可以通過setType設置 type。
但是要注意的是如果您需要同時設置URI和MIME類型,只能調用setDataAndType()方法,而不能分別調用setData()和setType(),因爲調用setData()時會首先將setType()中的內容置空,反之亦然。

component

component是要啓動目標組件的名字。對於顯式啓動,這是不可缺省的,沒有指定 ComponentName 屬性的 Intent 被稱爲隱式 Intent。

可以通過調用setComponent(), setClass(), setClassName()等方法設置或者通過Intent的構造方法設置。

category

category是要執行動作的目標所具有的特質或行爲歸類(爲 Action 增加額外的附加類別信息)
幾個常見的category如下:

Intent.CATEGORY_DEFAULT(android.intent.category.DEFAULT)// 默認的category

Intent.CATEGORY_PREFERENCE(android.intent.category.PREFERENCE) //表示該目標Activity是一個首選項界面;

Intent.CATEGORY_BROWSABLE(android.intent.category.BROWSABLE)//指定了此category後,在網頁上點擊圖片或鏈接時,系統會考慮將此目標Activity列入可選列表,供用戶選擇以打開圖片或鏈接。

可以通過setCategory來進行設置。

flags

flags爲intent添加元數據(meta-data),flag可以指導系統以何種方式啓動一個activity、是否將啓動的activity放在該應用的任務棧中,等等。
常用flags:

FLAG_ACTIVITY_NEW_TASK:設置這個標記位的話,是爲 Activity 指定 “singleTask” 啓動模式,它的作用和在清單文件中指定該啓動模式的效果一樣。
FLAG_ACTIVITY_SINGLE_TOP:設置這個標記位的話,是爲 Activity 指定 “singleTop” 啓動模式,它的作用和在清單文件中指定該啓動模式的效果一樣。
FLAG_ACTIVITY_CLEAR_TOP:具有此標記位的 Activity ,在它啓動時,在同一個任務棧中所有位於它上面的 Activity 都要出棧。
FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS:具有這個標記的 Activity 不會出現在歷史 Activity 的列表中。它等同於在清單文件中指定 Activity 的屬性 android:excludeFromRecents=“true”

可以通過setFlags來進行設置。

extras

extras用於添加一些附加信息,它的屬性值是一個 Bundle 對象,通過鍵值對的形式存儲數據。

需要注意的是在使用putExtras方法設置Bundle對象之後,系統進行的不是引用操作,而是複製操作,所以如果設置完之後再更改bundle實例中的數據,將不會影響Intent內部的附加信息。

可以通過putExtras來進行設置。

Intent屬性小結

Component name, action, data, and category代表了intent的屬性,通過設置這些參數,系統可以篩選出符合條件的目標組件。但是,Extras、Flags這兩個參數系統不會用來篩選目標組件。

五、IntentFilter匹配規則

IntentFilter是manifest文件中組件內部的一個標籤,該標籤描述了組件具備什麼特性,如果您未配置intent-filters,那個該組件只能被顯式啓動。我們在mainfest中設置的ntent-filters如果可以匹配某個隱式Intent那麼該組件就可以被啓動。IntentFilter在做匹配時主要是根據action, type, category這三個屬性且匹配優先級是:action>data>category

action匹配規則:

如果Intent指明定了action,則目標組件的IntentFilter的action列表中就必須包含有這個action,否則不能匹配。一個Intent Filter中可聲明多個action,此時Intent中的action與其中的任一個action在字符串形式上完全相同即可匹配成功。

特殊情況:
如果filter中沒有設置任何action 那麼所有的intent匹配都會失敗
如果action只和category組合使用(隱式調用的條件),intent中不指定action,那麼無法啓動目標組件
如果action和category、data組合使用,intent中不指定action但是filter中至少存在一個action 那麼是可以匹配成功的

category的匹配規則:

Intent-filter可定義零到多個category標籤intent中的定義的每一個category都需要匹配上intent-filter中的category標籤,反之不成立(intent-filter中的category標籤可能比intent中的定義的category多)。所以無論intent-filter中是否定義了category標籤,未添加category的intent總能匹配上該intent-filter。

注意:
通過startActivity()或startActivityForResult()方法隱式啓動的intent中,將自動被添加一個CATEGORY_DEFAULT的category,所以若您希望自己的activity能夠被隱式啓動,則需要在intent-filter中添加一個android.intent.category.DEFAULT的category標籤。

data匹配規則:

intent filter可定義零到多個data標籤每個data標籤都能設置mimeType和URI 結構,其中URI可分成四部分:scheme, host, port 和 path。但是是有一個線性依賴:若scheme 未指定,則host被忽略;若host未指定,則port被忽略;
若scheme和host均未指定,則path被忽略;

在intent中添加的data只需要匹配一部分intent-filter中的data:

  • 若filter只定義了scheme,則intent的data定義的URI中只要包含了相同的scheme,就能匹配;
  • 若filter只定義了scheme和host,則intent的data定義的URI中只要包含了相同的scheme和host,就能匹配;
  • 若filter只定義了scheme、host和port,則intent的data定義的URI中只要包含了相同的scheme、host和port,就能匹配

六、Intent源碼解析

Intent的源碼大約1W行其中對我們有用打大概有以下幾類:

構造函數

public Intent() {
}
public Intent(String action) {
    setAction(action);
}
public Intent(String action, Uri uri) {
    setAction(action);
    mData = uri;
}

Intent提供了8中構造函數供我們使用。

屬性的set和get方法

public @Nullable Uri getData() {
    return mData;
}
public @Nullable String getType() {
    return mType;
}
public @NonNull Intent setData(@Nullable Uri data) {
    mData = data;
    mType = null;
    return this;
}
public @NonNull Intent setType(@Nullable String type) {
    mData = null;
    mType = type;
    return this;
}
public @NonNull Intent setDataAndType(@Nullable Uri data, @Nullable String type) {
    mData = data;
    mType = type;
    return this;
}
//...

此處我們看到下setData/Type,單獨調用都會把對方置空,所以如果你想設置data和type需要調用setDataAndType,而不能先調用其中一個然後再調用另一個。
此外還有putExtra等相關函數這裏就不做分析了,有興趣的可自行查看源碼。
最後看一個平時我們並不常用的函數toUri:

public String toUri(@UriFlags int flags) {
    StringBuilder uri = new StringBuilder(128);
    if ((flags&URI_ANDROID_APP_SCHEME) != 0) {
        if (mPackage == null) {
            throw new IllegalArgumentException(
                    "Intent must include an explicit package name to build an android-app: "
                    + this);
        }
        uri.append("android-app://");
        uri.append(mPackage);
        String scheme = null;
        if (mData != null) {
            scheme = mData.getScheme();
            if (scheme != null) {
                uri.append('/');
                uri.append(scheme);
                String authority = mData.getEncodedAuthority();
                if (authority != null) {
                    uri.append('/');
                    uri.append(authority);
                    String path = mData.getEncodedPath();
                    if (path != null) {
                        uri.append(path);
                    }
                    String queryParams = mData.getEncodedQuery();
                    if (queryParams != null) {
                        uri.append('?');
                        uri.append(queryParams);
                    }
                    String fragment = mData.getEncodedFragment();
                    if (fragment != null) {
                        uri.append('#');
                        uri.append(fragment);
                    }
                }
            }
        }
        toUriFragment(uri, null, scheme == null ? Intent.ACTION_MAIN : Intent.ACTION_VIEW,
                mPackage, flags);
        return uri.toString();
    }
    String scheme = null;
    if (mData != null) {
        String data = mData.toString();
        if ((flags&URI_INTENT_SCHEME) != 0) {
            final int N = data.length();
            for (int i=0; i<N; i++) {
                char c = data.charAt(i);
                if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')
                        || c == '.' || c == '-') {
                    continue;
                }
                if (c == ':' && i > 0) {
                    // Valid scheme.
                    scheme = data.substring(0, i);
                    uri.append("intent:");
                    data = data.substring(i+1);
                    break;
                }

                // No scheme.
                break;
            }
        }
        uri.append(data);

    } else if ((flags&URI_INTENT_SCHEME) != 0) {
        uri.append("intent:");
    }

    toUriFragment(uri, scheme, Intent.ACTION_VIEW, null, flags);

    return uri.toString();
}

它的作用是把一個Intent轉化爲一個Uri,轉化後的Uri包含原先Intent的action, categories, type, flags, package, component, and extras等屬性。同時Intent還提供了getIntent方法把Uri轉換回Intent。
Intent轉換Uri過程就是將Intent的屬性值讀取出來進行拼接然後序列化。不過需要注意的是它沒有關於 Bundle 的參數傳遞,所以轉換過程會把設置的bundle數據丟失。
利用該函數我們可以不必在調用startActivity前去new Intent而是通過getIntent把一個Uri轉化爲Intent然後再startActivity,這樣做的好處是startActivity中傳入的intent變爲”可控”的。

七、相關問題

path、pathPrefix、pathPattern 之間的區別

path 用來匹配完整的路徑,如:http://example.com/blog/abc.html,這裏將 path 設置爲 /blog/abc.html 才能夠進行匹配;
pathPrefix 用來匹配路徑的開頭部分,拿上來的 Uri 來說,這裏將 pathPrefix 設置爲 /blog 就能進行匹配了;
pathPattern 用表達式來匹配整個路徑,這裏需要說下匹配符號與轉義。
匹配符號:
“” 用來匹配0次或更多,如:“a” 可以匹配“a”、“aa”、“aaa”…
“.” 用來匹配任意字符,如:“.” 可以匹配“a”、“b”,“c”…
因此 “.” 就是用來匹配任意字符0次或更多,如:“.html” 可以匹配 “abchtml”、“chtml”,“html”,“sdf.html”…

Intent傳遞數據的大小限制

intent傳遞過大的數據會導致TransactionTooLargeException,其本質原因是intent使用binder進行數據傳遞。在過程中Intent 中的數據,會作爲 Parcel 被存儲在 Binder 的事務緩衝區(Binder transaction buffer)中的對象進行傳輸。但是Binder 的事務緩衝區大小爲1M,並且該緩衝區是進程共享的。

解決方法:
1、避免傳遞過大數據
2、使用 EventBus 的粘性事件來解決

查詢是否有Activity可以匹配我們指定Intent的組件

在啓動Activity時傳入intent找不到符合條件的Activity那麼程序將會崩潰,所以我們每次startActivity時最好查詢下是否有Activity可以匹配我們指定Intent的組件,可以使用以下方法:

  • PackageManager的resolveActivity或者Intent的resolveActivity方法會獲得最適合Intent的一個Activity
  • 調用PackageManager的queryIntentActivities會返回所有成功匹配Intent的Activity

android.intent.action.MAIN 與android.intent.category.LAUNCHER、android.intent.category.HOME的區別

android.intent.action.MAIN

應用的入口即最先啓動的組件

android.intent.category.LAUNCHER

決定是否在桌面顯示圖標

android.intent.category.HOME

按住“HOME”鍵,該程序顯示在HOME列表裏

有多個匹配組件時如何設置每次都彈窗

當有多個應用可以響應我們的隱式 Activity 時,系統會彈出一個選擇框,讓用戶選擇需要打開的應用,用戶也可以選擇記住要自己打開的應用,這樣下次就不會再彈出選擇框。那麼假如我希望每次都彈窗,不讓用戶記住呢?我們可以使用 createChooser() 創建 Intent

參考文章:

  • https://mp.weixin.qq.com/s?__biz=MzIxNjc0ODExMA==&mid=2247484812&idx=1&sn=14886c84cff5b1bfea5b210a7e261672&chksm=97851cada0f295bbfc1b4bd970ca45988105a8e1e6ac7a498b99521b8261cfb06386b0f3d3dd&scene=38#wechat_redirect
  • https://mp.weixin.qq.com/s?__biz=MzA5MzI3NjE2MA==&mid=2650235926&idx=1&sn=58d03be2956944647df6a9719c90d13b&scene=38#wechat_redirect
  • https://blog.csdn.net/mynameishuangshuai/article/details/51673273
  • Android 基礎知識5:Intent 和 Intent 過濾器 - 掘金
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章