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

来源:互联网 发布:单片机 http 编辑:程序博客网 时间:2024/06/08 03:48

从参加工作至今参与开发的项目大大小小也有七八个了,基本上每个项目都用到了相机功能,以前都是从这儿复制到那儿。从来没有说去仔细研究一下这中间的具体细节,正好这两天稍微有点空,然后把郭霖的《第一行代码》第二版给翻了一遍,正好里面有关于相机的一部分,然后我就仔细的拜读了一下,在此做个总结性记录。

我语言表达不是很好,直接看代码说话吧。

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

我在点击事件中直接调用下面这个方法去

打开相机

//打开相机    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);    }

我先科普一个概念叫做应用关联缓存目录:就是指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;    }


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

package wang.yang.com.takephoto;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);    }}


0 0
原创粉丝点击