Android7-0后相机相册调用

1. 前言

需求提出:之前在帮助别人做东西的时候有这样一个功能,相机拍照到一个固定的目录,相册选取图片(单选、多选)。

Android6.0以后提出新的权限管理机制,7.0后相册访问机制也进一步更改。所以我们除了相机、相册功能外还要考虑权限问题。下面就稍微讲解一下如何实现这些功能。

先说明一点,本文所实现的代码没有兼容7.0以前,只实现了7.0之后的相关代码。

相关知识:

  1. 相机、相册权限请求
  2. 相机、相册调用
  3. 相机拍照后将图片移动一个固定的文件夹下(文件IO操作)

2. 代码实现

先来看一下实现的效果:

2.1 材料准备

为实现代码方便,这里引用两个开源库

1
2
3
4
5
6
7
//动态权限申请库
implementation 'pub.devrel:easypermissions:1.3.0'
// 多选相册(如果不实现多选,不用引用这个库,调用系统相册选择已经足够)
implementation 'com.github.HuanTanSheng:EasyPhotos:2.4.4'
// Glide 多选相册依赖,不用多选就不用添加下面两项
implementation 'com.github.bumptech.glide:glide:4.4.0'
annotationProcessor 'com.github.bumptech.glide:compiler:4.4.0'

注意要引用EasyPhotos相关库,需要在项目的gradle文件下增添一个仓库地址maven { url "https://jitpack.io" },最后的效果如下:

1
2
3
4
5
6
7
allprojects {
repositories {
google()
jcenter()
maven { url "https://jitpack.io" }
}
}

2.2 相关配置

在manifest文件中添加权限

1
2
3
4
5
6

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" />

<uses-permission android:name="android.permission.CAMERA" />

再在manifest文件的application标签中添加以下代码:

1
2
3
4
5
6
7
8
9
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="com.raven.demo.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
provider>

注意这里的android:authorities="com.raven.demo.fileprovider"字段,需要将该字段中的com.raven.demo改为自己的包名。包名可在app模块的gradle文件中查看。

再在资源文件下建立file_paths.xml,如图:

添加以下代码:

1
2
3
4

<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path name="external_files" path="."/>
paths>

2.3 界面设计与点击事件

为方便测试,这里仅给了三个Button,一个相机调用,一个单相片选取,一个多相片选取。

效果如下:

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical"
tools:context=".MainActivity">

<Button
android:id="@+id/bt_camera"
android:text="相机"
android:layout_width="200dp"
android:layout_height="wrap_content" />

<Button
android:id="@+id/bt_single_pic"
android:text="单相片选取"
android:layout_width="200dp"
android:layout_height="wrap_content" />

<Button
android:id="@+id/bt_multi_pic"
android:text="多相片选取"
android:layout_width="200dp"
android:layout_height="wrap_content" />
Linearout>

初始化这几个button并添加相应的点击事件,加入一些辅助变量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
// 控件
Button btCamera,btSinglePic,btMultiPic;

//相机请求码
private static final int CAMERA_REQUEST_CODE = 1;

//单张请求码
private static final int SINGLE_REQUEST_CODE = 2;

// 多张请求码
private static final int MULTI_REQUEST_CODE = 3;

// 权限集合
private String[] permissions = {Manifest.permission.CAMERA,Manifest.permission.WRITE_EXTERNAL_STORAGE};

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

// 初始化view
btCamera = findViewById(R.id.bt_camera);
btSinglePic = findViewById(R.id.bt_single_pic);
btMultiPic = findViewById(R.id.bt_multi_pic);

// 增添点击事件
btCamera.setOnClickListener(this);
btSinglePic.setOnClickListener(this);
btMultiPic.setOnClickListener(this);
}

@Override
public void onClick(View v) {
int id = v.getId();
if(R.id.bt_camera == id){
// 调用系统相机
callSystemCamera();
}else if(R.id.bt_single_pic == id){
// 调用单张相册
callSinglePic();
}else if(R.id.bt_multi_pic == id){
// 调用多张相册选择
callMultiPic();
}
}

2.4 设置权限回调

1
2
3
4
5
6
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
//框架要求必须这么写
EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this);
}

2.5 实现三个功能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
/**
* 调用系统相机,包含权限检测
*/
private void callSystemCamera() {
if (EasyPermissions.hasPermissions(this, permissions)) {
//已经打开权限
useCamera();
} else {
//没有打开相关权限、申请权限
getPermission();
}
}

/**
* 调用相机
*/
private void useCamera() {
Intent intent = new Intent();
//此处之所以诸多try catch,为了兼容
intent.setAction(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA);
startActivityForResult(intent, CAMERA_REQUEST_CODE);
}

/**
* 调用系统相册,选择单张图片,包含权限检测
*/
private void callSinglePic() {
if (EasyPermissions.hasPermissions(this, permissions)) {
//已经打开权限
chooseSinglePhoto();
} else {
//没有打开相关权限、申请权限
getPermission();
}
}

/**
* 选择单张图片
*/
private void chooseSinglePhoto() {
Intent intent = new Intent();
intent.setAction(Intent.ACTION_PICK);
intent.setType("image/*");
startActivityForResult(intent, SINGLE_REQUEST_CODE);
}

/**
* 调用第三方相册,选择多张图片
*/
private void callMultiPic() {
if (EasyPermissions.hasPermissions(this, permissions)) {
//已经打开权限
chooseSMultiPhotos();
} else {
//没有打开相关权限、申请权限
getPermission();
}
}

private void chooseSMultiPhotos() {
// 初始化相册引擎
EasyPhotos.createAlbum(this, false, GlideEngine.getInstance())
.setCount(20) // 设置最大选取张数
.setCleanMenu(false)
.setPuzzleMenu(false)
.start(MULTI_REQUEST_CODE);
}

其中GlideEngine类我会在文末尾给出。

2.6 回调处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/**
* 处理相机或者相册的回调
*
* @param requestCode 请求码
* @param resultCode 结果码
* @param data 传出data
*/
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
// 相机回调处理
if (requestCode == CAMERA_REQUEST_CODE && resultCode == RESULT_CANCELED) {
Toast.makeText(this,"拍照完成",Toast.LENGTH_SHORT).show();
}

// 单张回调处理
if (requestCode == SINGLE_REQUEST_CODE && resultCode == RESULT_OK) {
String photoPath = GetPhotoFromAlbum.getRealPathFromUri(this,
data != null ? data.getData() : null);
Toast.makeText(this,"选择的照片路径为"+photoPath,Toast.LENGTH_SHORT).show();
}

// 多张回调处理
if (requestCode == MULTI_REQUEST_CODE && resultCode == RESULT_OK) {
// 获取选择图片集合路径
ArrayList photosPath = data.getStringArrayListExtra(EasyPhotos.RESULT_PATHS);
Toast.makeText(this, String.format("共选择了%d张图片", photosPath.size()),Toast.LENGTH_SHORT).show();
}
super.onActivityResult(requestCode, resultCode, data);
}

3. 相册、相机处理中的FAQ

Q1: 为什么我拍照后相册没有得到更新?

A1: 可以通过以下方法更新:

1
2
3
4
5
6
7
8
9
// 通知图库更新
MediaScannerConnection.scanFile(MainActivity.this, new String[]{path}, null,
new MediaScannerConnection.OnScanCompletedListener() {
public void onScanCompleted(String path, Uri uri) {
Intent mediaScanIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
mediaScanIntent.setData(uri);
sendBroadcast(mediaScanIntent);
}
});

其中path参数就是你新拍照后的照片路径。

Q2: 有没有办法设置相机拍照后的相片到一个指定相册?

A2: 说实话这也让我挺头疼的问题。因为在我搜索出来的结果中,Android似乎并没有提供这样的Api给我们。最后我的实现方法是将刚拍照的图片找出来移动到需要的目录去。这里给一个思路。

  1. 如何找到刚拍照的照片?可以通过时间戳的方式。在进入相机时记录一下时间,退出相机时记录一下时间,然后到系统的相册集中去找在这个时间段的所有照片。
  2. 使用Java IO的相关API去移动图片,注意最好在线程里面做,避免因为移动过多图片造成主线程卡死。

4. 代码中用到的其他文件

GlideEngine.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
package com.raven.mnist_recon;
import android.content.Context;
import android.graphics.Bitmap;
import android.widget.ImageView;
import com.bumptech.glide.Glide;
import com.huantansheng.easyphotos.engine.ImageEngine;

import static com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions.withCrossFade;

/**
* Glide4.x的加载图片引擎实现,单例模式
* Glide4.x的缓存机制更加智能,已经达到无需配置的境界。如果使用Glide3.x,需要考虑缓存机制。
* Created by huan on 2018/1/15.
*/

public class GlideEngine implements ImageEngine {
//单例
private static GlideEngine instance = null;
//单例模式,私有构造方法
private GlideEngine() {
}
//获取单例
public static GlideEngine getInstance() {
if (null == instance) {
synchronized (GlideEngine.class) {
if (null == instance) {
instance = new GlideEngine();
}
}
}
return instance;
}

/**
* 加载图片到ImageView
*
* @param context 上下文
* @param photoPath 图片路径
* @param imageView 加载到的ImageView
*/
@Override
public void loadPhoto(Context context, String photoPath, ImageView imageView) {
Glide.with(context).load(photoPath).transition(withCrossFade()).into(imageView);
}

/**
* 加载gif动图图片到ImageView,gif动图不动
*
* @param context 上下文
* @param gifPath gif动图路径
* @param imageView 加载到的ImageView
*


* 备注:不支持动图显示的情况下可以不写
*/
@Override
public void loadGifAsBitmap(Context context, String gifPath, ImageView imageView) {
Glide.with(context).asBitmap().load(gifPath).into(imageView);
}

/**
* 加载gif动图到ImageView,gif动图动
*
* @param context 上下文
* @param gifPath gif动图路径
* @param imageView 加载动图的ImageView
*


* 备注:不支持动图显示的情况下可以不写
*/
@Override
public void loadGif(Context context, String gifPath, ImageView imageView) {
Glide.with(context).asGif().load(gifPath).transition(withCrossFade()).into(imageView);
}


/**
* 获取图片加载框架中的缓存Bitmap
*
* @param context 上下文
* @param path 图片路径
* @param width 图片宽度
* @param height 图片高度
* @return Bitmap
* @throws Exception 异常直接抛出,EasyPhotos内部处理
*/
@Override
public Bitmap getCacheBitmap(Context context, String path, int width, int height) throws Exception {
return Glide.with(context).asBitmap().load(path).submit(width, height).get();
}


}

AlbumUtil.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
package com.raven.demo;

import android.annotation.SuppressLint;
import android.content.ContentUris;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.os.Build;
import android.provider.DocumentsContract;
import android.provider.MediaStore;

public class AlbumUtil {
/**
* 根据Uri获取图片的绝对路径
*
* @param context 上下文对象
* @param uri 图片的Uri
* @return 如果Uri对应的图片存在, 那么返回该图片的绝对路径, 否则返回null
*/
public static String getRealPathFromUri(Context context, Uri uri) {
int sdkVersion = Build.VERSION.SDK_INT;
if (sdkVersion >= 19) {
return getRealPathFromUriAboveApi19(context, uri);
} else {
return getRealPathFromUriBelowAPI19(context, uri);
}
}

/**
* 适配api19以下(不包括api19),根据uri获取图片的绝对路径
*
* @param context 上下文对象
* @param uri 图片的Uri
* @return 如果Uri对应的图片存在, 那么返回该图片的绝对路径, 否则返回null
*/
private static String getRealPathFromUriBelowAPI19(Context context, Uri uri) {
return getDataColumn(context, uri, null, null);
}

/**
* 适配api19及以上,根据uri获取图片的绝对路径
*
* @param context 上下文对象
* @param uri 图片的Uri
* @return 如果Uri对应的图片存在, 那么返回该图片的绝对路径, 否则返回null
*/
@SuppressLint("NewApi")
private static String getRealPathFromUriAboveApi19(Context context, Uri uri) {
String filePath = null;
if (DocumentsContract.isDocumentUri(context, uri)) {
// 如果是document类型的 uri, 则通过document id来进行处理
String documentId = DocumentsContract.getDocumentId(uri);
if (isMediaDocument(uri)) {
// 使用':'分割
String id = documentId.split(":")[1];

String selection = MediaStore.Images.Media._ID + "=?";
String[] selectionArgs = {id};
filePath = getDataColumn(context, MediaStore.Images.Media.EXTERNAL_CONTENT_URI, selection, selectionArgs);
} else if (isDownloadsDocument(uri)) {
Uri contentUri = ContentUris.withAppendedId(Uri.parse("content://downloads/public_downloads"), Long.valueOf(documentId));
filePath = getDataColumn(context, contentUri, null, null);
}
} else if ("content".equalsIgnoreCase(uri.getScheme())) {
// 如果是 content 类型的 Uri
filePath = getDataColumn(context, uri, null, null);
} else if ("file".equals(uri.getScheme())) {
// 如果是 file 类型的 Uri,直接获取图片对应的路径
filePath = uri.getPath();
}
return filePath;
}

/**
* 获取数据库表中的 _data 列,即返回Uri对应的文件路径
*
*/
private static String getDataColumn(Context context, Uri uri, String selection, String[] selectionArgs) {
String path = null;

String[] projection = new String[]{MediaStore.Images.Media.DATA};
Cursor cursor = null;
try {
cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs, null);
if (cursor != null && cursor.moveToFirst()) {
int columnIndex = cursor.getColumnIndexOrThrow(projection[0]);
path = cursor.getString(columnIndex);
}
} catch (Exception e) {
if (cursor != null) {
cursor.close();
}
}
return path;
}

/**
* @param uri the Uri to check
* @return Whether the Uri authority is MediaProvider
*/
private static boolean isMediaDocument(Uri uri) {
return "com.android.providers.media.documents".equals(uri.getAuthority());
}

/**
* @param uri the Uri to check
* @return Whether the Uri authority is DownloadsProvider
*/
private static boolean isDownloadsDocument(Uri uri) {
return "com.android.providers.downloads.documents".equals(uri.getAuthority());
}
}

本文所有代码都已上传至github,有需求朋友可自行下载查看。

地址:https://github.com/raven-misc/Android-Camera-Album

#
Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×