我正在与同样的问题作斗争,并在提出问题后很久才得到解决方案,但我相信这可能对社区有用。
随着 Android 6.0 / API 级别 23 中引入的新动态权限,主题问题变得尤为重要,因为您需要在运行时请求权限并处理用户的接受和拒绝反应。要使用相机活动,您需要先请求相应的权限 ( android.permission.CAMERA
)。那么,如果将图片存放在外部目录,对应的权限android.permission.READ_EXTERNAL_STORAGE
还需要由用户授予您的应用程序。在用户即将执行预期动作的时刻(例如,如果相机访问许可请求恰好在按下“拍照”按钮之后出现),运行时许可请求对用户来说似乎很自然。但是,如果您使用外部存储保存相机图片,则您的应用在拍照时需要同时请求两个权限:(1)使用相机和(2)访问外部存储。后者可能令人沮丧,因为不一定清楚为什么您的应用程序试图访问用户文件,而用户只希望拍摄一张照片。
允许避免外部存储并直接保存相机图片的解决方案在于使用内容提供者。根据存储选项文档,
Android 为您提供了一种将您的私人数据甚至公开给其他应用程序的方法——使用内容提供程序。内容提供者是一个可选组件,它公开对您的应用程序数据的读/写访问,受您想要施加的任何限制的约束。
这正是您需要允许相机活动将图片直接保存到应用程序的本地存储中,这样您就可以轻松访问它而无需请求其他权限(仅需要授予相机访问权限)。
这里提供了一篇带有代码示例的好文章。以下受本文启发的通用代码在我们的应用程序中用于实现这一目的。
内容提供者类:
/**
* A content provider that allows to store the camera image internally without requesting the
* permission to access the external storage to take shots.
*/
public class CameraPictureProvider extends ContentProvider {
private static final String FILENAME = "picture.jpg";
private static final Uri CONTENT_URI = Uri.parse("content://xyz.example.app/cameraPicture");
@Override
public boolean onCreate() {
try {
File picture = new File(getContext().getFilesDir(), FILENAME);
if (!picture.exists())
if (picture.createNewFile()) {
getContext().getContentResolver().notifyChange(CONTENT_URI, null);
return true;
}
} catch (IOException | NullPointerException e) {
e.printStackTrace();
}
return false;
}
@Nullable
@Override
public ParcelFileDescriptor openFile(@NonNull Uri uri, @NonNull String mode) throws FileNotFoundException {
try {
File picture = new File(getContext().getFilesDir(), FILENAME);
if (!picture.exists())
picture.createNewFile();
return ParcelFileDescriptor.open(picture, ParcelFileDescriptor.MODE_READ_WRITE);
} catch (IOException | NullPointerException e) {
e.printStackTrace();
}
return null;
}
@Nullable
@Override
public Cursor query(@NonNull Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
return null;
}
@Nullable
@Override
public String getType(@NonNull Uri uri) {
String lc = uri.getPath().toLowerCase();
if (lc.endsWith(".jpg") || lc.endsWith(".jpeg"))
return "image/jpeg";
return null;
}
@Nullable
@Override
public Uri insert(@NonNull Uri uri, ContentValues values) {
return null;
}
@Override
public int delete(@NonNull Uri uri, String selection, String[] selectionArgs) {
return 0;
}
@Override
public int update(@NonNull Uri uri, ContentValues values, String selection, String[] selectionArgs) {
return 0;
}
}
需要在应用清单中声明内容提供者:
<provider android:authorities="xyz.example.app"
android:enabled="true"
android:exported="true"
android:name="xyz.example.app.CameraPictureProvider" />
最后,为了使用内容提供程序来捕获相机图片,从调用活动中调用以下代码:
Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
// Ensure that there's a camera activity to handle the intent
takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, CameraPictureProvider.CONTENT_URI);
startActivityForResult(takePictureIntent, 0);
请注意,相机权限请求需要单独处理(在提供的代码示例中未完成)。
还值得注意的是,仅当您使用 23 或更高版本的构建工具时才需要处理权限请求。相同的代码与较低级别的构建工具兼容,并且在您不受运行时权限请求困扰但只想避免使用外部存储的情况下很有用。