Android 10 open epäonnistui: EACCES (Lupa evätty)

äskettäin yksi apps että olin työskennellyt, muutimme tavoite SDK API tasolle 29 (Android 10). Muutoksen jälkeen yksi työnkulkumme katkesi. Työnkulku on yksinkertainen, kysymme käyttäjältä, haluaako hän ladata logon profiiliinsa. Annamme käyttäjän valita tiedoston mediagalleriastaan ja ladata tämän kuvan palvelimelle.

tämän työnkulun suorittaminen Android 10-laitteella epäonnistui kuvien lataamisessa. Avautunut virhe epäonnistui: EACCES (Lupa evätty)

niin, mikä muuttui?

Android-dokumentaation mukaan.

Android 10 (API-taso 29) ja sitä uudemmille sovelluksille annetaan oletusarvoisesti scoped-käyttöoikeus ulkoiseen tallennuslaitteeseen tai scoped-tallennustilaan. Tällaiset sovellukset voivat nähdä vain sovelluskohtaisen hakemistonsa

myös dokumentaatiossa

päästäkseen käsiksi muihin toisen sovelluksen luomiin tiedostoihin, mukaan lukien tiedostot ”lataukset” – hakemistossa, sovelluksen on käytettävä Tallennuskäyttökehystä, jonka avulla käyttäjä voi valita tietyn tiedoston.

takaisin työnkulkuumme, kun käyttäjä valitsee tiedoston galleriasta, ei ole takeita siitä, että valittu tiedosto on lisätty tai muokattu jollakin muulla sovelluksella. Niin, jos käyttäjä poimii tiedoston, joka sanotaan kuuluu toiseen sovellukseen joutuisimme lupa-asioita. Miten ratkaisemme tämän?

on olemassa pari tapaa tehdä tämä, tässä artikkelissa tarkastelemme kahta tapaa.

Jos sovelluksesi ei ole valmis Android 10: lle tuleviin muutoksiin, voit ”opt-out” – toiminnon asettamalla lippupyyntöviestin true-muotoon manifestissasi.

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

puhuin erään Tietosuojatiimin kehittäjän kanssa äskettäisessä Android Development Summitissa ja heidän suosituksensa oli käyttää tätä ratkaisua väliaikaisena

käyttäen openfiledescriptoria

ideana on tehdä kopio tiedostosta, jonka käyttäjä valitsee mediagalleriassa, ja lisätä se sovelluksen välimuistihakemistoon.

joten ensimmäinen vaihe on avata tiedosto openFileDescriptor

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

luodaanpa siitä tulovirta.

val inputStream = FileInputStream(parcelFileDescriptor.fileDescriptor)

seuraava vaihe on saada tiedostonimi urista, voimme kirjoittaa tälle Laajennusfunktion ContentResolver-luokkaan. Huomaa, että logiikka alla on innoittamana näyte koodi android docs.

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
}

Vaihtoehtoisesti voit aina antaa Oman luomasi tiedostonimen.

nyt kun tiedostonimi tiedetään, luodaan tiedosto sovelluksemme välimuistihakemistoon.

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

nyt tehdään OutputStream tästä tiedostosta

val outputStream = FileOutputStream(file)

viimeinen vaihe on kopioida alkuperäisen tiedoston sisältö juuri luotuun tiedostoon välimuistihakemistossamme.

tämän vaiheen voi tehdä monella tavalla, yksi helppo tapa on käyttää IOUtils-luokan kopiointifunktiota (org. apache. commons. io. IOUtils).

IOUtils.copy(inputStream, outputStream)

kokoamalla kaikki yhteen, lopullinen koodi olisi jotain tällaista.

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

nyt kun sovelluksellasi on kopio ladattavasta tiedostosta, voit nyt turvallisesti ladata sen taustapalvelimellesi.

presenter.uploadLogo(file, ....)

tämän pitäisi korjata mahdolliset käyttöoikeusongelmat.

Vastaa

Sähköpostiosoitettasi ei julkaista.