az Android 10 megnyitása sikertelen: EACCES (engedély megtagadva)

nemrégiben az egyik alkalmazásnál, amelyen dolgoztam, megváltoztattuk a cél SDK-t API szintre 29 (Android 10). A változás óta az egyik munkafolyamatunk megszakadt. A munkafolyamat egyszerű, megkérdezzük a felhasználót, hogy szeretne-e logót feltölteni a profiljába. Hagyjuk, hogy a felhasználó válasszon egy fájlt a médiagalériájából, és töltse fel ezt a képet a szerverre.

a munkafolyamat futtatása Android 10 eszközön nem sikerült feltölteni a képeket. A hiba, amit láttunk, nyitva volt, sikertelen: EACCES (engedély megtagadva)

Szóval, mi változott?

az Android dokumentációja szerint.

az Android 10-es (API 29-es szint) vagy újabb verzióját célzó alkalmazások alapértelmezés szerint hatókörű hozzáférést kapnak egy külső tárolóeszközhöz vagy hatókörű tárolóhoz. Az ilyen alkalmazások csak az alkalmazásspecifikus könyvtárukat láthatják

a dokumentációban is

bármely más alkalmazás által létrehozott fájl eléréséhez, beleértve a “letöltések” könyvtárban található fájlokat is, az alkalmazásnak a Storage Access Framework-et kell használnia, amely lehetővé teszi a felhasználó számára egy adott fájl kiválasztását.

vissza a munkafolyamatunkhoz, amikor a felhasználó kiválaszt egy fájlt a galériából, nincs garancia arra, hogy a kiválasztott fájlt más alkalmazás hozzáadta vagy szerkesztette. Tehát, ha a Felhasználó olyan fájlt választ, amely mondjuk egy másik alkalmazáshoz tartozik, akkor az engedélyezési problémákba ütközünk. Szóval, hogyan oldjuk meg ezt?

ennek néhány módja van, ebben a cikkben kétféleképpen fogunk megvizsgálni.

ha az alkalmazás nem áll készen a változások jönnek az Android 10 akkor “opt-out” beállításával a zászló requestLegacyExternalStorage igaz a nyilvánvaló.

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

beszéltem az Adatvédelmi csapat egyik fejlesztőjével a legutóbbi Android dev Summit során, és azt javasolták, hogy ezt a megoldást ideiglenes megoldásként használja

az openFileDescriptor

az ötlet az, hogy készítsen egy másolatot a fájlról, amelyet a felhasználó kiválaszt a médiagalériában, és adja hozzá az alkalmazás gyorsítótár könyvtárához.

tehát az első lépés a fájl megnyitása openFileDescriptor

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

hozzunk létre egy bemeneti adatfolyamot belőle.

val inputStream = FileInputStream(parcelFileDescriptor.fileDescriptor)

a következő lépés a fájlnév lekérése az URI-ból, ehhez írhatunk egy kiterjesztési függvényt A ContentResolver osztályra. Felhívjuk figyelmét, hogy az alábbi logikát az android docs mintakódja ihlette.

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
}

Alternatív megoldásként mindig megadhatja a saját generált fájlnevét.

most, hogy tudjuk, hogyan lehet megszerezni a fájlnevet, hozzunk létre egy fájlt az alkalmazás gyorsítótár-könyvtárában.

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

most tegyük OutputStream ki ezt a fájlt

val outputStream = FileOutputStream(file)

az utolsó lépés az, hogy másolja a tartalmát az eredeti fájlt az újonnan létrehozott fájlt a cache könyvtárban.

ezt a lépést többféle módon is megteheti, az egyik egyszerű módszer az IOUtils osztály másolási funkciójának használata (org. apache. commons. io. IOUtils).

IOUtils.copy(inputStream, outputStream)

összerakva az egészet, a végső kód valami ilyesmi lenne.

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

most, hogy az alkalmazás rendelkezik a feltöltendő fájl másolatával, biztonságosan feltöltheti ezt a háttérkiszolgálóra.

presenter.uploadLogo(file, ....)

ez megoldja az esetlegesen felmerülő engedélyezési problémákat.

Vélemény, hozzászólás?

Az e-mail-címet nem tesszük közzé.