我在为同样的问题而战,并在很久后得到了一个问题的解决方案,但我相信这可能对社区有用。
随着在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);
请不要e相机许可请求需要单独处理(不在所提供的代码示例中完成)。
还值得注意的是,只有在使用构建工具版本23或更高版本时才需要处理权限请求。相同的代码与低级构建工具兼容,并且在您不受运行时权限请求困扰的情况下很有用,但只希望避免使用外部存储。
看,这正是我一直在向我的老板争论的。但不幸的是,因为上面的代码在某些时候起作用,他们希望以这种方式保留它。我确实有一个问题需要澄清:'Context.MODE_WORLD_WRITEABLE'。我做了一个假设,它允许任何应用程序从那里访问文件和内容,因为它不是'MODE_PRIVATE'。这种方式不行吗?基本上,因为它在某个时刻起作用,我会猜测它做了什么。 – Andy
@Andy:“但不幸的是,因为上面的代码在某种程度上起作用,他们希望以这种方式保留它” - 如果有的话,它肯定无法可靠地工作。 “这种方式不行吗?” - 是和不是。是的,该文件是可写的。您认为相机应用正在写入现有文件。它可能会尝试删除现有文件并创建一个新文件。 *目录*是世界可写的,它们可能会成功删除,但是您将无法读取结果文件,因为它将由其他应用程序拥有,可能是私有的。 – CommonsWare
@Andy:请记住,有数千个相机应用程序,其中任何一个都可以响应你的'Intent',因此行为会有所不同。仅仅因为你可以获得一个应用程序的工作并不意味着所有人都会。 – CommonsWare