[英]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:我已经看到了这个问题的几十个答案,但他们都有两个问题之一:
Environment.getExternalStoragePublicDirectory
which is deprecated, or他们正在使用已弃用的Environment.getExternalStoragePublicDirectory
,或者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
.然后使用openOutputStream
和outputStream.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.