Android 10 öffnen fehlgeschlagen: EACCES (Berechtigung verweigert)

Kürzlich haben wir für eine der Apps, an denen ich gearbeitet habe, das Ziel-SDK auf API-Ebene 29 (Android 10) geändert. Seit der Änderung ist einer unserer Workflows kaputt gegangen. Der Workflow ist einfach, wir fragen den Benutzer, ob er ein Logo in sein Profil hochladen möchte. Wir lassen den Benutzer eine Datei aus seiner Mediengalerie auswählen und dieses Bild auf den Server hochladen.

Beim Ausführen dieses Workflows auf einem Android 10-Gerät konnten die Bilder nicht hochgeladen werden. Der Fehler, den wir sahen, war open failed: EACCES (Permission denied)

Also, was hat sich geändert?

Gemäß der Android-Dokumentation.

Apps, die auf Android 10 (API-Stufe 29) und höher abzielen, erhalten standardmäßig Zugriff auf ein externes Speichergerät oder einen Bereichsspeicher. Solche Apps können nur ihr app-spezifisches Verzeichnis sehen

Auch in der Dokumentation

Um auf eine andere Datei zuzugreifen, die eine andere App erstellt hat, einschließlich Dateien in einem „Downloads“ -Verzeichnis, muss Ihre App das Storage Access Framework verwenden, mit dem der Benutzer eine bestimmte Datei auswählen kann.

Zurück zu unserem Workflow: Wenn ein Benutzer eine Datei aus der Galerie auswählt, kann nicht garantiert werden, dass die ausgewählte Datei von einer anderen App hinzugefügt oder bearbeitet wurde. Wenn der Benutzer also eine Datei auswählt, die beispielsweise zu einer anderen App gehört, treten Berechtigungsprobleme auf. Also, wie lösen wir das?

Es gibt verschiedene Möglichkeiten, dies zu tun.

Wenn Ihre App nicht für die Änderungen bereit ist, die für Android 10 vorgenommen werden, können Sie sich „abmelden“, indem Sie das Flag requestLegacyExternalStorage in Ihrem Manifest auf „true“ setzen.

<manifest ... > <application android:requestLegacyExternalStorage="true" ... >
...
</application></manifest>

Ich habe während des letzten Android Dev Summit mit einem der Entwickler des Datenschutzteams gesprochen und empfohlen, diese Lösung als temporäre zu verwenden

Mit openFileDescriptor

Die Idee ist, eine Kopie der Datei zu erstellen, die der Benutzer in der Mediengalerie auswählt, und sie Ihrem Anwendungscache-Verzeichnis hinzuzufügen.

Der erste Schritt besteht also darin, die Datei mit openFileDescriptor

val parcelFileDescriptor = context.contentResolver.openFileDescriptor(fileUri, "r", null)

zu öffnen.

val inputStream = FileInputStream(parcelFileDescriptor.fileDescriptor)

Der nächste Schritt besteht darin, den Dateinamen aus der URI zu erhalten. Bitte beachten Sie, dass die folgende Logik vom Beispielcode aus Android-Dokumenten inspiriert ist.

fun ContentResolver.getFileName(fileUri: Uri): String {
var name = ""
val returnCursor = this.query(fileUri, null, null, null, null)
if (returnCursor != null) {
val nameIndex = returnCursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)
returnCursor.moveToFirst()
name = returnCursor.getString(nameIndex)
returnCursor.close()
}
return name
}

Alternativ können Sie jederzeit Ihren eigenen generierten Dateinamen angeben.

Nachdem wir nun wissen, wie wir den Dateinamen erhalten, erstellen wir eine Datei im Cache-Verzeichnis unserer Anwendung.

val file = File(context.cacheDir, getFileName(context.contentResolver, fileUri))

Jetzt machen wir OutputStream aus dieser Datei

val outputStream = FileOutputStream(file)

Der letzte Schritt besteht darin, den Inhalt der Originaldatei in unsere neu erstellte Datei in unserem Cache-Verzeichnis zu kopieren.

Sie können diesen Schritt auf verschiedene Arten ausführen, eine einfache Möglichkeit ist die Verwendung der Kopierfunktion aus der IOUtils-Klasse (org.apache.commons.io.IOUtils).

IOUtils.copy(inputStream, outputStream)

Wenn man alles zusammenfügt, wäre der endgültige Code ungefähr so.

val parcelFileDescriptor = context.contentResolver.openFileDescriptor(fileUri, "r", null)
parcelFileDescriptor?.let {
val inputStream = FileInputStream(parcelFileDescriptor.fileDescriptor)
val file = File(context.cacheDir, context.contentResolver.getFileName(fileUri))
val outputStream = FileOutputStream(file)
IOUtils.copy(inputStream, outputStream)
}

Nachdem Ihre App nun über eine Kopie der Datei verfügt, die hochgeladen werden muss, können Sie diese sicher auf Ihren Backend-Server hochladen.

presenter.uploadLogo(file, ....)

Dies sollte die Berechtigungsprobleme beheben, mit denen Sie möglicherweise konfrontiert sind.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.