原文地址
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);
}
}
网友评论