【问题标题】:Android 11: Save a file that can other apps can accessAndroid 11:保存其他应用可以访问的文件
【发布时间】:2021-08-17 21:58:57
【问题描述】:

(这个问题是关于 Android 11 的)

我想将崩溃日志打印到其他应用程序可以读取的文件中(具体来说,我希望能够导航到该文件并使用“文件”应用查看数据)。

我已经看过几十个这个问题的答案,但他们都有以下两个问题之一:

  1. 他们正在使用已弃用的Environment.getExternalStoragePublicDirectory,或者
  2. 他们正在使用getExternalFilesDir,但这会返回一个对其他应用程序不可见的目录。

请注意,我想在我的 Application 课堂上执行此操作,而不是在活动中。

根据official docs我可以将它保存为“文档”,但是那里提供的示例有问题;它是从活动中调用的。由于我试图从应用程序中调用它,所以方法 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...
}

【问题讨论】:

  • 我还注意到,Android 文档中提供的非常有用的示例(我的问题中的链接)实际上并未显示如何将任何数据保存到文件中。
  • 很抱歉。将其添加到标题中。
  • 我假设您希望在没有任何用户交互的情况下保存崩溃日志,对吧?
  • (填充文本,因为 StackOverflow 逻辑)是
  • getContext(), getApplicationContext()?

标签: android file kotlin


【解决方案1】:

Android 11 API 30 中,您需要在清单中授予此权限才能在任何位置写入或创建新文件/文件夹:

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

然后在你的代码中 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);
}

这是Java 代码,但你可以得到它的Kotlin 等价

Update

也许这是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)
    }

【讨论】:

    【解决方案2】:

    你应该将你的字符串转换为字节数组并循环写入它们

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

    【讨论】:

      【解决方案3】:

      对于 Android 11。在你的情况下,我认为contentResolver.insert 会更合适。

      您需要使用MediaStore.Files.getContentUri 并将您的内容值放入其中。然后使用openOutputStream & outputStream.write 将您的崩溃日志写入其中。

      这也可以在Application 类中使用。

      以下是在 Android 11 上测试的代码 sn-p。

      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
              }
          }
      }
      

      我从这个 github 项目中得到了这个想法。我建议你也检查一下。 https://github.com/philipplackner/AndroidStorage

      【讨论】:

        【解决方案4】:

        您自己的第一个建议是Environment.getExternalStoragePublicDirectory。如果你去documentation,它确实被弃用了几天,并将你重定向到Content. getExternalFilesDir()。这包含如何保存数据和获取权限的示例代码。

        虽然您选择的是来自data storage docs 的文档,但这并不适合您:每次您想要保存日志时,都会向您的用户显示保存文件对话框。这就是函数被称为startActivityForResult 的方式,因为它启动了一个新活动。这也是为什么应该从现有活动中调用它的原因,因此用户已经处于 UI 流中,并且可能期待一个新屏幕。

        你需要写权限,因为你想在没有每个文件同意的情况下写一个文件;这被认为具有潜在危险。 data storage docs 表示您不需要权限,因为它是已经拥有权限的新活动,并且用户可以选择要写入的单个文件,从而降低危险。

        此外,并非所有用户都会授予该权限。更好的选择是将崩溃存储在本地并最终使用startActivityForResult() 将其公开。

        【讨论】:

        • 我同意。但是,我仍然不明白我应该如何编写文件保存代码。
        • 你看过developer.android.com/reference/android/content/…中的第一个代码sn-p吗?
        • 关于getExternalFilesDir 它说:“这些文件是应用程序内部的,通常不作为媒体对用户可见。”。这可能就是为什么我无法查看手机上保存的文件的原因。不过,我可以从我的电脑上查看它们。但我希望能够通过手机查看文件。
        猜你喜欢
        • 2011-12-04
        • 2020-01-13
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-10-10
        相关资源
        最近更新 更多