Android中常見Intent習慣用法-上篇(附原始碼下載)

孫群發表於2015-09-19

Android中的Intent是一個非常重要的類,如果對Intent不是特別瞭解,可以參見博文《Android中Intent概述及使用》。如果對Intent Filter不是特別瞭解,可以參見博文《Android中Intent物件與Intent Filter過濾匹配過程詳解》

本文著重講一下Android中一些常見的Intent的習慣用法,比如如何通過Intent傳送簡訊、傳送郵件、啟動攝像機拍照錄視訊、設定鬧鈴、開啟WIFI設定介面等等。

限於篇幅,本博文分為上下兩篇,這是上篇。


傳送簡訊

傳送簡訊的時候,我們要使用的action是Intent.ACTION_SENDTO,並且要指定其URI是smsto:協議,這樣能保證是簡訊應用接收並處理我們的intent物件,而不是其他應用接收,從而準確實現傳送簡訊的目的。如果我們的action不是Intent.ACTION_SENDTO,而是Intent.ACTION_SEND,且沒有指定smsto:協議的URI的話,那麼Android在接收到intent物件之後不會直接啟動簡訊應用,而是彈出了App Chooser,讓我們選擇要啟動哪個應用,比如電子郵件、QQ等等,所以為了確保直接啟動簡訊應用,我們應該使用Intent.ACTION_SENDTO並且指定smsto:協議的URI。

示例程式碼如下:

//使用ACTION_SENDTO而不是ACTION_SEND
Intent intent = new Intent(Intent.ACTION_SENDTO);
//指定URI使用smsto:協議,協議後面是接收簡訊的物件
Uri uri = Uri.parse("smsto:10086");
intent.setData(uri);
//設定訊息體
intent.putExtra("sms_body", "手頭有點緊,借點錢吧~~");

ComponentName componentName = intent.resolveActivity(getPackageManager());
if(componentName != null){
    startActivity(intent);
}

在構造傳送簡訊的URI時,前面是smsto:協議,後面跟的是接收簡訊的對方的手機號。如果在構建URI時,只寫了smsto:,而沒有寫後面的手機的號的話,那麼該intent也可以成功啟動簡訊應用,不過這種情形下,在啟動了簡訊應用之後,還需要我們自己再手動輸入接收資訊的手機號。我們通過key為sms_body的extra設定簡訊的內容。

需要注意的是,在執行了startActivity(intent)之後,雖然簡訊應用啟動了,但是簡訊沒有直接發出去,需要我們再點選一下傳送訊息才可以。


傳送郵件

傳送郵件的時候,我們要使用的action也是Intent.ACTION_SENDTO,並且要指定其URI是mailto:協議,這樣能保證是郵件應用接收並處理我們的intent物件,而不是其他應用接收,從而準確實現傳送郵件的目的。如果我們的action不是Intent.ACTION_SENDTO,而是Intent.ACTION_SEND,且沒有指定mailto:協議的URI的話,那麼Android在接收到intent物件之後不會直接郵件應用,而是彈出了App Chooser,讓我們選擇要啟動哪個應用,比如簡訊、QQ等等,所以為了確保直接啟動郵件應用,我們應該使用Intent.ACTION_SENDTO並且指定mailto:協議的URI。

示例程式碼如下:

//使用ACTION_SENDTO而不是ACTION_SEND
Intent intent = new Intent(Intent.ACTION_SENDTO);
//指定URI使用mailto:協議,確保只有郵件應用能接收到此intent物件
Uri uri = Uri.parse("mailto:");
intent.setData(uri);
String[] addresses = {"zhangsan@126.com", "lisi@126.com"};
String[] cc = {"boss@126.com"};
String[] bcc = {"girlfriend@126.com"};
String subject = "加班";
String content = "國慶正常上班~~";
//設定郵件的接收方
intent.putExtra(Intent.EXTRA_EMAIL, addresses);
//設定郵件的抄送方
intent.putExtra(Intent.EXTRA_CC, cc);
//設定郵件的密送方
intent.putExtra(Intent.EXTRA_BCC, bcc);
//設定郵件標題
intent.putExtra(Intent.EXTRA_SUBJECT, subject);
//設定郵件內容
intent.putExtra(Intent.EXTRA_TEXT, content);
//設定郵件附件
//intent.putExtra(Intent.EXTRA_STREAM, Uri.parse(...));
ComponentName componentName = intent.resolveActivity(getPackageManager());
if(componentName != null){
    startActivity(intent);
}

啟動郵件應用後的截圖如下所示:
這裡寫圖片描述

我們分別通過key為Intent.EXTRA_EMAILIntent.EXTRA_CCIntent.EXTRA_BCC的extra依次設定郵件的接收方、抄送方、密送方,其值均為String陣列。我們通過key為Intent.EXTRA_SUBJECT的extra設定郵件標題,通過key為Intent.EXTRA_TEXT的extra設定郵件內容。如果想傳送附件,那麼可以將附件封裝成Uri的形式,然後通過key為Intent.EXTRA_STREAM的extra設定郵件附件。

需要注意的是,在執行了startActivity(intent)之後,雖然郵件應用啟動開啟了,但是郵件沒有直接發出去,需要我們再點選一下右上角的傳送按鈕才能將郵件發出去。


打電話

要想通過Intent打電話,我們有兩個可以使用的action:Intent.ACTION_DIALIntent.ACTION_CALL,二者有一定的區別。

  • 如果使用Intent.ACTION_DIAL作為intent物件的action,那麼當執行startActivity(intent)之後,會啟動打電話應用,並且會自動輸入指定的手機號,但是不會自動撥打,需要我們手動按下撥打按鈕才能真正給對方打電話。

  • 如果使用Intent.ACTION_CALL作為intent物件的action,那麼當執行startActivity(intent)之後,會啟動打電話應用,並且直接撥打我們指定的手機號,無需我們再手動按下撥打按鈕。但是需要注意的是,該action需要許可權android.permission.CALL_PHONE,如果在應用的AndroidManifest.xml檔案中沒有新增該許可權,那麼當指定到startActivity(intent)這句程式碼的時候,就會丟擲異常,應用崩潰退出。

以下是示例程式碼:

//Intent.ACTION_DIAL只撥號,不打電話
//Intent intent = new Intent(Intent.ACTION_DIAL);
//Intent.ACTION_CALL直接撥打指定電話,需要android.permission.CALL_PHONE許可權
Intent intent = new Intent(Intent.ACTION_CALL);
Uri uri = Uri.parse("tel:10086");
intent.setData(uri);
ComponentName componentName = intent.resolveActivity(getPackageManager());
if(componentName != null){
    startActivity(intent);
}

在該示例程式碼中,我們使用了Intent.ACTION_CALL作為intent物件的action,並且在AndroidManifest.xml中新增了如下許可權:

<uses-permission android:name="android.permission.CALL_PHONE"></uses-permission>

我們使用tel:協議的URI,在協議後面的是要撥打的號碼,將該Uri作為intent物件的data。


拍照

要想通過Intent啟動攝像機進行拍照,我們需要設定intent物件的action值為MediaStore.ACTION_IMAGE_CAPTURE的action。然後我們用key為MediaStore.EXTRA_OUTPUT的extra設定圖片的輸出路徑,最後呼叫startActivityForResult()方法以啟動攝像機應用,並重寫我們的onActivityResult()以便在該方法中得知拍照完成。

示例程式碼如下:

//表示用於拍照的requestCode
    private final int REQUEST_CODE_IMAGE_CAPTURE = 1;
    //我們儲存照片的輸出路徑,以便後續使用
    private Uri imageOutputUri = null;

    //拍照
    private void captureImage(){
        PackageManager pm = getPackageManager();

        //先判斷本機是否在硬體上有攝像能力
        if(pm.hasSystemFeature(PackageManager.FEATURE_CAMERA)){
            Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
            ComponentName componentName = intent.resolveActivity(pm);
            //判斷手機上有無攝像機應用
            if(componentName != null){
                //建立圖片檔案,以便於通過Uri.fromFile()生成對應的Uri
                File imageFile = createImageFile();
                if(imageFile != null){
                    //根據imageFile生成對應的Uri
                    imageOutputUri = Uri.fromFile(imageFile);
                    //利用該Uri作為拍照完成後照片的儲存路徑,注意,一旦設定了儲存路徑,我們就不能獲取縮圖了
                    intent.putExtra(MediaStore.EXTRA_OUTPUT, imageOutputUri);
                    //呼叫startActivityForResult()方法,以便在onActivityResult()方法中進行相應處理
                    startActivityForResult(intent, REQUEST_CODE_IMAGE_CAPTURE);
                }else{
                    Toast.makeText(this, "無法建立影象檔案!", Toast.LENGTH_LONG).show();
                }
            }else{
                Toast.makeText(this, "未在本機找到Camera應用,無法拍照!", Toast.LENGTH_LONG).show();
            }
        }else{
            Toast.makeText(this, "本機沒有攝像頭,無法拍照!", Toast.LENGTH_LONG).show();
        }
    }

    //建立圖片檔案,以便於通過Uri.fromFile()生成對應的Uri
    private File createImageFile(){
        File image = null;

        //用時間戳拼接檔名稱,防止檔案重名
        String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
        String imageFileName = "JPEG_" + timeStamp + "_";
        File storageDir = getExternalFilesDir(Environment.DIRECTORY_PICTURES);

        try{
            image = File.createTempFile(
                    imageFileName,  //字首
                    ".jpg",         //字尾
                    storageDir      //資料夾
            );
        }catch (IOException e){
            image = null;
            e.printStackTrace();
            Log.e("DemoLog", e.getMessage());
        }

        return image;
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
        //首先判斷是否正確完成
        if(resultCode == RESULT_OK){
            switch (requestCode){
                case REQUEST_CODE_IMAGE_CAPTURE:
                    //此處,我們可以通過imageOutputUri獲取到我們想要的圖片
                    String imagePath = imageOutputUri.toString();
                    Log.i("DemoLog", "照片路徑是: " + imagePath);
                    Toast.makeText(this, "照片路徑是: " + imagePath, Toast.LENGTH_LONG).show();

                    //以下程式碼嘗試獲取縮圖
                    //如果設定MediaStore.EXTRA_OUTPUT作為extra的時候,那麼此處的intent為null,需要判斷
                    if(intent != null){
                        Bitmap thumbnail = intent.getParcelableExtra("data");
                        //有的手機並不會給拍照的圖片生成縮圖,所以此處也要判斷
                        if(thumbnail != null){
                            Log.i("DemoLog", "得到縮圖");
                        }
                    }
                default:
                    break;
            }
        }
    }

我們分析一下上面的程式碼片段:

  1. 不是所有的Android裝置都能拍照的,所以首先我們呼叫了PackageManagerhasSystemFeature(PackageManager.FEATURE_CAMERA)方法,判斷當前裝置在硬體層級是否具有拍照的能力。

  2. 然後我們建立了一個action為MediaStore.ACTION_IMAGE_CAPTURE的intent物件。

  3. 然後我們通過呼叫intent.resolveActivity(pm)方法判斷當前裝置有無攝像機應用以便我們啟動。如果沒有攝像機應用但是我們卻把intent物件傳遞給startActivity()startActivityForResult()的話,就會丟擲異常,應用崩潰退出。

  4. 我們自己寫了一個createImageFile方法,通過該方法我們在自己的應用所對應的外設儲存卡上建立了一個圖片檔案。需要注意的是,此步驟需要WRITE_EXTERNAL_STORAGE許可權,在AndroidManifest.xml中註冊如下:

    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
        android:maxSdkVersion="18"></uses-permission>

    關於該許可權的更多資訊,可參見WRITE_EXTERNAL_STORAGE

  5. 我們利用上面生成的圖片檔案生成了對應的Uri,將其儲存在Activity中型別為Uri的欄位imageOutputUri中,之後我們執行了intent.putExtra(MediaStore.EXTRA_OUTPUT, imageOutputUri),利用該Uri作為拍照完成後照片的儲存路徑。
    此處需要特別注意的是,一旦設定了儲存路徑,我們就不能在onActivityResult()中獲取縮圖了。

  6. 最後我們需要呼叫方法startActivityForResult(intent, REQUEST_CODE_IMAGE_CAPTURE)以啟動攝像機應用進行拍照,其中REQUEST_CODE_IMAGE_CAPTURE是我們自定義指定的用於拍照的requestCode。

  7. 我們覆寫了onActivityResult方法,拍照完成後觸發該方法的執行。首先我們要判斷resultCode是否與RESULT_OK相等,只有相等才表明拍照成功,然後我們判斷如果requestCode是否等於REQUEST_CODE_IMAGE_CAPTURE,若相等表明是拍照返回的結果。那麼此時,我們就可以通過我們之前儲存的imageOutputUri獲取剛剛拍完的照片了,其URI字串如:
    file:///storage/sdcard0/Android/data/com.ispring.commonintents/files/Pictures/JPEG_20150919_112704_533002075.jpg
    需要注意的是,如果我們在第5步之中設定MediaStore.EXTRA_OUTPUT作為照片輸出路徑的話,那麼在onActivityResult中無法獲取從攝像機應用換回的Intent,即為null,這樣也就無法獲取縮圖。反之,如果在第5步沒有設定MediaStore.EXTRA_OUTPUT作為照片輸出路徑的話,intent不為空,可以嘗試執行Bitmap thumbnail = intent.getParcelableExtra("data")獲取縮圖,如果thumbnail不為空,表示能成功獲取縮圖。但是有的手機並不會給拍照的圖片生成縮圖,所以此處的thumbnail也有可能是null,所以在使用之前要先判斷。


攝像

通過Intent啟動攝像機進行攝像的步驟與上面剛提到的通過Intent啟動攝像機進行拍照的步驟非常相似,稍有區別。要啟動Camera進行攝像,我們需要給intent設定值為MediaStore.ACTION_VIDEO_CAPTURE的action,然後我們用key為MediaStore.EXTRA_OUTPUT的extra設定圖片的輸出路徑,最後呼叫startActivityForResult()方法以啟動攝像機應用,並重寫我們的onActivityResult()以便在該方法中得知攝像完成。

以下是示例程式碼:

//表示用於錄視訊的requestCode
    private final int REQUEST_CODE_VIDEO_CAPTURE = 2;

    //我們儲存視訊的輸出路徑,以便後續使用
    private Uri videoOutputUri = null;

    //攝像
    private void captureVideo(){
        PackageManager pm = getPackageManager();

        //先判斷本機是否在硬體上有攝像能力
        if(pm.hasSystemFeature(PackageManager.FEATURE_CAMERA)){
            //將intent的action設定為MediaStore.ACTION_VIDEO_CAPTURE
            Intent intent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE);
            ComponentName componentName = intent.resolveActivity(pm);
            //判斷手機上有無攝像機應用
            if(componentName != null){
                //建立視訊檔案,以便於通過Uri.fromFile()生成對應的Uri
                File videoFile = createVideoFile();
                if(videoFile != null){
                    //根據videoFile生成對應的Uri
                    videoOutputUri = Uri.fromFile(videoFile);
                    //利用該Uri作為攝像完成後視訊的儲存路徑
                    intent.putExtra(MediaStore.EXTRA_OUTPUT, videoOutputUri);
                    //呼叫startActivityForResult()方法,以便在onActivityResult()方法中進行相應處理
                    startActivityForResult(intent, REQUEST_CODE_VIDEO_CAPTURE);
                }else{
                    Toast.makeText(this, "無法建立視訊檔案!", Toast.LENGTH_LONG).show();
                }
            }else{
                Toast.makeText(this, "未在本機找到Camera應用,無法攝像!", Toast.LENGTH_LONG).show();
            }
        }else{
            Toast.makeText(this, "本機沒有攝像頭,無法攝像!", Toast.LENGTH_LONG).show();
        }
    }

    //建立視訊檔案,以便於通過Uri.fromFile()生成對應的Uri
    private File createVideoFile(){
        File videoFile = null;

        //用時間戳拼接檔名稱,防止檔案重名
        String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
        String imageFileName = "MP4" + timeStamp + "_";
        File storageDir = getExternalFilesDir(Environment.DIRECTORY_MOVIES);

        try{
            videoFile = File.createTempFile(
                    imageFileName,  //字首
                    ".mp4",         //字尾
                    storageDir      //資料夾
            );
        }catch (IOException e){
            videoFile = null;
            e.printStackTrace();
            Log.e("DemoLog", e.getMessage());
        }

        return videoFile;
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
        //首先判斷是否正確完成
        if(resultCode == RESULT_OK){
            switch (requestCode){
                case REQUEST_CODE_VIDEO_CAPTURE:
                    //如果設定MediaStore.EXTRA_OUTPUT作為extra的時候,
                    //在有的手機上,此處的intent為不為null,但是在有的手機上卻為null,
                    //所以不建議從intent.getData()中獲取視訊路徑
                    //我們應該自己記錄videoOutputUri來得知視訊路徑,下面註釋的程式碼不建議使用
                    /*if(intent != null){
                        Uri videoUri = intent.getData();
                        if(videoUri != null){
                            //路徑格式如content://media/external/video/media/130025
                            Log.i("DemoLog", "視訊路徑是: " + videoUri.toString());
                        }
                    }*/

                    String videoPath = videoOutputUri.toString();
                    //1.如果沒有設定MediaStore.EXTRA_OUTPUT作為視訊檔案儲存路徑,那麼路徑格式如下所示:
                    //  路徑格式如content://media/external/video/media/130025
                    //2.如果設定了MediaStore.EXTRA_OUTPUT作為視訊檔案儲存路徑,那麼路徑格式如下所示:
                    //  路徑格式如file:///storage/sdcard0/Android/data/com.ispring.commonintents/files/Movies/MP420150919_184132_533002075.mp4
                    Log.i("DemoLog", "視訊路徑是: " + videoPath);
                    Toast.makeText(this, "視訊路徑是: " + videoPath, Toast.LENGTH_LONG).show();
                    break;
                default:
                    break;
            }
        }
    }

可以看到上面啟動Camera攝像的程式碼與拍照的程式碼幾乎完全一樣,具體解釋參見對拍照程式碼的描述。在該示例程式碼中,我們通過MediaStore.EXTRA_OUTPUT設定了視訊的存放路徑,拍照的時候我們也通過它設定了照片的輸出路徑,但是二者稍有區別:
1. 對於拍照,設定了MediaStore.EXTRA_OUTPUT之後,onActivityResult中的Intent引數是null,不能從Intent中得知照片的儲存路徑。
2. 對於攝像,設定了MediaStore.EXTRA_OUTPUT之後,onActivityResult中的Intent引數在有的手機上是null,但是在有的手機上不是null,我的手機小米1s得到的intent物件就不是null,所以此處很奇怪。如果intent不是null,可以通過intent.getData()獲取到視訊檔案的儲存路徑,但是由於intent是否為null不確定,所以儘量不要通過intent.getData()方法獲取其路徑,而應該自己在Activity中儲存一個欄位儲存我們之前設定的檔案路徑,這樣就沒問題了。

原始碼免費下載連結:
http://download.csdn.net/detail/sunqunsunqun/9120445

相關閱讀:
Android中Intent概述及使用
Android中Intent物件與Intent Filter過濾匹配過程詳解

相關文章