背景:在維護公司移動辦公平臺APP 時,由於項目太過老舊,在兼容到最新版本時,出現了拍照、選擇圖片等問題,在此記錄一下遇到的問題及解決的方案。
調用系統相機拍照方案:
Tips:記得申請權限和做運行時權限處理
btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//在應用關聯緩存目錄中新建 output_image.jpg 圖片文件,因爲Android6.0之後,讀取SDCard被列爲危險權限,如將圖片存放在
//SD卡的任何其他目錄,均需進行運行時權限處理,而使用應用關聯目錄則可跳過該步驟,同時應用卸載會刪除該目錄
//具體路勁:/sdcard/Android/data/<pacaage name>/cache
//創建File 對象,用於存儲拍攝的照片,存儲在手機SD 卡的應用關聯緩存目錄下
File outputImage = new File(getExternalCacheDir(), "output_image.jpg");
try {
if (outputImage.exists()) {
outputImage.delete();
}
outputImage.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
//系統版本判斷
if (Build.VERSION.SDK_INT >= 24) {
//系統版本高於 Android7.0,者調用 FileProvider.getUriForFile() 方法,將file對象轉換爲一個封裝過的Uri對象,
// 參數一:Context 參數二:任意唯一的字符串 參數三:file對象
imageUri = FileProvider.getUriForFile(MainActivity.this, "com.cloudc.notificationtest.provider", outputImage);
} else {
//系統版本低於 Android7.0,就調用Uri.fromFile()方法,將file對象轉換爲Uri對象,該Uri對象標誌着 output_image.jpg 圖片
//的真實路勁,1
imageUri = Uri.fromFile(outputImage);
}
//啓動相機程序 隱式調用系統相機
Intent intent = new Intent("android.media.action.IMAGE_CAPTURE");
//添加這一句表示對目標應用臨時授權該Uri所代表的文件
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
//指定圖片的輸出地址
intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);
startActivityForResult(intent, TAKE_PHOTO);
}
});
因爲從 Android7.0 開始,直接使用本地真實路勁的 Uri 被認爲是不安全的,會拋出一個FileUriExposedException 異常,而FileProvider則是一種特殊的內容提供器來對數據進行保護,可以選擇行的將封裝過的 Uri 共享給外部,從而提高了應用的安全性。
注意:在Android 4.4 之前,訪問SD 卡的關聯目錄也是需要聲明權限的,從4.4 系統開始不在需要權限聲明
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<!--external-path 用來指定 Uri 共享,
name 屬性可隨便填寫,
path 屬性值表示共享的具體路徑,爲空表示將整個SD 卡共享
path:你所共享的子目錄。雖然name屬性是一個URI路徑片段,但是path是一個真實的子目錄名。
注意,path是一個子目錄,而不是單個文件或者多個文件。
-->
<external-path name="my_images"
path="."/>
</paths>
Manifest.xml文件
....
<!--name 屬性固定
authorities 屬性必須和代碼中 FileProvider.getUriForFile()中的第二個參數一致
-->
<!--meta-data 中指定了Uri 共享路徑,並引用了一個xml文件資源-->
<provider android:name="android.support.v4.content.FileProvider"
android:authorities="com.example.yhadmin.gank.test.Test2Activity.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths">
</meta-data>
</provider>
....
獲取圖片兼容方案:
前提得獲取SDCard 讀寫權限:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
部分代碼
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
switch (requestCode) {
case CHOOSE_PHOTO: {
if (resultCode == RESULT_OK) {
//判斷手機系統版本號
if (Build.VERSION.SDK_INT >= 19) {
//4.4及以上系統使用此方法處理圖片
handleImageOnKitKat(data);
} else {
//4.4以下系統使用此方法處理圖像
handleImageBeforeKitKat(data);
}
}
break;
}
default:
break;
}
}
//4.4以下系統使用此方法處理圖像
private void handleImageBeforeKitKat(Intent data) {
Uri uri = data.getData();
String imagePath = getImagePath(uri, null);
displayImage(imagePath);
}
@TargetApi(19)//4.4及以上系統使用此方法處理圖片
private void handleImageOnKitKat(Intent data) {
String imagePath = null;
Uri uri = data.getData();
Logger.t("test2Activity").d("URI:"+uri.toString());
if (DocumentsContract.isDocumentUri(this, uri)) {
//如果是 document 類型的 Uri,則通過 document id 處理
String documentId = DocumentsContract.getDocumentId(uri);
Logger.t("test2Activity").d("documentId:"+uri.toString());
if ("com.android.providers.media.documents".equals(uri.getAuthority())) {
//解析出數字格式的 id
String id = documentId.split(":")[1];
String selection = MediaStore.Images.Media._ID + "=" + id;
imagePath = getImagePath(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, selection);
}
} else if ("content".equalsIgnoreCase(uri.getScheme())) {
//如果是 content 類型的 Uri,則使用普通方式處理
Logger.t("test2Activity").d("content:"+uri.getScheme());
imagePath = getImagePath(uri, null);
} else if ("file".equalsIgnoreCase(uri.getScheme())) {
//如果是 Fiel 類型的 Uri,直接獲取圖片路徑即可
Logger.t("test2Activity").d("uri.getScheme():"+uri.getScheme());
imagePath = uri.getPath();
}
displayImage(imagePath);//加載圖片並顯示
}
//加載圖片
private void displayImage(String imagePath) {
if (imagePath!=null){
Bitmap bitmap = BitmapFactory.decodeFile(imagePath);
bindingView.picture.setImageBitmap(bitmap);
}else {
ToastHelper.show(Test2Activity.this,"獲取圖片失敗!");
}
}
//獲取圖片路徑
private String getImagePath(Uri uri, String selection) {
String path = null;
Cursor cursor = getContentResolver().query(uri, null, selection, null, null);
if (cursor != null) {
if (cursor.moveToFirst()) {
path = cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media.DATA));
}
cursor.close();
}
return path;
}
關於調用系統照相機不執行OnActivityResult的解決方式
1、確認權限是否添加
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>
/**
* Same as calling {@link #startActivityForResult(Intent, int, Bundle)}
* with no options.
*
* @param intent The intent to start.
* @param requestCode If >= 0, this code will be returned in
* onActivityResult() when the activity exits.
*
* @throws android.content.ActivityNotFoundException
*
* @see #startActivity
*/
public void startActivityForResult(@RequiresPermission Intent intent, int requestCode) {
startActivityForResult(intent, requestCode, null);
}
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(new File(filePath, TEMP_IMAGE_NAME)));
startActivityForResult(intent, 1);
1、Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM).getAbsolutePath();
2、 Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES);
android:configChanges="orientation|keyboardHidden"
android:screenOrientation="portrait"
@Override
protected void onSaveInstanceState(Bundle outState) {
outState.putString("file_path", mFilePath);
super.onSaveInstanceState(outState);
}
然後在onCreate()方法裏,判斷savedInstanceState不爲空時,取出圖片路徑的值。@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
if (savedInstanceState != null) {
mFilePath = savedInstanceState.getString("file_path");
// your code here
}
}
網上看到另外一種恢復的方法。Activity沒有重新創建,而是成員變量被回收了,當拍照返回時,在onActivityResult()方法中mFilePath爲空。解決方法是從onRestoreInstanceState()方法恢復數據。這種情況暫時沒遇到,做個記錄。@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
if (TextUtils.isEmpty(mFilePath)) {
mFilePath = savedInstanceState.getString("file_path");
}
super.onRestoreInstanceState(savedInstanceState);
}
/**
* 獲取圖片的旋轉角度
* @param path 圖片的絕對路徑
*/
private int getBitmapDegree(String imagePath) {
int degree = 0;
try {
// 從指定路徑下讀取圖片,並獲取其EXIF信息
ExifInterface exifInterface = new ExifInterface(imagePath);
// 獲取圖片的旋轉信息
int orientation = exifInterface.getAttributeInt(ExifInterface.TAG_ORIENTATION,
ExifInterface.ORIENTATION_NORMAL);
switch (orientation) {
case ExifInterface.ORIENTATION_ROTATE_90:
degree = 90;
break;
case ExifInterface.ORIENTATION_ROTATE_180:
degree = 180;
break;
case ExifInterface.ORIENTATION_ROTATE_270:
degree = 270;
break;
}
} catch (IOException e) {
e.printStackTrace();
}
return degree;
}
進行圖形變換如旋轉、縮放、移動的操作,可以使用Matrix類來完成。如下代碼展示了使用Matrix對圖片旋轉,生成新的Bitmap。
/**
* 將圖片按照某個角度進行旋轉
* @param bitmap 需要旋轉的圖片
* @param degree 旋轉角度
*/
public static Bitmap rotateBitmapByDegree(Bitmap bitmap, int degree) {
Bitmap result = null;
// 根據旋轉角度,生成旋轉矩陣
Matrix matrix = new Matrix();
matrix.postRotate(degree);
try {
// 將原始圖片按照旋轉矩陣進行旋轉,並得到新的圖片
result = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
} catch (Exception e) {
}
if (bitmap != null && !bitmap.isRecycled()) {
bitmap.recycle();
bitmap = null;
}
return result;
}