美文网首页
【转载】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