在上一篇 學習安卓開發[3] 使用RecyclerView顯示列表 中瞭解了在進行列表展示時RecyclerView的使用,本次記錄的是在應用中如何通過隱式Intent調用其它應用的功能,比如發簡訊、打電話、拍照等 隱式Intent 簡訊 判斷是否存在相關APP 相機 FileProvider Bi ...
在上一篇學習安卓開發[3] - 使用RecyclerView顯示列表中瞭解了在進行列表展示時RecyclerView的使用,本次記錄的是在應用中如何通過隱式Intent調用其它應用的功能,比如發簡訊、打電話、拍照等
- 隱式Intent
- 簡訊
- 判斷是否存在相關APP
- 相機
- FileProvider
- Bitmap
- 功能聲明
隱式Intent
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的形式提供
判斷是否存在相關APP
使用隱式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
要使用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只存儲實際像素數據,即使是已經壓縮過的照片,存入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"表示不強制拍照功能,因為如果設備沒有相機,會禁掉拍照按鈕。