如何在 Android API LEVEL28 之后使用 Android SAF(存储访问框架)将文档文件保存到外部存储中

4

这段代码可以在媒体文件中正常工作,但我需要解决文档文件的问题。

我不知道如何将contentValues应用于文档文件。

fun getFile(fileName: String): File? {
    with(sharePrefHelper.app){
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
            val values = ContentValues()
            // Here is My Question That what should i Do Here Because this is for document not for image
            values.put(MediaStore.Images.Media.DISPLAY_NAME, fileName)
            // for MIME_TYPE "image/jpg" this is working
            values.put(MediaStore.Images.Media.MIME_TYPE, "text/csv")
            values.put(MediaStore.MediaColumns.RELATIVE_PATH, "DCIM/Donny")
            contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values)?.let {
                it.path?.let { finalPath ->
                    return File(finalPath)
                }
            }
        } else {
            val directory: File = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM + "/Donny")
            if (!directory.exists()){
                directory.mkdirs()
            }
            return File(directory, fileName)
        }
        return null
    }
}

这段代码可以正常处理媒体文件

我的问题是如何将CSV文件保存在安卓设备的外部文件夹中


你想将文件保存在哪里?文件最初存储在哪里? - Louis Chabert
我想要使用应用程序命名的文件夹保存在下载或DCIM文件夹中 目前,我将这些文件存储在API 28以上的应用程序范围存储Android/data/appPackageName中 - Gulab Sagevadiya
好的,我做了类似的事情,但是在Java Android中而不是Kotlin,但我可以给你一个例子。 - Louis Chabert
快速提问,您是否在意将应用发布到Google Play上,还是仅拥有APK对您来说已足够? - H.A.H.
是的,这已经在我的实时应用程序上实现了,这就是为什么我正在寻找一个至少可以使用2年的适当解决方案来处理所有可能的设备的正确方法。这就是为什么我在答案中要求SAF方法的原因。 - Gulab Sagevadiya
@gulabpatel 或许这个链接可以帮到你:https://dev59.com/flIH5IYBdhLWcg3weOUq - Louis Chabert
2个回答

3

编辑:

嗯嗯嗯,我仍在尝试将任何类型的文件添加到“下载”目录中。 个人而言,我正在尝试从我的assetFolder复制文件并粘贴到“Download”文件夹中。我还没有成功。 但是,我可以使用下面的方法在该文件夹中创建任何类型的文件。我希望这可以帮助您。

这是我的代码:

    public void saveFileToPhone(InputStream inputStream, String filename) {
    OutputStream outputStream;
    Context myContext = requireContext();
    try {
        if(Build.VERSION.SDK_INT >=Build.VERSION_CODES.Q){
            ContentResolver contentResolver = requireContext().getContentResolver();
            ContentValues contentValues = new ContentValues();
            contentValues.put(MediaStore.Downloads.DISPLAY_NAME,filename);
            contentValues.put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DOWNLOADS);
            Uri collection = MediaStore.Downloads.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY);
            Uri fileUri = contentResolver.insert(collection, contentValues);
            outputStream = contentResolver.openOutputStream(Objects.requireNonNull(fileUri));
            Objects.requireNonNull(outputStream);

        }
    }catch (FileNotFoundException e) {

        e.printStackTrace();
    }
}

这将在Android 10中运行,但从Android 11开始,我需要强制使用SAF来保存文档文件。我对此进行了一些研究,发现只能通过https://developer.android.com/reference/android/content/Intent#ACTION_CREATE_DOCUMENT实现。您可以在范围存储中了解更多信息,因此我需要至少适用于Android 12的解决方案。 - Gulab Sagevadiya
在这里,他们提到了所有类型和方法。请参阅:https://developer.android.com/training/data-storage#scoped-storage - Gulab Sagevadiya
1
所以,你在问题中提到了API 28,但它不是在API 28之后,而是在API 30之后? - Louis Chabert
他确实要求从28版本开始。这意味着从28版本到当前版本。您不需要在当前和之前的Android版本上工作。 - H.A.H.
1
我保存了一个0字节的文件。你有一个inputStream参数,但你没有使用它。那么如何将这个inputStream写入那个空文件中呢? - Arnab Kundu
显示剩余2条评论

1

这里是我用简洁的方式完成的内容:

这是文件提供者活动

class FileProviderActivity : AppCompatActivity() {

    var commonIntentLauncher: ActivityResultLauncher<Intent?> = registerForActivityResult(
        ActivityResultContracts.StartActivityForResult()) { result ->
        if (result.resultCode == Activity.RESULT_OK) {
            result.data?.let {
                intent.getParcelableExtra<ResultReceiver>("FileReceiver")?.send(0, bundleOf(
                    "FileUri" to result?.data?.data.toString()
                ))
                finish()
            }
        }
    }


    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q){
            val activityIntent = Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
                addCategory(Intent.CATEGORY_OPENABLE)
                type = intent.getStringExtra("fileType")
                putExtra(Intent.EXTRA_TITLE, intent.getStringExtra("fileName"))
                putExtra(DocumentsContract.EXTRA_INITIAL_URI, MediaStore.Downloads.EXTERNAL_CONTENT_URI)
            }
            commonIntentLauncher.launch(activityIntent)
        }else{
            val directory: File = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS + "/Dunny/CSVFiles")
            if (!directory.exists()){
                directory.mkdirs()
            }

            intent.getParcelableExtra<ResultReceiver>("FileReceiver")?.send(0, bundleOf(
                "FileUri" to File(directory, intent.getStringExtra("fileName")!!).toURI().toString()
            ))
            finish()
        }
    }
}

这个FileProviderHelper

class MyFileProvider {

    companion object {
        fun with(context: Context) = FileRequest(context)
    }

    class FileRequest(private val context: Context) {

        fun request(fileName: String, fileType: String = "application/*", file: (Uri?) -> Unit ) {
            val intent = Intent(context, FileProviderActivity::class.java)
                .addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP)
                .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
                .putExtra("fileName",fileName)
                .putExtra("fileType", fileType)
                .putExtras(bundleOf("FileReceiver" to FileReceiver(file)))
            context.startActivity(intent)
        }
    }

    internal class FileReceiver(private val file: (Uri?) -> Unit) : ResultReceiver(Handler(Looper.getMainLooper())) {
        override fun onReceiveResult(resultCode: Int, resultData: Bundle?) {
            super.onReceiveResult(resultCode, resultData)
            resultData?.let {
                file(it.getString("FileUri")?.toUri())
            }
        }
    }
}

这里是使用该函数的示例:

MyFileProvider.with(this).request("TestFile.csv","application/*") { fileUri ->
    toast(fileUri.toString())
}

你知道如何使用你的方法插入资产文件吗?能帮我一下吗? - Louis Chabert
requireActivity().contentResolver.openFileDescriptor(fileUri, "w")?.use { parcelFileDescriptor -> FileOutputStream(parcelFileDescriptor.fileDescriptor).use { fos -> csvWriter().open(fos) { writeRow(allStudents[0].keys.toList()) writeRows(allStudents.map { it.values.toList() }) } toast("生成成功!!!") } } 我已经像这样使用了该URI - Gulab Sagevadiya
我不知道如何将这个与我上面的代码结合使用,但是谢谢你的尝试。 - Louis Chabert
使用OpenFileDescriptor打开文件描述符,然后使用该文件描述符创建fileOutputStream,并将该fileOutputStream用于复制。在我的情况下,我正在将此fos提供给csvWriter对象以将数据写入该文件。 - Gulab Sagevadiya

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