1. 程式人生 > >Android呼叫系統相機、相簿、裁剪圖片並壓縮上傳(適配7.0)

Android呼叫系統相機、相簿、裁剪圖片並壓縮上傳(適配7.0)

作者:八怪不姓醜
連結:http://www.jianshu.com/p/e11a34e2ea4f
著作權歸作者所有,本文經作者授權推送。

一、前言

最近在開發中遇到了一個比較棘手的問題
由於在之前使用的版本-targetSdkVersion小於24也就是小於7.0所以在使用相機拍照的時候不會出現問題,但是當targetSdkVersion版本大於或者等於7.0的時候用原來的方法呼叫相機就會丟擲一個SecurityException安全異常

通過搜尋發現是出於對系統安全的考慮,在sdk24及以上,對相機的操作需要使用FileProvider才行。
雖然有些麻煩,但除非用第三方框架,不然也只能自己動手去解決了。

二、操作流程

1、定義全域性標識

用於接收相簿選擇或拍照完成後的結果回撥

    //相簿
    private static final int PHOTO_TK = 0;   
   //拍照    private static final int PHOTO_PZ = 1;    
   //裁剪    private static final int PHOTO_CLIP = 2;

定義全域性的uri

private Uri contentUri;

2、相簿操作

這裡用的是一個自定義的dialog

update_dialog_TK.setOnClickListener(new
View.OnClickListener() {            
           @Override            public void onClick(View v) {                
               //呼叫系統圖庫,選擇圖片                Intent intent = new Intent(Intent.ACTION_PICK, null);                intent.setDataAndType(                        MediaStore.Images.Media.INTERNAL_CONTENT_URI, "image/*"
);                
               //返回結果和標識                startActivityForResult(intent, PHOTO_TK);                dialog.dismiss();            }        });

3、相機操作

3.1 Android7.0以下版本

直接呼叫系統相機,通過日誌可以看到會返回一個類似
file:///storage/emulated/0/temp.jpg的檔案

update_dialog_PZ.setOnClickListener(new View.OnClickListener() {           
           @Override            public void onClick(View v) {              
              // 啟動系統相機                Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);                
               // 獲取拍完後的uri                Uri mImageCaptureUri = Uri.fromFile(new File(Environment.getExternalStorageDirectory(), "temp.jpg"));                intent.putExtra(MediaStore.EXTRA_OUTPUT, mImageCaptureUri);              
                //  返回結果和標識                startActivityForResult(intent, PHOTO_PZ);                dialog.dismiss();            }        });

3.2 相容Android7.0以上版本

在新的版本中,Android對內容提供者做了限制,返回的不再是uri,而需要一個FileProvider
使用 content://代替了 file:///
所以如果直接使用原來的方法就會報錯。
所以在之前我們要給AndroidManifest檔案中application標籤新增許可權

<provider
            android:name="android.support.v4.content.FileProvider"
            android:authorities="你的包名.fileProvider"
            android:exported="false"
            android:grantUriPermissions="true">
            <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/provider_paths" />
        </provider>

並且需要建立一個xml
(可以在上面
android:resource="@xml/provider_paths"直接使用快捷鍵alt+enter建立,然後將程式碼拷貝進去)
external-path標籤用來指定Uri共享,name屬性的值可以自定義,path屬性的值表示共享的具體位置

<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <external-path name="external_files" path="."/>
</paths>

然後在呼叫系統相機的時候判斷

update_dialog_PZ.setOnClickListener(new View.OnClickListener() {           
           @Override            public void onClick(View v) {              
              // 啟動系統相機                Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);                Uri mImageCaptureUri;              
               // 判斷7.0android系統                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {                  
                //臨時新增一個拍照許可權                  intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);                    
                //通過FileProvider獲取uri                  contentUri = FileProvider.getUriForFile(UpdatePhotoActivity.this,                            
                "你的包名.fileProvider",  new File(Environment.getExternalStorageDirectory(), "temp.jpg"));                    intent.putExtra(MediaStore.EXTRA_OUTPUT, contentUri);                } else {                    mImageCaptureUri = Uri.fromFile(new File(Environment.getExternalStorageDirectory(), "temp.jpg"));                    intent.putExtra(MediaStore.EXTRA_OUTPUT, mImageCaptureUri);                }                startActivityForResult(intent, PHOTO_PZ);                dialog.dismiss();            }        });

4、 onActivityResult

使用onActivityResult接收操作完成的回撥

   @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {        
                 super.onActivityResult(requestCode, resultCode, data);        
                if (resultCode == Activity.RESULT_OK) {            
                 switch (requestCode) {                
                   case PHOTO_PZ:                  
                   //獲取拍照結果,執行裁剪                   Uri pictur;                  
                   //如果是7.0android系統,直接獲取uri                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {                        pictur = contentUri;                    } else {                        pictur = Uri.fromFile(new File(                                Environment.getExternalStorageDirectory() + "/temp.jpg"));                    }                    startPhotoZoom(pictur);                    
                   break;              
                   case PHOTO_TK:                    
                   //獲取相簿結果,執行裁剪                    startPhotoZoom(data.getData());                    
                 break;                  case PHOTO_CLIP:                    
                 //裁剪完成後的操作,上傳至伺服器或者本地設定                    break;            }        }    }

5、裁剪

當拍照完成後或者本地選擇圖片完畢之後會執行該方法。同時也做了7.0適配

/**
     * 裁剪圖片的方法.
     * 用於拍照完成或者選擇本地圖片之後
     */
    private Uri uritempFile;    
       public void startPhotoZoom(Uri uri) {        Log.e("uri=====", "" + uri);        Intent intent = new Intent("com.android.camera.action.CROP");        intent.setDataAndType(uri, "image/*");        intent.putExtra("crop", "true");        intent.putExtra("aspectX", 1);        intent.putExtra("aspectY", 1);        intent.putExtra("outputX", 60);        intent.putExtra("outputY", 60);        
       //uritempFile為Uri類變數,例項化uritempFile        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {            
           //開啟臨時許可權            intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION);          
          //重點:針對7.0以上的操作            intent.setClipData(ClipData.newRawUri(MediaStore.EXTRA_OUTPUT, uri));            uritempFile = uri;        } else {            uritempFile = Uri.parse("file://" + "/" + Environment.getExternalStorageDirectory().getPath() + "/" + "small.jpg");        }        intent.putExtra(MediaStore.EXTRA_OUTPUT, uritempFile);        intent.putExtra("return-data", false);        intent.putExtra("outputFormat", Bitmap.CompressFormat.JPEG.toString());        intent.putExtra("noFaceDetection", true);        startActivityForResult(intent, PHOTO_CLIP);    }

三、裁剪後的處理

在onActivityResult方法還有一個最後的處理,前面只是在給圖片做操作,下面是將完成後的圖片選擇載入到本地或者上傳到專案的服務端,一般在實際開發中用的比較多

1、載入至本地

使用Picasso載入,因為Picasso支援Uri、File、Stirng型別,不需要做進一步的轉換就可以直接用。

Picasso.with(this)
                 .load(uritempFile)
                 .into(cardviewImg);

2、轉成File並壓縮、上傳

在實際開發中有時候需要對圖片進行進一步的處理,比如傳到伺服器需要File型別的檔案,所以就需要進行再一次的轉換。
具體可以根據需求來進行相應的操作

//裁剪後的影象轉成BitMap
photo1 = BitmapFactory.decodeStream(getContentResolver().openInputStream(uritempFile));
//建立路徑
String path = Environment.getExternalStorageDirectory()                                .getPath() + "/Pic";
//獲取外部儲存目錄
file = new File(path);
Log.e("file", file.getPath());
 //建立新目錄
file.mkdirs();
 //以當前時間重新命名檔案
long i = System.currentTimeMillis();
 //生成新的檔案
file = new File(file.toString() + "/" + i + ".png"); Log.e("fileNew", file.getPath());
 //建立輸出流 OutputStream out = new FileOutputStream(file.getPath());
 //壓縮檔案,返回結果
boolean flag = photo1.compress(Bitmap.CompressFormat.JPEG, 100, out);

專案demo地址:https://github.com/wapchief/android-CollectionDemo