简体   繁体   English

Android 11:保存其他应用可以访问的文件

[英]Android 11: Save a file that can other apps can access

(This question is regarding Android 11) (这个问题是关于 Android 11)

I want to print crash logs to a file that other applications can read (specifically, I want to be able to navigate to the file and view the data with the "Files" app).我想将崩溃日志打印到其他应用程序可以读取的文件中(具体来说,我希望能够导航到该文件并使用“文件”应用程序查看数据)。

I've seen dozens of answers to this question, but they all have one of two problems:我已经看到了这个问题的几十个答案,但他们都有两个问题之一:

  1. they're using Environment.getExternalStoragePublicDirectory which is deprecated, or他们正在使用已弃用的Environment.getExternalStoragePublicDirectory ,或者
  2. they're using getExternalFilesDir but this returns a directory that is not visible to other apps.他们正在使用getExternalFilesDir但这会返回一个对其他应用程序不可见的目录。

Please note I want to do this in my Application class, not in an activity.请注意,我想在我的Application class 中执行此操作,而不是在活动中。

According to the official docs I could save it as a "document", but the example provided there has a problem;根据官方文档,我可以将其保存为“文档”,但那里提供的示例有问题; it is invoked from an activity.它是从活动中调用的。 Since I am trying to invoke it from an application, the method startActivityForResult doesn't exist.由于我试图从应用程序中调用它,因此startActivityForResult方法不存在。

// Request code for creating a PDF document.
const val CREATE_FILE = 1

private fun createFile(pickerInitialUri: Uri) {
    val intent = Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
        addCategory(Intent.CATEGORY_OPENABLE)
        type = "application/pdf"
        putExtra(Intent.EXTRA_TITLE, "invoice.pdf")

        // Optionally, specify a URI for the directory that should be opened in
        // the system file picker before your app creates the document.
        putExtra(DocumentsContract.EXTRA_INITIAL_URI, pickerInitialUri)
    }
    startActivityForResult(intent, CREATE_FILE) // <--- This does not compile, a subclass of Application doesn't have this method avaiable...
}

For Android 11. In your case, I think contentResolver.insert will be more suitable.对于 Android 11. 在你的情况下,我认为contentResolver.insert会更合适。

You'll need to use MediaStore.Files.getContentUri & put your content values in it.您需要使用MediaStore.Files.getContentUri并将您的内容值放入其中。 Then write your crash log to it by using openOutputStream & outputStream.write .然后使用openOutputStreamoutputStream.write将您的崩溃日志写入其中。

This can be used in the Application class as well.这也可以在Application class 中使用。

Following is the code snippet tested on Android 11.以下是在 Android 11 上测试的代码片段。

class MyApp : Application() {

    override fun onCreate() {
        super.onCreate()
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
            saveFileToExternalStorage(
                System.currentTimeMillis().toString(),
                "This is test crash log"
            )
        } else {
            //your old code for below Q
        }
    }

    @RequiresApi(Build.VERSION_CODES.Q)
    private fun saveFileToExternalStorage(displayName: String, content: String): Boolean {
        val externalUri = MediaStore.Files.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)

        val relativeLocation = Environment.DIRECTORY_DOCUMENTS

        val contentValues = ContentValues().apply {
            put(MediaStore.Files.FileColumns.DISPLAY_NAME, "$displayName.txt")
            put(MediaStore.Files.FileColumns.MIME_TYPE, "application/text")
            put(MediaStore.Files.FileColumns.TITLE, "Log")
            put(MediaStore.Files.FileColumns.DATE_ADDED, System.currentTimeMillis() / 1000)
            put(MediaStore.Files.FileColumns.RELATIVE_PATH, relativeLocation)
            put(MediaStore.Files.FileColumns.DATE_TAKEN, System.currentTimeMillis())
        }


        return try {
            contentResolver.insert(externalUri, contentValues)?.also { uri ->
                contentResolver.openOutputStream(uri).use { outputStream ->
                    outputStream?.let {
                        outputStream.write(content.toByteArray())
                        outputStream.close()
                    }
                }
            } ?: throw IOException("Couldn't create MediaStore entry")
            true
        } catch (e: IOException) {
            e.printStackTrace()
            false
        }
    }
}

I got this idea from this github project.我从这个 github 项目中得到了这个想法。 I would suggest you check it out too.我建议你也检查一下。 https://github.com/philipplackner/AndroidStorage https://github.com/philipplackner/AndroidStorage

Your own first suggestion was Environment.getExternalStoragePublicDirectory .您自己的第一个建议是Environment.getExternalStoragePublicDirectory If you go to the documentation , it indeed days it is deprecated and redirects you to Content.如果您 go 到文档,确实有天它已被弃用并将您重定向到内容。 getExternalFilesDir() . 获取外部文件目录() This contains example code how to save data and get permission.这包含如何保存数据和获取权限的示例代码。

Although your pick was a document from the data storage docs , this is not suitable for you: every time yo want to save a log, your user is presented a save file dialog.尽管您选择的是数据存储文档中的文档,但这并不适合您:每次您想要保存日志时,都会向您的用户显示一个保存文件对话框。 That is way the function is called startActivityForResult , because it starts a new activity.这就是 function 被称为startActivityForResult的方式,因为它启动了一个新活动。 That is also why it should be called from an existing activity, so the user is in a UI flow already and it may expect a new screen.这也是为什么应该从现有活动中调用它的原因,因此用户已经处于 UI 流中,并且可能期待一个新屏幕。

You need write permission, because you want to write a file without consent per file;你需要写权限,因为你想在没有每个文件同意的情况下写一个文件; which is considered potentially dangerous.这被认为具有潜在危险。 The data storage docs says you don't need permission, because it's the new activity that already owns the permission and the user can pick a single file to be written, making it less dangerous.数据存储文档说您不需要权限,因为它是已经拥有权限的新活动,并且用户可以选择要写入的单个文件,从而降低危险性。

Also, not all users will grant the permission.此外,并非所有用户都会授予该权限。 A better alternative would be is to store the crashes locally and use startActivityForResult() eventually to make it public.更好的选择是将崩溃存储在本地并最终使用startActivityForResult()将其公开。

You should convert your string to byte array and write them in loop您应该将字符串转换为字节数组并将它们写入循环

val sdcard = Environment.getExternalStorageDirectory()
val file = File(sdcard, "filename")
val fis = FileOutputStream(file)
fis.write("bytes", 0, count)
fis.close()

In Android 11 API 30 you need to grant this permission in manifest to be able to write or create new File/Folder in any location:Android 11 API 30中,您需要在清单中授予此权限才能在任何位置写入或创建新文件/文件夹:

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

then in your code on you MainActivity onCreate:然后在MainActivity onCreate 上的代码中:

if (Environment.isExternalStorageManager()) {
//todo when permission is granted
} else {
//request for the permission
    Intent intent = new 
Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION);
Uri uri = Uri.fromParts("package", getPackageName(), null);
intent.setData(uri);
startActivity(intent);
}

This is Java code but you can get its Kotlin equvelant这是Java代码,但你可以得到它的Kotlin等价物

Update

Maybe this is the Kotlin Version也许这是Kotlin版本

    if (Environment.isExternalStorageManager()) {
        //todo when permission is granted
    } else {
        //request for the permission
        val intent = Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION)
        val uri: Uri = Uri.fromParts("package", packageName, null)
        intent.data = uri
        startActivity(intent)
    }

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM