安卓開發——拍照、裁剪並儲存為頭像報錯:裁剪圖片無法儲存的

來源:互聯網
上載者:User

標籤:margin   class   env   image   需要   media   啟動   format   nts   

 

在做學校大創項目的安卓開發時,需要從相簿擷取圖片或者拍照,然後裁剪儲存為頭像。由於我是第一次弄安卓開發,也對Android現在越來越多的許可權限制不瞭解,debug過程真的是異常心塞啊。

  閑話不說(文末慢慢話癆),我開始是在網上找了一些代碼打算用到項目上試試,但是連個拍照或者從相簿選擇圖片都頻繁報錯(應該還是因為sd卡許可權之類的吧),折騰了一晚上沒有解決,第二天還是老老實實的看《第一行代碼》,邊學邊寫。在這裡我簡單梳理一下流程(關於裁剪後圖片無法儲存的問題的解釋請直接跳到水平線之後):

  •   調用手機網路攝影機拍照:

 

 //建立file檔案,用於儲存相機拍下的照片,這裡我命名為my_head_image.jpg,並將它放在 //手機SD卡的應用關聯緩衝中。              /*  File outputImage = new File(getExternalCacheDir(), "my_head_image.jpg");               try {                    if (outputImage.exists()) {                        outputImage.delete();                    }                    outputImage.createNewFile();                } catch (IOException e) {                    e.printStackTrace();                }//將File對象轉換為Uri對象,先進行系統版本的判定,Android7.0以後的版本和之前的版本不 //太一樣                if (Build.VERSION.SDK_INT >= 24) {                    imageUri = FileProvider.getUriForFile(ChangeMyDetails.this, "com.example.write.fileprovider", outputImage);                } else {                    imageUri = Uri.fromFile(outputImage);*/
    File outputImage = new File(Environment.getExternalStorageDirectory(), "my_head_image.jpg");
    String path = outputImage.getAbsolutePath();
    Log.i("ChangeMyDetails", "outputImage路徑為 "+path);
    try {
     if (outputImage.exists()) {
     outputImage.delete();
     }
     outputImage.createNewFile();
    } catch (IOException e) {
     e.printStackTrace();
    }

    imageUri = Uri.fromFile(outputImage);
  //啟動相機程式
Intent intent = new Intent("android.media.action.IMAGE_CAPTURE");
intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);
startActivityForResult(intent, TAKE_PHOTO);

  橙色部分的代碼是《第一行代碼》上的,如果拍照後的圖片直接作為頭像不裁剪的話這段是沒問題的,但是你懂得,後來就崩了。在這裡首先建立fFile對象,用於存放拍下的照片,並將它儲存在SD卡的應用關聯緩衝目錄下,調用getCacheDir()得到這個目錄,具體路徑是/sdcard/Android/data/<你的package name>/cache。為什麼要放在這裡呢? 因為從Android6.0系統開始,讀寫SD卡被視為危險許可權,如何放在其他目錄,都要在運行時進行許可權處理,而使用應用關聯目錄則可以跳過這一步。注意:在這裡我就種下了裁剪後無法儲存的隱患。(橙色下面的更正代碼是後話了,因為還要涉及許可權問題,我後面會講,所以你看到這裡欣喜的粘貼到你的項目裡還是會報錯的)

  接著進行系統版本判斷,FileProvider的getUriForFile()方法將File對象封裝為Uri對象。getUriForFile()方法接收3個參數,第一個是要求傳入的context對象,第二個可以是任意唯一的字串(後面manifest.xml中註冊<procider>的android:authority要用到),第三個是要封裝的這個File對象。之所以添加這一步,因為Android7.0系統開始,直接使用本地Uri被認為是不安全的,會拋出FIleURIExposedException異常。FileProvider則是一種特殊的得內容提供器,可以選擇性的將封裝過的Uri共用給外部,更加安全。

  關於內容提供器,還要在manifest,xml中進行註冊:

 

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

 

  其中,android:authorities屬性值必須與FileProvider.getUriForFile()方法中的第二個參數一致,另外,用<meta-data>來指定Uri的共用路徑,並引用@xml/provider_paths資源,這個資源需要自己建立。

  右擊res目錄→New→Directory,建立一個xml目錄,然後右擊xml目錄→New→File,建立一個provider_paths.xml的檔案:

<?xml version="1.0" encoding="utf-8"?><paths xmlns:android="http://schemas.android.com/apk/res/android">    <external-path name="my_images" path="."/></paths>

  其中,external-path 就是用來指定Uri共用的,name屬性值自訂就可以了,path屬性為空白表示整個SD卡進行共用。

 

  •   裁剪照片:

   拍照時,使用startActivityForResult(intent, TAKE_PHOTO)來啟動活動,因此拍完照會有結果返回到onActivityResult()方法中,拍照成功後執行接下來的裁剪。 onActivityResult()方法中還有從相簿選擇圖片、裁剪成功後返回執行的操作,我就一起貼出來了。

 


@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {        //使用者沒有進行有效操作,返回        if (requestCode == RESULT_CANCELED) {            Toast.makeText(getApplication(), "取消", Toast.LENGTH_LONG).show();            return;        }        switch (requestCode) {            case FROM_GALLERY:                if (resultCode == RESULT_OK) {                    if (Build.VERSION.SDK_INT >= 19) {                        //4.4以上系統使用                        handleImageOnKitKat(data);                    } else {                        handleImageBeforeKitKat(data);                    }                }                break;            case TAKE_PHOTO://   裁剪照片                    cropRawPhoto(imageUri);                         break;            case RESULT_REQUEST_CODE:                 if (cropImgUri !=null) {                    try {                        Bitmap headImage = BitmapFactory.decodeStream(getContentResolver().openInputStream(cropImgUri));                        headImageButton.setImageBitmap(headImage);                    } catch (Exception e) {                        e.printStackTrace();                    }                }else {                    Toast.makeText(this,"cropImgUri為空白!",Toast.LENGTH_SHORT).show();                }                break;        }    }

public void cropRawPhoto(Uri uri) {
//建立file檔案,用於儲存剪裁後的照片
File cropImage = new File(Environment.getExternalStorageDirectory(), "crop_image.jpg");
String path = cropImage.getAbsolutePath();
try {
if (cropImage.exists()) {
cropImage.delete();
}
cropImage.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
cropImgUri = Uri.fromFile(cropImage);
Intent intent = new Intent("com.android.camera.action.CROP");
//設定源地址uri
intent.setDataAndType(imageUri, "image/*");
intent.putExtra("crop", "true");
intent.putExtra("aspectX", 1);
intent.putExtra("aspectY", 1);
intent.putExtra("outputX", 200);
intent.putExtra("outputY", 200);
intent.putExtra("scale", true);
//設定目的地址uri
intent.putExtra(MediaStore.EXTRA_OUTPUT, cropImgUri);
//設定圖片格式
intent.putExtra("outputFormat", Bitmap.CompressFormat.JPEG.toString());
intent.putExtra("return-data", false);
intent.putExtra("noFaceDetection", true); // no face detection
startActivityForResult(intent, RESULT_REQUEST_CODE);
}
 

   startActivityForResult(intent, RESULT_REQUEST_CODE);執行後,跳轉到onActivityResult()中執行case RESULT_REQUEST_CODE:部分,代碼已經貼出來了。就是調用BitmapFactory.decodeStream()方法將cropImage解析為bitmap對象。

  然後,開始運行。

  那麼,問題來了(猜測是:由於裁剪後的圖片儲存到Cache裡會耗費大量記憶體,Android是不允許你這樣做的):

  這裡有一篇一篇博文進行瞭解釋:http://www.cnblogs.com/tianzhijiexian/p/4059006.html

 

 

  最開始,我是把相機拍下的照片和裁剪後的照片都存在關聯應用緩衝裡,就是前面橙色部分代碼的操作(貼上的代碼是我後來改正過的沒問題的代碼)。啟動相機程式拍照並儲存是正常的,圖片也儲存了。但是,裁剪之後的圖片無法儲存到Cache目錄裡,我沿著/sdcard/Android/data/<你的package name>/cache路徑開啟看了看,是0kb。

 

 

 

  本來最開始我就懷疑這個Cache儲存可能會有問題,但是又想,拍下照片都可以好好儲存為什麼裁剪後的就不能儲存呢?這不公平啊!於是乎,我著手改其他的我也懷疑的地方,在網上搜尋相關解答折騰很久還是解決不了。最後,我決定驗證最後一個猜想:裁剪後的圖片以某種詭異不明的方式,無法儲存到Cache裡面。說幹就幹:

  step1:把File路徑換成普通的,也就是把File outputImage = new File(getExternalCacheDir(), "my_head_image.jpg")換成File outputImage = new File(Environment.getExternalStorageDirectory(), "my_head_image.jpg");同理更改File cropImage = new File(Environment.getExternalStorageDirectory(), "crop_image.jpg");

  step2:在manifest.xml中註冊許可權:<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>  

   step3:進行到這裡,我運行了一次,還是異常,應該還是許可權問題沒有處理完,我在onCreate()方法裡添加了這樣一段:

StrictMode.VmPolicy.Builder builder = new StrictMode.VmPolicy.Builder();        StrictMode.setVmPolicy(builder.build());        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {            builder.detectFileUriExposure();        }

 

   再運行,It works!

 

文末嘮叨

  關於StrictMode我就不細講了(心累+懶)。

  你懂得,在網上找解決bug的方法需要技巧、運氣、時間,兜了一大圈,對於我來說,我在網上找solution八成都會花掉大堆時間,很多問題那都是別人遇到的麻煩和解決方案,對自己不一定適用,但是自己還是得作死的去多嘗試,然後折騰一下午或者一晚上,心想著還不如在這個時間裡換種方式浪費生命,比如看劇、睡覺、和朋友閑聊、以及吃……

  對於安卓開發來說,太久之前的solution可能並不適用於現在了,比如現在越來越嚴格的許可權問題。

  有時候,在網上瞎找,不如好好看書,搞清楚到底是怎麼一個流程,哪裡會出錯,反而會更快一些解決問題,也更有收穫。

  總而言之,要高效率解決問題,還是得清楚整個代碼的流程。

  好了,現在我要換種方式浪費生命了……

 

 

 

 

安卓開發——拍照、裁剪並儲存為頭像報錯:裁剪圖片無法儲存的

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.