Utviklere: Det er superenkelt å omgå Androids skjulte API-begrensninger

Android 9 Pie og Android 10 gir advarsler eller blokkerer direkte tilgang til skjulte APIer. Her er hvordan utviklere kan komme seg rundt de skjulte API-begrensningene.

Tilbakeblikk til over et år siden, og vi gleder oss alle til å se hva som kommer i betaversjonen av Android P. Brukere ser frem til nye funksjoner, og utviklere ser frem til noen nye verktøy for å gjøre appene deres bedre. Dessverre for noen av disse utviklerne kom den første Android P-betaen med litt av en stygg overraskelse: skjulte API-begrensninger. Før jeg dykker inn i nøyaktig hva det betyr, la meg forklare litt om konteksten.

Hva handler dette om?

Android-apputviklere trenger ikke starte fra bunnen av når de lager en app. Google tilbyr verktøy for å gjøre apputvikling enklere og mindre repeterende. Et av disse verktøyene er Android SDK. SDK-en er i hovedsak en referanse til alle funksjonene utviklere trygt kan bruke i appene sine. Disse funksjonene er standard på alle varianter av Android som Google godkjenner. SDK-en er imidlertid ikke uttømmende. Det er ganske mange "skjulte" deler av Androids rammeverk som ikke er en del av SDK.

Disse "skjulte" delene kan være utrolig nyttige for mer hacky eller lavt nivå ting. For eksempel min Widget-skuff-app bruker en ikke-SDK-funksjon for å oppdage når en bruker starter en app fra en widget slik at skuffen automatisk kan lukkes. Du tenker kanskje: "Hvorfor ikke bare gjøre disse ikke-SDK-funksjonene til en del av SDK-en?" Vel, problemet er at operasjonen deres ikke er fullt forutsigbar. Google kan ikke sørge for at hver enkelt del av rammeverket fungerer på hver enkelt enhet det godkjenner, så viktigere metoder velges for å bli verifisert. Google garanterer ikke at resten av rammeverket forblir konsistent. Produsenter kan endre eller helt fjerne disse skjulte funksjonene. Selv i forskjellige versjoner av AOSP, vet du aldri om en skjult funksjon fortsatt vil eksistere eller fungere som den pleide.

Hvis du lurer på hvorfor jeg har brukt ordet "skjult", er det fordi disse funksjonene ikke en gang er en del av SDK-en. Normalt, hvis du prøver å bruke en skjult metode eller klasse i en app, vil den mislykkes i å kompilere. Å bruke dem krever speilbilde eller en modifisert SDK.

Med Android P bestemte Google seg for at det ikke var nok å skjule dem. Da den første betaen ble utgitt, det ble annonsert det de fleste (ikke alle) skjulte funksjoner var ikke lenger tilgjengelige for apputviklere. Den første betaversjonen vil advare deg når appen din brukte en svartelistet funksjon. Den neste betaversjonen krasjet ganske enkelt appen din. I det minste for meg var denne svartelisten ganske irriterende. Ikke bare brøt det ganske mye av Navigasjonsbevegelser, men siden skjulte funksjoner er svartelistet som standard (Google må manuelt hvitliste noen per utgivelse), hadde jeg store problemer med å få Widget Drawer til å fungere.

Nå var det noen måter å omgå svartelisten på. Den første var ganske enkelt å beholde appmålrettings-API 27 (Android 8.1), siden svartelisten bare gjaldt apper som målrettet mot den nyeste API-en. Dessverre med Googles minimums API-krav for publisering i Play Store, ville det bare være mulig å målrette mot API 27 så lenge. Fra 1. november 2019, må alle appoppdateringer til Play-butikken målrettes mot API 28 eller nyere.

Den andre løsningen er faktisk noe Google har bygget inn i Android. Det er mulig å kjøre en ADB-kommando for å deaktivere svartelisten helt. Det er flott for personlig bruk og testing, men jeg kan fortelle deg på egen hånd at å prøve å få sluttbrukere til å sette opp og kjøre ADB er et mareritt.

Så hvor etterlater det oss? Vi kan ikke målrette API 27 lenger hvis vi vil laste opp til Play Store, og ADB-metoden er bare ikke levedyktig. Det betyr imidlertid ikke at vi er tom for alternativer.

Den skjulte API-svartelisten gjelder bare for ikke-hvitelistede brukerapplikasjoner. Systemapplikasjoner, applikasjoner signert med plattformsignaturen og applikasjoner spesifisert i en konfigurasjonsfil er alle unntatt fra svartelisten. Morsomt nok er Google Play Services-pakken alle spesifisert i den konfigurasjonsfilen. Jeg antar at Google er bedre enn oss andre.

Uansett, la oss fortsette å snakke om svartelisten. Den delen vi er interessert i i dag er at systemapplikasjoner er unntatt. Det betyr for eksempel at System UI-appen kan bruke alle de skjulte funksjonene den vil ha siden den er på systempartisjonen. Selvfølgelig kan vi ikke bare skyve en app til systempartisjonen. Det trenger root, en god filbehandler, kunnskap om tillatelser... Det ville vært enklere å bruke ADB. Det er ikke den eneste måten vi kan være en systemapp på, i det minste når det gjelder den skjulte API-svartelisten.

Kast tankene tilbake til syv avsnitt siden da jeg nevnte refleksjon. Hvis du ikke vet hva refleksjon er, anbefaler jeg å lese denne siden, men her er en rask oppsummering. I Java er refleksjon en måte å få tilgang til normalt utilgjengelige klasser, metoder og felt. Det er et utrolig kraftig verktøy. Som jeg sa i det avsnittet, pleide refleksjon å være en måte å få tilgang til ikke-SDK-funksjoner. API-svartelisten blokkerer bruken av refleksjon, men den blokkerer ikke bruken av dobbelt-speilbilde.

Nå, her er hvor det blir litt rart. Normalt, hvis du ønsker å kalle en skjult metode, ville du gjort noe sånt som dette (dette er i Kotlin, men Java er likt):

val someHiddenClass = Class.forName("android.some.hidden.Class")
val someHiddenMethod = someHiddenClass.getMethod("someHiddenMethod", String::class.java)

someHiddenMethod.invoke(null, "some important string")

Takket være API-svartelisten vil du imidlertid bare få en ClassNotFoundException. Men hvis du reflekterer to ganger, fungerer det fint:

val forName = Class::class.java.getMethod("forName", String:: class.java)
val getMethod = Class::class.java.getMethod("getMethod", String:: class.java, arrayOf>()::class.java)
val someHiddenClass = forName.invoke(null, "android.some.hidden.Class") asClass
val someHiddenMethod = getMethod.invoke(someHiddenClass, "someHiddenMethod", String::class.java)

someHiddenMethod.invoke(null, "some important string")

Rart ikke sant? Vel, ja, men også nei. API-svartelisten sporer hvem som ringer en funksjon. Hvis kilden ikke er unntatt, krasjer den. I det første eksemplet er kilden appen. Men i det andre eksemplet er kilden selve systemet. I stedet for å bruke refleksjon for å få det vi ønsker direkte, bruker vi det til å fortelle systemet å få det vi ønsker. Siden kilden til oppfordringen til den skjulte funksjonen er systemet, påvirker ikke svartelisten oss lenger.

Så vi er ferdige. Vi har en måte å omgå API-svartelisten nå. Det er litt klønete, men vi kan skrive en innpakningsfunksjon slik at vi ikke trenger å dobbeltreflektere hver gang. Det er ikke bra, men det er bedre enn ingenting. Men faktisk er vi ikke ferdige. Det er en bedre måte å gjøre dette på som lar oss bruke normal refleksjon eller en modifisert SDK, som i de gode gamle dager.

Siden svartelistens håndhevelse blir evaluert per prosess (som er det samme som per app i de fleste tilfeller), kan det være en måte for systemet å registrere om den aktuelle appen er unntatt eller ikke. Heldigvis finnes det, og det er tilgjengelig for oss. Ved å bruke det nyoppdagede dobbeltrefleksjonshacket, har vi en engangskodeblokk:

val forName = Class::class.java.getDeclaredMethod("forName", String:: class.java)
val getDeclaredMethod = Class::class.java.getDeclaredMethod("getDeclaredMethod", String:: class.java, arrayOf>()::class.java)

val vmRuntimeClass = forName.invoke(null, "dalvik.system.VMRuntime") asClass
val getRuntime = getDeclaredMethod.invoke(vmRuntimeClass, "getRuntime", null) as Method
val setHiddenApiExemptions = getDeclaredMethod.invoke(vmRuntimeClass, "setHiddenApiExemptions", arrayOf(arrayOf<String>()::class.java)) asMethod

val vmRuntime = getRuntime.invoke(null)

setHiddenApiExemptions.invoke(vmRuntime, arrayOf("L"))

Ok, så teknisk sett forteller dette ikke systemet at appen vår er unntatt fra API-svartelisten. Det er faktisk en annen ADB-kommando du kan kjøre for å spesifisere funksjoner som ikke skal svartelistes. Det er det vi drar nytte av ovenfor. Koden overstyrer i utgangspunktet det systemet mener er unntatt for appen vår. Passerer "L" på slutten betyr at alle metoder er unntatt. Hvis du vil unnta spesifikke metoder, bruk Smali-syntaksen: Landroid/some/hidden/Class; Landroid/some/other/hidden/Class;.

Nå er vi faktisk ferdige. Lag en tilpasset applikasjonsklasse, legg den koden i onCreate() metode, og bam, ingen flere restriksjoner.


Takk til XDA Member weishu, utvikleren av VirtualXposed og Taichi, for opprinnelig å oppdage denne metoden. Vi vil også takke XDA Recognized Developer topjohnwu for å peke på denne løsningen til meg. Her er litt mer om hvordan det fungerer, selv om det er på kinesisk. jeg også skrev om dette på Stack Overflow, med et eksempel i JNI også.