我正在与同样的问题作斗争,并在提出问题后很久才得到解决方案,但我相信这可能对社区有用。
随着 Android 6.0 / API 级别 23 中引入的新 dynamic permissions,主题问题变得尤为重要,因为您需要在运行时请求权限并处理用户的接受和拒绝反应。要使用相机活动,您需要先请求相应的权限(android.permission.CAMERA)。那么,如果你将图片存储在外部目录中,那么用户也需要给你的应用授予相应的权限android.permission.READ_EXTERNAL_STORAGE。在用户即将执行预期操作时(例如,如果在按下“拍照”按钮后立即出现相机访问权限请求),运行时权限请求对用户来说似乎很自然。但是,如果您使用外部存储来保存相机图片,则需要在您的应用拍照时同时要求两个权限:(1)使用相机和(2)访问外部存储。后者可能会令人沮丧,因为不一定清楚为什么您的应用会尝试访问用户文件,而用户只希望拍摄一张照片。
解决方案允许避免外部存储并直接保存相机图片包括使用content providers。根据storage options documentation,
Android 为您提供了一种方法,甚至可以将您的私人数据暴露给其他应用程序 - 使用内容提供程序。内容提供者是一个可选组件,它公开对您的应用程序数据的读/写访问权限,受制于您想要施加的任何限制。
这正是您需要允许相机活动将图片直接保存到应用程序的本地存储中,这样您就可以轻松访问它而无需请求其他权限(仅需要授予相机访问权限) .
提供了一篇带有代码示例的好文章here。以下受本文启发的通用代码在我们的应用程序中使用。
内容提供者类:
/**
* 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 或更高版本的构建工具时才需要处理权限请求。相同的代码与较低级别的构建工具兼容,并且在您不受运行时权限请求困扰但只想避免使用外部存储的情况下很有用。