Articles

Praktické ProGuard pravidla, příklady

Wojtek Kaliciński
Wojtek Kaliciński

Sledovat

Únor 20, 2018 · 9 min číst

V mém předchozím článku jsem vysvětlil, proč každý by měl používat ProGuard pro jejich Android aplikací, jak povolit a jaké chyby se mohou vyskytnout při tom. Bylo v tom hodně teorie, protože si myslím, že je důležité pochopit základní principy, abychom byli připraveni řešit případné problémy.

také jsem hovořil v samostatném článku o velmi specifickém problému konfigurace ProGuard pro okamžité sestavení aplikace.

v této části bych chtěl mluvit o praktických příkladech pravidel ProGuard na středně velké ukázkové aplikaci: Plaid od Nicka Butchera.

Kostkované ve skutečnosti se ukázalo být skvělé téma pro výzkum ProGuard problémy, protože obsahuje mix 3rd party knihovny, které používají věci jako anotace zpracování a generování kódu, reflexe, java zatížení zdrojů a nativní kód (JNI). Extrahoval jsem a poznačil několik praktických rad, které by se mělo vztahovat na jiné aplikace obecně:

Data třídy

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

Pravděpodobně každá aplikace má nějaký druh dat třídy (také známý jako DMOs, modely, atd. v závislosti na kontextu a kde sedí v architektuře vaší aplikace). Věc, o data objects je to, že obvykle na nějakém místě, které bude načten nebo uložen (pokračování) na nějaké jiné médium, jako je například síť (HTTP request), databáze (prostřednictvím ORM), JSON souboru na disku nebo v Firebase data store.

mnoho nástrojů, které zjednodušují serializaci a deserializaci těchto polí, se spoléhá na reflexi. GSON, Retrofit, Firebase — všichni zkontrolujte názvy polí v datových tříd a proměnit je do jiné reprezentace (např.: {"name”: "Sue”, "age”: 28}), a to buď pro přepravu nebo skladování. Totéž se stane, když čtou data do objektu Java-vidí pár klíčových hodnot "name”:”John” a pokusí se je použít na objekt Java vyhledáním pole String name .

závěr: nemůžeme nechat ProGuard přejmenovat nebo odstranit všechna pole v těchto datových třídách, protože musí odpovídat serializovanému formátu. Je to sázka na jistotu, chcete-li přidat @Keep anotace na celou třídu, nebo zástupný pravidlo na všechny své modely:

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

Upozornění: Při testování, zda je vaše aplikace náchylná k tomuto problému, je možné udělat chybu. Například, pokud jste serializaci objektu JSON a uložit na disk ve verzi N své aplikace, aniž by správné, aby pravidla, uložená data může vypadat například takto: {"a”: "Sue”, "b”: 28}. Protože ProGuard přejmenoval vaše pole na a a b, zdá se, že vše funguje, data budou uložena a načtena správně.

Nicméně, když budete stavět své aplikace znovu a verzi N+1 aplikace, ProGuard se může rozhodnout, abyste přejmenovat své pole, aby něco jiného, jako je cd. V důsledku toho se dříve uložená data nezdaří načíst.

musíte se ujistit, že máte správná pravidla keep na prvním místě.

Java kódu volal z rodné straně (JNI)

Android je výchozí ProGuard soubory (měli byste vždy zahrnout, mají pár opravdu užitečných pravidlech) již obsahují pravidla pro metody, které jsou implementovány na nativní straně (-keepclasseswithmembernames class * { native <methods>; }). Bohužel neexistuje žádný catch-all způsob, jak udržet kód vyvolán v opačném směru: z JNI do Javy.

S JNI je zcela možné postavit objekt JVM nebo najít a zavolat metodu na rukojeti JVM z kódu C / C++ a ve skutečnosti to dělá jedna z knihoven používaných v Plaid.

Závěr: Protože ProGuard můžete pouze zkontrolujte, zda Java tříd, to nebude vědět o nějaké zvyklosti, které se dějí v nativním kódu. Takové použití tříd a členů musíme explicitně zachovat pomocí pravidla @Keep anotace nebo -keep.

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

Otevření zdrojů z JAR/APK

Android má své vlastní zdroje a aktiva systému, které obvykle by neměl být problém pro ProGuard. V prosté Javě však existuje další mechanismus pro načítání zdrojů přímo ze souboru JAR a některé knihovny třetích stran jej mohou používat i při kompilaci v aplikacích pro Android(v takovém případě se pokusí načíst z APK).

problém je v tom, že obvykle tyto třídy budou hledat zdroje pod vlastním názvem balíčku(což se promítá do cesty k souboru v JAR nebo APK). ProGuard může přejmenovat názvy balíčků při zmatení, takže po kompilaci se může stát, že třída A její zdrojový soubor již nejsou ve stejném balíčku v konečném APK.

identifikovat načítání zdrojů tímto způsobem, můžete se podívat pro volání do Class.getResourceAsStream / getResourceClassLoader.getResourceAsStream / getResource v kódu a v žádné knihoven třetích stran, jste závislí na.

závěr: pomocí tohoto mechanismu bychom měli zachovat název jakékoli třídy, která načte zdroje z APK.

V Kostkované, tam jsou vlastně dva — jeden v OkHttp knihovna a jeden v Jsoup:

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

Jak přijít s pravidly pro knihoven třetích stran

V ideálním světě, každý závislost použití by jejich dodání požadované ProGuard pravidla, ve výroční zprávě o ČINNOSTI. Někdy to zapomenou nebo zveřejní pouze sklenice, které nemají standardizovaný způsob, jak dodat pravidla ProGuard.

v takovém případě, než začnete ladit aplikaci a přijít s pravidly, nezapomeňte zkontrolovat dokumentaci. Někteří autoři knihovny dodávají doporučená pravidla ProGuard (například Retrofit použitý v přehozu), což vám může ušetřit spoustu času a frustrace. Bohužel, mnoho knihoven ne (jako je tomu v případě Jsoup a Bypass zmíněné v tomto článku). Uvědomte si také, že v některých případech bude konfigurace dodávaná s knihovnou fungovat pouze s optimalizacemi deaktivovanými, takže pokud je zapínáte, můžete být na nezmapovaném území.

Jak tedy přijít s pravidly, když je knihovna nedodává?
mohu vám dát jen několik ukazatelů:

  1. Přečtěte si výstup sestavení a logcat!
  2. Sestavení varování vám řekne, které -dontwarn pravidla přidat
  3. ClassNotFoundExceptionMethodNotFoundExceptionFieldNotFoundException vám řekne, které -keep pravidla přidat

měl Bys být rád, když vaše aplikace havaruje s ProGuard povolena — budete muset někde začít vyšetřování 🙂

nejhorší třídu problémů, ladění je, když aplikace funguje, ale například neukazuje obrazovku nebo nenačte data ze sítě.

to je místo, kde budete muset zvážit některé scénáře jsem popsal v tomto článku, a dostat své špinavé ruce, dokonce i potápění do kódu třetích stran a pochopit, proč to může selhat, jako když to používá reflexe, introspekce nebo JNI.

ladění a stopy zásobníku

ProGuard ve výchozím nastavení odstraní mnoho atributů kódu a skrytých metadat, které nejsou nutné pro provádění programu . Některé z nich jsou ve skutečnosti užitečné pro vývojáře — například, možná budete chtít zachovat zdroje, názvy souborů a čísla řádků pro trasování zásobníku, aby se ladění jednodušší:

-keepattributes SourceFile, LineNumberTable

měli Byste také pamatovat, aby zachránil ProGuard mapování souborů produkován, když budete stavět verzi a nahrát na Play získat de-obfuscated trasování zásobníku z nějaké pády zkušených uživatelů.

Pokud se chystáte připojit ladicí program krokovat kód metody v ProGuarded budovat své aplikace, také byste měli mít následující atributy zachovat některé ladicích informací o lokálních proměnných (budete potřebovat pouze tento řádek debug vytvořit typ):

-keepattributes LocalVariableTable, LocalVariableTypeTable

ladění Minified typ sestavení

výchozí stavět typy jsou konfigurovány tak, že ladění nefunguje ProGuard. To dává smysl, protože chceme iterovat a kompilovat rychle při vývoji, ale přesto chceme, aby sestavení verze používalo ProGuard, aby bylo co nejmenší a optimalizováno.

Ale aby se plně testovat a ladit nějaké ProGuard problémy, je dobré nastavit samostatný, minified debug build, jako je tento:

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

tento typ sestavení, budete moci připojit ladicí program, spustit testy UI (také na CI serveru) nebo monkey otestujte si své aplikace na možné problémy na sestavení, která je co nejblíže k vaší verzi sestavení, jak je to možné.

závěr: Při použití ProGuard byste měli vždy QA vaše vydání staví důkladně, a to buď tím, že end-to-end testy nebo ručně prochází všechny obrazovky v aplikaci, aby zjistili, zda něco chybí nebo shazovat.

Runtime anotace, zadejte introspekci

ProGuard ve výchozím nastavení odstraní všechny anotace a dokonce i některé informace o typu přebytku z vašeho kódu. U některých knihoven to není problém — ty, které zpracovávají anotace a generují kód v době kompilace (například Dagger 2 nebo Glide a mnoho dalších), nemusí tyto anotace později při spuštění programu potřebovat.

existuje další třída nástrojů, které skutečně kontrolují anotace nebo se dívají na informace o typu parametrů a výjimek za běhu. Retrofit například dělá to tím, že zachytí metodu volání pomocí Proxy objekt, pak při pohledu na anotace a zadejte informace rozhodnout, co dát nebo číst z požadavku HTTP.

závěr: někdy je nutné uchovávat informace o typu a anotace, které jsou čteny za běhu, na rozdíl od doby kompilace. Seznam atributů si můžete prohlédnout v příručce ProGuard.

-keepattributes *Annotation*, Signature, Exception

Pokud používáte výchozí Android ProGuard konfigurační soubor (getDefaultProguardFile('proguard-android.txt')), první dvě možnosti — Popisy a Podpis — jsou uvedeny pro vás. Pokud nepoužíváte výchozí nastavení, musíte je přidat sami (také není na škodu duplikovat je, pokud víte, že jsou požadavkem pro vaši aplikaci).

přesunutí všeho do výchozího balíčku

volba -repackageclasses není ve výchozím nastavení přidána v konfiguraci ProGuard. Pokud jste již zmatení svůj kód a mají opraveny nějaké problémy s řádnými pravidly keep, můžete přidat tuto možnost k dalšímu snížení velikosti DEX. Funguje tak, že přesune všechny třídy do výchozího (kořenového) balíčku, v podstatě uvolní prostor zabíraný řetězci jako “ com.příklad.aplikace.somepackage“.

-repackageclasses

ProGuard optimalizace

Jak jsem již zmínil dříve, ProGuard můžete udělat 3 věci pro vás:

  1. zbaví nepoužitý kód,
  2. přejmenuje identifikátory, aby se kód menší,
  3. provádí celý program optimalizace.

jak to vidím, každý by se měl pokusit nakonfigurovat jejich sestavení tak, aby získal 1. a 2. práce.

odemknout 3. (další optimalizace), musíte použít jiný výchozí konfigurační soubor ProGuard. Změnit na proguard-android.txt parametr proguard-android-optimize.txtbuild.gradle:

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

To umožní, aby vaše verze sestavení pomalejší, ale bude případně, aby vaše aplikace běžet rychleji, a snížit velikost kódu i další, díky optimalizace jako metody inlining, třída slučování a více agresivní kód odstranění. Buďte však připraveni, že by to mohlo zavést nové a obtížné diagnostikovat chyby, takže je používejte opatrně a pokud něco nefunguje, nezapomeňte zakázat určité optimalizace nebo úplně zakázat použití optimalizační konfigurace.

V případě, Kostkované, ProGuard optimalizace zasahovat s tím, jak se Dodatečná používá Proxy objekty, bez konkrétní implementace, a pryč některé parametry metody, které byly skutečně potřebné. Musel jsem přidat tento řádek do své konfigurace:

-optimizations !method/removal/parameter

seznam možných optimalizací a jejich deaktivace najdete v příručce ProGuard.

Kdy použít @Dál a dál

@Keep podpora je ve skutečnosti realizována jako banda -keep pravidla ve výchozím Android ProGuard soubor pravidel, takže jsou v podstatě rovnocenné. Zadání -keep pravidla je flexibilnější, protože nabízí zástupné znaky, můžete také použít různé varianty, které mírně odlišné věci (-keepnames-keepclasseswithmembers a více).

kdykoli je potřeba jednoduché pravidlo“ zachovat tuto třídu „nebo“ zachovat tuto metodu“, dávám přednost jednoduchosti přidání@Keep anotace ke třídě nebo členu, protože zůstává blízko kódu, téměř jako dokumentace.

Pokud nějaký jiný vývojář přichází po mně chce refaktorovat kód, budou okamžitě vědět, že třída/členské označeny @Keep vyžaduje zvláštní zacházení, aniž byste si museli pamatovat konzultovat ProGuard konfigurace a riskovat, že něco zničí. Také většina refaktorů kódu v IDE by si měla ponechat @Keep anotaci s třídou automaticky.

Plaid stats

zde jsou některé statistiky z Plaid, které ukazují, kolik kódu se mi podařilo odstranit pomocí ProGuard. Na složitější aplikaci s více závislostmi a větším DEX mohou být úspory ještě podstatnější.