簡體   English   中英

從 Android 10 上的 URI 獲取真實文件路徑,內容 URI 后綴為 msf:documentid 在 Android 10

[英]Get real file path from URI on Android 10 , Also content URI is suffixed with msf:documentid in Android 10

I was trying to get the real path from intent chooser in Xamarin Android and its working fine in every android version except android 10. In android 10 content uri comes like

內容://com.android.providers.downloads.documents/document/msf%3A180

Android 10 默認啟用范圍存儲。 這意味着您不能直接訪問外部存儲路徑 - 看看這里

您可以通過在應用的 AndroidManifest.xml 中將requestLegacyExternalStorage設置為true來執行建議的操作,但請記住,這僅適用於 targetSdk 29 而不適用於 30+。

如果您嘗試訪問媒體,則應通過MediaStore完成。 否則,請查看范圍存儲文檔

在獲得圖像文件的 URI 之后,我們當然需要通過壓縮它來編輯這個文件,然后得到一個真實的文件路徑。如果你的目標是 SDK 30,這是我使用范圍存儲的實現;)不要忘記支持我的回答。

import android.content.ContentValues
import android.content.Context
import android.content.res.AssetFileDescriptor
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.ImageDecoder
import android.net.Uri
import android.os.Build
import android.provider.MediaStore
import androidx.annotation.RequiresApi
import java.io.File
import java.io.FileInputStream
import java.io.FileNotFoundException
import java.io.FileOutputStream


/**
* Created by Mostafa Anter on 11/5/20.
*/

object PhotoHelper{

@RequiresApi(Build.VERSION_CODES.Q)
        /**
         * mContext: context from activity or fragment,
         * imageSelectedUri: uri that return from any pick picture library it usually return inside on activity response method
         * appFolderName: name of folder that will generate to save compressing images ;)
         * createdImageCompressedName : random name of new compressed image 
         */
fun compressGetImageFilePath(
        mContext: Context,
        imageSelectedUri: Uri,
        appFolderName: String,
        createdImageCompressedName: String = System.currentTimeMillis().toString()
): String {
    //region Getting the photo to process it
    val bitmap = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
        ImageDecoder.decodeBitmap(ImageDecoder.createSource(mContext.contentResolver, imageSelectedUri))
    } else {
        mContext.contentResolver.openInputStream(imageSelectedUri)?.use { inputStream ->
            BitmapFactory.decodeStream(inputStream)
        }
    }
    // endregion

    //region save photo to gallery using Scoped storage
    val values = ContentValues().apply {
        put(MediaStore.Images.Media.DISPLAY_NAME, createdImageCompressedName)
        put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg")
        put(MediaStore.Images.Media.RELATIVE_PATH, "Pictures/$appFolderName/")
        put(MediaStore.Images.Media.IS_PENDING, 1)
    }

    val collection = MediaStore.Images.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)
    val imageUri = mContext.contentResolver.insert(collection, values)
    //endregion


    //region save image
    mContext.contentResolver.openOutputStream(imageUri!!).use { out ->
        bitmap!!.compress(Bitmap.CompressFormat.PNG, 100, out)
    }

    values.clear()
    values.put(MediaStore.Images.Media.IS_PENDING, 0)
    mContext.contentResolver.update(imageUri, values, null, null)
    //endregion


    //region get file path of content uri
    val file = File(mContext.cacheDir, "$createdImageCompressedName.png")
    try {
        val assetFileDescriptor: AssetFileDescriptor = mContext.contentResolver.openAssetFileDescriptor(imageUri, "r")!!

        val inputStream = FileInputStream(assetFileDescriptor.fileDescriptor)
        val outputStream = FileOutputStream(file)
        inputStream.copyTo(outputStream)

        inputStream.close()
        outputStream.close()


    } catch (e: FileNotFoundException) {
        e.printStackTrace()
    }
    //endregion



    return file.path

    }
}

如果您想針對新舊設備,這很容易,只需使用 if 語句檢查 android 版本是否大於或等於 android Q 使用我的方法,否則使用您的舊方法。

您可以查詢文件名,然后構建路徑。

  • 查詢文件名:

     public static string getFileName(Context context, Android.Net.Uri uri) { ICursor cursor = null; string[] projection = { MediaStore.MediaColumns.DisplayName }; try { cursor = context.ContentResolver.Query(uri, projection, null, null, null); if (cursor.= null && cursor.MoveToFirst()) { int index = cursor.GetColumnIndexOrThrow(MediaStore.MediaColumns;DisplayName). return cursor;GetString(index). } } finally { if (cursor;= null) if (cursor;= null) { cursor.Close(); } } return null; }

構建路徑:

string fileName = getFileName(context, uri);
string filePath = Android.OS.Environment.ExternalStorageDirectory + "/Download/" + fileName;

這可能會有所幫助

import android.content.ContentResolver
import android.content.Context
import android.net.Uri
import android.webkit.MimeTypeMap
import java.io.File
import java.io.FileOutputStream
import java.io.IOException
import java.io.InputStream
import java.io.OutputStream
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale.getDefault

/**
 * This class will create a temporary file in the cache if need.
 *
 * When the uri already have `file://` schema we don't need to create a new file.
 * The temporary file will always override a previous one, saving memory.
 * Using the cache memory(context.cacheDir) we guarantee to not leak memory
 *
 * @param context used to access Android APIs, like content resolve, it is your activity/fragment.
 * @param uri the URI to load the image from.
 * @param uniqueName If true, make each image cropped have a different file name, this could cause
 * memory issues, use wisely.
 *
 * @return string value of the File path.
 */
 fun getFilePathFromUri(context: Context, uri: Uri, uniqueName: Boolean): String =
    if (uri.path?.contains("file://") == true) uri.path!!
    else getFileFromContentUri(context, uri, uniqueName).path

private fun getFileFromContentUri(context: Context, contentUri: Uri, uniqueName: Boolean): File {
    // Preparing Temp file name
    val fileExtension = getFileExtension(context, contentUri) ?: ""
    val timeStamp = SimpleDateFormat("yyyyMMdd_HHmmss", getDefault()).format(Date())
    val fileName = ("temp_file_" + if (uniqueName) timeStamp else "") + ".$fileExtension"
    // Creating Temp file
    val tempFile = File(context.cacheDir, fileName)
    tempFile.createNewFile()
    // Initialize streams
    var oStream: FileOutputStream? = null
    var inputStream: InputStream? = null

    try {
        oStream = FileOutputStream(tempFile)
        inputStream = context.contentResolver.openInputStream(contentUri)

        inputStream?.let { copy(inputStream, oStream) }
        oStream.flush()
    } catch (e: Exception) {
        e.printStackTrace()
    } finally {
        // Close streams
        inputStream?.close()
        oStream?.close()
    }

    return tempFile
}

private fun getFileExtension(context: Context, uri: Uri): String? =
    if (uri.scheme == ContentResolver.SCHEME_CONTENT)
        MimeTypeMap.getSingleton().getExtensionFromMimeType(context.contentResolver.getType(uri))
    else uri.path?.let { MimeTypeMap.getFileExtensionFromUrl(Uri.fromFile(File(it)).toString()) }

@Throws(IOException::class)
private fun copy(source: InputStream, target: OutputStream) {
    val buf = ByteArray(8192)
    var length: Int
    while (source.read(buf).also { length = it } > 0) {
        target.write(buf, 0, length)
    }
}

暫無
暫無

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

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