Android Intent 教程

來源:互聯網
上載者:User

標籤:top   his   protected   一件事   開發   搜尋   事先   螢幕   default   

原文:Android: Intents Tutorial
Darryl Bayliss
譯者:kmyhy

人不會漫無目的地瞎逛,他們所做的大部分事情——比如看電視、購物、編寫下一個殺手級 app —— 都帶有特定的目的或者意圖,即 intent。

Android 也是同樣的。在一個 app 幹某件事情之前,它需要知道這件事情的目的或 intent,才能正確地完成整件事情。
這說明人和 Android 並無不同。
在本文,你將利用 Intent 去建立一個模因軟體(一種用於惡搞的圖片製作軟體,在國外非常流行,可以利用這款軟體來簡單的PS之後來黑別人或自娛自樂)。通過這個 demo,你可以學到:

  • Intent 是什麼,以及它在 Android 中廣泛用途。
  • 如何用 Intent 建立和檢索其它 app 中的內容並使用到你的 app 中。
  • 如何接收和響應一個別的 app 發來的 Intent。

如果你是一個新手,極度建議你閱讀Android Tutorial for Beginners教程,以瞭解最基本的工具和概念。

準備好要模因的照片。本教程將你的 Android 開發技能提升到 9000 +以上!

開始

從這裡下載開始項目。

在開始項目中,你會找到 XML 布局和相關的 activity、一些模式化的代碼、用於展開位元影像的助手類、以及後面會用到的一些資源如 Drawable 和 String 等。

如果你已經開啟了 Android Studio,點擊 File\Import Project 然後選擇你所下載的開始項目的最上層目錄。 否則,請開啟 Android Studio 並從歡迎屏中選擇 Open an existing Android Studio project,並選擇開始項目的最上層目錄。

花點時間瀏覽下開始項目。TakePictureActivity 包含了一個 ImageView,點擊它會開啟裝置網路攝影機。當你點擊 LETS MEMEIFY!,你會將 ImageView 中位元影像的檔案路徑傳遞給 EnterTextActivity,這個 activity 中真正的樂子開始了,你可以在這裡輸入一段惡搞的文本並將你的照片轉換成下一個病毒式的模因!

建立第一個 Intent

運行 app。你會看到:

現在還沒有什麼功能,如果你根據提示去做並點擊 ImageView,什麼也不會發生!

你會添加一些代碼讓事情變得有趣些。

開啟 TakePictureActivity.java 在類中加入常量定義:

private static final int TAKE_PHOTO_REQUEST_CODE = 1;

這個常量用於在返回時唯一標識你的 intent——後面你就知道了。

注意:本文假設你會解決類未引入警告,不需要專門說明需要引入某個類。簡單提示一下,如果你忘記引入某個類,你可以將滑鼠放在提示“類未引入警告”的類上,按 alt+斷行符號鍵來匯入它。

在 onClick() 方法下添加如下代碼,請自行添加必要的 import 語句:

private void takePictureWithCamera() {  // 建立一個 intent 用於從網路攝影機拍照  Intent captureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);  File photoFile = createImageFile();  selectedPhotoPath = Uri.parse(photoFile.getAbsolutePath());  captureIntent.putExtra(android.provider.MediaStore.EXTRA_OUTPUT, Uri.fromFile(photoFile));  startActivityForResult(captureIntent, TAKE_PHOTO_REQUEST_CODE);}

這個方法的代碼有點多,因此分成幾步來添加代碼。
第一行聲明了一個 Intent 對象。說起來簡單,但那究竟是啥子意思呢?

一個 Intent 是一個任務或功能的抽象,它會被 app 在未來某個時刻執行。簡單說,它告訴你的 app 需要做的事情。最基本的 intent 由一下幾個部分組成:

  • Actions: 也就是 intent 需要做的事情,比如撥打某個電話號碼,開啟一個 URL,編輯某些資料。一個 action 是一個簡單字串常量描述應該完成的事情。
  • Data: 即 intent 需用使用的資源。在 Android 中它會用一個 URI(唯一資源標識符) 或者 Uri 對象來表示。資料類型需要根據 action 而變。例如,在一個打電話的 intent 中,你不能將電話號碼搞成一張圖片吧?

將 action 和 data 組合在一起,Android 就能夠知道這個 intent 是幹什麼的以及用什麼來乾的問題。
就是這樣簡單!

回到 takePictureWithCamera() 方法,建立 intent 時我們使用 ACTION_IMAGE_CAPTURE 來作為 intent 的 action。你可能猜到這個 intent 是用來拍照的了,這恰巧是一個模因軟體必需要用到的東西!

後面兩行建立了一個臨時檔案以便儲存照片。開始項目中已經有建立臨時檔案的代碼,你可以看一眼這些代碼。理解它是怎麼實現的。

瞭解 Extra

第4行代碼將一個 extra 添加到建立的 intent 中。
你說的 extra 是啥子東東?

Extra 是一種傳遞給 intent 的包含額外資訊的KVStore for Redis對象,以便讓 intent 用於完成特定的動作。比方說,如果事先準備一些東西的話,人才能更好地完成某個任務,Android 也是同樣的。一個好的 intent 總是需要準備好必要的附屬物(extra)。

一個 intent 的 extra 類型是已知的,並且根據 action 而定;這和提供給 action 的 data 的類型是一樣的道理。

一個極好的例子是建立 action 為 ACTION_WEB_SEARCH 的 intent。這種 action 接收一個鍵為 QUERY 的 extra,用於表示你想搜尋的查詢字串。這個 key 通常是一個字串常量,表示你不應該改變它。用這樣的 action 和 extra 開啟一個 intent 會顯示一個 Google 的搜尋網頁,並列出你的搜尋結果。

再來看一下 captureIntent.putExtra() 這句,EXTRA_OUTPUT 表示你會將網路攝影機擷取的照片儲存到檔案——這樣,Uri 就用來指向早先建立的空檔案。

調用 intent

現在一個功能正常的 intent 已經準備就緒,附上一個經典的 intent 的心智模型:

只剩一件事情,就是在 takePictureWithCamera() 方法最後一行讓 intent 去完成它的使命。也就是這句:

startActivityForResult(captureIntent, TAKE_PHOTO_REQUEST_CODE);

這句讓 Android 開啟一個 activity 並執行 captureIntent 指定的 action:拍照並儲存到檔案。一旦 activity 完成了 action,你需要獲得捕獲的照片。TAKE_PHOTO_REQUEST_CODE 是我們預先定義的常量,它會被用於在 intent 返回時標識這個 intent 。

在 onClick() 的 switch case 語句的 R.id.picture_imageview 分支中,在 break 語句之前加入:

takePictureWithCamera();

當你點擊 image view 時,會調用 takePictureWithCamera 方法。

來檢查一下你的工作成果!運行 app,點擊 ImageView 開啟網路攝影機:

這時你可以拍張照,但你無法用它來做什麼,我們將在下一節來解決這個。

注意:如果你在模擬器中運行,你需要編輯 AVD 中的相機設定。開啟 Tools\Android\AVD Manager 菜單,點擊你想設定的虛機右邊的綠色鉛筆表徵圖。然後點擊視窗左下角的 Show Advanced Settings。在 Camera 一節,確保所有 enabled camera 下拉框中的選項都設定為 Emulated。

隱式 intent

如果你在真機上運行 app,而這個裝置上安裝了許多相機類 app,則你會發現有時候會出現這種情況:

會詢問你需要用哪個 app 來處理這類 intent。
當你建立一個 intent 時,你可以顯示地指定或者不指定由哪種 app 來完成 intent 的 action。ACTION_IMAGE_CAPTURE 是一個極好的隱式 intent 的例子。

隱式 intent 告訴 Android 由使用者進行選擇。如果使用者已經有一個 app,他們就是喜歡用這個 app 來完成某種任務,那麼利用這個 app 的功能來為你服務又有什麼錯呢?至少,這避免了在你的 app 中重複發明輪子。

一個隱式 intent 告訴 Android 它需要某個 app 為它執行 action。Android 系統會詢問每個已安裝的 app,誰能夠處理這類 action,然後由這個 app 進行處理。如果不止一個 app 能處理這類 intent,則提示由使用者進行選擇:

如果只有一個 app 能夠處理,intent 自動用它來執行 action。如果沒有任何 app 能夠處理,Android 什麼也不會返回,給你一個空,並導致 app 崩潰!

要避免這個問題,我們可以檢查返回結果,以確保在開啟 intent 之前,至少有一個 app 能處理該 action。或者在 AndroidManifest.xml 中聲明要安裝這個 app 必須在裝置上有一個網路攝影機。

這個 demo 中使用了第二個方法。
你用一個隱式 intent 來進行拍照,但你沒有在 app 中擷取照片。而沒有照片,你的模因 app 就無法繼續下去。

在 TakePictureActivity 的 takePictureWithCamera() 後添加方法:

@Overrideprotected void onActivityResult(int requestCode, int resultCode, Intent data) {  super.onActivityResult(requestCode, resultCode, data);  if (requestCode == TAKE_PHOTO_REQUEST_CODE && resultCode == RESULT_OK) {    setImageViewWithImage();  }}

在 takePictureWithCamera() 方法中用 startActivityForResult() 方法開啟的activity 被關閉並返回 app 時會執行這個方法。

上面的 if 語句判斷返回的 requestCode 是否和你傳入的 TAKE_PHOTO_REQUEST_CODE 匹配,以確保返回的 intent 是我們所希望的 intent。同時還要判斷 resultCode 是等於一個 Android 常量 RESULT_OK,這個常量表示操作成功的意思。

如果一切 OK,說明我們的照片已經準備就緒,因此調用 setImageViewWithImage()。

這個方法現在定義。
首先,在 TakePictureActivity 頂部,添加一個 Boolean 變數:

private Boolean pictureTaken = false;

這個變數記錄是否拍過照,如果你連續拍了幾張照片的話,這個變數就有用了。很快我們就會用到它。

然後,在 onActivityResult()後面添加:

private void setImageViewWithImage() {  Bitmap pictureBitmap = BitmapResizer.ShrinkBitmap(selectedPhotoPath.toString(),                                                      takePictureImageView.getWidth(),                                                        takePictureImageView.getHeight());  takePictureImageView.setImageBitmap(pictureBitmap);  lookingGoodTextView.setVisibility(View.VISIBLE);  pictureTaken = true;}

BitmapResizer 是開始項目中附帶的一個助手類,確保你從相機中獲得的照片壓縮到適合於你的裝置螢幕的尺寸。雖然裝置也會展開照片,但壓縮照片更能節省記憶體。

運行 app,選擇你喜歡的相機 app——如果詢問你的時候——然後拍照。
這次,照片會被壓縮並顯示在 ImageView 中:

你還會在下方 TextView 中,看到一句話,讚美了你的拍攝技巧!有教養是一件很好的事情!:]

顯式 Intents

接下來進入這個模因 app 的第二階段,但首先得讓你的照片傳給另一個 activity,因為你擔心這裡的螢幕空間有點緊張。

仍然是 TakePictureActivity, 在其它常量後面添加:

private static final String IMAGE_URI_KEY = "IMAGE_URI";private static final String BITMAP_WIDTH = "BITMAP_WIDTH";private static final String BITMAP_HEIGHT = "BITMAP_HEIGHT";

這些鍵會用於你要傳給下一個 activity 的 extra 中。

然後,在 TakePictureActivity 添加下列方法, 請自行匯入相關類:

private void moveToNextScreen() {  if (pictureTaken) {    Intent nextScreenIntent = new Intent(this, EnterTextActivity.class);    nextScreenIntent.putExtra(IMAGE_URI_KEY, selectedPhotoPath);    nextScreenIntent.putExtra(BITMAP_WIDTH, takePictureImageView.getWidth());    nextScreenIntent.putExtra(BITMAP_HEIGHT, takePictureImageView.getHeight());    startActivity(nextScreenIntent);  } else {    Toast.makeText(this, R.string.select_a_picture, Toast.LENGTH_SHORT).show();  }}

這裡判斷了 pictureTaken 是否為 true,這表明你的 ImageView 已經從相機獲得了一張照片。如果沒有,你的 activity 會顯示一個 Toast 資訊,讓你去拍張照片。如果為 true,則建立一個 intent,使用先前定義的常量 key 設定它的 extra。

接著,在 onClick() 方法中的 R.id.enter_text_button 分支的 break 之前調用這個方法:

moveToNextScreen();

運行 app,點擊 LETS MEMEIFY! ,如果你不拍照的話,你會看到一個 Toast 顯示:

如果已經拍過照,moveToNextScreen() 方法會走到建立 intent 並進入輸入文本的 activity 這一步。同時會附加 extra 到這個 intent,比如照片的 Uri 和寬高。這將在下一個 activity 中用到。

你已經建立了你的第一個顯式 intent。和隱式 intent 比較,顯式 intent 要穩妥得多,因為它描述了一個組件,這個組件會用來建立和開啟 Intent。它可能是來自於你的 app 的另一個 activity,也可能是 app 中的某個服務,比如在後台下載一個檔案。

這個 intent 在構造時會指定一個上下文(例如,這裡的 this)以及 intent 想開啟的 class(EnterTextActivity.class)。因為你已經顯式地說明 intent 如何從 A 找到 B,Android 進行簡單的調用就可以了。使用者無法控制 intent 如何完成:

運行 app。拍照,但這次點擊 LETS MEMEIFY!,你的顯式 intent 會將 action 交給下個 activity :

在開始項目中已經在 Manifest 中聲明過這個 activity 了,你不必做這個步驟。

模因的第二階段

看起來這個 intent 真棒。但你的 extra 傳遞到哪了?它們有沒有在最後一塊記憶體處拐錯彎了?我們該把它們找出來讓它們幹活了!

在 EnterTextActivity 頂部加入下列常量:

private static final String IMAGE_URI_KEY = "IMAGE_URI";private static final String BITMAP_WIDTH = "BITMAP_WIDTH";private static final String BITMAP_HEIGHT = "BITMAP_HEIGHT";

我們簡單地將前一個 activity 中的常量複製到這裡。

然後,在 onCreate() 方法中的最後一行後面加入:

pictureUri = getIntent().getParcelableExtra(IMAGE_URI_KEY);int bitmapWidth = getIntent().getIntExtra(BITMAP_WIDTH, 100);int bitmapHeight = getIntent().getIntExtra(BITMAP_HEIGHT, 100);Bitmap selectedImageBitmap = BitmapResizer.ShrinkBitmap(pictureUri.toString(), bitmapWidth, bitmapHeight);selectedPicture.setImageBitmap(selectedImageBitmap);

在建立 activity 時,將上一個 activity 傳入的 Uri 賦給 pictureUri,要獲得當前 intent 請使用 getIntent() 方法。獲得 intent 後,就可以訪問 extra 中儲存的值了。

因為變數和對象以不同的形式儲存,要從 intent 中訪問它們需要用不同的方法。例如要訪問 Uri 對象,需要使用 getParcelableExtra()。其他 extra 方法用於訪問其他類型的變數比如字串和未經處理資料類型。

原始的 getExtra() 方法也可以讓你指定一個預設值。如果要訪問的值沒有提供,或者 key 缺失,則使用預設值。

擷取到所需的 extra 之後,就可以用 Uri 和 BITMAP_WIDTH、BITMAP_HEIGHT 指定的大小建立位元影像。最後設定 ImageView 的 image 以顯示照片

除了 ImageView,螢幕上還有 2 個 EditText view,使用者可以輸入他們的惡搞文字。開始項目已經為你完成了這些工作,將惡搞的文字 PS 到照片上。

你需要做的僅僅是修改 onClick()。在 switch case 語句的 R.id.write_text_to_image_button 分支加入:

createMeme();

噹噹噹噹! 運行 App。拍照,在第二個 activity 輸入你的惡搞文字,點擊 LETS MEMEIFY!:

你編寫了你自己的模因 app! 但別高興得太早——你還要為 app 進行一些“拋光工程”。

怎麼儲存?

如果能將你的惡搞圖片儲存成圖片並分享給全世界就好了!它自己是不會進行病毒式傳播的!:]

幸運的是開始項目已經完成了大部分工作——你只需要將導線串連起來。

在 saveImageToGallery() 方法中加入以下代碼,就在 try 塊後面,第二個 Toast.makeText() 之前:

// 建立一個 intent,請求掃描建立的檔案,將照片 uri 傳遞給 intent,然後廣播 intent。Intent mediaScanIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);mediaScanIntent.setData(Uri.fromFile(imageFile));sendBroadcast(mediaScanIntent);

這個 intent 使用了一個 ACTION_MEDIA_SCANNER_SCAN_FILE 的 action 去請求 Android 的媒體庫,將 image 的 url 添加到媒體庫。這樣,app 就能夠使用 image 的 Uri 訪問媒體庫了。

ACTION_MEDIA_SCANNER_SCAN_FILE action 需要 intent 提供額外資料,資料需要是一個 Uri,這個 Uri 我們用已儲存的位元影像檔案的 File 對象來構造。

最後,通過 Android 系統來廣播 intent,以便所有有興趣的人——這裡即 media scanner——去執行。因為 media scanner 沒有使用者介面,你無法啟動一個 activity,所以我們只能用廣播 intent 的方式替代。

現在,在 onClick() 方法的 R.id.save_image_button 分支的 break 之前添加:

saveImageToGallery(viewBitmap);

當使用者點擊 SAVE IMAGE,上述代碼會進行一些錯誤處理,如果一切正常,則開啟 intent。

運行 app,拍照,輸入惡搞文字,點擊 LETS MEMEIFY!,當圖片合成後,點擊 SAVE IMAGE。

關閉 app,開啟 Photos 程式。如果用模擬器測試,則開啟 Gallery 程式。你會看到一張帶有惡搞文字的新照片:

你的惡搞照片能夠不受 app 的限制並可以發送到社交媒體或用你想用的任何方式分享出去。你的模因 app 做好了!

Intent 過濾

現在,你已經充分瞭解針對不同的任務使用不同的 intent 了。但是,這個關於誠實的 intent 故事裡還有另一個情節:當發送一個隱式 intent 時,你的 app 怎麼知道哪個 intent 需要處理。

在 app/manifests 檔案夾下,開啟 AndroidManifest.xml 在第一個 activity 標籤你會看到:

關鍵是 intent-filter 節點。一個 Intent Filter 允許你的 app 的組件能夠響應隱式 intent。

當 Android 試圖實現一個其他 app 發來的隱式 intent 時,這就好像一面旗幟。一個 app 可以擁有多個 intent filter,它們像旗幟一樣揮舞著,希望 Android 要找的人就是它。

這就像是 intent 和 app 們在進行一場網上約會!:]
為了證明自己就是和某個 intent 匹配的 app,intent 過濾器需要提供三樣東西:

  • Intent 的 Action: 這個 app 能夠完成的 action;例如相機 app 會為你的 app 執行 ACTION_IMAGE_CAPTURE 操作。
  • Intent 的 Data: 這個 intent 能夠接受的資料類型。取值範圍可能是某個檔案路徑、連接埠、MIME 類型比如 image 和 video。你可以一個或多個屬性,可以嚴格也可以寬泛地控制 app 從 intent 中獲得的資料類型。
  • Intent 的 Category: 能夠接受的 intent 的類別。這是一種用於指定隱式 intent 中哪個 action 能被處理的附加方式。

讓 Memeify(樣本 app) 通過一個隱式 intent 將圖片和其它 app 共用是一個不錯的主意——這個過程會簡單到令你驚奇。

在 Manifest 檔案的第一個 intent 過濾器之後添加代碼:

<intent-filter>    <action android:name="android.intent.action.SEND" />    <category android:name="android.intent.category.DEFAULT" />    <data android:mimeType="@string/image_mime_type" /></intent-filter>

這個新的過濾器指明你的 app 會處理 action 為 SEND 的隱式 intent。同時將 intent 類別指定為 DEFAULT,表明在這種情況下我們不需要對類別進行任何特殊的設定。同時只需要提供 MIME 類型為 image 的資料。

開啟 TakePictureActivity.java 在類中添加如下方法:

private void checkReceivedIntent() {  Intent imageRecievedIntent = getIntent();  String intentAction = imageRecievedIntent.getAction();  String intentType = imageRecievedIntent.getType();  if (Intent.ACTION_SEND.equals(intentAction) && intentType != null) {    if (intentType.startsWith(MIME_TYPE_IMAGE)) {      Uri contentUri = imageRecievedIntent.getParcelableExtra(Intent.EXTRA_STREAM);      selectedPhotoPath = getRealPathFromURI(contentUri);      setImageViewWithImage();    }  }}

我們在這裡擷取了開啟這個 activity 的 Intent 並找出它的 action 和 類型,將它們和 intent filter 中的定義進行比較,看它的 data 是不是 MIME_TYPE_IMAGE 類型。

如果是,繼續擷取圖片的 Uri,通過開始項目中提供的助手方法擷取位元影像,並讓 ImageView 顯示位元影像。

然後在 onCreate() 方法後加入:

@Overridepublic void onWindowFocusChanged(boolean hasFocus) {  super.onWindowFocusChanged(hasFocus);  checkReceivedIntent();}

當你的 app 的視窗將焦點切換到你的 activity 上時,對 intent 進行檢查。這使得 activity 一顯示到螢幕上就顯示位元影像。

運行 app。立即回到 Home 屏,然後再回到 Photos 程式,或者 Gallery 程式——如果你正在使用模擬器的話。選擇一張照片,點擊分享按鈕,從快顯功能表中選擇 Memeify:

Memeify 正準備接受你的照片!點擊 Memeify 看看會發生什麼—— Memeify 會開啟,並在 ImageView 中顯示你選中的照片。

你的 app 現在毫不客氣地接受了這個 intent!

結束

你可以從這裡下載完成的項目。

Intent 是構建 Android 的磚石之一。沒有它,Android 引以為傲的開放和連通根本無法做到。學會如何利用好 intent,你會獲得一個強大的盟友。

如果你想學習更多關於 intent 和 intent 過濾器的內容,請參考Google 的 Intents 文檔。
有任何問題及建議,請在下面留言。

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.