Articles

käytännön ProGuard rules examples

Wojtek Kaliciński
Wojtek Kaliciński

Follow
Feb 20, 2018 · 9 min read

edellisessä artikkelissani selitin, miksi kaikkien tulisi käyttää proguardia Android-sovelluksissaan, miten se otetaan käyttöön ja millaisia virheitä saatat kohdata sitä tehdessäsi. Mukana oli paljon teoriaa, koska mielestäni on tärkeää ymmärtää taustalla olevat periaatteet, jotta voidaan olla valmiita käsittelemään mahdollisia ongelmia.

puhuin myös erillisessä artikkelissa Proguardin määrittämisestä Instant App build-sovellukseen.

tässä osassa haluaisin puhua ProGuard-sääntöjen käytännön esimerkeistä keskikokoisessa näytesovelluksessa: Plaid by Nick Butcher.

Plaid osoittautui itse asiassa suureksi aiheeksi ProGuard-ongelmien tutkimiseen, sillä se sisältää sekoituksen 3rd party-kirjastoja, jotka käyttävät sellaisia asioita kuin annotation processing ja code generation, reflection, java resource loading ja native code (jni). Olen poiminut ja jottain joitakin käytännön neuvoja, jotka pitäisi soveltaa muihin sovelluksiin yleensä:

Dataluokat

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

luultavasti jokainen sovellus on jonkinlainen dataluokka (tunnetaan myös nimellä DMOs, mallit, jne. riippuen asiayhteydestä ja siitä, missä ne istuvat sovelluksen arkkitehtuurissa). Tietoobjektien juttu on, että yleensä jossain vaiheessa ne ladataan tai tallennetaan (sarjallistetaan) johonkin muuhun mediaan, kuten verkkoon (HTTP-pyyntö), tietokantaan (ORM: n kautta), JSON-tiedostoon levylle tai Firebase-datakauppaan.

monet näiden kenttien sarjallistamista ja deserialisointia yksinkertaistavat työkalut nojaavat pohdintaan. GSON, Retrofit, Firebase – ne kaikki tarkastavat kenttänimet dataluokissa ja muuttavat ne toiseksi edustukseksi (esimerkiksi: {"name”: "Sue”, "age”: 28}), joko kuljetusta tai varastointia varten. Sama tapahtuu, kun he lukevat dataa Java — objektiin-he näkevät avainarvoparin "name”:”John” ja yrittävät soveltaa sitä Java-objektiin etsimällä String name kentän.

johtopäätös: Emme voi antaa Proguardin nimetä uudelleen tai poistaa mitään kenttiä näistä dataluokista, koska niiden on vastattava sarjamuotoista muotoa. On turvallista lisätä @Keep huomautus koko luokalle tai jokerimerkkisääntö kaikille malleille:

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

Varoitus: On mahdollista tehdä virhe testattaessa, jos sovellus on altis tämän ongelman. Jos esimerkiksi sarjalisoit objektin jsonille ja tallennat sen levylle sovelluksen versiossa N ilman asianmukaisia keep-sääntöjä, tallennetut tiedot saattavat näyttää tältä: {"a”: "Sue”, "b”: 28}. Koska ProGuard nimesi kentät uudelleen muotoon a ja b, kaikki näyttää toimivan, tiedot tallennetaan ja ladataan oikein.

kuitenkin, kun rakennat sovelluksesi uudelleen ja julkaiset sovelluksesi version N+1, ProGuard saattaa päättää nimetä kenttäsi uudelleen joksikin muuksi, kuten c ja d. Tämän seurauksena aiemmin tallennetut tiedot eivät lataudu.

sinun on varmistettava, että sinulla on ylipäätään asianmukaiset pitosäännöt.

Java-koodi nimeltään natiivipuolelta (JNI)

Androidin oletusproguard-tiedostot (ne kannattaa aina ottaa mukaan, niissä on joitain todella hyödyllisiä sääntöjä) sisältävät jo säännön natiivipuolella toteutettaville menetelmille (-keepclasseswithmembernames class * { native <methods>; }). Valitettavasti ei ole catch-kaikki tapa pitää koodi vedotaan vastakkaiseen suuntaan: JNI Java.

JNI: n avulla on täysin mahdollista rakentaa JVM-objekti tai löytää ja kutsua menetelmä JVM-kahvalla c / c++ – koodista ja itse asiassa yksi Plaid-ohjelmistossa käytetyistä kirjastoista tekee sen.

johtopäätös: koska ProGuard voi tarkastaa vain Java-luokkia, se ei tiedä mitään käyttötapoja, jotka tapahtuvat natiivikoodissa. Meidän on nimenomaisesti säilytettävä tällaiset luokkien ja jäsenten käytöt @Keep annotation tai -keep sääntö.

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

avaavat resurssit JAR/APK

Androidilla on omat resurssit ja varat-järjestelmä, jonka ei normaalisti pitäisi olla Proguardille ongelma. Kuitenkin, plain Java on toinen mekanismi ladata resursseja suoraan JAR tiedosto ja jotkut kolmannen osapuolen kirjastot saattavat käyttää sitä, vaikka käännetty Android-Sovellukset (siinä tapauksessa he yrittävät ladata APK).

ongelmana on, että yleensä nämä luokat etsivät resursseja omalla pakettinimellään (joka kääntyy TIEDOSTOPOLUKSI purkissa tai APK: ssa). ProGuard voi nimetä paketin nimiä kun obfuscating, niin sen jälkeen kokoelma voi tapahtua, että luokka ja sen resurssi tiedosto eivät ole enää samassa paketissa Lopullinen APK.

tunnistaaksesi Latausresurssit tällä tavalla, voit etsiä soittoja Class.getResourceAsStream / getResource ja ClassLoader.getResourceAsStream / getResource koodistasi ja kaikista kolmannen osapuolen kirjastoista, joista olet riippuvainen.

johtopäätös: meidän pitäisi pitää nimi tahansa luokan, joka lataa resursseja APK käyttäen tätä mekanismia.

Plaid — kirjastossa on oikeastaan kaksi-yksi OkHttp://p>

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

miten keksiä sääntöjä kolmansien osapuolten kirjastoille

ideaalimaailmassa jokainen käyttämäsi riippuvuus toimittaisi vaaditut ProGuard-sääntönsä AAR: iin. Joskus he unohtavat tehdä tämän tai julkaisevat vain purkkeja, joilla ei ole standardoitua tapaa toimittaa ProGuard-sääntöjä.

siinä tapauksessa, ennen kuin aloitat sovelluksen virheenkorjauksen ja sääntöjen keksimisen, muista tarkistaa dokumentaatio. Jotkut kirjaston kirjoittajat toimittaa suositeltuja ProGuard sääntöjä (kuten jälkiasennus käytetään ruudullinen), joka voi säästää paljon aikaa ja turhautumista. Valitettavasti, monet kirjastot eivät (kuten on tapauksessa Jsoup ja Ohitus mainittu tässä artikkelissa). Huomaa myös, että joissakin tapauksissa kirjaston mukana toimitetut asetukset toimivat vain optimointien ollessa pois päältä, joten jos kytket ne päälle, saatat olla kartoittamattomalla alueella.

Joten miten keksiä sääntöjä, kun kirjasto ei niitä tarjoa?
I can only give you some pointers:

  1. Read the build output and logcat!
  2. Rakentamisvaroitukset kertovat kumpi -dontwarn säännöt lisätä
  3. ClassNotFoundExceptionMethodNotFoundException ja FieldNotFoundException kertoo kumpi -keep säännöt lisätä

sinun pitäisi olla iloinen, kun sovelluksesi kaatuu ProGuard käytössä — sinulla on paikka aloittaa tutkimuksesi 🙂

pahimmat ongelmat debugille ovat silloin, kun sovellus toimii, mutta ei esimerkiksi näytä näyttöä tai lataa tietoja verkosta.

siinä sinun täytyy harkita joitakin skenaarioita, jotka olen kuvannut tässä artikkelissa ja saada kätesi likaisiksi, jopa sukeltamalla kolmannen osapuolen koodiin ja ymmärtämällä, miksi se saattaa epäonnistua, kuten silloin, kun se käyttää reflektiota, itsetutkiskelua tai JNI: tä.

virheenkorjaus ja pinojäljet

ProGuard poistaa oletusarvoisesti monia koodin attribuutteja ja piilotettuja metatietoja, joita ei tarvita ohjelman suorittamiseen . Jotkut näistä ovat todella hyödyllisiä kehittäjälle — esimerkiksi, saatat haluta säilyttää lähdekooditiedostojen nimet ja rivinumerot pinon jäljille, jotta virheenkorjaus olisi helpompaa:

-keepattributes SourceFile, LineNumberTable

sinun tulisi myös muistaa tallentaa ProGuard-kuvaukset tuotetut tiedostot, Kun rakennat julkaisuversion ja lataat ne pelattavaksi saadaksesi de-obfuscated stack traces kaikista kaatumisista, joita sinun käyttäjät.

Jos aiot liittää vianetsintätyökalun astuaksesi metodikoodin läpi sovelluksesi Progruarded-rakenteessa, sinun tulisi myös säilyttää seuraavat attribuutit säilyttääksesi joitain vianetsintätietoja paikallisista muuttujista (tarvitset vain tämän rivin debug build type):

-keepattributes LocalVariableTable, LocalVariableTypeTable

minified debug build type

oletusmuodostustyypit on määritetty siten, että debug ei suorita proguardia. Tämä on järkevää, koska haluamme iteroida ja kääntää nopeasti kehitettäessä, mutta silti haluamme release build käyttää ProGuard on mahdollisimman pieni ja optimoitu.

mutta jotta ProGuard-ongelmat voidaan täysin testata ja debugata, on hyvä perustaa erillinen, pienennetty debug-rakenne näin:

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

tällä rakentamistyypillä voit liittää debuggerin, suorittaa KÄYTTÖLIITTYMÄTESTEJÄ (myös CI-palvelimella) tai monkey testata sovellustasi mahdollisten ongelmien varalta rakenteella, joka on mahdollisimman lähellä julkaisusi rakennetta.

johtopäätös: Kun käytät ProGuard sinun pitäisi aina QA julkaisu rakentaa perusteellisesti, joko ottamalla päästä päähän testejä tai manuaalisesti läpi kaikki näytöt sovelluksen nähdä, jos jotain puuttuu tai kaatuu.

Runtime annotations, type introspection

ProGuard poistaa oletusarvoisesti kaikki merkinnät ja jopa osan ylimääräisistä tyyppitiedoista koodistasi. Joillekin kirjastoille Se ei ole ongelma — ne, jotka käsittelevät merkintöjä ja tuottavat koodia käännösaikaan (kuten Dagger 2 tai Glide ja monet muut), eivät välttämättä tarvitse näitä merkintöjä myöhemmin, kun ohjelma suoritetaan.

on olemassa toinen työkaluluokka, joka tarkastelee annotaatioita tai tarkastelee suoritettaessa parametrien ja poikkeusten tyyppitietoja. Retrofit esimerkiksi tekee tämän sieppaamalla metodipuhelut käyttämällä Proxy object, sitten katsomalla merkintöjä ja kirjoita tietoja päättää, mitä laittaa tai lukea HTTP-pyynnöstä.

johtopäätös: joskus on säilytettävä suorituksen aikana luettavat tyyppitiedot ja merkinnät, toisin kuin käännösaika. Voit tarkistaa attribuuttiluettelon Proguardin ohjekirjasta.

-keepattributes *Annotation*, Signature, Exception

Jos käytät Androidin ProGuard — asetustiedostoa (getDefaultProguardFile('proguard-android.txt')), sinulle on määritelty kaksi ensimmäistä vaihtoehtoa — merkinnät ja allekirjoitus. Jos et käytä oletuksena sinun täytyy varmistaa lisätä ne itse (se ei myöskään haittaa vain kopioida ne, jos tiedät ne vaatimus sovelluksen).

kaiken siirtäminen oletuspakettiin

-repackageclasses valintaa ei ole oletusarvoisesti lisätty ProGuard-asetukseen. Jos olet jo obfuscating koodin ja ovat korjanneet ongelmia asianmukaisen pitää sääntöjä, voit lisätä tämän vaihtoehdon edelleen vähentää DEX kokoa. Se toimii siirtämällä kaikki luokat oletuksena (root) paketti, lähinnä vapauttaa tilaa ottanut merkkijonoja, kuten ”com.esimerkiksi.myapp.some package”.

-repackageclasses

ProGuard optimizations

kuten aiemmin mainitsin, ProGuard voi tehdä sinulle 3 asiaa:

  1. se pääsee eroon käyttämättömästä koodista,
  2. nimeää tunnisteita, jotta koodi olisi pienempi,
  3. suorittaa kokonaisia ohjelmaoptimointeja.

nähdäkseni jokaisen pitäisi yrittää konfiguroida rakenteensa niin, että saadaan 1. ja 2. työskentely.

avata 3. (lisäoptimoinnit), sinun täytyy käyttää eri oletuksena ProGuard asetustiedosto. Muuta proguard-android.txt parametri proguard-android-optimize.txtbuild.gradle:

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

Tämä tekee julkaisusi rakentamisesta hitaampaa, mutta mahdollisesti tekee sovelluksestasi nopeamman ja pienentää koodin kokoa entisestään, kiitos optimointeihin, kuten method inlining, luokan yhdistäminen ja aggressiivisempi koodin poisto. Ole valmis kuitenkin, että se saattaa ottaa käyttöön uusia ja vaikea diagnosoida vikoja, joten käytä sitä varoen ja jos jokin ei toimi, muista poistaa tiettyjä optimointeja tai poistaa käytöstä optimointi config kokonaan.

ruudun tapauksessa ProGuard-optimoinnit puuttuivat siihen, miten jälkiasennus käyttää Proxy-objekteja ilman konkreettisia toteutuksia, ja riisuivat pois joitakin metodiparametreja, joita todellisuudessa tarvittiin. Minun täytyi lisätä tämä rivi minun config:

-optimizations !method/removal/parameter

löydät Proguardin käsikirjasta listan mahdollisista optimoinneista ja niiden käytöstä poistamisesta.

when to use @Keep and-keep

@Keep support is actually implemented as a bunch of -keep rules in the default Android ProGuard rules file, so they ’ re oleellisesti equivalent. Määrittely -keep säännöt on joustavampi, koska se tarjoaa jokerimerkkejä, voit käyttää myös erilaisia variantteja, jotka tekevät hieman eri asioita (-keepnames-keepclasseswithmembers ja enemmän).

aina kun tarvitaan yksinkertaista ”pidä tämä luokka” tai ”pidä tämä menetelmä” – sääntöä, itse asiassa pidän parempana yksinkertaisuutta lisätä@Keep merkintä luokkaan tai jäseneen, koska se pysyy lähellä koodia, melkein kuin dokumentaatio.

Jos joku muu perässäni tuleva kehittäjä haluaa koodata koodin uudelleen, he tietävät heti, että @Keep merkitty Luokka / jäsen vaatii erikoiskäsittelyä, ilman että tarvitsee muistaa konsultoida Proguardin konfiguraatiota ja riskeerata jotain. Myös useimpien koodien refaktorointien IDE: ssä tulisi säilyttää @Keep huomautus luokan kanssa automaattisesti.

Plaid stats

Tässä muutamia ruudullisia tilastoja, jotka osoittavat kuinka paljon koodia onnistuin poistamaan Proguardilla. Monimutkaisemmassa sovelluksessa, jossa on enemmän riippuvuuksia ja suurempi DEX, säästöt voivat olla vieläkin merkittävämpiä.