Udviklere: Det er super nemt at omgå Androids skjulte API-begrænsninger

Android 9 Pie og Android 10 giver advarsler eller blokerer direkte adgang til skjulte API'er. Her er, hvordan udviklere kan komme uden om de skjulte API-begrænsninger.

Flashback til over et år siden, og vi er alle spændte på at se, hvad der kommer i Android P-betaerne. Brugere ser frem til nye funktioner, og udviklere ser frem til nogle nye værktøjer til at gøre deres apps bedre. Desværre for nogle af disse udviklere kom den første Android P beta med lidt af en Nasty Surprise: skjulte API-begrænsninger. Inden jeg dykker ned i, hvad det præcis betyder, så lad mig forklare lidt om dens sammenhæng.

Hvad handler det her om?

Android app-udviklere behøver ikke at starte fra bunden, når de laver en app. Google leverer værktøjer til at gøre appudvikling lettere og mindre gentagende. Et af disse værktøjer er Android SDK. SDK'en er i det væsentlige en reference til alle de funktioner, udviklere sikkert kan bruge i deres apps. Disse funktioner er standard på alle varianter af Android, som Google godkender. SDK'et er dog ikke udtømmende. Der er en del "skjulte" dele af Androids framework, som ikke er en del af SDK.

Disse "skjulte" dele kan være utrolig nyttige til mere hacky eller lavt niveau ting. For eksempel min Widget Drawer app gør brug af en ikke-SDK-funktion til at registrere, når en bruger starter en app fra en widget, så skuffen automatisk kan lukke. Du tænker måske: "Hvorfor ikke bare gøre disse ikke-SDK-funktioner til en del af SDK'et?" Nå, problemet er, at deres operation ikke er fuldt forudsigelig. Google kan ikke sikre, at hver enkelt del af rammeværket fungerer på hver enkelt enhed, det godkender, så vigtigere metoder vælges til at blive verificeret. Google garanterer ikke, at resten af ​​rammen forbliver konsekvent. Producenter kan ændre eller helt fjerne disse skjulte funktioner. Selv i forskellige versioner af AOSP ved du aldrig, om en skjult funktion stadig eksisterer eller fungerer, som den plejede.

Hvis du undrer dig over, hvorfor jeg har brugt ordet "skjult", er det, fordi disse funktioner ikke engang er en del af SDK'et. Normalt, hvis du prøver at bruge en skjult metode eller klasse i en app, vil den ikke kompilere. Brug af dem kræver afspejling eller et ændret SDK.

Med Android P besluttede Google, at det ikke var nok at skjule dem. Da den første beta blev udgivet, det blev annonceret de fleste (ikke alle) skjulte funktioner var ikke længere tilgængelige for app-udviklere. Den første beta vil advare dig, når din app brugte en sortlistet funktion. De næste betaversioner styrtede simpelthen din app ned. I det mindste for mig var denne sortliste ret irriterende. Ikke nok med at det knækkede en del af Navigationsbevægelser, men da skjulte funktioner er sortlistet som standard (Google skal manuelt hvidliste nogle pr. udgivelse), havde jeg mange problemer med at få Widget Drawer til at virke.

Nu var der et par måder at omgå sortlisten på. Den første var simpelthen at beholde din app-målretnings-API 27 (Android 8.1), da sortlisten kun gjaldt for apps, der målrettede mod den seneste API. Desværre med Googles minimums API-krav for udgivelse i Play Butik, ville det kun være muligt at målrette API 27 så længe. Fra 1. november 2019, skal alle appopdateringer til Play Butik målrette API 28 eller nyere.

Den anden løsning er faktisk noget, Google har indbygget i Android. Det er muligt at køre en ADB-kommando for at deaktivere sortlisten helt. Det er fantastisk til personlig brug og test, men jeg kan fortælle dig på egen hånd, at forsøget på at få slutbrugere til at konfigurere og køre ADB er et mareridt.

Så hvor efterlader det os? Vi kan ikke målrette API 27 længere, hvis vi vil uploade til Play Butik, og ADB-metoden er bare ikke levedygtig. Det betyder dog ikke, at vi er ude af muligheder.

Den skjulte API-sortliste gælder kun for ikke-hvidlistede brugerapplikationer. Systemapplikationer, applikationer signeret med platformsignaturen og applikationer specificeret i en konfigurationsfil er alle undtaget fra sortlisten. Sjovt nok er Google Play Services-pakken alle specificeret i den konfigurationsfil. Jeg tror, ​​Google er bedre end os andre.

I hvert fald, lad os blive ved med at tale om sortlisten. Den del, vi er interesseret i i dag, er, at systemapplikationer er undtaget. Det betyder for eksempel, at System UI-appen kan bruge alle de skjulte funktioner, den ønsker, da den er på systempartitionen. Vi kan naturligvis ikke bare skubbe en app til systempartitionen. Det kræver root, en god filhåndtering, viden om tilladelser... Det ville være nemmere at bruge ADB. Det er dog ikke den eneste måde, vi kan være en systemapp på, i det mindste hvad angår den skjulte API-sortliste.

Kast dit sind tilbage til for syv afsnit siden, da jeg nævnte refleksion. Hvis du ikke ved hvad refleksion er, anbefaler jeg at læse denne side, men her er en hurtig oversigt. I Java er refleksion en måde at få adgang til normalt utilgængelige klasser, metoder og felter. Det er et utroligt stærkt værktøj. Som jeg sagde i det afsnit, plejede refleksion at være en måde at få adgang til ikke-SDK-funktioner. API-sortlisten blokerer brugen af ​​refleksion, men den blokerer ikke brugen af dobbelt-afspejling.

Nu, her er hvor det bliver lidt underligt. Normalt, hvis du ville kalde en skjult metode, ville du gøre noget som dette (dette er i Kotlin, men Java ligner):

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-sortlisten ville du dog bare få en ClassNotFoundException. Men hvis du reflekterer to gange, 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")

Underligt ikke? Nå, ja, men også nej. API-sortlisten sporer, hvem der kalder en funktion. Hvis kilden ikke er fritaget, går den ned. I det første eksempel er kilden appen. Men i det andet eksempel er kilden selve systemet. I stedet for at bruge refleksion til at få det, vi ønsker direkte, bruger vi det til at fortælle systemet, at det skal få det, vi ønsker. Da kilden til opkaldet til den skjulte funktion er systemet, påvirker sortlisten os ikke længere.

Så vi er færdige. Vi har en måde at omgå API-sortlisten nu. Det er lidt klodset, men vi kunne skrive en indpakningsfunktion, så vi ikke behøver at dobbeltreflektere hver gang. Det er ikke fantastisk, men det er bedre end ingenting. Men faktisk er vi ikke færdige. Der er en bedre måde at gøre dette på, som lader os bruge normal refleksion eller en modificeret SDK, som i de gode gamle dage.

Da sortlistens håndhævelse evalueres pr. proces (hvilket er det samme som pr. app i de fleste tilfælde), kan der være en måde for systemet at registrere, om den pågældende app er fritaget eller ej. Det er der heldigvis, og det er tilgængeligt for os. Ved at bruge det nyfundne dobbeltreflekterende hack, har vi en kodeblok til engangsbrug:

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

Okay, så teknisk set fortæller dette ikke systemet, at vores app er undtaget fra API-sortlisten. Der er faktisk en anden ADB-kommando, du kan køre for at specificere funktioner, der ikke skal sortlistes. Det er det, vi udnytter ovenfor. Koden tilsidesætter stort set alt, hvad systemet mener er undtaget for vores app. At passere "L" til sidst betyder, at alle metoder er undtaget. Hvis du vil undtage specifikke metoder, skal du bruge Smali-syntaksen: Landroid/some/hidden/Class; Landroid/some/other/hidden/Class;.

Nu er vi faktisk færdige. Lav en brugerdefineret applikationsklasse, indsæt den kode i onCreate() metode, og bam, ikke flere restriktioner.


Tak til XDA Member weishu, udvikleren af ​​VirtualXposed og Taichi, for oprindeligt at opdage denne metode. Vi vil også gerne takke XDA Recognized Developer topjohnwu for at påpege denne løsning til mig. Her er lidt mere om, hvordan det fungerer, selvom det er på kinesisk. Også mig skrev om dette på Stack Overflow, med et eksempel i JNI også.