我曾经也遇到过同样的问题,但是我在问题被提出后很长一段时间才找到解决办法,但我相信这可能对社区有用。
随着 Android 6.0 / API 级别 23 引入的新的
动态权限,话题问题变得特别重要,因为您需要在运行时请求权限并处理用户的接受和拒绝反应。如果要使用相机活动,则需要首先请求相应的权限(
android.permission.CAMERA
)。然后,如果将图片存储在外部目录中,则还需要获得用户授予的相应的权限
android.permission.READ_EXTERNAL_STORAGE
。当用户即将执行所需操作时(例如,如果相机访问权限请求出现在按下“拍照”按钮后),运行时权限请求对用户来说似乎是自然的。但是,如果您使用外部存储器保存相机图像,则在应用程序拍照时需要同时请求两个权限:(1)使用相机和(2)访问外部存储器。后者可能令人沮丧,因为不一定清楚为什么您的应用程序试图访问用户文件,而用户只期望拍摄照片。
解决方案,可以避免使用外部存储并直接保存相机图片的方法是使用
内容提供者。根据
存储选项文档,
Android为您提供了一种将甚至您的私有数据暴露给其他应用程序的方法——使用内容提供者。内容提供者是一个可选组件,它公开了对您的应用程序数据的读/写访问权限,受到您想要强制执行的任何限制。
这正是您需要允许相机活动直接将图片保存到您应用程序的本地存储中,以便您稍后可以轻松访问它,而无需请求其他权限(只需要授予相机访问权限)。
一个很好的文章和代码示例在
这里提供。我们应用程序中使用以下通用代码来完成此操作。
内容提供者类:
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或更高的构建工具时,才需要处理权限请求。相同的代码与较低级别的构建工具兼容,并且在您不受运行时权限请求干扰但仅想避免使用外部存储时非常有用。
Context.MODE_WORLD_WRITEABLE
。我做了一个假设,认为它允许任何应用程序从那里访问文件和其他东西,因为它不是MODE_PRIVATE
。它不是这样工作的吗?基本上因为它曾经起过作用,我猜想它就是这样做的。 - Andy