美文网首页
【转载】Android全兼容版本的拍照和获取相册功能

【转载】Android全兼容版本的拍照和获取相册功能

作者: 一缕阳忆往昔 | 来源:发表于2018-08-09 11:21 被阅读0次

    原文地址

    http://www.voidcn.com/article/p-frjafnjw-bau.html

    要知道在项目中设置头像一般都会给用户两个选择,要么直接调用相机拍一个,要么调用相册去选择一个。

    打开相机

    openCamara() {
        //创建file对象,用于存储拍照后的图片
      File outputImg = new File(getExternalCacheDir(), "output_image.jpg");
     try {
            if (outputImg.exists()) {
                outputImg.delete();
      }
            outputImg.createNewFile();
      } catch (IOException e) {
            e.printStackTrace();
      }
        if (Build.VERSION.SDK_INT >= 24) {
            imageUri = FileProvider.getUriForFile(MainActivity.this, "com.example.cameraalbumtest.fileprovider", outputImg);
      } else {
            imageUri = Uri.fromFile(outputImg);
      }
        //启动相机
      Intent intent = new Intent("android.media.action.IMAGE_CAPTURE");
      intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);
      startActivityForResult(intent, TAKE_PHOTO);
    }
    

    科普一个概念叫做应用关联缓存目录:就是指SD卡中专门用于存放当前应用缓存数据的位置,调用getExternalCacheDir()可以得到这个目录。具体路径是:/sdcard/Android/data/<package name>/cache

    那么为什么要使用应用关联缓存目录来存放图片呢?
    因为从 android6.0 开始读写SD卡被列为了危险权限,如果将图片放在SD卡的其他地方,都要进行运行时权限处理才行。而使用应用缓存数据的位置则可以跳过这一步。

    在代码中还做了一次版本的判别,原因是从 android7.0 开始,直接使用本地真实路径的URI被认为是不安全的,会抛出一个FileUriExposedException异常,所以在低于 7.0 的时候调用URI的fromFile()方法将 File 对象转换成 URI 对象,这个 URI 对象标志着图片地址的真实路径。
    在高于 7.0 的时候调用 FileProvider 的getUriForFile()方法将 file 对象转换成一个封装过的uri对象。FileProvider是一个特殊的内容提供器,它使用了和内容提供器类似的机制来保护数据,可以选择性的将封装过的URI共享外部,从而提高应用的安全性。

    getUriForFile(context,Stirng,file)第一个是context对象,第二个是任意字符串,第三个是file对象

    因为在这里我们使用了FileProvider,上面已经说了,它是一个内容提供器,根据Android四大组件使用原则,我们要去清单中注册一下

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

    name属性的值是固定的
    authorities属性的值是自己定的,不过要跟代码中的一致

    在这里我们使用的是startactivityforResult方法,当你拍好照片点击确认的时候,系统自动就把拍好的照片返回到你提供的 Uri 中了。

    打开相册

    在打开相册的时间里面,我做了一个权限的判断,因为 google 的要求有一些权限属于高危权限,要在动态运行时去处理,正好相册中的照片在 SD 卡上,我们去读取它就需要有读取 SD 卡的权限,而这个读取权限就属于高危权限,所以我做了如下处理

    if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
       ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 1);
    } else {//打开相册
      openAlbum();
    }
    

    在这里我想判断有没有这个读取权限,有就直接去打开相册读取照片,没有的话我就去动态申请。下面就是申请的回调:

    public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
            switch (requestCode) {
                case 1:
                    if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                        openAlbum();
                    } else {
                        Toast.makeText(this, "You denied the permission", Toast.LENGTH_SHORT).show();
                    }
                    break;
            }
        }
    

    获取成功就直接打开相册,获取失败的话就弹一个toast提醒。

        //打开相册
        private void openAlbum() {
            //打开相册
            Intent intent = new Intent("android.intent.action.GET_CONTENT");
            intent.setType("image/*");
            startActivityForResult(intent, CHOOSE_FROM_AIBUM);
        }
    

    这是打开了相册,也是使用的startActivityForResult,所以接收图片也是在onActivityResult中,但是在这里有个小问题。

    因为从android4.4开始,选取相册中的图片就不再返回图片的真实URI了,而是返回一个封装过的URI,如果是4.4以上的就必须先对这个URI进行解析才行。

    那么对于4.4以上的做如下处理

        @TargetApi(19)
        private void handleImageOnKitKat(Intent data) {
            String imagePath = null;
            Uri uri = data.getData();
            if (DocumentsContract.isDocumentUri(this,uri)){
                //如果是document类型的uri,则通过document id 处理
                String docId = DocumentsContract.getDocumentId(uri);
                if ("com.android.providers.media.documents".equals(uri.getAuthority())){
                    String id = docId.split(":")[1];//解析出数字格式的id
                    String selection = MediaStore.Images.Media._ID + "=" +id;
                    imagePath = getImagePath(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,selection);
                }else if("com.android.providers.downloads.documents".equals(uri.getAuthority())){
                    Uri contentUri = ContentUris.withAppendedId(Uri.parse("content://downloads/public_downloads"),Long.valueOf(docId));
                    imagePath = getImagePath(contentUri,null);
                }
            }else if ("content".equalsIgnoreCase(uri.getScheme())){
                //如果是content类型的uri则使用普通的方式处理
                imagePath = getImagePath(uri,null);
            }else if ("file".equalsIgnoreCase(uri.getScheme())){
                //如果是file类型的URI则直接获取图片路径即可
                imagePath = uri.getPath();
            }
            displayImage(imagePath);//根据图片路径显示图片
        }
    

    那么关于4.4以上的系统,对于返回的URI怎么进行解析呢?
    一般就是集中判断返回的URI是不是document类型,如果是那么就取出来document id 进行处理,如果不是就进行普通的URI处理如果URI的authority是media格式的话,document id 还需要再进行一次解析,通过字符串的分割取出真正的id,取出来的id用于构建新的URI和条件语句.

    关于4.4以下的:

    private void handleImageBeforeKitKat(Intent data) {
        Uri uri = data.getData();
        String imagePath = getImagePath(uri,null);
        displayImage(imagePath);
    }
    

    根据uri获取路径的方法:

        private String getImagePath(Uri uri, String selection) {
            String path = null;
            //通过URI和selection来获取真是的图片路径
            Cursor cursor = getContentResolver().query(uri,null,selection,null,null);
            if (cursor != null){
                if (cursor.moveToFirst()){
                    path = cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media.DATA));
                }
                cursor.close();
            }
            return path;
        }
    

    好了,至此也就是大体的逻辑了,下面我贴一下完整的代码,供大家参考:

    import android.Manifest;
    import android.annotation.TargetApi;
    import android.content.ContentUris;
    import android.content.Intent;
    import android.content.pm.PackageManager;
    import android.database.Cursor;
    import android.net.Uri;
    import android.os.Build;
    import android.os.Bundle;
    import android.provider.DocumentsContract;
    import android.provider.MediaStore;
    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.view.View;
    import android.widget.Button;
    import android.widget.ImageView;
    import android.widget.Toast;
    
    import com.bumptech.glide.Glide;
    import com.bumptech.glide.request.target.Target;
    
    import java.io.File;
    import java.io.IOException;
    
    public class MainActivity extends AppCompatActivity implements View.OnClickListener {
        public static final int TAKE_PHOTO = 1;
        public static final int CHOOSE_FROM_AIBUM = 2;
        private ImageView picture;
        private Uri imageUri;
        private Button take_photo,choose_from_album;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            initView();
        }
    
        private void initView() {
            take_photo = (Button) findViewById(R.id.take_photo);
            choose_from_album = (Button) findViewById(R.id.choose_from_album);
            picture = (ImageView) findViewById(R.id.picture);
    
            take_photo.setOnClickListener(this);
            choose_from_album.setOnClickListener(this);
        }
    
        @Override
        public void onClick(View view) {
            switch (view.getId()) {
                case R.id.take_photo:
                        openCamara();
                    break;
                /**
                 * 因为相片是存在SD卡上的,所以我们需要SD卡的读写权限,因为读写权限在7.0属于高危权限了,所以在动态运行时去判断是否有这个权限,有则继续运行,没有则调用动态注册
                 */
                case R.id.choose_from_album:
                    if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
                        ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 1);
                    } else {
                        openAlbum();
                    }
                    break;
            }
        }
        //打开相册
        private void openAlbum() {
            //打开相册
            Intent intent = new Intent("android.intent.action.GET_CONTENT");
            intent.setType("image/*");
            startActivityForResult(intent, CHOOSE_FROM_AIBUM);
        }
    
        @Override
        public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
            switch (requestCode) {
                case 1:
                    if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                        openAlbum();
                    } else {
                        Toast.makeText(this, "You denied the permission", Toast.LENGTH_SHORT).show();
                    }
                    break;
            }
        }
    
        /**
         * 应用关联缓存目录:就是指SD卡中专门用于存放当前应用缓存数据的位置,调用getExternalCacheDir()可以得到这个目录。具体路径是:/sdcard/Android/data/<package name>/cache
         * 那么为什么要使用应用关联缓存目录来存放图片呢?应为从android6.0开始读写SD卡被列为了危险权限,如果将图片放在SD卡的其他地方,都要进行运行时权限处理才行,而使用应用缓存数据的位置则可以跳过这一步
         * 从android7.0开始,直接使用本地真实路径的URI被认为是不安全的,会抛出一个FileUriExposedException异常,所以在低于7.0的时候调用URI的fromFile()方法将File对象转换成URI对象,这个URI对象标志着
         * 图片地址的真实路径。
         * 在高于7.0的时候调用FileProvider的getUriForFile()方法将file对象转换成一个封装过的uri对象。FileProvider是一个特殊的内容提供器,它使用了和内容提供器类似的机制来保护数据,可以选择性的将封装过的URI共享外部,从而提高应用的安全性
         * getUriForFile(context,Stirng,file)第一个是context对象,第二个是任意字符串,第三个是file对象
         */
        //打开相机
        private void openCamara() {
            //创建file对象,用于存储拍照后的图片
            File outputImg = new File(getExternalCacheDir(), "output_image.jpg");
            try {
                if (outputImg.exists()) {
                    outputImg.delete();
                }
                outputImg.createNewFile();
            } catch (IOException e) {
                e.printStackTrace();
            }
            if (Build.VERSION.SDK_INT >= 24) {
                imageUri = FileProvider.getUriForFile(MainActivity.this, "com.example.cameraalbumtest.fileprovider", outputImg);
            } else {
                imageUri = Uri.fromFile(outputImg);
            }
            //启动相机
            Intent intent = new Intent("android.media.action.IMAGE_CAPTURE");
            intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);
            startActivityForResult(intent, TAKE_PHOTO);
        }
    
        @Override
        protected void onActivityResult(int requestCode, int resultCode, Intent data) {
            switch (requestCode) {
                case TAKE_PHOTO:
                    if (resultCode == RESULT_OK) {
                        //将照片显示出来
                        Glide.with(this).load(imageUri).override(Target.SIZE_ORIGINAL,Target.SIZE_ORIGINAL).into(picture);
                    }
                    break;
                /**
                 * 因为从android4.4开始,选取相册中的图片就不再返回图片的真实URI了,而是返回一个封装过的URI,如果是4.4以上的就必须先对这个URI进行解析才行
                 */
                case CHOOSE_FROM_AIBUM:
                    if (resultCode == RESULT_OK) {
                        //判断手机系统版本号
                        if (Build.VERSION.SDK_INT >= 19){
                            //4.4以上的系统使用这个方法处理照片
                            handleImageOnKitKat(data);
                        }else{
                            //4.4以下的使用这个方法处理
                            handleImageBeforeKitKat(data);
                        }
                    }
                    break;
            }
        }
    
        /**
         * 那么关于4.4以上的系统,对于返回的URI怎么进行解析呢?
         * 一般就是集中判断
         * 返回的URI是不是document类型,如果是那么就取出来document id 进行处理,如果不是就进行普通的URI处理
         * 如果URI的authority是media格式的话,document id 还需要再进行一次解析,通过字符串的分割取出真正的id,取出来的id用于构建新的URI和条件语句
         * @param data
         */
        @TargetApi(19)
        private void handleImageOnKitKat(Intent data) {
            String imagePath = null;
            Uri uri = data.getData();
            if (DocumentsContract.isDocumentUri(this,uri)){
                //如果是document类型的uri,则通过document id 处理
                String docId = DocumentsContract.getDocumentId(uri);
                if ("com.android.providers.media.documents".equals(uri.getAuthority())){
                    String id = docId.split(":")[1];//解析出数字格式的id
                    String selection = MediaStore.Images.Media._ID + "=" +id;
                    imagePath = getImagePath(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,selection);
                }else if("com.android.providers.downloads.documents".equals(uri.getAuthority())){
                    Uri contentUri = ContentUris.withAppendedId(Uri.parse("content://downloads/public_downloads"),Long.valueOf(docId));
                    imagePath = getImagePath(contentUri,null);
                }
            }else if ("content".equalsIgnoreCase(uri.getScheme())){
                //如果是content类型的uri则使用普通的方式处理
                imagePath = getImagePath(uri,null);
            }else if ("file".equalsIgnoreCase(uri.getScheme())){
                //如果是file类型的URI则直接获取图片路径即可
                imagePath = uri.getPath();
            }
            displayImage(imagePath);//根据图片路径显示图片
        }
    
    
        /**
         * 通过传过来的条件,获取图片的真实路径
         * @param uri
         * @param selection
         * @return
         */
        private String getImagePath(Uri uri, String selection) {
            String path = null;
            //通过URI和selection来获取真是的图片路径
            Cursor cursor = getContentResolver().query(uri,null,selection,null,null);
            if (cursor != null){
                if (cursor.moveToFirst()){
                    path = cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media.DATA));
                }
                cursor.close();
            }
            return path;
        }
    
        /**
         * 在这里我使用的是GLIDE来显示图片,你可以选择使用其他方法
         * @param imagePath
         */
        private void displayImage(String imagePath) {
            if (imagePath != null){
                //将照片显示出来
                Glide.with(this).load(imagePath).override(Target.SIZE_ORIGINAL,Target.SIZE_ORIGINAL).into(picture);
            }else{
                Toast.makeText(getApplicationContext(),"failed to get image!",Toast.LENGTH_SHORT).show();
            }
        }
    
        private void handleImageBeforeKitKat(Intent data) {
            Uri uri = data.getData();
            String imagePath = getImagePath(uri,null);
            displayImage(imagePath);
        }
    }
    

    相关文章

      网友评论

          本文标题:【转载】Android全兼容版本的拍照和获取相册功能

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