D10NGYANG / FileProviderAssistant

文件或文件夹 Uri 转真实路径 DEMO,适配Android 10
Apache License 2.0
0 stars 0 forks source link

代码在哪? #1

Open longzekai opened 2 years ago

D10NGYANG commented 2 years ago

当时开了头正准备写的,后来有项目,忙起来忘记这回事了,可以参考这份代码:

package com.hailiao.guowang.generalaviation.devicechecktestapp.utils

import android.annotation.SuppressLint
import android.app.Activity
import android.content.ContentUris
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.content.pm.ResolveInfo
import android.database.Cursor
import android.net.Uri
import android.os.Build
import android.os.Environment
import android.provider.DocumentsContract
import android.provider.MediaStore
import android.util.Log
import androidx.core.content.FileProvider
import java.io.File
import java.io.IOException

/**
 * Uri 与 File 互换工具
 *
 * 配合 {@link android.support.v4.content.FileProvider} 使用的工具类
 * @author D10NG
 * @date on 2020-01-03 16:43
 */
object FileProviderUtils {

    /**
     * 获取文件的Uri
     * @param context
     * @param file
     * @return
     */
    fun getUriForFile(context: Context, file: File): Uri {
        return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            getUriForFile24(context, file)
        } else {
            Uri.fromFile(file)
        }
    }

    /**
     * Android 7 获取文件的Uri
     * @param context
     * @param file
     * @return
     */
    private fun getUriForFile24(
        context: Context,
        file: File
    ): Uri {
        return FileProvider.getUriForFile(context, context.packageName + ".fileprovider", file)
    }

    /**
     * 设定intent的data和type
     * @param context
     * @param intent
     * @param type
     * @param file
     * @param writeAble
     */
    fun setIntentDataAndType(
        context: Context,
        intent: Intent,
        type: String?,
        file: File,
        writeAble: Boolean
    ) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            val uri = getUriForFile(context, file)
            intent.setDataAndType(uri, type)
            intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
            if (writeAble) {
                intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
            }
        } else {
            intent.setDataAndType(Uri.fromFile(file), type)
            // 放在cache文件中,需要获取读写权限
            chmod("777", file.absolutePath)
        }
    }

    fun setIntentGrantUriPermission(
        context: Context,
        intent: Intent,
        uri: Uri
    ) {
        val act = context as Activity
        val resInfoList: List<ResolveInfo> = act.packageManager.queryIntentActivities(intent,
            PackageManager.MATCH_DEFAULT_ONLY)
        for (info in resInfoList) {
            val packageName = info.activityInfo.packageName
            act.grantUriPermission(packageName, uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
            act.grantUriPermission(packageName, uri, Intent.FLAG_GRANT_READ_URI_PERMISSION)
        }
    }

    /**
     * 修改文件权限
     * @param permission
     * @param path
     */
    fun chmod(permission: String, path: String) {
        try {
            val command = "chmod $permission $path"
            val runtime = Runtime.getRuntime()
            runtime.exec(command)
        } catch (e: IOException) {
            e.printStackTrace()
        }
    }

    /**
     * 设定intent的data
     * @param context
     * @param intent
     * @param file
     * @param writeAble
     */
    fun setIntentData(
        context: Context,
        intent: Intent,
        file: File,
        writeAble: Boolean
    ) {
        if (Build.VERSION.SDK_INT >= 24) {
            intent.data = getUriForFile(context, file)
            intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
            if (writeAble) {
                intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
            }
        } else {
            intent.data = Uri.fromFile(file)
        }
    }

    /**
     * 授予权限
     * @param context
     * @param intent
     * @param uri
     * @param writeAble
     */
    fun grantPermissions(
        context: Context,
        intent: Intent,
        uri: Uri?,
        writeAble: Boolean
    ) {
        var flag = Intent.FLAG_GRANT_READ_URI_PERMISSION
        if (writeAble) {
            flag = flag or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
        }
        intent.addFlags(flag)
        val resInfoList = context.packageManager
            .queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY)
        for (resolveInfo in resInfoList) {
            val packageName = resolveInfo.activityInfo.packageName
            context.grantUriPermission(packageName, uri, flag)
        }
    }

    /**
     * 根据URI获取文件真实路径(兼容多机型)
     * @param context
     * @param uri
     * @return
     */
    fun getFilePathByUri(
        context: Context,
        uri: Uri
    ): String? { // 判断uri的标头是 content 还是 file,分别用不同的方法处理
        if ("content".equals(uri.scheme, true)) {
            val sdkVersion = Build.VERSION.SDK_INT
            return if (sdkVersion >= 19) {
                getRealPathFromUriAboveApi19(context, uri)
            } else {
                getRealPathFromUriBelowAPI19(context, uri)
            }
        } else if ("file".equals(uri.scheme, true)) {
            return uri.path
        }
        return null
    }

    /**
     * 适配api19及以上,根据uri获取图片的绝对路径
     *
     * @param context 上下文对象
     * @param uri     图片的Uri
     * @return 如果Uri对应的图片存在, 那么返回该图片的绝对路径, 否则返回null
     */
    @SuppressLint("NewApi")
    private fun getRealPathFromUriAboveApi19(
        context: Context,
        uri: Uri
    ): String? {
        var filePath: String? = null
        if (DocumentsContract.isDocumentUri(
                context,
                uri
            )
        ) { // 如果是document类型的 uri, 则通过document id来进行处理
            val documentId = DocumentsContract.getDocumentId(uri)
            if (isMediaDocument(uri)) {
                // MediaProvider
                // 使用':'分割
                val type = documentId.split(":").toTypedArray()[0]
                val id = documentId.split(":").toTypedArray()[1]
                val selection = MediaStore.Images.Media._ID + "=?"
                val selectionArgs = arrayOf(id)
                // 判断文件类型
                var contentUri: Uri? = null
                when (type) {
                    "image" -> {
                        contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI
                    }
                    "video" -> {
                        contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI
                    }
                    "audio" -> {
                        contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI
                    }
                }
                filePath = getDataColumn(context, contentUri, selection, selectionArgs)
            } else if (isDownloadsDocument(uri)) {
                // DownloadsProvider
                val contentUri = ContentUris.withAppendedId(
                    Uri.parse("content://downloads/public_downloads"),
                    java.lang.Long.valueOf(documentId)
                )
                filePath = getDataColumn(context, contentUri, null, null)
            } else if (isExternalStorageDocument(uri)) {
                // ExternalStorageProvider
                val docId = DocumentsContract.getDocumentId(uri)
                val split = docId.split(":").toTypedArray()
                val type = split[0]
                if ("primary".equals(type, true)) {
                    filePath = Environment.getExternalStorageDirectory().toString() + "/" + split[1]
                }
            } else {
                Log.e("FileProviderUtils", "路径错误")
            }
        } else if ("content".equals(uri.scheme, true)) {
            // 如果是 content 类型的 Uri
            filePath = getDataColumn(context, uri, null, null)
        } else if ("file" == uri.scheme) {
            // 如果是 file 类型的 Uri,直接获取图片对应的路径
            filePath = uri.path
        }
        return filePath
    }

    /**
     * 适配api19以下(不包括api19),根据uri获取图片的绝对路径
     *
     * @param context 上下文对象
     * @param uri     图片的Uri
     * @return 如果Uri对应的图片存在, 那么返回该图片的绝对路径, 否则返回null
     */
    private fun getRealPathFromUriBelowAPI19(
        context: Context,
        uri: Uri
    ): String? {
        return getDataColumn(context, uri, null, null)
    }

    /**
     * 获取数据库表中的 _data 列,即返回Uri对应的文件路径
     *
     * @return
     */
    private fun getDataColumn(
        context: Context,
        uri: Uri?,
        selection: String?,
        selectionArgs: Array<String>?
    ): String? {
        var path: String? = null
        val projection = arrayOf(
            MediaStore.Images.Media.DATA
        )
        var cursor: Cursor? = null
        try {
            cursor =
                context.contentResolver.query(uri!!, projection, selection, selectionArgs, null)
            if (cursor != null && cursor.moveToFirst()) {
                val columnIndex = cursor.getColumnIndexOrThrow(projection[0])
                path = cursor.getString(columnIndex)
            }
        } catch (e: Exception) {
            cursor?.close()
        }
        return path
    }

    /**
     * @param uri the Uri to check
     * @return Whether the Uri authority is MediaProvider
     */
    private fun isMediaDocument(uri: Uri): Boolean {
        return "com.android.providers.media.documents" == uri.authority
    }

    private fun isExternalStorageDocument(uri: Uri): Boolean {
        return "com.android.externalstorage.documents" == uri.authority
    }

    /**
     * @param uri the Uri to check
     * @return Whether the Uri authority is DownloadsProvider
     */
    private fun isDownloadsDocument(uri: Uri): Boolean {
        return "com.android.providers.downloads.documents" == uri.authority
    }
}

以及这个工程:https://github.com/D10NGYANG/photoTest