美文网首页
使用Android系统相机捕获图片(三)

使用Android系统相机捕获图片(三)

作者: R_flash | 来源:发表于2018-11-09 14:36 被阅读0次

    写文章的目的

    1. 静下心来学习
    2. 知识点的积累
    3. 总结,做笔记

    导读

    解决上一篇文章留下的问题使用Android系统相机捕获图片(二)

    需求

    • 在高版本手机上,使用Android系统相机捕获图片(大图)。

    问题

    1. 在Android6.0以上需要动态的申请读写权限READ_EXTERNAL_STORAGEWRITE_EXTERNAL_STORAGE

    2.直接将上一篇的项目运行在Android7.0的手机上会直接报错:FileUriExposedException: file:///storage/emulated/0/rflashmy.jpg exposed beyond app through ClipData.Item.getUri()

    代码解构

    1.动态申请权限。

    • 步骤1:在AndroidManifest.xml中注册读写权限
       <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    
    • 步骤2:判断读写权限是否申请。
      /**
         * 检查权限是否申请
         *
         * @param permission 权限
         * @return true :权限已申请
         */
        private boolean checkPermission(String permission) {
            //是否申请权限
            boolean hasPermission = false;
            //通过api去校验权限是否申请,返回判断标志
            int i = ContextCompat.checkSelfPermission(this, permission);
            if (PackageManager.PERMISSION_GRANTED == i) {
                //PERMISSION_GRANTED表示权限已申请
                hasPermission = true;
            } else if (PackageManager.PERMISSION_DENIED == i) {
                //PERMISSION_DENIED表示权限未申请
                hasPermission = false;
            }
            return hasPermission;
    
        }
    
    • 步骤3:申请权限。
      /**
         * 请求权限
         */
        private void requestPermission(String... permission) {
            if (ActivityCompat.shouldShowRequestPermissionRationale(this, permission[0])) {
                //shouldShowRequestPermissionRationale()方法解释
                //1.第一次请求该权限,返回false
                //2.请求过该权限并被用户拒绝,返回true
                //3.请求过该权限,但用户拒绝的时候勾选不再提醒,返回false。
                //总结一下,这个方法的目的:就是说去告诉用户,为什么需要用户同意该权限。
                //todo 所以这里可以给一个ui提示用户同意权限的好处。
                //我这里只是toast。可以理解为伪代码,只是提供一种思路
                Toast.makeText(this, "求求你授权吧!", Toast.LENGTH_SHORT).show();
                //提示之后可以继续请求权限
                ActivityCompat.requestPermissions(this, permission, PERMISSION_RESULT);
            } else {
                //没有权限,去请求权限
                ActivityCompat.requestPermissions(this, permission, PERMISSION_RESULT);
            }
        }
    
    • 步骤4:回调结果中处理权限。
         /**
         * 读写权限
         */
        private final String mPermission = Manifest.permission.WRITE_EXTERNAL_STORAGE;
    
    
        @Override
        public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
            super.onRequestPermissionsResult(requestCode, permissions, grantResults);
            if (requestCode != PERMISSION_RESULT) {
                return;
            }
    
            if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                //因为我就申请一个权限,所以可以通过grantResults[0]就是我申请的权限。
                //用户同意权限,打开相机
                openCameraForResult();
            }else {
                //这里本意并不是重新请求权限,是通过方法shouldShowRequestPermissionRationale()去给用户做提示。
                requestPermission(mPermission);
            }
        }
    

    2.检验权限代码是否能获取权限。在 button点击事件里面调用获取权限方法。会发现点击完全没反应(手机:一加5T,系统版本7.1)。是代码有问题吗?之后我换成ACCESS_COARSE_LOCATION,发现弹出了需要用户授权的dialog。what the fuck?

    Screenshot_20181109-095322.jpg
    3.也就是说代码没问题,之后我换了小米手机,发现是需要获取权限的。
    4.这下好了权限也解决了,接下来就是解决Android 7.0上报错FileUriExposedException。这是因为Android 7.0以后不能识别以file://开头的Uri。也就是说在不同版本需要构建不同的Uri。
    • 步骤一:res目录下创建xml资源文件夹(如果有不必建),然后创建一个xml文件(以file.xml为例)。

      image.png
    • 步骤二:完成files.xml。还记得我们需要把拍照图片存在哪吗?Environment.getExternalStorageDirectory()+"/rflash"+"/my.jpg"。那么对应的xml文件:

        <!--external-path 对应的是Environment.getExternalStorageDirectory()-->
        <!--name 我的理解是Uri上的名字-->
        <!--path 就是图片存储的真实路径-->
        <!--可以通过生成的Uri去理解这下值-->
    <?xml version="1.0" encoding="utf-8"?>
    <paths xmlns:android="http://schemas.android.com/apk/res/android">
        <external-path
            name="image"
            path="rflash" />
    </paths>
    
    • 步骤三:在AndroidManifest.xmlapplication标签里面添加provider标签。
      <!--authorities 对应值不一定非是applicationId-->
      <provider
                android:name="android.support.v4.content.FileProvider"
                android:authorities="com.rflash.captrueimage03"
                android:exported="false"
                android:grantUriPermissions="true">
                 <!--resource 对应值是刚刚构建的files.xml-->
                <meta-data
                    android:name="android.support.FILE_PROVIDER_PATHS"
                    android:resource="@xml/files" />
            </provider>
    
    • 步骤四:java代码中构建Uri。
     /**
         * 获取图片uri
         *
         * @return
         */
        private Uri getImageUri() {
            File file = new File(mImageFilePath);
            //###下面代码有些手机需要(如:小米手机)###
            //可以试下去掉下面的代码在小米手机上报什么错
            File parentFile = file.getParentFile();
            //去创建图片存放的父路径
            if (!parentFile.exists()){
                parentFile.mkdirs();
            }
            //###上面面代码有些手机需要(如:小米手机)###
            Uri imageUri;
            if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.N) {
                //N以前
                imageUri = Uri.fromFile(file);
            } else {
                //N以后,通过FileProvider生成
                //特别地,第二个参数authority对应AndroidManifest.xml下provider标签里面的authorities值。
                //我直接使用的application_id
                imageUri = FileProvider.getUriForFile(this, BuildConfig.APPLICATION_ID, file);
                Log.i("---", imageUri.toString());
                //打印结果
                //I/---: content://com.rflash.captrueimage03/image/my.jpg
            }
            return imageUri;
        }
    
    

    5.小结下,权限问题7.0上Uri问题都已经解决,接下来调用系统相机。直接上代码(与上一篇调用是一样的)。

     /**
         * 打开相机
         */
        private void openCameraForResult() {
            //创建intent ,设置action
            Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
            //构建图片路径
            createImageFilePath();
            //将捕获的图片保存在imageUri
            intent.putExtra(MediaStore.EXTRA_OUTPUT, getImageUri());
            //调用相机
            startActivityForResult(intent, IMAGE_RESULT);
        }
    

    6.拿到图片并显示(显示结果就不截图了)。

     @Override
        protected void onActivityResult(int requestCode, int resultCode, Intent data) {
            super.onActivityResult(requestCode, resultCode, data);
            if (resultCode != RESULT_OK) {
                return;
            }
            if (requestCode == IMAGE_RESULT) {
                //这里不能通过Intent对象去获取"data",
                // 因为在打开相机时已经通过MediaStore.EXTRA_OUTPUT告诉相机:你把图片放在我传给你的Uri中
                //所以可以直接通过BitmapFactory在存储路径中获取图片
                Bitmap bitmap = BitmapFactory.decodeFile(mImageFilePath);
                imageView.setImageBitmap(bitmap);
            }
        }
    
    

    总结

    1.调用系统相机捕获图片,需要注意:不同版本需要构建不同的Uri,具体区分是Android.N。在N以前可以直接使用Uri.fromFile(file)创建Uri,在N以后需要通过FileProvider,具体步骤文章上面有。
    2.特别注意:有些手机需要手动给图片创建父路径。
    3.那么,获取了这么大的图片并显示,内存肯定消耗很大,有什么优化吗?请听下回分解。

    代码样例

    1.res/layout/activity_main.xml

    <?xml version="1.0" encoding="utf-8"?>
    <android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity">
    
        <Button
            android:id="@+id/btn"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="capture_image"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent" />
    
        <ImageView
            android:id="@+id/iv"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="30dp"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/btn" />
    
    
    </android.support.constraint.ConstraintLayout>
    

    2.MainActivity.java

    package com.rflash.captrueimage03;
    
    import android.Manifest;
    import android.content.Intent;
    import android.content.pm.PackageManager;
    import android.graphics.Bitmap;
    import android.graphics.BitmapFactory;
    import android.net.Uri;
    import android.os.Build;
    import android.os.Bundle;
    import android.os.Environment;
    import android.provider.MediaStore;
    import android.support.annotation.NonNull;
    import android.support.v4.app.ActivityCompat;
    import android.support.v4.content.ContextCompat;
    import android.support.v4.content.FileProvider;
    import android.support.v7.app.AppCompatActivity;
    import android.util.Log;
    import android.view.View;
    import android.widget.Button;
    import android.widget.ImageView;
    import android.widget.Toast;
    
    import java.io.File;
    
    public class MainActivity extends AppCompatActivity implements View.OnClickListener {
    
        private Button button;
        private ImageView imageView;
    
        /**
         * 打开系统相机的requestCode
         */
        private final int IMAGE_RESULT = 0;
    
        /**
         * 请求权限的requestCode
         */
        private final int PERMISSION_RESULT = 1;
    
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            button = findViewById(R.id.btn);
            imageView = findViewById(R.id.iv);
            button.setOnClickListener(this);
        }
    
        @Override
        public void onClick(View v) {
            int id = v.getId();
            switch (id) {
                case R.id.btn:
                    clickButton();
                    break;
            }
        }
    
        /**
         * 点击 capture_image button
         */
        private void clickButton() {
            if (!checkPermission(mPermission)) {
                requestPermission(mPermission);
            } else {
                //打开相机
                openCameraForResult();
            }
        }
    
        /**
         * 读写权限
         */
        private final String mPermission = Manifest.permission.WRITE_EXTERNAL_STORAGE;
    
        /**
         * 图片存储路径
         */
        private String mImageFilePath;
    
        /**
         * 构建图片路径
         * 将图片保存在Environment.getExternalStorageDirectory()+"/rflash"+"/my.jpg"
         */
        private void createImageFilePath() {
            mImageFilePath = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + "rflash" + File.separator + "my.jpg";
        }
    
        /**
         * 获取图片uri
         *
         * @return
         */
        private Uri getImageUri() {
            File file = new File(mImageFilePath);
            //###下面代码有些手机需要(如:小米手机)###
            //可以试下去掉下面的代码在小米手机上报什么错
            File parentFile = file.getParentFile();
            //去创建图片存放的父路径
            if (!parentFile.exists()){
                parentFile.mkdirs();
            }
            //###上面面代码有些手机需要(如:小米手机)###
            Uri imageUri;
            if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.N) {
                //N以前
                imageUri = Uri.fromFile(file);
            } else {
                //N以后,通过FileProvider生成
                //特别地,第二个参数authority对应AndroidManifest.xml下provider标签里面的authorities值。
                //我直接使用的application_id
                imageUri = FileProvider.getUriForFile(this, BuildConfig.APPLICATION_ID, file);
                Log.i("---", imageUri.toString());
                //打印结果
                //I/---: content://com.rflash.captrueimage03/image/my.jpg
            }
            return imageUri;
        }
    
        /**
         * 检查权限是否申请
         *
         * @param permission 权限
         * @return true :权限已申请
         */
        private boolean checkPermission(String permission) {
            //是否申请权限
            boolean hasPermission = false;
            //通过api去校验权限是否申请,返回判断标志
            int i = ContextCompat.checkSelfPermission(this, permission);
            if (PackageManager.PERMISSION_GRANTED == i) {
                //PERMISSION_GRANTED表示权限已申请
                hasPermission = true;
            } else if (PackageManager.PERMISSION_DENIED == i) {
                //PERMISSION_DENIED表示权限未申请
                hasPermission = false;
            }
            return hasPermission;
    
        }
    
    
        /**
         * 请求权限
         */
        private void requestPermission(String... permission) {
            if (ActivityCompat.shouldShowRequestPermissionRationale(this, permission[0])) {
                //shouldShowRequestPermissionRationale()方法解释
                //1.第一次请求该权限,返回false
                //2.请求过该权限并被用户拒绝,返回true
                //3.请求过该权限,但用户拒绝的时候勾选不再提醒,返回false。
                //总结一下,这个方法的目的:就是说去告诉用户,为什么需要用户同意该权限。
                //todo 所以这里可以给一个ui提示用户同意权限的好处。
                //我这里只是toast。可以理解为伪代码,只是提供一种思路
                Toast.makeText(this, "求求你授权吧!", Toast.LENGTH_SHORT).show();
                //提示之后可以继续请求权限
                ActivityCompat.requestPermissions(this, permission, PERMISSION_RESULT);
            } else {
                //没有权限,去请求权限
                ActivityCompat.requestPermissions(this, permission, PERMISSION_RESULT);
            }
        }
    
        /**
         * 打开相机
         */
        private void openCameraForResult() {
            //创建intent ,设置action
            Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
            //构建图片路径
            createImageFilePath();
            //将捕获的图片保存在imageUri
            intent.putExtra(MediaStore.EXTRA_OUTPUT, getImageUri());
            //调用相机
            startActivityForResult(intent, IMAGE_RESULT);
        }
    
        @Override
        protected void onActivityResult(int requestCode, int resultCode, Intent data) {
            super.onActivityResult(requestCode, resultCode, data);
            if (resultCode != RESULT_OK) {
                return;
            }
            if (requestCode == IMAGE_RESULT) {
                //这里不能通过Intent对象去获取"data",
                // 因为在打开相机时已经通过MediaStore.EXTRA_OUTPUT告诉相机:你把图片放在我传给你的Uri中
                //所以可以直接通过BitmapFactory在存储路径中获取图片
                Bitmap bitmap = BitmapFactory.decodeFile(mImageFilePath);
                imageView.setImageBitmap(bitmap);
            }
        }
    
        @Override
        public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
            super.onRequestPermissionsResult(requestCode, permissions, grantResults);
            if (requestCode != PERMISSION_RESULT) {
                return;
            }
    
            if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                //因为我就申请一个权限,所以可以通过grantResults[0]就是我申请的权限。
                //用户同意权限,打开相机
                openCameraForResult();
            } else {
                //这里本意并不是重新请求权限,是通过方法shouldShowRequestPermissionRationale()去给用户做提示。
                requestPermission(mPermission);
            }
        }
    }
    
    

    3.res/xml/files.xml

    <?xml version="1.0" encoding="utf-8"?>
    <paths xmlns:android="http://schemas.android.com/apk/res/android">
        <!--external-path 对应的是Environment.getExternalStorageDirectory()-->
        <!--name 我的理解是Uri上的名字-->
        <!--path 就是图片存储的真实路径-->
        <!--可以通过生成的Uri去理解这下值-->
        <external-path
            name="image"
            path="rflash" />
    </paths>
    

    4.AndroidManifest.xml

    <?xml version="1.0" encoding="utf-8"?>
    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
        package="com.rflash.captrueimage03">
    
        <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    
        <application
            android:allowBackup="true"
            android:icon="@mipmap/ic_launcher"
            android:label="@string/app_name"
            android:roundIcon="@mipmap/ic_launcher_round"
            android:supportsRtl="true"
            android:theme="@style/AppTheme">
            <activity android:name=".MainActivity">
                <intent-filter>
                    <action android:name="android.intent.action.MAIN" />
    
                    <category android:name="android.intent.category.LAUNCHER" />
                </intent-filter>
            </activity>
    
    
            <!--authorities 对应值不一定非是applicationId-->
            <provider
                android:name="android.support.v4.content.FileProvider"
                android:authorities="com.rflash.captrueimage03"
                android:exported="false"
                android:grantUriPermissions="true">
                <!--resource 对应值是刚刚构建的files.xml-->
                <meta-data
                    android:name="android.support.FILE_PROVIDER_PATHS"
                    android:resource="@xml/files" />
            </provider>
        </application>
    
    </manifest>
    

    相关文章

      网友评论

          本文标题:使用Android系统相机捕获图片(三)

          本文链接:https://www.haomeiwen.com/subject/gtdvxqtx.html