Articles

praktiske ProGuard regler eksempler

div>
feb 20, 2018 · 9 min læs

i min tidligere artikel forklarede jeg, hvorfor alle skulle bruge ProGuard til deres Android-apps, hvordan man aktiverer det, og hvilken slags fejl du måtte støde på, når du gør det. Der var meget teori involveret, da jeg synes, det er vigtigt at forstå de underliggende principper for at være parat til at håndtere eventuelle problemer.

Jeg talte også i en separat artikel om det meget specifikke problem med at konfigurere ProGuard til en øjeblikkelig appopbygning.

i denne del vil jeg gerne tale om de praktiske eksempler på ProGuard-regler på en mellemstor prøveapp: Plaid af Nick Butcher.Plaid viste sig faktisk at være et godt emne til at undersøge ProGuard-problemer, da det indeholder en blanding af 3.parts biblioteker, der bruger ting som annotationsbehandling og kodegenerering, refleksion, java resource loading og native code (JNI). Jeg udpakkede og noterede nogle praktiske råd, der skulle gælde for andre apps generelt:

dataklasser

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

sandsynligvis har hver app en slags dataklasse (også kendt som DMOs, modeller osv. afhængigt af kontekst og hvor de sidder i din apps arkitektur). Sagen ved dataobjekter er, at de normalt på et tidspunkt vil blive indlæst eller gemt (serialiseret) i et andet medium, såsom netværk (en HTTP-anmodning), en database (gennem en ORM), en JSON-fil på disken eller i en Firebase-datalager.

mange af de værktøjer, der forenkler serialisering og deserialisering af disse felter, er afhængige af refleksion. GSON, Retrofit, Firebase-de inspicerer alle feltnavne i dataklasser og gør dem til en anden repræsentation (for eksempel: {"name”: "Sue”, "age”: 28}), enten til transport eller opbevaring. Det samme sker, når de læser data i et Java — objekt-de ser et nøgleværdipar "name”:”John” og forsøger at anvende det på et Java-objekt ved at slå et String name felt.

konklusion: Vi kan ikke lade ProGuard omdøbe eller fjerne felter på disse dataklasser, da de skal matche det serialiserede format. Det er en sikker indsats at tilføje en @Keep annotation på hele klassen eller en jokertegnregel på alle dine modeller:

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

advarsel: Det er muligt at lave en fejl, når du tester, om din app er modtagelig for dette problem. Hvis du f.eks. serialiserer et objekt til JSON og gemmer det på disken i version N af din app uden de korrekte keep-regler, kan de gemte data se sådan ud: {"a”: "Sue”, "b”: 28}. Fordi ProGuard omdøbte dine felter til a og b, alt ser ud til at fungere, data gemmes og indlæses korrekt.

Når du bygger din app igen og frigiver version N+1 af din app, kan ProGuard dog beslutte at omdøbe dine felter til noget andet, såsom c og d. Som et resultat vil data, der er gemt tidligere, ikke indlæses.

Du skal sikre dig, at du har de rette holdregler i første omgang.

Java-kode kaldet fra native side (JNI)

Android ‘ s Standardproguard-filer (du skal altid inkludere dem, de har nogle virkelig nyttige regler) indeholder allerede en regel for metoder, der implementeres på den native side (-keepclasseswithmembernames class * { native <methods>; }). Desværre er der ingen catch-all måde at holde kode påberåbt i den modsatte retning: fra JNI til Java.

med JNI er det fuldt ud muligt at konstruere et JVM-objekt eller finde og kalde en metode på et JVM-håndtag fra C/C++ – kode, og faktisk gør et af de biblioteker, der bruges i Plaid, det.

konklusion: fordi ProGuard kun kan inspicere Java klasser, vil det ikke vide om nogen kutymer, der sker i native kode. Vi skal eksplicit bevare sådanne anvendelser af klasser og medlemmer via en @Keep annotation eller -keep regel.

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

åbning ressourcer fra JAR/APK

Android har sine egne ressourcer og aktiver system, der normalt ikke bør være et problem for ProGuard. I almindelig Java er der dog en anden mekanisme til indlæsning af ressourcer direkte fra en JAR-fil, og nogle tredjepartsbiblioteker bruger muligvis den, selv når de kompileres i Android-apps (i så fald vil de forsøge at indlæse fra APK).

problemet er, at disse klasser normalt vil lede efter ressourcer under deres eget Pakkenavn (som oversættes til en filsti i krukken eller APK). ProGuard kan omdøbe pakkenavne, når de tilsløres, så efter kompilering kan det ske, at klassen og dens ressourcefil ikke længere er i den samme pakke i den endelige APK.

for at identificere indlæsningsressourcer på denne måde kan du søge efter opkald til Class.getResourceAsStream / getResourceog ClassLoader.getResourceAsStream / getResource i din kode og i eventuelle tredjepartsbiblioteker, du er afhængig af.

konklusion: vi bør beholde navnet på enhver klasse, der indlæser ressourcer fra APK ved hjælp af denne mekanisme.

i Plaid er der faktisk to — en i OkHttp-biblioteket og en i Jsoup:

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

Sådan kommer du op med regler for tredjepartsbiblioteker

i en ideel verden vil enhver afhængighed, du bruger, levere deres krævede ProGuard-regler i AAR. Nogle gange glemmer de at gøre dette eller kun offentliggøre krukker, som ikke har en standardiseret måde at levere ProGuard-regler på.

i så fald skal du huske at kontrollere dokumentationen, inden du begynder at debugge din app og komme med regler. Nogle bibliotek forfattere leverer anbefalede ProGuard regler (såsom eftermontering bruges i Plaid), som kan spare dig for en masse tid og frustration. Desværre gør mange biblioteker ikke (som det er tilfældet med Jsoup og Bypass nævnt i denne artikel). Vær også opmærksom på, at den konfiguration, der følger med biblioteket, i nogle tilfælde kun fungerer med optimeringer deaktiveret, så hvis du tænder dem, er du muligvis i ukendt område.

så hvordan kommer man op med regler, når biblioteket ikke leverer dem?
Jeg kan kun give dig nogle pointers:

  1. Læs build output og logcat!
  2. Build advarsler vil fortælle dig, hvilke-dontwarn regler for at tilføje
  3. ClassNotFoundExceptionMethodNotFoundException ogFieldNotFoundException vil fortælle dig, hvilke-keep regler for at tilføje

Du skal være glad, når din app går ned med ProGuard aktiveret — du har et sted at starte din undersøgelse:)

den værste klasse af problemer at debugge er, når du app fungerer, men for eksempel viser ikke en skærm eller indlæser ikke data fra netværket.

det er her, du skal overveje nogle af de scenarier, jeg beskrev i denne artikel, og få dine hænder beskidte, endda dykke ned i tredjepartskoden og forstå, hvorfor den muligvis mislykkes, f.eks.

Debugging and stack traces

ProGuard fjerner som standard mange kodeattributter og skjulte metadata, der ikke kræves til programudførelse . Nogle af dem er faktisk nyttige for udvikleren — for eksempel vil du måske beholde kildefilnavne og linjenumre til stakspor for at gøre debugging lettere:

-keepattributes SourceFile, LineNumberTable

Du skal også huske at gemme ProGuard-kortlægningsfilerne, der er produceret, når du bygger en udgivelsesversion og uploader dem til at spille for at få de-tilslørede stakspor fra eventuelle nedbrud, som din brugere.

Hvis du vil vedhæfte en debugger for at gå gennem metodekode i en Proguarderet build af din app, skal du også beholde følgende attributter for at bevare nogle debugoplysninger om lokale variabler (du har kun brug for denne linje i din debug build type):

-keepattributes LocalVariableTable, LocalVariableTypeTable

minified debug build type

standardbyggetyperne er konfigureret således, at debug ikke kører ProGuard. Det giver mening, fordi vi ønsker at gentage og kompilere hurtigt, når vi udvikler, men stadig ønsker, at udgivelsesbygningen skal bruge ProGuard til at være så lille og optimeret som muligt.

men for fuldt ud at teste og debug eventuelle ProGuard problemer, er det godt at oprette en separat, minified debug build som denne:

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

Med denne build type, vil du være i stand til at forbinde debugger, køre UI tests (også på en CI-server) eller abe teste din app for mulige problemer på en build, der er så tæt på din release build som muligt.

konklusion: Når du bruger ProGuard, skal du altid bygge din udgivelse grundigt, enten ved at have ende-til-ende-test eller manuelt gennemgå alle skærme i din app for at se, om der mangler noget eller går ned.

Runtime anmærkninger, skriv introspektion

ProGuard vil som standard fjerne alle anmærkninger og endda nogle overskud typen oplysninger fra din kode. For nogle biblioteker, der ikke er et problem — dem, der behandler anmærkninger og genererer kode på kompileringstidspunktet (såsom Dagger 2 eller Glide og mange flere), har muligvis ikke brug for disse anmærkninger senere, når programmet kører.

der er en anden klasse af værktøjer, der faktisk inspicerer kommentarer eller ser på typeoplysninger om parametre og undtagelser ved kørsel. Eftermontering gør for eksempel dette ved at opfange dine metodeopkald ved hjælp af et Proxy objekt, så se på kommentarer og skriv oplysninger for at beslutte, hvad du skal sætte eller læse fra HTTP-anmodningen.

konklusion: nogle gange er det nødvendigt at bevare typeoplysninger og kommentarer, der læses ved kørsel, i modsætning til kompileringstid. Du kan tjekke listen attributter i ProGuard manual.

-keepattributes *Annotation*, Signature, Exception

Hvis du bruger standard Android ProGuard — konfigurationsfilen (getDefaultProguardFile('proguard-android.txt')), er de to første muligheder — kommentarer og signatur-angivet for dig. Hvis du ikke bruger standard, skal du sørge for at tilføje dem selv (det gør heller ikke ondt at bare duplikere dem, hvis du ved, at de er et krav til din app).

flytning af alt til standardpakken

-repackageclasses er ikke tilføjet som standard i ProGuard config. Hvis du allerede tilslører din kode og har løst eventuelle problemer med ordentlige keep-regler, kan du tilføje denne mulighed for yderligere at reducere størrelsen. Det fungerer ved at flytte alle klasser til standardpakken (root) og i det væsentlige frigøre den plads, der optages af strenge som “com.eksempel.myapp.somepackage”.

-repackageclasses

ProGuard-optimeringer

som jeg nævnte før, kan ProGuard gøre 3 ting for dig:

  1. det slipper af ubrugt kode,
  2. omdøber identifikatorer for at gøre koden mindre,
  3. udfører hele programoptimeringer.

som jeg ser det, bør alle forsøge at konfigurere deres build for at få 1. og 2. arbejde.

for at låse op 3. (yderligere optimeringer), skal du bruge en anden standard ProGuard konfigurationsfil. Skift proguard-android.txt parameter til proguard-android-optimize.txt i din build.gradle:

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

dette vil gøre din release bygge langsommere, men vil potentielt gøre din app køre hurtigere og reducere kode størrelse endnu mere, tak til optimeringer såsom metode inlining, klasse sammenlægning og mere aggressiv kode fjernelse. Vær dog forberedt på, at det kan introducere nye og vanskelige at diagnosticere fejl, så brug det med forsigtighed, og hvis noget ikke fungerer, skal du sørge for at deaktivere visse optimeringer eller deaktivere brugen af optimeringskonfigurationen helt.i tilfælde af Plaid interfererede ProGuard-optimeringer med, hvordan eftermontering bruger Fuldmagtsobjekter uden konkrete implementeringer, og fjernede nogle metodeparametre, der faktisk var nødvendige. Jeg var nødt til at tilføje denne linje til min config:

-optimizations !method/removal/parameter

Du kan finde en liste over mulige optimeringer og hvordan du deaktiverer dem i ProGuard manualen.

Hvornår skal du bruge @Keep and-keep

@Keep support er faktisk implementeret som en flok-keep regler i standard Android ProGuard rules-filen, så de er i det væsentlige ækvivalente. Angivelse af-keep regler er mere fleksible, da det tilbyder jokertegn, du kan også bruge forskellige varianter, der gør lidt forskellige ting (-keepnames-keepclasseswithmembers og mere).

Når en simpel “keep this class” eller “keep this method” – regel er nødvendig, foretrækker jeg faktisk enkelheden ved at tilføje en@Keep annotation på klassen eller medlemmet, da det forbliver tæt på koden, næsten som dokumentation.

hvis en anden udvikler, der kommer efter mig, ønsker at refactor koden, vil de straks vide, at en klasse/medlem markeret med @Keep kræver særlig håndtering uden at skulle huske at konsultere ProGuard-konfigurationen og risikere at bryde noget. Også de fleste kode refactorings i IDE bør beholde @Keep annotation med klassen automatisk.

Plaid stats

Her er nogle statistikker fra Plaid, som viser, hvor meget kode jeg formåede at fjerne ved hjælp af ProGuard. På en mere kompleks app med flere afhængigheder og en større dybde kan besparelserne være endnu større.