Android:在没有外部存储权限的情况下读取文件

5
我不希望我的应用程序需要任何权限,但是我想让用户能够选择要读取的文件。我的应用程序不需要对文件系统进行任意访问。然而,到目前为止,所有我研究过的打开文件对话框实现似乎都默认具有访问外部存储权限。
我能想到的一个解决方法是将我的应用程序配置为打开某种类型的文件的应用程序列表之一。我还没有尝试过这个方法,但我希望这可以在不获取访问外部存储权限的情况下工作。然而,在这种情况下,用户指导会不太理想。我更喜欢使用对话框解决方案并让用户选择文件。
我认为这个要求不会破坏安全性,因为用户可以完全控制我的应用程序可以读取哪些文件。这是否有可能呢?

3
完全不可能。没有授权你就无法访问。将抛出SecurityException异常。 - Shabbir Dhangot
2个回答

9
然而,到目前为止我所研究的所有 openfiledialog 实现都似乎默认具有访问外部存储权限。将你的 minSdkVersion 设为 19,然后使用 ACTION_OPEN_DOCUMENT(存储访问框架的一部分)。或者,如果你需要的 minSdkVersion 低于 19,在旧设备上使用 ACTION_GET_CONTENT。你将通过 onActivityResult() 得到一个 Uri。使用 ContentResolver 和诸如 openInputStream() 的方法来使用该 Uri 标识的内容。我没有尝试过这个,但我希望它可以在没有访问外部存储权限的情况下工作。
只有当你排除file: Uri值时才有效。例如,支持仅content: Uri值的<intent-filter>将起作用。

0

Android 11 解决文件访问问题,无需使用MANAGE_EXTERNAL_STORAGE。我已添加代码以获取文档文件并上传到服务器。

AndroidManifest

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
 <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

<application
 android:preserveLegacyExternalStorage="true"
 android:requestLegacyExternalStorage="true"
</application>

现在,在你的项目中添加这个库

https://github.com/FivesoftCode/FilePicker

将以下代码添加到Activity/Fragment中

FilePicker.from(activity)
            .setFileTypes(FilePicker.IMAGE, FilePicker.VIDEO) //Set file types you want to pick.
            .setAllowMultipleFiles(true) //Allow user to select multiple files
            .setListener { files ->  //Wait for results
                if (files != null && files.size > 0) {
                    //Do something with uris.
                    for (items in files) {
                        val extension: String = getMimeType(activity!!,items)!!
                        if (extension == "pdf") {
                            val cacheDir: String = context!!.cacheDir.toString()
                            val getCopyFilePath = copyFileToInternalStorage(context!!,items,cacheDir)
                            Log.e("TAG", "getPathToUploadDoc: " + getCopyFilePath )
                        }
                    }
                } else {
                     //Add msg here...
                }
            }
            .setTitle("Pick a file from My Files")
            .pick() //Open file picker

请添加以下方法以获取Mime类型。
fun getMimeType(context: Context, uri: Uri): String? {
            val extension: String?

            //Check uri format to avoid null
            extension = if (uri.scheme == ContentResolver.SCHEME_CONTENT) {
                //If scheme is a content
                val mime = MimeTypeMap.getSingleton()
                mime.getExtensionFromMimeType(context.contentResolver.getType(uri))
            } else {
                //If scheme is a File
                //This will replace white spaces with %20 and also other special characters. This will avoid returning null values on file name with spaces and special characters.
                MimeTypeMap.getFileExtensionFromUrl(Uri.fromFile(File(uri.path)).toString())
            }
            return extension
        }


fun getFiledetails(uri: Uri,context: Context,getCopyFilePath:String): NormalFile? {
//        var result: String? = null
            
            if (uri.scheme == "content") {
                val cursor: Cursor = context.contentResolver.query(uri,
                    FileLoader.FILE_PROJECTION, null, null, null)!!
                try {
                    if (cursor != null && cursor.moveToFirst()) {
//                    result = cursor.getString(cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME))

                        val path: String = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DATA))
                        if (path != null && path != "") {
                            //Create a File instance
                           
                            cursor.getLong(cursor.getColumnIndexOrThrow(BaseColumns._ID))
//                          cursor.getLong(cursor.getColumnIndexOrThrow(BaseColumns._ID)).toInt()
                       cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.TITLE))
//                          cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DATA))
                          cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.SIZE))
                         cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DATE_ADDED))
                           cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns.MIME_TYPE))
                        }
                    }
                } finally {
                    cursor.close()
                }
            }

            /*if (result == null) {
                result = uri.path
                val cut = result!!.lastIndexOf('/')
                if (cut != -1) {
                    result = result.substring(cut + 1)
                }
            }*/
            return file
        }


fun copyFileToInternalStorage(context: Context?,uri: Uri, newDirName: String): String? {
            val returnCursor = context!!.contentResolver.query(
                uri, arrayOf(
                    OpenableColumns.DISPLAY_NAME, OpenableColumns.SIZE
                ), null, null, null
            )


            /*
             * Get the column indexes of the data in the Cursor,
             *     * move to the first row in the Cursor, get the data,
             *     * and display it.
             * */
            val nameIndex = returnCursor!!.getColumnIndex(OpenableColumns.DISPLAY_NAME)
            val sizeIndex = returnCursor.getColumnIndex(OpenableColumns.SIZE)
            returnCursor.moveToFirst()
            val name = returnCursor.getString(nameIndex)
            val size = java.lang.Long.toString(returnCursor.getLong(sizeIndex))
            val output: File
            output = if (newDirName != "") {
                val dir = File(/*context!!.filesDir.toString() + "/" +*/ newDirName)
                if (!dir.exists()) {
                    dir.mkdir()
                }
                File(/*context!!.filesDir.toString() + "/" +*/ newDirName + "/" + name)
            } else {
                File(context!!.filesDir.toString() + "/" + name)
            }
            try {
                val inputStream: InputStream? = context!!.contentResolver.openInputStream(uri)
                val outputStream = FileOutputStream(output)
                var read = 0
                val bufferSize = 1024
                val buffers = ByteArray(bufferSize)
                while (inputStream?.read(buffers).also { read = it!! } != -1) {
                    outputStream.write(buffers, 0, read)
                }
                inputStream?.close()
                outputStream.close()
            } catch (e: Exception) {
                Log.e("Exception", e.message!!)
            }
            return output.path
        }

上传文档

implementation 'net.gotev:uploadservice:2.1'

var uploadId = UUID.randomUUID().toString()
        val url = ServerConfig.MAIN_URL
        uploadReceiver.setDelegate(this)
        uploadReceiver.setUploadID(uploadId)
        val data = MultipartUploadRequest(mContext, uploadId, url)
                .addFileToUpload(path, "attachment")
                .addHeader("Authentication", getMD5EncryptedString())
                .addParameter(USER_ID,1)
                .setMaxRetries(5)
                .startUpload()

fun getMD5EncryptedString(): String {
            val encTarget = ServerConfig.AUTHENTICATE_VALUE //Any pwd
            var mdEnc: MessageDigest? = null
            try {
                mdEnc = MessageDigest.getInstance("MD5")
            } catch (e: NoSuchAlgorithmException) {
                println("Exception while encrypting to md5")
                e.printStackTrace()
            }

            mdEnc!!.update(encTarget.toByteArray(), 0, encTarget.length)
            var md5 = BigInteger(1, mdEnc.digest()).toString(16)
            while (md5.length < 32) {
                md5 = "0$md5"
            }
            return md5
        }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接