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); }}
- Android全兼容版本的拍照和获取相册功能
- Android 拍照和相册选图 版本兼容
- android拍照或相册获取头像(兼容4.4以下,4.4,5.0,6.0版本)
- android 调用系统相册和系统拍照功能的Demo
- 拍照和选择相册(兼容高版本)
- Android 相册和拍照设置头像功能
- 获取android的拍照和自定义多选相册
- Android拍照和获取相册图片
- android 拍照和相册图片获取
- 拍照,和获取相册
- FileProvider的拍照和打开相册功能
- iOS 上传照片到服务器 获取相册和拍照功能的照片 照片压缩上传
- mui 拍照和相册 功能
- Android开发之获取相册照片和获取拍照照片
- Android:获取相册照片和获取拍照照片
- Android开发之获取相册照片和获取拍照照片
- Android开发之获取相册照片和获取拍照照片
- Android开发之获取相册照片和获取拍照照片
- css3 让图片铺满整个背景,并且不重复,居中
- Android多渠道打包详解
- mysql主从环境搭建
- 《JAVA与模式》之观察者模式
- 设计模式总结(《Head First设计模式》学习总结)
- Android全兼容版本的拍照和获取相册功能
- <C语言漫谈录>1——背景知识
- 《JavaScript 闯关记》之作用域和闭包
- 计算机视觉的三大会议
- 随笔
- C语言版扫雷
- SIFT算法和SURF算法区别
- OpenStack入门笔记(二)
- (斯坦福机器学习课程笔记)多项式回归练习