Android Arsenal – Dateisystem
Inhalt
Überblick
Je höher das API-Level, desto stärker beschränkt Google den Zugriff auf Dateien im Android-Speicher. Obwohl das Storage Access Framework (SAF) darauf ausgelegt ist, den Benutzerspeicher vor bösartigen Anwendungen zu schützen, macht es uns den Zugriff auf Dateien noch schwerer. Nehmen Sie das Beispiel wo java.io.File
ist in Android 10 veraltet.
Die einfache Speicherung erleichtert Ihnen den Zugriff auf und die Verwaltung von Dateien auf allen Ebenen der API. Wenn Sie mehr über den Hintergrund dieser Bibliothek erfahren möchten, lesen Sie diesen Artikel: Framework für einfachen Speicherzugriff in Android mit SimpleStorage
Das Hinzufügen von Simple Storage zu Ihrem Projekt ist ziemlich einfach:
implementation "com.anggrayudi:storage:X.Y.Z"
Wo X.Y.Z
ist eine Version der Bibliothek:
Alle Versionen sind zu finden Hier. Benutzen SNAPSHOT
Version müssen Sie diese URL zu Root-Gradle hinzufügen:
allprojects {
repositories {
google()
mavenCentral()
// add this line
maven { url "https://oss.sonatype.org/content/repositories/snapshots" }
}
}
Java-Kompatibilität
Simple Storage wurde in Kotlin erstellt. Folge dies Dokumentation um es in Ihrem Java-Projekt zu verwenden.
Terminologie
Andere Terminologie
Dateien lesen
In einfacher Lagerung, DocumentFile
Wird verwendet, um auf Dateien zuzugreifen, wenn Ihrer Anwendung vollständiger Speicherzugriff gewährt wurde, einschließlich Lese- und Schreibberechtigungs-URIs. Während MediaFile
wird verwendet, um auf Mediendateien zuzugreifen MediaStore
keine Speicherberechtigungs-URIs.
Sie können die Hilfsfunktionsdatei einlesen DocumentFileCompat
ich MediaStoreCompat
:
DocumentFileCompat
DocumentFileCompat.fromFullPath()
DocumentFileCompat.fromSimplePath()
DocumentFileCompat.fromFile()
DocumentFileCompat.fromPublicFolder()
Beispiel
val fileFromExternalStorage = DocumentFileCompat.fromSimplePath(context, basePath = "Downloads/MyMovie.mp4")
val fileFromSdCard = DocumentFileCompat.fromSimplePath(context, storageId = "9016-4EF8", basePath = "Downloads/MyMovie.mp4")
MediaStoreCompat
MediaStoreCompat.fromMediaId()
MediaStoreCompat.fromFileName()
MediaStoreCompat.fromRelativePath()
MediaStoreCompat.fromFileNameContains()
MediaStoreCompat.fromMimeType()
MediaStoreCompat.fromMediaType()
Beispiel
val myVideo = MediaStoreCompat.fromFileName(context, MediaType.DOWNLOADS, "MyMovie.mp4")
val imageList = MediaStoreCompat.fromMediaType(context, MediaType.IMAGE)
Dokumentenverwaltung
DocumentFile
Denn java.io.File
ist in Android 10 veraltet, also müssen Sie es verwenden DocumentFile
um Dateien zu verwalten.
Simple Storage fügt Kotlina-Erweiterungsfunktionen hinzu DocumentFile
So können Sie Dateien wie folgt verwalten:
DocumentFile.getStorageId()
DocumentFile.getStorageType()
DocumentFile.getBasePath()
DocumentFile.copyFileTo()
List<DocumentFile>.moveTo()
DocumentFile.search()
DocumentFile.deleteRecursively()
DocumentFile.getProperties()
DocumentFile.openOutputStream()
und vieles mehr…
MediaFile
Sie können ähnliche Optionen für Mediendateien haben DocumentFile
oder:
MediaFile.absolutePath
MediaFile.isPending
MediaFile.delete()
MediaFile.renameTo()
MediaFile.copyFileTo()
MediaFile.moveFileTo()
MediaFile.openInputStream()
MediaFile.openOutputStream()
etc.
Fordern Sie Zugang zum Lager an
Obwohl der Benutzer während der Laufzeit Lese- und Schreibberechtigungen erteilt hat, hat Ihre Anwendung möglicherweise noch keinen vollständigen Zugriff auf das Repository, sodass Sie keine Dateien suchen, verschieben und kopieren können. Ob Sie Zugriff auf das Lager haben, können Sie über prüfen SimpleStorage.hasStorageAccess()
.
Um vollen Zugriff auf das Repository zu gewähren, müssen Sie das SAF öffnen und dem Benutzer erlauben, URIs Lese- und Schreibzugriffsberechtigungen zu erteilen. Diese Bibliothek bietet Ihnen eine Hilfsklasse namens SimpleStorage
um den Bewerbungsprozess zu erleichtern:
class MainActivity : AppCompatActivity() {
private val storage = SimpleStorage(this)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
setupSimpleStorage()
btnRequestStorageAccess.setOnClickListener {
storage.requestStorageAccess(REQUEST_CODE_STORAGE_ACCESS)
}
}
private fun setupSimpleStorage() {
storage.storageAccessCallback = object : StorageAccessCallback {
override fun onRootPathNotSelected(
requestCode: Int,
rootPath: String,
uri: Uri,
selectedStorageType: StorageType,
expectedStorageType: StorageType
) {
MaterialDialog(this@MainActivity)
.message(text = "Please select $rootPath")
.negativeButton(android.R.string.cancel)
.positiveButton {
val initialRoot = if (expectedStorageType.isExpected(selectedStorageType)) selectedStorageType else expectedStorageType
storage.requestStorageAccess(REQUEST_CODE_STORAGE_ACCESS, initialRoot, expectedStorageType)
}.show()
}
override fun onCanceledByUser(requestCode: Int) {
Toast.makeText(baseContext, "Canceled by user", Toast.LENGTH_SHORT).show()
}
override fun onStoragePermissionDenied(requestCode: Int) {
/*
Request runtime permissions for Manifest.permission.WRITE_EXTERNAL_STORAGE
and Manifest.permission.READ_EXTERNAL_STORAGE
*/
}
override fun onRootPathPermissionGranted(requestCode: Int, root: DocumentFile) {
Toast.makeText(baseContext, "Storage access has been granted for ${root.getStorageId(baseContext)}", Toast.LENGTH_SHORT).show()
}
}
}
override fun onSaveInstanceState(outState: Bundle) {
storage.onSaveInstanceState(outState)
super.onSaveInstanceState(outState)
}
override fun onRestoreInstanceState(savedInstanceState: Bundle) {
super.onRestoreInstanceState(savedInstanceState)
storage.onRestoreInstanceState(savedInstanceState)
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
// Mandatory for Activity, but not for Fragment & ComponentActivity
storage.onActivityResult(requestCode, resultCode, data)
}
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
// Mandatory for Activity, but not for Fragment & ComponentActivity
storage.onRequestPermissionsResult(requestCode, permissions, grantResults)
}
}
Ordnerauswahl
private fun requestStoragePermission() {
/*
Request runtime permissions for Manifest.permission.WRITE_EXTERNAL_STORAGE
and Manifest.permission.READ_EXTERNAL_STORAGE
*/
}
private fun setupFolderPickerCallback() {
storage.folderPickerCallback = object : FolderPickerCallback {
override fun onStoragePermissionDenied(requestCode: Int) {
requestStoragePermission()
}
override fun onStorageAccessDenied(requestCode: Int, folder: DocumentFile?, storageType: StorageType) {
if (storageType == StorageType.UNKNOWN) {
requestStoragePermission()
return
}
MaterialDialog(this@MainActivity)
.message(
text = "You have no write access to this storage, thus selecting this folder is useless." +
"nWould you like to grant access to this folder?"
)
.negativeButton(android.R.string.cancel)
.positiveButton {
storage.requestStorageAccess(REQUEST_CODE_STORAGE_ACCESS, storageType)
}.show()
}
override fun onFolderSelected(requestCode: Int, folder: DocumentFile) {
Toast.makeText(baseContext, folder.getAbsolutePath(baseContext), Toast.LENGTH_SHORT).show()
}
override fun onCanceledByUser(requestCode: Int) {
Toast.makeText(baseContext, "Folder picker canceled by user", Toast.LENGTH_SHORT).show()
}
}
}
Dateiauswahl
private fun setupFilePickerCallback() {
storage.filePickerCallback = object : FilePickerCallback {
override fun onCanceledByUser(requestCode: Int) {
Toast.makeText(baseContext, "File picker canceled by user", Toast.LENGTH_SHORT).show()
}
override fun onStoragePermissionDenied(requestCode: Int, file: DocumentFile?) {
requestStoragePermission()
}
override fun onFileSelected(requestCode: Int, file: DocumentFile) {
Toast.makeText(baseContext, "File selected: ${file.name}", Toast.LENGTH_SHORT).show()
}
}
}
SimpleStorageHelper
Wenn Sie die Implementierung der Ordner- und Dateiauswahl und der vollständigen Festplattenanforderungen kompliziert genug finden, können Sie verwenden SimpleStorageHelper
um den Prozess zu vereinfachen. Diese Hilfsklasse enthält Standardstile für die Verwaltung des Warehouse-Zugriffs.
class MainActivity : AppCompatActivity() {
private val storageHelper = SimpleStorageHelper(this)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
storageHelper.onFolderSelected = { requestCode, folder ->
// do stuff
}
storageHelper.onFileSelected = { requestCode, files ->
// do stuff
}
btnRequestStorageAccess.setOnClickListener { storageHelper.requestStorageAccess() }
btnOpenFolderPicker.setOnClickListener { storageHelper.openFolderPicker() }
btnOpenFilePicker.setOnClickListener { storageHelper.openFilePicker() }
}
override fun onSaveInstanceState(outState: Bundle) {
storageHelper.onSaveInstanceState(outState)
super.onSaveInstanceState(outState)
}
override fun onRestoreInstanceState(savedInstanceState: Bundle) {
super.onRestoreInstanceState(savedInstanceState)
storageHelper.onRestoreInstanceState(savedInstanceState)
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
// Mandatory for Activity, but not for Fragment & ComponentActivity
storageHelper.storage.onActivityResult(requestCode, resultCode, data)
}
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
// Mandatory for Activity, but not for Fragment & ComponentActivity
storageHelper.onRequestPermissionsResult(requestCode, permissions, grantResults)
}
}
Einfacher, oder?
Verschieben und kopieren: Dateien und Ordner
Simple Storage hilft Ihnen beim Kopieren/Verschieben von Dateien und Ordnern über:
DocumentFile.copyFileTo()
DocumentFile.moveFileTo()
DocumentFile.copyFolderTo()
DocumentFile.moveFolderTo()
Sie können beispielsweise einen Ordner mit mehreren Codezeilen verschieben:
val folder: DocumentFile = ...
val targetFolder: DocumentFile = ...
// Since moveFolderTo() is annotated with @WorkerThread, you must execute it in background thread
folder.moveFolderTo(applicationContext, targetFolder, skipEmptyFiles = false, callback = object : FolderCallback() {
override fun onPrepare() {
// Show notification or progress bar dialog with indeterminate state
}
override fun onCountingFiles() {
// Inform user that the app is counting & calculating files
}
override fun onStart(folder: DocumentFile, totalFilesToCopy: Int, workerThread: Thread): Long {
return 1000 // update progress every 1 second
}
override fun onParentConflict(destinationFolder: DocumentFile, action: FolderCallback.ParentFolderConflictAction, canMerge: Boolean) {
handleParentFolderConflict(destinationFolder, action, canMerge)
}
override fun onContentConflict(
destinationFolder: DocumentFile,
conflictedFiles: MutableList<FolderCallback.FileConflict>,
action: FolderCallback.FolderContentConflictAction
) {
handleFolderContentConflict(action, conflictedFiles)
}
override fun onReport(report: Report) {
Timber.d("onReport() -> ${report.progress.toInt()}% | Copied ${report.fileCount} files")
}
override fun onCompleted(result: Result) {
Toast.makeText(baseContext, "Copied ${result.totalCopiedFiles} of ${result.totalFilesToCopy} files", Toast.LENGTH_SHORT).show()
}
override fun onFailed(errorCode: ErrorCode) {
Toast.makeText(baseContext, "An error has occurred: $errorCode", Toast.LENGTH_SHORT).show()
}
})
Das Beste an dieser Bibliothek ist, dass Sie Benutzer bitten können, „Zusammenführen“, „Ersetzen“, „Neu erstellen“ oder „Doppelte Ordner und Dateien überspringen“ zu wählen, wenn ein Konflikt über gefunden wird onConflict()
. Hier sind Screenshots des Codebeispiels zum Umgang mit Konflikten:
Lesen MainActivity
aus dem Beispielcode, wenn Sie die obigen Dialoge imitieren möchten.
FAQ
Probleme? Weiter lesen Häufig gestellte Fragen.
Weitere Beispiele für die Verwendung von SimpleStorage
In diesen Open-Source-Projekten kommt SimpleStorage zum Einsatz. Sehen Sie sich an, wie diese Repositories es verwenden:
Lizenz
Copyright © 2020-2022 Anggrayudi Hardiannico A.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.