這篇文章主要介紹了適配Android 10(Q)後,調用系統拍照,系統相冊,系統裁剪和上傳問題,這是一個很經常使用的功能,可是在Android 6.0,Android 7.0和Android 10.0以上版本的實現都有所不一樣,這篇文章從Android 4適配到Android 10。android
以前寫畢設的時候,在寫上傳頭像的功能時,參考網上的方法寫了一大堆,在個人手機(Android 9)上能夠正常運行,當時沒多想,覺得高版本能夠向下兼容,後來我把程序發給同窗去試驗,結果都告訴我上傳頭像用不了,一問才知道他們用的是Android 10的手機,因而只能上網查找緣由,而後發現Android 10的存儲方式發生了變化,Android 10的文件系統採用了沙盒文件系統,最顯著的變化就是文件系統變安全了,因而app也沒辦法拿到外部文件的絕對路徑了,網上給出的方法就是將共享文件複製到沙盒目錄下,而後再進行文件操做。話很少說,上代碼。git
demo源碼github
在文件清單AndroidManifest.xml中添加權限:安全
1 <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /><!-- 儲存卡的讀權限 --> 2 <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /><!-- 儲存卡的寫權限 --> 3 <uses-permission android:name="android.permission.CAMERA" /><!-- 調用相機權限 -->
在官方7.0的以上的系統中,嘗試傳遞 file://URI可能會觸發FileUriExposedException,使用FileProvider來共享文件,AndroidManifest.xml:app
<application ... <!-- 兼容Android7.0拍照閃退 --> <provider android:name="androidx.core.content.FileProvider" android:authorities="com.example.camera.test" android:exported="false" android:grantUriPermissions="true"> <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/file_paths" /> </provider> </application>
在主界面放一個ImageView和兩個按鈕,activity_main.xml:ide
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" tools:context=".MainActivity"> <ImageView android:id="@+id/image" android:layout_width="250dp" android:layout_height="250dp" android:layout_marginTop="20dp" android:layout_gravity="center_horizontal"/> <TextView android:id="@+id/tv_camera" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="50dp" android:text="相機" android:textSize="18sp" android:textColor="#FFF" android:padding="10dp" android:background="#1878FF" android:layout_marginHorizontal="20dp" android:gravity="center_horizontal"/> <TextView android:id="@+id/tv_album" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="50dp" android:text="相冊" android:textSize="18sp" android:textColor="#FFF" android:padding="10dp" android:background="#1878FF" android:layout_marginHorizontal="20dp" android:gravity="center_horizontal"/> </LinearLayout>
接下來是主頁面的代碼:ui
獲取控件,對兩個按鈕添加點擊監聽,判斷權限:this
private ImageView image;
private TextView tvCamera, tvAlbum;
@Override
protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); init(); } private void init(){ image = findViewById(R.id.image); tvCamera = findViewById(R.id.tv_camera); tvAlbum = findViewById(R.id.tv_album); tvCamera.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { //相機 if ((ContextCompat.checkSelfPermission(context, Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED) && (ContextCompat.checkSelfPermission(context, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) && (ContextCompat.checkSelfPermission(context, Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED)) { //權限都齊的狀況下,跳轉相機 openCamera(); } else { if (activity != null) { //請求權限 ActivityCompat.requestPermissions(activity, new String[]{ Manifest.permission.CAMERA, Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE }, PHOTO_REQUEST_CAMERA); } } } }); tvAlbum.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { //相冊 if ((ContextCompat.checkSelfPermission(context, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) && (ContextCompat.checkSelfPermission(context, Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED)) { //權限都齊的狀況下,跳轉相冊 openAlbum(); } else { if (activity != null) { //請求權限 ActivityCompat.requestPermissions(activity, new String[]{ Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE }, PHOTO_REQUEST_ALBUM); } } } }); }
權限申請回調:spa
@Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); switch (requestCode) { case PHOTO_REQUEST_CAMERA: //相機權限請求回調 if (grantResults.length > 0) { if (grantResults[0] == PackageManager.PERMISSION_GRANTED && grantResults[1] == PackageManager.PERMISSION_GRANTED && grantResults[2] == PackageManager.PERMISSION_GRANTED) { //跳轉相機 openCamera(); } else { //無權限提示 Toast.makeText(context, "權限未經過", Toast.LENGTH_SHORT).show(); } } break; case PHOTO_REQUEST_ALBUM: if (grantResults.length > 0) { if (grantResults[0] == PackageManager.PERMISSION_GRANTED && grantResults[1] == PackageManager.PERMISSION_GRANTED) { //跳轉相冊 openAlbum(); } else { //無權限提示 Toast.makeText(context, "權限未經過", Toast.LENGTH_SHORT).show(); } } break; } }
跳轉相機:code
private void openCamera(){ Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); //判斷是否有相機 if (activity != null && context != null && intent.resolveActivity(activity.getPackageManager()) != null){ File file; Uri uri = null; if (isAndroidQ){ //適配Android10 uri = createImageUri(context); } else { //Android10如下 file = createImageFile(context); if (file != null){ //Android10如下 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N){ //適配Android7.0文件權限 uri = FileProvider.getUriForFile(context, "com.example.camera.test", file); } else { uri = Uri.fromFile(file); } } } imageUri = uri; Log.e(TAG, "相機保存的圖片Uri:" + imageUri); if (uri != null){ intent.putExtra(MediaStore.EXTRA_OUTPUT, uri); intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION); startActivityForResult(intent, CAMERA_REQUEST_CODE); } } }
Android 10以上的建立Uri,Uri建立在沙盒內:
contentValues.put(MediaStore.Images.Media.RELATIVE_PATH, Environment.DIRECTORY_PICTURES + "/0/");
以上設置的保存路徑爲:".../包名/files/Pictures/0",可按需更改
用於保存拍照以後的照片:
private Uri createImageUri(@NonNull Context context){ String status = Environment.getExternalStorageState(); ContentValues contentValues = new ContentValues(); contentValues.put(MediaStore.Images.Media.DISPLAY_NAME, SAVE_AVATAR_NAME); contentValues.put(MediaStore.Images.Media.MIME_TYPE, "image/*"); contentValues.put(MediaStore.Images.Media.RELATIVE_PATH, Environment.DIRECTORY_PICTURES + "/0/"); //判斷是否有SD卡 if (status.equals(Environment.MEDIA_MOUNTED)){ return context.getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues); } else { return context.getContentResolver().insert(MediaStore.Images.Media.INTERNAL_CONTENT_URI, contentValues); } }
Android 10如下的返回一個file來保存拍照後的圖片:
private File createImageFile(@NonNull Context context){ File file = context.getExternalFilesDir(Environment.DIRECTORY_PICTURES); if (file != null && !file.exists()){ if (file.mkdir()){ Log.e(TAG, "文件夾建立成功"); } else { Log.e(TAG, "file爲空或者文件夾建立失敗"); } } File tempFile = new File(file, SAVE_AVATAR_NAME); Log.e(TAG, "臨時文件路徑:" + tempFile.getAbsolutePath()); if (!Environment.MEDIA_MOUNTED.equals(EnvironmentCompat.getStorageState(tempFile))){ return null; } return tempFile; }
跳轉相冊:
private void openAlbum(){ Intent intent = new Intent(Intent.ACTION_PICK, null); intent.setDataAndType(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, "image/*"); startActivityForResult(intent, ALBUM_REQUEST_CODE); }
跳轉裁剪,裁剪在相機拍照後跳轉,用一個file來加載:
private void openCrop(Uri uri){ if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED) && context != null){ file = new File(getExternalFilesDir(Environment.DIRECTORY_PICTURES + "/0"), SAVE_AVATAR_NAME); } Intent intent = new Intent("com.android.camera.action.CROP"); intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION); intent.setDataAndType(uri, "image/*"); // 設置裁剪 intent.putExtra("crop", "true"); // aspectX aspectY 是寬高的比例 intent.putExtra("aspectX", 1); intent.putExtra("aspectY", 1); // 裁剪後輸出圖片的尺寸大小 intent.putExtra("outputX", 250); intent.putExtra("outputY", 250); //適配Android10,存放圖片路徑 intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(file)); // 圖片格式 intent.putExtra("outputFormat", "PNG"); intent.putExtra("noFaceDetection", true);// 取消人臉識別 intent.putExtra("return-data", true);// true:不返回uri,false:返回uri startActivityForResult(intent, TAILOR_REQUEST_CODE); }
跳轉相機、相冊和裁剪的回調,若是有上傳需求的,直接上傳代碼中的file便可:
@Override protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { super.onActivityResult(requestCode, resultCode, data); if (resultCode == -1){ //回調成功 switch (requestCode) { case CAMERA_REQUEST_CODE: //相機回調 Log.e(TAG, "相機回調"); if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { //照片裁剪 openCrop(imageUri); } else { Toast.makeText(context, "未找到存儲卡", Toast.LENGTH_SHORT).show(); } break; case ALBUM_REQUEST_CODE: //相冊回調 Log.e(TAG, "相冊回調"); if (data != null && data.getData() != null) { image.setImageURI(data.getData()); //若是須要上傳操做的可使用這個方法 File file = FileUtils.uriToFile(data.getData(), context); //這裏的file就是須要上傳的圖片了 } break; case TAILOR_REQUEST_CODE: //圖片剪裁回調 Log.e(TAG, "圖片剪裁回調"); // Glide.with(context).load(file).into(image); Uri uri = Uri.fromFile(file); image.setImageURI(uri); //若是須要上傳全局的這個file就是須要上傳的圖片了 File file = this.file; break; } } else { Toast.makeText(context, "取消", Toast.LENGTH_SHORT).show(); } }
以上,若是有改進的建議的,歡迎騷擾
QQ:1336140321