簡體   English   中英

如何在android Q上的子目錄中保存圖像同時保持向后兼容

[英]How to save an image in a subdirectory on android Q whilst remaining backwards compatible

我正在創建一個簡單的圖像編輯器應用程序,因此需要加載和保存圖像文件。 我希望保存的文件顯示在單獨相冊的圖庫中。 從 Android API 28 到 29,應用程序能夠訪問存儲的程度發生了巨大變化。 我可以在 Android Q (API 29) 中做我想做的事,但這種方式不向后兼容。

當我想在較低的 API 版本中實現相同的結果時,到目前為止我只找到了需要使用已棄用代碼的方法(從 API 29 開始)。

這些包括:

  • MediaStore.Images.Media.DATA列的使用
  • 通過Environment.getExternalStoragePublicDirectory(...)獲取外部存儲的文件路徑
  • 通過MediaStore.Images.Media.insertImage(...)直接插入圖像

我的問題是:是否有可能以這種方式實現它,因此它向后兼容,但不需要棄用代碼? 如果沒有,在這種情況下是否可以使用已棄用的代碼,或者這些方法很快就會從 sdk 中刪除? 無論如何,使用不推薦使用的方法感覺非常糟糕,所以我寧願不:)

這是我發現適用於 API 29 的方式:

ContentValues values = new ContentValues();
String filename = System.currentTimeMillis() + ".jpg";

values.put(MediaStore.Images.Media.TITLE, filename);
values.put(MediaStore.Images.Media.DISPLAY_NAME, filename);
values.put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg");
values.put(MediaStore.Images.Media.DATE_ADDED, System.currentTimeMillis() / 1000);
values.put(MediaStore.Images.Media.DATE_TAKEN, System.currentTimeMillis());
values.put(MediaStore.Images.Media.RELATIVE_PATH, "PATH/TO/ALBUM");

getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,values);

然后我使用插入方法返回的 URI 來保存位圖。 問題是該字段 RELATIVE_PATH 是在 API 29 中引入的,所以當我在較低版本上運行代碼時,圖像被放入“圖片”文件夾而不是“PATH/TO/ALBUM”文件夾。

在這種情況下可以使用不推薦使用的代碼嗎,或者這些方法很快就會從 sdk 中刪除?

DATA選項在 Android Q 上不起作用,因為該數據不包含在query()結果中,即使您要求它也不能使用它返回的路徑,即使它們被返回。

Environment.getExternalStoragePublicDirectory(...)選項在 Android Q 上默認不起作用,但您可以添加清單條目以重新啟用它。 但是,該清單條目可能會在 Android R 中刪除,因此除非您時間緊迫,否則我不會走這條路。

AFAIK, MediaStore.Images.Media.insertImage(...)仍然有效,即使它已被棄用。

是否有可能以這種方式實現它,因此它向后兼容,但不需要棄用代碼?

我的猜測是您將需要使用兩種不同的存儲策略,一種用於 API 級別 29+,另一種用於舊設備。 我在這個示例應用程序中采用了這種方法,盡管我正在處理視頻內容,而不是圖像,因此insertImage()不是一個選項。

這是對我有用的代碼。 此代碼將圖像保存到手機上的子目錄文件夾中。 它檢查手機的 android 版本,如果它高於 android q,它運行所需的代碼,如果它低於它,它運行 else 語句中的代碼。

來源: https : //androidnoon.com/save-file-in-android-10-and-below-using-scoped-storage-in-android-studio/

 private void saveImageToStorage(Bitmap bitmap) throws IOException {
    OutputStream imageOutStream;
   
     if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {

        ContentValues values = new ContentValues();
        values.put(MediaStore.Images.Media.DISPLAY_NAME, 
        "image_screenshot.jpg");
        values.put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg");
        values.put(MediaStore.Images.Media.RELATIVE_PATH, 
         Environment.DIRECTORY_PICTURES + File.pathSeparator + "AppName");

        Uri uri = 
     getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, 
    values);

        imageOutStream = getContentResolver().openOutputStream(uri);

    } else {

        String imagesDir = 
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES). toString() + "/AppName";
        File image = new File(imagesDir, "image_screenshot.jpg");
        imageOutStream = new FileOutputStream(image);
    }

   
        bitmap.compress(Bitmap.CompressFormat.JPEG, 100, imageOutStream);
        imageOutStream.close();
    

}

對於舊 API (<29),我將圖像放入外部媒體目錄並通過 MediaScannerConnection 對其進行掃描。

讓我們看看我的代碼。

此函數創建一個圖像文件。 注意 appName 變量 - 它是將顯示圖像的相冊的名稱。

override fun createImageFile(appName: String): File {
    val dir = File(appContext.externalMediaDirs[0], appName)
    if(!dir.exists()) {
        ir.mkdir()
    }

    return File(dir, createFileName())
}

然后,我將圖像放入文件中,最后,我像這樣運行媒體掃描儀:

private suspend fun scanNewFile(shot: File): Uri? {
    return suspendCancellableCoroutine { continuation ->
        MediaScannerConnection.scanFile(
            appContext, 
            arrayOf<String>(shot.absolutePath), 
            arrayOf(imageMimeType)) { _, uri -> continuation.resume(uri)
        }
    }
}

經過反復試驗,我發現可以以向后兼容的方式使用MediaStore ,以便在不同版本的實現之間共享盡可能多的代碼。 唯一的技巧是記住,如果您使用MediaColumns.DATA ,您需要自己創建文件

讓我們看看我的項目(Kotlin)中的代碼 此示例用於保存音頻,而不是圖像,但您只需將MIME_TYPEDIRECTORY_MUSIC替換為您需要的任何內容。

private fun newFile(): FileDescriptor? {
    // Create a file descriptor for a new recording.
    val date = DateFormat.getDateTimeInstance().format(Calendar.getInstance().time)
    val filename = "$date.mp3"

    val values = ContentValues().apply {
        put(MediaColumns.TITLE, date)
        put(MediaColumns.MIME_TYPE, "audio/mp3")

        // store the file in a subdirectory
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
            put(MediaColumns.DISPLAY_NAME, filename)
            put(MediaColumns.RELATIVE_PATH, saveTo)
        } else {
            // RELATIVE_PATH was added in Q, so work around it by using DATA and creating the file manually
            @Suppress("DEPRECATION")
            val music = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MUSIC).path

            with(File("$music/P2oggle/$filename")) {
                @Suppress("DEPRECATION")
                put(MediaColumns.DATA, path)

                parentFile!!.mkdir()
                createNewFile()
            }
        }
    }

    val uri = contentResolver.insert(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, values)!!
    return contentResolver.openFileDescriptor(uri, "w")?.fileDescriptor
}

在 Android 10 及更高版本上,我們使用DISPLAY_NAME來設置文件名,使用RELATIVE_PATH來設置子目錄。 在舊版本中,我們使用DATA並手動創建文件(及其目錄)。 在此之后,兩者的實現是相同的:我們只是從MediaStore提取文件描述符並將其返回以供使用。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM