我在爲同樣的問題而戰,並在很久後得到了一個問題的解決方案,但我相信這可能對社區有用。
隨着在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