天天看點

學習安卓開發[4] - 使用隐式Intent啟動短信、聯系人、相機應用

在上一篇學習安卓開發[3] - 使用RecyclerView顯示清單中了解了在進行清單展示時RecyclerView的使用,本次記錄的是在應用中如何通過隐式Intent調用其它應用的功能,比如發短信、打電話、拍照等

  • 隐式Intent
  • 短信
    • 判斷是否存在相關APP
  • 相機
    • FileProvider
    • Bitmap
    • 功能聲明

Intent對象用來向作業系統說明需要處理的任務。使用顯式Intent時,要指定作業系統需要啟動的activity,但使用隐式intent,隻需告知作業系統想要進行的操作,系統就會啟動能完成該操作的activity,如果有多個符合條件的activity,會提供使用者一個應用清單供選擇

Android是如何通過隐式intent找到并啟動合适應用的呢?原因在于配置檔案中的itent過濾器設定,比如我們也想開發一款短信應用,那麼可以在AndroidMainfest的activity聲明中這樣設定:

<activity android:name=".CrimeListActivity">
    <intent-filter>
        <action android:name="android.intent.action.SEND" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
</activity>
           

隐式Intent的組成部分有

1)要執行的操作,通常以Intent類中的常量來表示,比如通路URL可以使用Intent.ACTION_VIEW,發送郵件使用Intent.ACTION_SEND

2)待通路資料的位置,這可能是裝置以外的資源,如某個網頁的URL,某個檔案的URI

3)操作涉及的資料類型,如text/html, audio/mpeg3等

4)可選類别,用來描述對activity的使用方式

那麼要啟動短信的隐式intent的方法為:

mReportButton.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {
        Intent i = new Intent(Intent.ACTION_SEND);
        i.setType("text/plain");
        i.putExtra(Intent.EXTRA_TEXT, getCrimeReport());
        i.putExtra(Intent.EXTRA_SUBJECT,
                getString(R.string.crime_report_suspect));
        i = Intent.createChooser(i, getString(R.string.send_report));
        startActivity(i);
    }
});
           

首先指定發送消息的操作名為ACTION_SEND,然後消息内容為文本,是以設定資料類型為text/plain,要發送的文本通過Extra的形式提供

使用隐式intent時,如果系統沒有安裝對應的軟體,應用就會奔潰,是以有必要在使用隐式intent時,檢查一下能夠找到對應的軟體,如果沒找到,就避免再去發生相關的隐式intent

final Intent pickContact = new Intent(Intent.ACTION_SEND);
PackageManager packageManager = getActivity().getPackageManager();
    if (packageManager.resolveActivity(pickContact, PackageManager.MATCH_DEFAULT_ONLY) == null) {
        mReportButton.setEnabled(false);
}
           

通過PackageManager可以搜尋需要的activity的資訊,flag标志MATCH_DEFAULT_ONLY限定隻搜尋帶CATEGORY_DEFAULT的activity,如果沒有找到,就禁用發短信按鈕。

如果所開發的APP有拍照功能,就可以使用系統相機了。拍攝的照片要儲存在裝置檔案系統,但這就涉及到私有存儲空間的問題。出于安全考慮,無法使用公共外部存儲轉存,那麼如果想共享檔案給其他應用,或者接收其他應用的檔案(如相機拍攝的照片),可以使用ContentProvider把要共享的檔案臨時暴露出來。對于接受相機拍攝的照片這樣的場景,系統提供的現成的FileProvider類。

要使用FileProvider類,需要在AndroidMainfest中添加聲明。

首先添加files.xml檔案

<paths>
    <files-path
        name="crime_photos"
        path="."/>
</paths>
           

這個描述性檔案把私有存儲空間的根路徑映射為crime_photos,這個名字僅供FileProvider自己使用。

然後添加FileProvider聲明:

<provider
    android:name="android.support.v4.content.FileProvider"
    android:authorities="com.example.zhixin.crimeintent.fileprovider"
    android:exported="false"
    android:grantUriPermissions="true">
    <meta-data
        android:name="android.support.FILE_PROVIDER_PATHS"
        android:resource="@xml/files" />
</provider>
           

通過這段聲明,提供了一個檔案儲存地,相機拍攝的照片就可以放在這裡了。exported="false"表示除了應用自己和給予授權的應用,其它的不允許使用這個FileProvider,grantUriPermissions="true"表示允許其他應用向指定文職的URI寫入檔案。

接下來就可以實作拍照功能了

mPhotoButton = (ImageButton) v.findViewById(R.id.crime_camera);
final Intent captureImage = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
boolean canTakePhoto = mPhotoFile != null &&
        captureImage.resolveActivity(packageManager) != null;
mPhotoButton.setEnabled(canTakePhoto);

mPhotoButton.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {
        Uri uri = FileProvider.getUriForFile(getActivity(),
                "com.example.zhixin.crimeintent.fileprovider", mPhotoFile);
        captureImage.putExtra(MediaStore.EXTRA_OUTPUT, uri);

        List<ResolveInfo> cameraActivities = getActivity().getPackageManager().queryIntentActivities(captureImage,
                PackageManager.MATCH_DEFAULT_ONLY);

        for (ResolveInfo activity : cameraActivities) {
            getActivity().grantUriPermission(activity.activityInfo.packageName,
                    uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
        }

        startActivityForResult(captureImage,REQUEST_PHOTO);
    }
});

mPhotoView = (ImageView) v.findViewById(R.id.crime_photo);
           

通過給所有目标activity授予Intent.FLAG_GRANT_WRITE_URI_PERMISSION權限,允許它們在URI指定的位置寫入檔案。mPhotoFile表示拍攝生成照片的名稱。

在相機拍攝完成後的回調方法中,取消之前的Intent.FLAG_GRANT_WRITE_URI_PERMISSION授權,并加載顯示照片。

Uri uri=FileProvider.getUriForFile(getActivity(),
        "com.example.zhixin.crimeintent.fileprovider",
        mPhotoFile);
getActivity().revokeUriPermission(uri,Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
updatePhotoView();
           

在顯示照片時還有一些工作要做。顯示照片要用到Bitmap,而Bitmap隻存儲實際像素資料,即使是已經壓縮過的照片,存入Bitmap後,檔案并不會同樣壓縮,比如一張1600萬像素24位的相機照片存為JPG格式約為5MB,但載入Bitmap後就會達到48MB左右。

要解決這個問題,需要手動縮放位圖照片。首先确認檔案大小,然後根據要顯示照片的區域大小合理縮放檔案,最後重新讀取縮放後的檔案,再建立Bitmap對象。

public class PictureUtils {
    public static Bitmap getScaledBitmap(String path, int destWidth, int destHeight) {
        // Read in the dimensions of the image on disk
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeFile(path, options);

        float srcWidth = options.outWidth;
        float srcHeight = options.outHeight;

        // Figure out how much to scale down by
        int inSampleSize = 1;
        if (srcHeight > destHeight || srcWidth > destWidth) {
            float heightScale = srcHeight / destHeight;
            float widthScale = srcWidth / destWidth;

            inSampleSize = Math.round(heightScale > widthScale ? heightScale :
                    widthScale);
        }

        options = new BitmapFactory.Options();
        options.inSampleSize = inSampleSize;

        // Read in and create final bitmap
        return BitmapFactory.decodeFile(path, options);
    }
}
           

還有一個問題是在Fragment.OnCreateView裡面加載照片的時候,無法知道要顯示照片的尺寸,隻有onCreate, onStart, onResume方法執行過後,才會有首個執行個體化布局出現。對于這種情況,可以根據Fragment所在的Activity尺寸确定螢幕的尺寸,按照螢幕尺寸縮放圖像。是以再添加一個getScaledBitmap的重載:

public static Bitmap getScaledBitmap(String path, Activity activity) {
    Point size = new Point();
    activity.getWindowManager().getDefaultDisplay()
            .getSize(size);
    return getScaledBitmap(path, size.x, size.y);
}
           

最後在OnCreateView和相機的回調方法更新照片。

既然APP需要用到拍照功能,但像拍照、NFC、紅外等并不是每個裝置都有,是以進行功能聲明,進而可以在應用前讓使用者知道,如果裝置缺少某項必須功能,應用商店會拒絕安裝應用。

在AndroidMainfest中添加:

<uses-feature
    android:name="android.hardware.camera"
    android:required="false">
</uses-feature>
           

android:required="false"表示不強制拍照功能,因為如果裝置沒有相機,會禁掉拍照按鈕。