適配Android7.0調(diào)取相機(jī)拍照并返回照片

Android調(diào)取系統(tǒng)相機(jī)拍照獲取到拍攝照片或從相冊(cè)中直接選取照片后展示上傳是Android開發(fā)中很常見的一個(gè)功能,實(shí)現(xiàn)的思路主要是:

  • 自Android 6.0以后對(duì)某些涉及用戶隱私權(quán)限的獲取需要?jiǎng)討B(tài)獲取指郁,所以首先是檢查權(quán)限蚓土,如沒有權(quán)限則動(dòng)態(tài)申請(qǐng)權(quán)限甩挫,這里我們需要用到的權(quán)限是WRITE_EXTERNAL_STORAGE和CAMERA。

  • 自Android 7.0后系統(tǒng)禁止應(yīng)用向外部公開file://URI 棍弄,因此需要FileProvider來向外界傳遞URI脊阴。

  • 獲取到拍照后的照片,按照現(xiàn)在的手機(jī)拍照文件大小來說不做處理直接展示很容易發(fā)生OOM责嚷,因此這一步需要對(duì)圖片做壓縮處理鸳兽。


一、動(dòng)態(tài)申請(qǐng)權(quán)限

首先在Mainfest.xml文件中聲明權(quán)限

<uses-permission android:name="android.permission.CAMERA"/>
<!--  因?yàn)榕恼招枰獙懭胛募?所以需要申請(qǐng)讀取內(nèi)存的權(quán)限 -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

接下來點(diǎn)擊Button按鈕模擬調(diào)取拍照

 private static final int REQUEST_PERMISSION_CODE = 101;

mButtonTakePhoto.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {//大于Android 6.0
                    if (!checkPermission()) { //沒有或沒有全部授權(quán)
                        requestPermissions(); //請(qǐng)求權(quán)限
                    }
                } else {
                    takePhoto();//拍照邏輯
                }
            }
        });

    //檢查權(quán)限
    private boolean checkPermission() {
        //是否有權(quán)限
        boolean haveCameraPermission = ContextCompat.checkSelfPermission(mContext, Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED;

        boolean haveWritePermission = ContextCompat.checkSelfPermission(mContext,
                Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED;

        return haveCameraPermission && haveWritePermission;

    }

     // 請(qǐng)求所需權(quán)限
    @RequiresApi(api = Build.VERSION_CODES.M)
    private void requestPermissions() {
        requestPermissions(new String[]{Manifest.permission.CAMERA, Manifest.permission.WRITE_EXTERNAL_STORAGE}, REQUEST_PERMISSION_CODE);
    }

  // 請(qǐng)求權(quán)限后會(huì)在這里回調(diào)
    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        switch (requestCode) {
            case REQUEST_PERMISSION_CODE:

                boolean allowAllPermission = false;

                for (int i = 0; i < grantResults.length; i++) {
                    if (grantResults[0] != PackageManager.PERMISSION_GRANTED) {//被拒絕授權(quán)
                        allowAllPermission = false;
                        break;
                    }
                    allowAllPermission = true;
                }

                if (allowAllPermission) {
                    takePhotoOrPickPhoto();//開始拍照或從相冊(cè)選取照片
                } else {
                    Toast.makeText(mContext, "該功能需要授權(quán)方可使用", Toast.LENGTH_SHORT).show();
                }

                break;
        }
    }

在點(diǎn)擊拍照按鈕后罕拂,調(diào)用 ContextCompat.checkSelfPermission( )方法檢查是否有權(quán)限贸铜,方法返回值為0說明已經(jīng)授權(quán)。沒授權(quán)的情況下聂受,調(diào)用requestPermissions( )方法,該方法的第一個(gè)參數(shù)為一個(gè)數(shù)組烤镐,數(shù)組中的值為你要申請(qǐng)的一個(gè)或多個(gè)權(quán)限的值蛋济,第二個(gè)參數(shù)為請(qǐng)求碼。

調(diào)用requestPermission( )方法后我們需要在Activity中重寫onRequestPermissionsResult()方法炮叶,在該方法中會(huì)得到回調(diào)結(jié)果碗旅,方法中第一個(gè)參數(shù)是請(qǐng)求碼,第二個(gè)參數(shù)是我們申請(qǐng)的權(quán)限數(shù)組镜悉,第三個(gè)參數(shù)數(shù)組中每一個(gè)值對(duì)應(yīng)申請(qǐng)的每一個(gè)權(quán)限的返回值祟辟,值為0或-1,0代表授權(quán)侣肄,-1代表拒絕授權(quán)旧困。源碼如下

 /**
     * Permission check result: this is returned by {@link #checkPermission}
     * if the permission has been granted to the given package.
     */
    public static final int PERMISSION_GRANTED = 0;//授權(quán)成功

    /**
     * Permission check result: this is returned by {@link #checkPermission}
     * if the permission has not been granted to the given package.
     */
    public static final int PERMISSION_DENIED = -1;//拒絕授權(quán)
二、FileProvider

在獲取所有所需的權(quán)限后,我們調(diào)取系統(tǒng)相機(jī)拍照


private void takePhoto() {
        // 步驟一:創(chuàng)建存儲(chǔ)照片的文件
        String path = getFilesDir() + File.separator + "images" + File.separator;
        File file = new File(path, "test.jpg");
        if(!file.getParentFile().exists())
        file.getParentFile().mkdirs();

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            //步驟二:Android 7.0及以上獲取文件 Uri 
            mUri = FileProvider.getUriForFile(PickPicActivity.this, "com.example.admin.custmerviewapplication", file);
        } else {
            //步驟三:獲取文件Uri
            mUri = Uri.fromFile(file);
        }
        //步驟四:調(diào)取系統(tǒng)拍照
        Intent intent = new Intent("android.media.action.IMAGE_CAPTURE");
        intent.putExtra(MediaStore.EXTRA_OUTPUT, mUri);
        startActivityForResult(intent, REQUEST_TAKE_PHOTO_CODE);
    }

在Android 7.0之前我們只需要步驟一吼具、三僚纷、四即可調(diào)取系統(tǒng)相機(jī)拍照,在此之后的話直接這么調(diào)取會(huì)報(bào)android.os.FileUriExposedException異常拗盒。所以我們需要對(duì)Android 7.0及以后的機(jī)型適配怖竭,采用FileProvider方式。

1. FileProvider是什么

FileProvider是ContentProvider的一個(gè)子類陡蝇,用于應(yīng)用程序之間私有文件的傳遞痊臭。自Android 7.0后系統(tǒng)禁止應(yīng)用向外部公開file://URI ,因此需要FileProvider來向外界傳遞URI登夫,傳遞的形式是content : //Uri广匙,使用時(shí)需要在清單文件中注冊(cè)。

2.注冊(cè)清單文件
<manifest>
    ...
    <application>
        ...
        <provider
            android:name="android.support.v4.content.FileProvider"
            android:authorities="com.example.admin.custmerviewapplication"
            android:exported="false"
            android:grantUriPermissions="true">
                 <meta-data
                    android:name="android.support.FILE_PROVIDER_PATHS"
                    android:resource="@xml/file_paths" />
        </provider>
        ...
    </application>
</manifest>

解釋上面provider標(biāo)簽的意思:

name 因?yàn)槲覀兪褂玫氖荲4包下的FileProvider 悼嫉,所以name的值就是V4包下FileProvider的相對(duì)路徑值艇潭。當(dāng)然我們也可以自定義類繼承于FileProvider,這時(shí)候name的值就是我們自定義類的相對(duì)路徑了

authorities 可以理解為標(biāo)識(shí)符戏蔑,是我們自己自定義的蹋凝。我們代碼中調(diào)用getUriForFile方法獲取Uri時(shí)第二個(gè)參數(shù)就是這里我們定義的值。

exported 代表是否可以輸出被外部程序使用总棵,填false就行鳍寂。

android:grantUriPermissions 是否允許為文件授予臨時(shí)權(quán)限,必須為true

<meta-data>標(biāo)簽里配置的內(nèi)容是用來指定那個(gè)文件夾下的文件是可被共享的情龄。
name 為固定的值android.support.FILE_PROVIDER_PATHS迄汛。
path 是對(duì)應(yīng)的xml文件路徑,@xml/file_paths代表在xml文件下的file_paths文件骤视。

3.指定可共享的文件路徑

我們?cè)趓es目錄下新建一個(gè)xml文件夾鞍爱,在文件夾下創(chuàng)建一個(gè)名為file_paths的xml文件

<paths xmlns:android="http://schemas.android.com/apk/res/android">
          <!--files-path  相當(dāng)于 getFilesDir()-->
    <files-path name="my_images" path="images"/>
          <!--cache-path  相當(dāng)于 getCacheDir()-->
    <cache-path name="lalala" path="cache_image"/>
          <!--external-path  相當(dāng)于 Environment.getExternalStorageDirectory()-->
    < external-path  name="hahaha" path="comeOn"/>
          <!--external-files-path  相當(dāng)于 getExternalFilesDir("") -->
    <external-files-path name="paly" path="freeSoft"/>
         <!--external-cache-path  相當(dāng)于 getExternalCacheDir() --> 
    <external-cache-path  name="lei" path="."/>
    ...
</paths>

files-path所代表的路徑等于getFilesDir(),打印getFileDir( )它的路徑是 /data/user/0/包名/files专酗。什么意思呢睹逃,<files-path name="my_images" path="images"/>的意思就是/data/user/0/包名/files + "/files-path標(biāo)簽中path的值/"路徑下的文件是可共享的,在生成Uri時(shí)name的值my_images會(huì)替代上面的路徑/data/user/0/包名/files / images /向外暴露祷肯。最終的Uri會(huì)是content : //com.example.admin.custmerviewapplication / my_images / test.jpg

我們?cè)诖a中獲取Uri的方法就是FileProvider.getUriForFile("上下文"沉填,"清單文件中authorities的值","共享的文件")佑笋;

三翼闹、圖片獲取并壓縮

我們調(diào)用startActivityForResult(intent, REQUEST_TAKE_PHOTO_CODE);進(jìn)行拍照,拍照結(jié)束后會(huì)回調(diào)onActivityResult( )方法蒋纬。

@Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (resultCode == RESULT_OK && requestCode == REQUEST_TAKE_PHOTO_CODE) {//獲取系統(tǒng)照片上傳

            Bitmap bm = null;
            try {
                bm = getBitmapFormUri(mUri);
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }

            mImageView.setImageBitmap(bm);
        }
    }

通過Uri直接獲取圖片加載到內(nèi)存然后顯示在ImageView很容易發(fā)生OOM猎荠,所以還需做進(jìn)一步的圖片壓縮坚弱。

public Bitmap getBitmapFormUri(Uri uri) throws FileNotFoundException, IOException {
        InputStream input = getContentResolver().openInputStream(uri);

        //這一段代碼是不加載文件到內(nèi)存中也得到bitmap的真是寬高,主要是設(shè)置inJustDecodeBounds為true
        BitmapFactory.Options onlyBoundsOptions = new BitmapFactory.Options();
        onlyBoundsOptions.inJustDecodeBounds = true;//不加載到內(nèi)存
        onlyBoundsOptions.inDither = true;//optional
        onlyBoundsOptions.inPreferredConfig = Bitmap.Config.RGB_565;//optional
        BitmapFactory.decodeStream(input, null, onlyBoundsOptions);
        input.close();
        int originalWidth = onlyBoundsOptions.outWidth;
        int originalHeight = onlyBoundsOptions.outHeight;
        if ((originalWidth == -1) || (originalHeight == -1))
            return null;
        
        //圖片分辨率以480x800為標(biāo)準(zhǔn)
        float hh = 800f;//這里設(shè)置高度為800f
        float ww = 480f;//這里設(shè)置寬度為480f
        //縮放比法牲,由于是固定比例縮放史汗,只用高或者寬其中一個(gè)數(shù)據(jù)進(jìn)行計(jì)算即可
        int be = 1;//be=1表示不縮放
        if (originalWidth > originalHeight && originalWidth > ww) {//如果寬度大的話根據(jù)寬度固定大小縮放
            be = (int) (originalWidth / ww);
        } else if (originalWidth < originalHeight && originalHeight > hh) {//如果高度高的話根據(jù)寬度固定大小縮放
            be = (int) (originalHeight / hh);
        }
        if (be <= 0)
            be = 1;
        //比例壓縮
        BitmapFactory.Options bitmapOptions = new BitmapFactory.Options();
        bitmapOptions.inSampleSize = be;//設(shè)置縮放比例
        bitmapOptions.inDither = true;
        bitmapOptions.inPreferredConfig = Bitmap.Config.RGB_565;
        input = getContentResolver().openInputStream(uri);
        Bitmap bitmap = BitmapFactory.decodeStream(input, null, bitmapOptions);
        input.close();

        return compressImage(bitmap);//再進(jìn)行質(zhì)量壓縮
    }

    public Bitmap compressImage(Bitmap image) {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        image.compress(Bitmap.CompressFormat.JPEG, 100, baos);//質(zhì)量壓縮方法,這里100表示不壓縮拒垃,把壓縮后的數(shù)據(jù)存放到baos中
        int options = 100;
        while (baos.toByteArray().length / 1024 > 100) {  //循環(huán)判斷如果壓縮后圖片是否大于100kb,大于繼續(xù)壓縮
            baos.reset();//重置baos即清空baos
            //第一個(gè)參數(shù) :圖片格式 停撞,第二個(gè)參數(shù): 圖片質(zhì)量,100為最高悼瓮,0為最差  戈毒,第三個(gè)參數(shù):保存壓縮后的數(shù)據(jù)的流
            image.compress(Bitmap.CompressFormat.JPEG, options, baos);//這里壓縮options,把壓縮后的數(shù)據(jù)存放到baos中
            options -= 10;//每次都減少10
            if (options<=0)
                break;
        }
        ByteArrayInputStream isBm = new ByteArrayInputStream(baos.toByteArray());//把壓縮后的數(shù)據(jù)baos存放到ByteArrayInputStream中
        Bitmap bitmap = BitmapFactory.decodeStream(isBm, null, null);//把ByteArrayInputStream數(shù)據(jù)生成圖片
        return bitmap;
    }

壓縮的步驟分為兩步横堡,第一步是先得到bitmap的真實(shí)寬高計(jì)算壓縮比例埋市,得到壓縮比例后進(jìn)行初步壓縮。第二步將初步壓縮的bitmap進(jìn)行質(zhì)量壓縮得到最終的圖片命贴。

從相冊(cè)中選取圖片步驟和調(diào)取相機(jī)拍照的步驟一致道宅,只是創(chuàng)建的intent和在onActivtyResult回調(diào)時(shí)獲取的Uri不同。

//調(diào)用相冊(cè)
Intent intent = new Intent(Intent.ACTION_PICK,android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_UR);
startActivityForResult(intent, PICK_IMAGE_CODE);


@Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        //獲取圖片路徑
        if (requestCode == PICK_IMAGE_CODE && resultCode == Activity.RESULT_OK && data != null) {
            mUri = data.getData();//通過getData獲取到Uri
                    .
                    .
                    .
        }
    }

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末胸蛛,一起剝皮案震驚了整個(gè)濱河市污茵,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌葬项,老刑警劉巖泞当,帶你破解...
    沈念sama閱讀 217,509評(píng)論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異民珍,居然都是意外死亡襟士,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,806評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門嚷量,熙熙樓的掌柜王于貴愁眉苦臉地迎上來陋桂,“玉大人,你說我怎么就攤上這事蝶溶∈壤” “怎么了?”我有些...
    開封第一講書人閱讀 163,875評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵身坐,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我落包,道長(zhǎng)部蛇,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,441評(píng)論 1 293
  • 正文 為了忘掉前任咐蝇,我火速辦了婚禮涯鲁,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己抹腿,他們只是感情好岛请,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,488評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著警绩,像睡著了一般崇败。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上肩祥,一...
    開封第一講書人閱讀 51,365評(píng)論 1 302
  • 那天后室,我揣著相機(jī)與錄音,去河邊找鬼混狠。 笑死岸霹,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的将饺。 我是一名探鬼主播贡避,決...
    沈念sama閱讀 40,190評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼予弧!你這毒婦竟也來了刮吧?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,062評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤桌肴,失蹤者是張志新(化名)和其女友劉穎皇筛,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體坠七,經(jīng)...
    沈念sama閱讀 45,500評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡水醋,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,706評(píng)論 3 335
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了彪置。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片拄踪。...
    茶點(diǎn)故事閱讀 39,834評(píng)論 1 347
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖拳魁,靈堂內(nèi)的尸體忽然破棺而出惶桐,到底是詐尸還是另有隱情,我是刑警寧澤潘懊,帶...
    沈念sama閱讀 35,559評(píng)論 5 345
  • 正文 年R本政府宣布姚糊,位于F島的核電站,受9級(jí)特大地震影響授舟,放射性物質(zhì)發(fā)生泄漏救恨。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,167評(píng)論 3 328
  • 文/蒙蒙 一释树、第九天 我趴在偏房一處隱蔽的房頂上張望肠槽。 院中可真熱鬧擎淤,春花似錦、人聲如沸秸仙。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,779評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽寂纪。三九已至席吴,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間弊攘,已是汗流浹背抢腐。 一陣腳步聲響...
    開封第一講書人閱讀 32,912評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留襟交,地道東北人迈倍。 一個(gè)月前我還...
    沈念sama閱讀 47,958評(píng)論 2 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像捣域,于是被迫代替她去往敵國(guó)和親啼染。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,779評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容