1. 程式人生 > >Pro Android學習筆記(三)——Intent

Pro Android學習筆記(三)——Intent

Intent

簡介

Android中使用Intent來呼叫元件,Android中的元件包括Activity,Service,Broadcast Receiver,Content Provider。Android將多種理念融入到了Intent的概念中。可以使用Intent從一個應用程式中呼叫外部應用程式,可以使用Intent從應用程式呼叫內部或者外部元件,可以使用Intent觸發時間,可以使用Intent發出警報等等。由上述可知,intent是具有相關資料負載的操作。

簡單來說,Intent是你可以告訴Android要執行(或呼叫)的一種操作。Android呼叫的操作取決於該操作所註冊的內容。

例如,編寫一個Activity:BasicViewActivity

public class BasicViewActivity extends Activity {

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.basic_view);
    }

}

basic_view佈局指向了/res/layout/目錄下的佈局檔案。我們可以在應用程式的描述檔案中註冊此活動,使其可以被其他應用程式呼叫。

註冊程式碼如下:

<activity android:name=".BasicViewActivity"
            android:label="Basic View Tests" >
            <intent-filter>
                <action android:name="com.zxn.intent.action.ShowBasicView" />
                <category android:name="android.intent.category.DEFAULT" />
            </intent-filter>
</activity>

註冊之後我們就可以使用Intent來呼叫此BasciViewActivity

public static void invokeBasicActivity(Activity activity) {
        String actionName = "com.zxn.intent.action.ShowBasicView";
        Intent intent = new Intent(actionName);
        activity.startActivity(intent);
    }

Android中可用的Intent

以上是我們使用Intent啟動另外一個元件的過程,也是最基本的用法。我們也可以使用Intent啟動Android一些自帶的程式。

例如:

public class IntentsUtils {

    // 使用瀏覽器開啟一個uri
    public static void invokeWebBrowser(Activity activity) {
        Intent intent = new Intent(Intent.ACTION_VIEW);
        intent.setData(Uri.parse("http://www.baidu.com"));
        activity.startActivity(intent);
    }

    public static void invokeWebSearch(Activity activity) {
        Intent intent = new Intent(Intent.ACTION_WEB_SEARCH);
        intent.setData(Uri.parse("http://www.baidu.com"));
        activity.startActivity(intent);
    }

    // 開啟撥號介面
    public static void dial(Activity activity) {
        Intent intent = new Intent(Intent.ACTION_DIAL);
        activity.startActivity(intent);
    }

    // 撥打一個電話
    public static void call(Activity activity) {
        Intent intent = new Intent(Intent.ACTION_CALL);
        intent.setData(Uri.parse("tel:904-905-5646"));
        activity.startActivity(intent);
    }

    // 使用一個地圖程式開啟指定位置
    public static void showMapAtLatLong(Activity activity) {
        Intent intent = new Intent(Intent.ACTION_VIEW);
        intent.setData(Uri.parse("geo:0,0?z=4&q=business+near+city"));
        activity.startActivity(intent);
    }

    // 啟動某個應用獲取返回的資料
    public static void invokePick(Activity activity) {
        Intent pickIntent = new Intent(Intent.ACTION_PICK);
        pickIntent.setData(Uri.parse("content://com.google.provider.NotePad/notes"));
        activity.startActivityForResult(pickIntent, 1);
    }

    // 啟動某個應用程式獲取返回的資料
    public  static void invokeGetContent(Activity activity) {
        Intent pickIntent = new Intent(Intent.ACTION_GET_CONTENT);
        pickIntent.setType("vnd.android.cursor.item/vnd.google.note");
        activity.startActivityForResult(pickIntent, 2);
    }

}
建立一個簡單的選單,以便我們呼叫上面這些程式碼。如下:

public class MainActivity extends ActionBarActivity {

    private final static String tag = "MainActivity";

    // Initialize this in onCreateOptions
    Menu myMenu = null;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        this.setupButton();
        this.setupEditText();
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        super.onCreateOptionsMenu(menu);
        this.myMenu = menu;
        getMenuInflater().inflate(R.menu.menu_main, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        try {
            handleMenus(item);
        } catch (Throwable t) {
            Log.d(tag, t.getMessage(), t);
            throw new RuntimeException("error", t);
        }
        return true;
    }

    private void handleMenus(MenuItem item) {
        this.appendMenuItemText(item);
        if(item.getItemId() == R.id.menu_clear) {
            this.emptyText();
        } else if(item.getItemId() == R.id.menu_basic_view) {
            IntentsUtils.invokeBasicActivity(this);
        } else if(item.getItemId() == R.id.menu_show_browser) {
            IntentsUtils.invokeWebBrowser(this);
        } else if(item.getItemId() == R.id.menu_dial) {
            IntentsUtils.dial(this);
        } else if(item.getItemId() == R.id.menu_call) {
            IntentsUtils.call(this);
        } else if(item.getItemId() == R.id.menu_map) {
            IntentsUtils.showMapAtLatLong(this);
        } else if(item.getItemId() == R.id.menu_testPick) {
            IntentsUtils.invokePick(this);
        } else if(item.getItemId() == R.id.menu_testGetContent) {
            IntentsUtils.invokeGetContent(this);
        }
    }

    private TextView getTextView() {
        TextView tv = (TextView)this.findViewById(R.id.textViewId);
        return tv;
    }

    public void appendText(String text) {
        TextView tv = (TextView)this.findViewById(R.id.textViewId);
        tv.setText(tv.getText() + text);
    }

    public void appendMenuItemText(MenuItem menuItem) {
        String title = menuItem.getTitle().toString();
        TextView tv = (TextView)this.findViewById(R.id.textViewId);
        tv.setText(tv.getText() + "\n" + title + ":" + menuItem.getItemId());
    }

    private void emptyText() {
        TextView tv = (TextView)this.findViewById(R.id.textViewId);
        tv.setText("");
    }

    private void dial() {
        Intent intent = new Intent(Intent.ACTION_DIAL);
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        this.startActivity(intent);
    }

    private void setupButton() {
        Button b = (Button)this.findViewById(R.id.button1);
        b.setOnClickListener(new Button.OnClickListener() {
            public void onClick(View v) {
                parentButtonClicked(v);
            }
        });
    }

    private void parentButtonClicked(View v) {
        this.appendText("\nbutton clicked");
        this.dialUsingEditText();
    }

    private void dialWithNumber(String tel) {
        String telUriString = "tel:" + tel;
        Log.d(tag, telUriString);
        Intent intent = new Intent(Intent.ACTION_DIAL);
        intent.setData(Uri.parse(telUriString));
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        this.startActivity(intent);
    }

    private void dialUsingEditText() {
        EditText etext = (EditText)this.findViewById(R.id.editTextId);
        String text = etext.getText().toString();
        if(PhoneNumberUtils.isGlobalPhoneNumber(text) == true) {
            dialWithNumber(text);
        }
    }

    private EditText getEditText() {
        EditText etext = (EditText)this.findViewById(R.id.editTextId);
        return etext;
    }

    private void setupEditText() {
        EditText etext = this.getEditText();
        etext.addTextChangedListener(new PhoneNumberFormattingTextWatcher());
    }

    protected void onActivityResult(int requestCode, int resultCode, Intent outputIntent) {
        super.onActivityResult(requestCode, resultCode, outputIntent);
        IntentsUtils.parseResult(this, requestCode, resultCode, outputIntent);
    }
}

Intent的組成

另一種確定Intent用途的方式是檢視Intent物件包含的內容。Intent包含操作、資料URI、extra資料元素的鍵值對映,以及一個顯示類名。

1.Intent和資料URI

我們在Intent中可以新增名為data的引數,這個引數指向一個URI,根據URI的不同,從而開啟不同的介面。 舉個栗子:開啟一個指定電話的撥號頁面。那麼我們不僅要指定傳遞intent的操作,還要指定intent的資料。程式碼如下:
    public static void call(Activity activity) {
        Intent intent = new Intent(Intent.ACTION_CALL);
        intent.setData(Uri.parse("tel:904-905-5646"));
        activity.startActivity(intent);
    }
以上程式碼中,Intent.ACTION_CALL是我們要執行的動作,開啟撥號介面。下面的setData是為該Intent設定資料,這裡的資料並非是真正的資料,而是指向資料的指標。資料部分是一個表示URI的字串,這個URI包含可被推斷的資料。

2.一般操作

在這裡,我們需要注意一點,操作和資料並非是一一對應的關係,在以上例子中,我們執行Intent.ACTION_CALL傳輸的資料是一個電話號碼,在以下例子中並非是這樣。
public static void invokeWebBrowser(Activity activity) {
        Intent intent = new Intent(Intent.ACTION_VIEW);
        intent.setData(Uri.parse("http://www.baidu.com"));
        activity.startActivity(intent);
    }
這個程式只是僅僅開啟一個view,並沒有告訴系統開啟一個瀏覽器,Android如何知道該呼叫什麼樣的Activity來響應Intent呢?在這種情況下,Android不僅依賴於一般操作,還依賴於URI的性質。如何區分不同的data呢?這個時候就需要Intent-flater了,intent過濾器,顧名思義,就是對傳遞過來的intent進行過濾,從而得出我們想要的。來看看我們這個Activity所註冊的描述資訊:
<activity......>
<span style="white-space:pre">	</span><intent-filter>
	<span style="white-space:pre">	</span><action android:name="android.intent.action.VIEW" />
	<span style="white-space:pre">	</span><data android:scheme="http" />
	<span style="white-space:pre">	</span><data android:scheme="https" />
<span style="white-space:pre">	</span></intent-filter>
</activity>
在intent-filter中,我們註冊了一個action和data,分別對應操作和資料,action中的“android.intent.action.VIEW“在Intent檔案中對應的就是ACTION_VIEW
Intent.java:
public static final String ACTION_VIEW = "android.intent.action.VIEW";
data部分我們定義了android:scheme的屬性為http和https。這樣intent-filter就能過濾請求為http和https的URI。 intent-filter的data子節點的子元素和特性包括:host、mimeType、path、pathPattern、pathPrefix、port、scheme。具體每種的意義可以參考android官網。 mimeType型別是一個經常用到的型別。例如:我們可以檢視一組筆記:
<intent-filter>
	<action android:name="android.intent.action.VIEW" />
	<data android:mimeType="vnd.android.cursor.dir/vnd.google.note" />
<intent-filter>
也可以檢視單個筆記
<intent-filter>
	<action android:name="android.intent.action.VIEW" />
	<data android:mimeType="vnd.android.cursor.item/vnd.google.note" />
<intent-filter>

3.使用extra資訊

除了操作和資料外,intent還包含extra額外資訊,extra可以向intent提供更多的資訊。extra以鍵值對的形式表示,鍵名稱通常是以包名開頭,值可以是任何基本的資料型別或任意物件,只要他實現了andorid.os.pracelable介面即可。extra使用andorid.os.bundle表示。 可以使用以下方法訪問extra bundle
// Get the Bundle from an Intent
Bundle extraBundle = intent.getExtras();

// Place a bundle in an intent
Bundle anotherBundle = new Bundle();

// populate the bundle with key/value pairs
...
// and then set the bundle on the Intent
intent.putExtras(anotherBudnle);
我們可以使用以下方法向extra新增資料:
putExtra(String name, boolean value);
putExtra(String name, int value);
putExtra(String name, double value);
putExtra(String name, String value);
putExtra(String name, int[] values);
putExtra(String name, float[] values);
putExtra(String name, Serializable value);
putExtra(String name, Parcelable value);
putExtra(String name, Bundle value);
putExtra(String name, Intent anotherIntent);
putIntegerArrayListExtra(String name, ArrayList arrayList);
putParcelableArrayListExtra(String name, ArrayList arrayList);
putStringArrayListExtra(String name, ArrayList arrayList);

4.使用元件直接呼叫活動

我們可以直接指定Activity的ComponentName來訪問Activity。具體如下:
setComponent(ComponentName name);
setClassName(String packageName, String classNameInThatPackage);
setClassName(Context context, String classNameInThatContext);
setClass(Context context, Class classObjectInThatContext);
ComponentName將一個包名和類名包裝在一起。例如,以下程式碼呼叫系統contacts活動:
Intent intent = new Intent();
intent.setComponent(new ComponentName("com.andorid.contacts", "com.android.aontacts.DialContactsEntryActivity"));
startActivity(intent);
也可以直接使用類名,不構造ComponentName,例如:
Intent directIntent = new Intent(activity, BasicViewActivity.class);
activity.start(directIntent);
別忘記,在AndroidManifest.xml中註冊Activity
<activity android:name=".BasicViewActivity"
	android:label="Test Activity">
這裡我們不需要intent-filter,因為這種型別的Intent是顯式Intent,顯式Intent指定了一個完全限定的Android元件,所以會忽略掉該Intent的其他部分。

5.Intent類別

可以將活動分為不同的類別,以便根據類別名來搜尋。比如,我們定義一個啟動頁面:
<activity
<span style="white-space:pre">	</span>android:name=".MainActivity"
        android:label="@string/app_name" >
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />
            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
</activity>
其中android.intent.category.LAUNCHER則將這個頁面定義為啟動頁面。 更多類別可以訪問android官網檢視: http://developer.android.com/intl/zh-cn/reference/android/content/Intent.html。 之後我們就可以使用一下程式碼進行訪問:
Intent mainIntent = new Intent(Intent.ACTION_MAIN, null);
mainIntent.adCategory(Intent.CATEGORY_LAUNCHER);
PackageManager pm = getPackageManager();
List<ResolveInfo> list = pm.queryIntentActivities(mainIntent, 0);
藉助於PackagerManager就可以找到與Intent相匹配的活動。

6.Intent解析規則

Android使用多種策略,基於Intent過濾器來將Intent和他們的目標活動相匹配。Intent分為顯式和隱式。如果設定了名稱,則這個Intent就是顯式Intent,對於顯式Intent,重要的是元件名稱,Intent的所有其他方面或者特性都會被忽略。如果沒有指定名稱的Intent就是隱式Intent,解析隱式Intent目標的規則非常多。 基本規則是,傳的Intent的操作,類別和資料特徵必須匹配Intent過濾器中指定的特徵。與Intent不同,一個Intent過濾器可以指定多個操作,類別和資料特徵。這意味著一個Intent過濾器可以滿足多個Intent的需求,也就是說一個活動響應多個Intent。但是,“匹配”的含義在操作,資料特徵和類別之間各不相同。我們看看隱式Intent的每部分匹配條件。 1.操作 如果Intent有一個操作,Intent過濾器必須將這個操作新增到操作列表,或者不包含任何操作。如果Intent過濾器沒有定義任何操作,則該Intent過濾器可以匹配所有傳入的Intent操作。相應,如果Intent過濾器中定義了一個或多個操作,則至少一個操作與傳入的Intent操作匹配。 2.資料 如果Intent中沒有指定資料型別,他將不匹配包含任何資料或者資料特徵的傳入的Intent,他將僅查詢沒有指定資料型別的Intent。 在過濾器中,缺少資料和缺少操作的情況是相反的。如果過濾器沒有操作,將匹配所有內容。如果過濾器中沒有資料,將都不匹配。 3.資料型別 要匹配一種資料型別,傳入Intent的資料型別必須是Intent過濾器中指定的資料型別之一。Intent中的資料型別必須存在於Intent過濾器中。 傳入Intent的資料型別可以通過以下兩種方式來確定。第一種:如果資料URI是一個內容或者檔案URI,ContentProvider或者Android將確定型別。第二種:檢視 Intent的顯式資料型別,這種方式要生效,傳入的Intent不應該設定資料URI,因為當對Intent呼叫setType()是會自動設定他。 作為MIME類型範圍的一部分,Android還支援使用星號(*)作為子型別來涵蓋所有可能的子型別。 4.資料模式 要匹配資料模式,傳入Intent的資料模式必須是Intent過濾器中的指定的模式之一,換句話說,傳入的資料模式必須存在於Intent過濾器中。 5.資料授權 如果過濾器中沒有資料授權,則可以匹配任何傳入的資料URI的授權,如果在過濾器中指定了授權,那麼一種模式和授權應該與傳入的Intent的資料URI想匹配。 6.資料路徑 如果過濾器中沒有資料路徑,則可以匹配任何傳入的資料URI的路徑,如果在過濾器中指定了路徑,那麼一種模式,授權和路徑應該與傳入的Intent的資料URI想匹配。 換句話說,模式,授權和路徑協同驗證傳入Intent的URI,所以path、authority和scheme不是孤立工作的,而是協同工作的。