Articles

Praktiske Eksempler På ProGuard-regler

Wojtek Kaliciń
Wojtek Kaliciń

Følg

feb 20, 2018 · 9 min lese

i min forrige artikkel forklarte jeg hvorfor alle bør bruke proguard for sine android-apper, hvordan du aktiverer det Og hva slags feil Du Kan Støte På Når Du gjør Det. Det var mye teori involvert, da jeg synes det er viktig å forstå de underliggende prinsippene for å være forberedt på å håndtere eventuelle potensielle problemer.jeg snakket også i en egen artikkel om det svært spesifikke problemet med å konfigurere ProGuard For En Umiddelbar Appbygging.I denne delen vil jeg snakke om de praktiske eksemplene På ProGuard-regler på en mellomstor prøveapp: Plaid av Nick Butcher.Plaid viste seg faktisk å være et flott emne for å undersøke ProGuard-problemer, da det inneholder en blanding av 3. parts biblioteker som bruker ting som annotasjonsbehandling og kodegenerering, refleksjon, java resource loading og native code (JNI). Jeg hentet og noterte ned noen praktiske råd som bør gjelde for andre apper generelt:

dataklasser

public class User {
String name;
int age;
...
}

Sannsynligvis har hver app en slags dataklasse (også kjent som DMOs, modeller, etc. avhengig av kontekst og hvor de sitter i appens arkitektur). Tingen om dataobjekter er at de vanligvis på et tidspunkt vil bli lastet eller lagret (serialisert) i et annet medium, for eksempel nettverk (EN HTTP-forespørsel), en database (via EN ORM), EN JSON-fil på disk eller I En Firebase datalager.Mange av verktøyene som forenkler serialisering og deserialisering av disse feltene, er avhengige av refleksjon. GSON, Retrofit, Firebase-de inspiserer alle feltnavn i dataklasser og gjør dem til en annen representasjon (for eksempel: {"name”: "Sue”, "age”: 28}), enten for transport eller lagring. Det samme skjer når de leser data i Et Java — objekt-de ser et nøkkelverdipar "name”:”John” og prøver å bruke det på Et Java-objekt ved å slå opp et String name felt.

Konklusjon: Vi kan ikke la ProGuard gi nytt navn til eller fjerne felt i disse dataklassene, da de må samsvare med serialisert format. Det er trygt å legge til en@Keep merknad på hele klassen eller en jokertegnregel på alle modellene dine:

-keep class io.plaidapp.data.api.dribbble.model.** { *; }

Advarsel: Det er mulig å gjøre en feil når du tester om appen din er utsatt for dette problemet. Hvis DU for eksempel serialiserer et objekt TIL JSON og lagrer det på disk i versjon N av appen din uten de riktige keep-reglene, kan de lagrede dataene se slik ut: {"a”: "Sue”, "b”: 28}. Fordi ProGuard omdøpt feltene dine til a og b, vil alt virke, data vil bli lagret og lastet riktig.

Men når du bygger appen din på nytt og slipper Versjon N+1 av appen din, kan ProGuard bestemme seg for å gi feltene nytt navn til noe annet, for eksempel cog d. Som et resultat vil data lagret tidligere ikke lastes inn.

du må sørge for at du har de riktige reglene i utgangspunktet.

Java-kode kalt fra innfødt side (JNI)

Android standard ProGuard-filer (Du bør alltid inkludere Dem, de har noen veldig nyttige regler) inneholder allerede en regel for metoder som implementeres på den innfødte siden (-keepclasseswithmembernames class * { native <methods>; }). Dessverre er det ingen catch-all måte å holde koden påberopt i motsatt retning: FRA JNI Til Java.Med JNI er det helt mulig å konstruere ET jvm-objekt eller finne og ringe en metode på ET jvm-håndtak Fra C / C++ – kode, og faktisk gjør et av bibliotekene som brukes i Plaid det.Konklusjon: Fordi ProGuard bare kan inspisere Java-klasser, vil Den ikke vite om noen bruksområder som skjer i innfødt kode. Vi må eksplisitt beholde slike bruksområder av klasser og medlemmer via en@Keep annotasjon eller-keep regel.

-keep, includedescriptorclasses 
class in.uncod.android.bypass.Document { *; }
-keep, includedescriptorclasses
class in.uncod.android.bypass.Element { *; }

Åpne ressurser FRA JAR/APK

Android Har sine egne ressurser og eiendeler system som normalt ikke bør være et problem for ProGuard. Men i vanlig Java er det en annen mekanisme for å laste ressurser rett fra EN JAR-fil, og noen tredjepartsbiblioteker kan bruke det selv når de er kompilert I Android-apper (i så fall vil de prøve å laste fra APK).problemet er at disse klassene vanligvis vil se etter ressurser under eget pakkenavn(som oversetter til en filbane i KRUKKEN eller APK). ProGuard kan omdøpe pakkenavn når obfuscating, så etter kompilering kan det hende at klassen og ressursfilen ikke lenger er i samme pakke i den endelige APK.

for å identifisere lasteressurser på denne måten kan du se etter anrop til Class.getResourceAsStream / getResource og ClassLoader.getResourceAsStream / getResource i koden din og i eventuelle tredjepartsbiblioteker du er avhengig av.Konklusjon: vi bør beholde navnet på en klasse som laster ressurser fra APK ved hjelp av denne mekanismen.

I Plaid er det faktisk to — en I okhttp-biblioteket og En I Jsoup:

-keepnames class okhttp3.internal.publicsuffix.PublicSuffixDatabase
-keepnames class org.jsoup.nodes.Entities

Hvordan komme opp med regler for tredjepartsbiblioteker

i en ideell verden vil hver avhengighet du bruker levere sine nødvendige ProGuard-regler i AAR. Noen ganger glemmer de å gjøre dette eller bare publisere Krukker, som ikke har en standardisert måte å levere proguard-regler på.

i så fall må du huske å sjekke dokumentasjonen før du begynner å feilsøke appen din og komme opp med regler. Noen biblioteksforfattere leverer anbefalte ProGuard-regler (for Eksempel Ettermontering brukt i Plaid) som kan spare deg for mye tid og frustrasjon. Dessverre gjør mange biblioteker ikke (som det er tilfelle Med Jsoup og Bypass nevnt i denne artikkelen). Vær også oppmerksom på at i noen tilfeller config leveres med biblioteket vil bare fungere med optimaliseringer deaktivert, så hvis du slår dem på du kan være i ukjent territorium.

Så Hvordan komme opp med regler når biblioteket ikke leverer dem?
jeg kan bare gi deg noen tips:

  1. Les bygge utgang og logcat!
  2. bygg advarsler vil fortelle deg hvilke -dontwarn regler for å legge til
  3. ClassNotFoundExceptionMethodNotFoundException og FieldNotFoundException vil fortelle deg hvilke -keep regler for å legge til

du bør være glad når appen krasjer med proguard aktivert — du har et sted å starte undersøkelsen din 🙂

den verste klassen av problemer å feilsøke er når du app fungerer, men for eksempel viser ikke en skjerm eller laster ikke data fra nettverket.det er her du må vurdere noen av scenariene jeg beskrev i denne artikkelen og få hendene skitne, til og med dykke inn i tredjepartskoden og forstå hvorfor det kan mislykkes, for eksempel når det bruker refleksjon, introspeksjon eller JNI.

Feilsøking og stakkspor

ProGuard vil som standard fjerne mange kodeattributter og skjulte metadata som ikke kreves for programkjøring . Noen av disse er faktisk nyttige for utvikleren — for eksempel vil du kanskje beholde kildefilnavn og linjenumre for stakkspor for å gjøre feilsøking enklere:

-keepattributes SourceFile, LineNumberTable

Du bør også huske å lagre ProGuard-mappingsfilene som produseres når du bygger en utgivelsesversjon og laster dem opp For Å Spille for å få de-obfuscated stack spor fra eventuelle krasjer opplevd av din brukere.

hvis du skal legge ved en debugger for å gå gjennom metodekoden i en ProGuarded build av appen din, bør du også beholde følgende attributter for å beholde noen feilsøkingsinformasjon om lokale variabler (du trenger bare denne linjen idebug byggetype):

-keepattributes LocalVariableTable, LocalVariableTypeTable

minified debug build type

standard byggetyper er konfigurert slik at debug ikke kjører proguard. Det er fornuftig, fordi vi ønsker å iterere og kompilere raskt når vi utvikler, men vil fortsatt at utgivelsesbygningen skal bruke ProGuard til å være så liten og optimalisert som mulig.

Men for å fullt ut teste Og feilsøke Eventuelle ProGuard problemer, er det godt å sette opp en egen, minified debug build som dette:

buildTypes {
debugMini {
initWith debug
minifyEnabled true
shrinkResources true
proguardFiles getDefaultProguardFile('proguard-android.txt'),
'proguard-rules.pro'
matchingFallbacks =
}
}

med denne bygge typen, vil du være i stand til å koble debugger, kjøre UI tester (også PÅ EN CI server) eller ape teste app for mulige problemer på en build som er så nær utgivelsen bygge som mulig.

Konklusjon: Når Du bruker ProGuard, bør du ALLTID QA-utgivelsen din bygger grundig, enten ved å ha ende-til-ende-tester eller manuelt gå gjennom alle skjermer i appen din for å se om noe mangler eller krasjer.

Runtime merknader, type introspection

ProGuard vil som standard fjerne alle merknader og enda noen overskudd type informasjon fra koden. For noen biblioteker som ikke er et problem — de som behandler merknader og genererer kode ved kompileringstid (For Eksempel Dagger 2 eller Glide og mange flere) trenger kanskje ikke disse merknadene senere når programmet kjører.

det er en annen klasse verktøy som faktisk inspiserer merknader eller ser på typeinformasjon for parametere og unntak under kjøring. Ettermontering gjør for eksempel dette ved å fange opp metodeanropene dine ved å bruke et Proxy – objekt, og deretter se på merknader og skriv inn informasjon for å bestemme hva DU skal sette eller lese FRA HTTP-forespørselen.

Konklusjon: Noen ganger er det nødvendig å beholde typeinformasjon og merknader som leses under kjøring, i motsetning til kompileringstid. Du kan sjekke ut listen attributter I proguard manualen.

-keepattributes *Annotation*, Signature, Exception

hvis Du bruker Standard android ProGuard konfigurasjonsfil (getDefaultProguardFile('proguard-android.txt')), er de to første alternativene — Merknader og Signatur — spesifisert for deg. Hvis du ikke bruker standard, må du sørge for å legge til dem selv (det gjør heller ikke vondt for å bare duplisere dem hvis du vet at de er et krav til appen din).

Flytte alt til standardpakken

-repackageclasses alternativet er ikke lagt til som standard i ProGuard config. Hvis du allerede forvirrer koden din og har løst eventuelle problemer med riktig keep-regler, kan du legge til dette alternativet for å redusere DEX-størrelsen ytterligere. Det fungerer ved å flytte alle klasser til standard (root) pakke, i hovedsak frigjøre plass tatt opp av strenger som » com.eksempel.myapp.somepackage».

-repackageclasses

ProGuard optimaliseringer

Som jeg nevnte før, Kan ProGuard gjøre 3 ting for deg:

  1. det blir kvitt ubrukt kode,
  2. omdøper identifikatorer for å gjøre koden mindre,
  3. utfører hele programoptimaliseringer.Slik jeg ser det, bør alle prøve å konfigurere deres bygg for å få 1. og 2. arbeide.

    for å låse opp 3. (flere optimaliseringer), må du bruke en annen standard ProGuard konfigurasjonsfil. Endre proguard-android.txt parameter til proguard-android-optimize.txt i build.gradle:

    release {
    minifyEnabled true
    proguardFiles
    getDefaultProguardFile('proguard-android-optimize.txt'),
    'proguard-rules.pro'
    }

    dette vil gjøre utgivelsen bygge tregere, men vil potensielt gjøre din app kjøre raskere og redusere kodestørrelsen ytterligere, takk til optimaliseringer som metode inlining, klasse sammenslåing og mer aggressiv kode fjerning. Vær imidlertid forberedt på at det kan introdusere nye og vanskelige å diagnostisere feil, så bruk det med forsiktighet, og hvis noe ikke fungerer, må du deaktivere visse optimaliseringer eller deaktivere bruken av optimaliseringskonfigurasjonen helt.Når Det gjelder Plaid, forstyrret ProGuard-optimaliseringer hvordan Ettermontering bruker Proxy-objekter uten konkrete implementeringer, og fjernet noen metodeparametere som faktisk var nødvendige. Jeg måtte legge denne linjen til min config:

    -optimizations !method/removal/parameter

    du finner en liste over mulige optimaliseringer og hvordan du deaktiverer dem i proguard-håndboken.

    når du skal bruke @Keep og-keep

    @Keep støtte er faktisk implementert som en haug med -keep regler i Standard Android ProGuard rules-filen, så de er i hovedsak ekvivalente. Angi-keep regler er mer fleksible da det tilbyr jokertegn, du kan også bruke forskjellige varianter som gjør litt forskjellige ting (-keepnames-keepclasseswithmembers og mer).Når en enkel «hold denne klassen» eller «hold denne metoden» – regelen er nødvendig, foretrekker jeg faktisk enkelheten ved å legge til en@Keep merknad på klassen eller medlemmet, da det forblir nær koden, nesten som dokumentasjon.

    hvis en annen utvikler som kommer etter meg, ønsker å refactor koden, vil de umiddelbart vite at en klasse/medlem merket med @Keep krever spesiell håndtering uten å måtte huske å konsultere proguard-konfigurasjonen og risikere å bryte noe. Også de fleste kode refactorings I IDE bør beholde@Keep annotasjonen med klassen automatisk.

    Plaid stats

    Her er noen statistikk Fra Plaid, som viser hvor mye kode jeg klarte å fjerne ved Hjelp Av ProGuard. På en mer kompleks app med flere avhengigheter og en større DEX besparelsene kan bli enda mer betydelig.