Начиная с api 19 (KitKat) практически невозможно напрямую записать содержимое SDcard как простой File
, поскольку они смонтированы как ТОЛЬКО ЧТЕНИЕ, по крайней мере, для пользователя по умолчанию (система все еще может записывать в него).
DocumentsProvider
API довольно запутанный (даже образец в документах трудно читать), поэтому DocumentFile
был добавлен для имитации поведения File
для облегчения доступа.
Предполагая, что пользователь уже предоставил разрешения на чтение / запись для хранения, нам нужно получить URI документа корневого каталога SDcard (в Activity
):
var sdCardUri : Uri? = null
private fun requestSDCardPermissions(){
if(Build.VERSION.SDK_INT < 24){
startActivityForResult(Intent(Intent.ACTION_OPEN_DOCUMENT_TREE), REQ_PICK_DIRECTORY)
return
}
// find removable device using getStorageVolumes
val sm = getSystemService(Context.STORAGE_SERVICE) as StorageManager
val sdCard = sm.storageVolumes.find { it.isRemovable }
if(sdCard != null){
startActivityForResult(sdCard.createAccessIntent(null), REQ_SD_CARD_ACCESS)
}
}
Прежде чем пользователю API 24 потребуется открыть средство выбора документов ивручную выберите SD-карту.Это не идеально, но команда Android упустила из виду тот факт, что отсутствует API SD-карты.
В более новой версии getStorageVolumes()
позволяет нам находить SD-карту с помощью кода и отображать только явное предупреждение дляПользователь, который будет доступен.Это НЕ тот же диалог, что и разрешение на чтение / запись.
Теперь для обработки полученных Uri
нам нужно только взять data.data
результата.Это может быть хорошим местом для сохранения его в общих настройках, чтобы не просить пользователя снова и снова:
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if(requestCode == REQ_SD_CARD_ACCESS || requestCode == REQ_PICK_DIRECTORY){
if(resultCode == RESULT_OK) {
if(data == null){
Log.e(TAG, "Error obtaining access")
}else{
sdCardUri = data.data
Log.d("StorageAccess", "obtained access to $sdCardUri")
// optionally store uri in preferences as well here { ... }
}
}else
Toast.makeText(this, "access denied", Toast.LENGTH_SHORT).show()
return
}
super.onActivityResult(requestCode, resultCode, data)
}
Я пропущу большинство проверок ошибок / проверок существующих файлов, но теперь вы можете использовать полученный sdCardUri
вот так (копирование файла "sample.txt" из корня внутреннего хранилища в корень SDcard):
private fun copyToSDCard(){
val sdCardRoot = DocumentFile.fromTreeUri(this, sdCardUri)
val internalFile = File(Environment.getExternalStorageDirectory(), "sample.txt")
// get or create file
val sdCardFile = sdCardRoot.findFile("sample.txt") ?: sdCardRoot.createFile(null, "sample.txt")
val outStream = contentResolver.openOutputStream(sdCardFile.uri)
outStream.write(internalFile.readBytes())
outStream.flush()
outStream.close()
Toast.makeText(this, "copied to SDCard", Toast.LENGTH_SHORT).show()
}
И наоборот (из SDcard во внутреннее хранилище):
private fun copyToInternal(){
val sdCardRoot = DocumentFile.fromTreeUri(this, sdCardUri)
val internalFile = File(Environment.getExternalStorageDirectory(), "sample.txt")
val sdCardFile = sdCardRoot.findFile("sample.txt")
val inStream = contentResolver.openInputStream(sdCardFile.uri)
internalFile.writeBytes(inStream.readBytes())
inStream.close()
Toast.makeText(this, "copied to internal", Toast.LENGTH_SHORT).show()
}