1. 程式人生 > >Android截圖,相容android 5.0和大圖片

Android截圖,相容android 5.0和大圖片

Android中選擇一張圖片然後擷取部分作為頭像是一個非常常見的需求。當然很多個性化的應用中都會有自己專門定製的選擇圖片和裁剪圖片。但本文現在主要討論的是一些系統的東西,包括開啟系統圖片的選擇、呼叫系統的截圖功能。
       以前在呼叫系統的截圖的時候我都是這樣使用的。

Intent intent = new Intent(Intent.ACTION_PICK, android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
intent.setType("image/*");
intent.putExtra("crop", "true");
intent.putExtra("aspectX", 1);
intent.putExtra("aspectY", 1);
intent.putExtra("outputX", 120);
intent.putExtra("outputY", 120);
intent.putExtra("return-data", true);
startActivityForResult(intent, CROP_IMG);
這樣寫好像真的很簡單也很好懂,Intent.ACTION_PICK開啟選擇圖片的介面,然後通過下面的intent的一些設定就可以擷取一張圖片了。這樣寫在我的很多的機器上確實也沒有什麼問題,但是直到碰到了5.0的手機。在5.0的手機中,首先出現的一個問題是,上面的程式碼沒有出現圖片的裁剪功能。流程變成了選擇圖片,點選圖片以後就選擇了整張的圖片。選擇一整張圖片確實是沒問題,但是需求是必須給使用者一個擷取圖片的過程(其實選取一整張圖片還是有一個很嚴重的問題的,就是圖片太大的問題)。
經過自己的分析,這個的問題是在5.0的手機中,只執行了一個選擇圖片的過程。或許應該將圖片的選擇和圖片的擷取分開才能完成自己的需求。
第一步.如何開啟圖片的選擇介面

Intent.ACTION_PICK = "android.intent.action.PICK";選擇一個數據返回它的URI。返回的格式:content://media/external/images/media/67。在所有的版本中都是返回這樣的格式。

Intent.ACTION_GET_CONTENT = "android.intent.action.GET_CONTENT";選擇一個數據。
API 19及以上,返回的Uri:content://com.android.providers.media.documents/document/image%3A57425
API 19以下,返回的Uri:content://media/external/images/media/31956
在API 19中又可以從下載內容

(其實在API 19及以上從很多不同的地方拿到的Uri不相同)中開啟一些內容,Uri是這樣的型別。
content://com.android.providers.downloads.documents/document/4

Intent.ACTION_OPEN_DOCUMENT = "android.intent.action.OPEN_DOCUMENT";(API 19以後才有這個),可以開啟雲端的檔案。只有在API 19以上才可以使用,得到的Uri跟API 19在Intent.ACTION_GET_CONTENT中得到的一樣。

所以第一步可以有三種選擇來開啟一個選擇圖片的介面。根據我們的需求應該選擇ACTION_PICK來做,因為只有這個可以相容所有的版本。(當然使用ACTION_GET_CONTENT 和ACTION_OPEN_DOCUMENT 也是可以做的,但是區分其實蠻大的

使用ACTION_PICK,開啟圖片選擇的過程。

Intent intent = new Intent("android.intent.action.PICK");
intent.setDataAndType(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, "image/*");
startActivityForResult(intent, PICK_IMG);
在選中一張圖片以後就會在onActivityResult中得到一張圖片的Uri。然後就需要開啟這張圖片並裁剪。

第二步.如何的擷取圖片

系統的截圖 com.android.camera.action.CROP。

Intent intent = new Intent("com.android.camera.action.CROP");
intent.setDataAndType(uri, "image/*"); 
intent.putExtra("crop", "true");  //為true才會出現裁剪的框
intent.putExtra("aspectX", 1);    //寬高的比例
intent.putExtra("aspectY", 1);
intent.putExtra("outputX", width); // 裁剪得到的寬
intent.putExtra("outputY", height);// 裁剪得到的高
intent.putExtra("return-data", true); // 返回資料
intent.putExtra("scale", true);       
intent.putExtra("scaleUpIfNeeded", true); // 去掉黑邊
intent.putExtra("noFaceDetection", true); // 不需要人臉識別
startActivityForResult(intent, CUT_IMG);

這樣就可以在onActivityResult()方法中做相關的處理。

Bitmap bitmap = data.getParcelableExtra("data"); 

其實只有基於AOSP的Android手機才會具有com.android.camera.action.CROP,所以需要做相應的異常的處理。所以需要加一個ActivityNotFoundException的異常的處理。

try
    {
        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", width);
        intent.putExtra("outputY", height);
        intent.putExtra("return-data", true); 
        intent.putExtra("scale", true);
        intent.putExtra("scaleUpIfNeeded", true);
        intent.putExtra("noFaceDetection", true);
        startActivityForResult(intent, CUT_IMG);
    }
    catch (ActivityNotFoundException e)
    {
        // 如果手機不是基於AOSP的話就是不能這麼做的。可以直接通過出過來的uri來得到整張的圖片
        Bitmap bitmap = null;
        bitmap = MediaStore.Images.Media.getBitmap(this.getContentResolver(), uri);
}

這樣在很多機器上測試的時候都沒有問題,但是這樣就結束了嗎?還沒有。當圖片很大的時候,這樣還是會出現問題,一整張圖片可能直接會使記憶體溢位,或者說裁剪的一張圖片會使記憶體溢位(特別是對手機畫素很高的來說)。所有呢,最好的情況是對圖片進行壓縮。
BitmapFactory.Options opts = new Options();
opts.inJustDecodeBounds = true; //為true的話就不會decode的時候生成Bitmap。
BitmapFactory.decodeStream(is, null, opts);
//但是可以通過opts拿到圖片的寬和高
int bmpWidth = opts.outWidth;    
int bmpHeight = opts.outHeight;
//  選擇一個比較大的比例,這樣縮放以後就不會放不下
int scale = Math.max(bmpWidth / 300, bmpHeight / 300);
opts.inSampleSize = scale;  // 設定縮放的比例
// 這樣得到的就是一個壓縮以後的圖片,一般不會記憶體溢位(注意這個地方是is1)
Bitmap bitmap = BitmapFactory.decodeStream(is1, null, opts);

所以內可以專門寫一個用來壓縮的函式。(我這裡是指定大小為300,300)

/*
     * 通過Uri得到壓縮以後的圖片
     */
    private Bitmap getBitmapFromBigImagByUri(Uri uri)
    {
        Bitmap result = null;
        InputStream is1 = null;
        InputStream is2 = null;
        try
        {
            // 如果圖片太大,這個地方依舊會出現問題
            // Bitmap bitmap = MediaStore.Images.Media.getBitmap(this.getContentResolver(), uri);
            // 使用兩個inputstream的原因
            // http://stackoverflow.com/questions/12841482/resizing-bitmap-from-inputstream
            is1 = getContentResolver().openInputStream(uri);
            is2 = getContentResolver().openInputStream(uri);
            BitmapFactory.Options opts1 = new Options();
            opts1.inJustDecodeBounds = true;
            BitmapFactory.decodeStream(is1, null, opts1);
            int bmpWidth = opts1.outWidth;
            int bmpHeight = opts1.outHeight;
            int scale = Math.max(bmpWidth / 300, bmpHeight / 300);
            BitmapFactory.Options opts2 = new Options();
            // 縮放的比例
            opts2.inSampleSize = scale;
            // 記憶體不足時可被回收
            opts2.inPurgeable = true;
            // 設定為false,表示不僅Bitmap的屬性,也要載入bitmap
            opts2.inJustDecodeBounds = false;
            result = BitmapFactory.decodeStream(is2, null, opts2);
        }
        catch (Exception ex)
        {
        }
        finally
        {
            if (is1 != null)
            {
                try
                {
                    is1.close();
                }
                catch (IOException e1)
                {
                }
            }
            if (is2 != null)
            {
                try
                {
                    is2.close();
                }
                catch (IOException e2)
                {
                }
            }
        }
        return result;
    }


decodeStream()的坑:點選開啟連結