[Optimisation des performances Android] : ProGuard, obscurcissement, optimisation R8

Auteur : Xiaoyu

Préface

Le code source écrit en Java est compilé pour générer le fichier de classe correspondant, mais le fichier de classe est un fichier très standard. De nombreux logiciels sur le marché peuvent décompiler le fichier de classe. Pour la sécurité de notre application, nous devons utiliser Android Le code obscurcit cette fonctionnalité. Pour l'obscurcissement Java, ProGuard est un outil d'obfuscation couramment utilisé, et ce n'est pas seulement un outil d'obscurcissement, il peut également compresser, optimiser et obscurcir le code.

Présentons brièvement le flux de travail ProGuard.

1 Flux de travail ProGuard Le flux de travail ProGuard comprend quatre étapes : réduire, optimiser, obscurcir et préserver . Ces quatre étapes sont facultatives, mais l'ordre reste inchangé .

  • rétrécir : Détectez et supprimez les classes, champs, méthodes et propriétés inutilisés dans le projet.
  • optimiser : optimiser le bytecode, supprimer les instructions inutiles ou effectuer l'optimisation des instructions.
  • obfuscate : obfuscation du code, utilisant des noms dénués de sens pour représenter les classes, champs, méthodes et attributs dans le code, réduisant la lisibilité du code après décompilation.
  • preverify : Pré-vérifiez Java 1.6 et supérieur et vérifiez les propriétés StackMap/StackMapTable. Il peut être désactivé pendant la compilation pour accélérer la compilation .

2 Optimisation et obfuscation du code dans Android

Dans les versions Android, ProGuard était également utilisé pour l'optimisation et l'obscurcissement du code avant AGP 3.4.0, mais après la version 3.4.0, Google a confié ce travail au compilateur R8 avec de meilleures performances . Bien que ProGuard soit abandonné, le compilateur R8 est toujours compatible avec les règles de configuration de ProGuard . Les optimisations suivantes peuvent être effectuées à l'aide du compilateur R8 :

  • 1. Réduction des codes
  • 2. Réduction des ressources
  • 3. Obscurcissement du code
  • 4. Optimisation du code
1. Réduction de code :

La réduction de code fait référence à : R8 détecte intelligemment les classes, champs, méthodes, propriétés, etc. inutilisés dans le code lors de la compilation et les supprime.

Par exemple, votre projet s'appuie sur de nombreuses bibliothèques, mais n'utilise qu'une petite partie du code de la bibliothèque. Afin de supprimer cette partie du code, R8 déterminera tous les points d'entrée du code de l'application en fonction du fichier de configuration : y compris la première activité ou service démarré par l'application, etc., R8 détectera le code de l'application en fonction de l'entrée, et construira un graphique pour lister les méthodes, les variables membres et les classes accessibles pendant l'exécution de l'application, et traitera le code qui n'est pas associé à la carte comme code amovible. .

Comme indiqué ci-dessous:

Position d'entrée dans la figure : MainActivity. Dans l'ensemble du lien appelant, les fonctions foo, bar et la fonction faz de la classe AwesomeApi sont utilisées, donc cette partie du code sera intégrée dans le graphe de dépendances, et la classe OkayApi et son baz Les fonctions ne sont pas accessibles. , alors cette partie du code peut être optimisée. Comment utiliser:

android {
    buildTypes {
        release {
            ...
            minifyEnabled true
        }
    }
    ...
}

minifyEnabled est défini sur true et la fonction de réduction de code R8 sera activée par défaut. Il y a deux situations auxquelles il faut prêter attention lors de l'optimisation du code : 1. Dans le cas des appels de réflexion 2. Dans le cas des appels JNI, R8 ne détecte pas la réflexion et JNI. S'ils ne sont pas traités dans le fichier de configuration, cette partie du code sera ignorée. Une exception NoClassFindException se produit, comment la résoudre ?

  • 1.1 : Utilisez -keep dans le fichier de configuration pour décrire cette catégorie :
-keep public class MyClass
  • 1.2 : Ajouter l'annotation @keep au code qui doit être conservé

Prérequis : 1. Déclarer l'utilisation d'AndroidX 2. Utiliser le fichier AGP ProGuard par défaut.

Fichier de configuration R8

R8 utilise les fichiers de règles ProGuard pour déterminer quelles parties du code doivent être conservées. Les sources des fichiers de configuration sont divisées comme suit :

  • 1.AndroidStudio :

Localisation : /proguard-rules.proDescription :

Lors de la création d'un nouveau module, un fichier par défaut : proguard-rules.pro sera créé dans le répertoire du module actuel.

  • 2.Plug-in AGP

Emplacement : proguard-android-optimize.txt généré par AGP au moment de la compilation Description :

Le plugin Android Gradle génère proguard-android-optimize.txt (qui contient des règles utiles pour la plupart des projets Android) et active les annotations @Keep*.

Après compilation, 3 fichiers seront générés dans le répertoire \build\intermediates\proguard-files\ :

proguard-android-optimize.txt-4.1.1 : fichier de configuration ProGuard qui doit être optimisé pour l'optimisation du code. proguard-android.txt-4.1.1 : Indique un fichier ProGuard qui ne nécessite pas d'optimisation du code. 4.1.1 : Indique la version du plug-in AGP du module actuel .

  • 3. Dépendances de la bibliothèque

Emplacement : Bibliothèque AAR : /proguard.txt
Bibliothèque JAR : /META-INF/proguard/ Description :

La bibliothèque de packages aar ou jar introduite contient par défaut des règles d'optimisation proguard, qui seront également incluses dans les éléments de configuration R8 pendant le processus de compilation, accordez donc une attention particulière au conflit entre le proguad introduit dans l'aar et les règles du projet d'origine.

  • 4.AAPT2 (outil d'empaquetage de ressources Android)

Emplacement : Après avoir construit le projet à l'aide de minifyEnabled true : /build/intermediates/proguard-rules/debug/aapt_rules.txt Description :

AAPT2 génère des règles de conservation basées sur des références aux classes, mises en page et autres ressources d'application dans le manifeste de l'application. Par exemple, AAPT2 ajoute une règle de conservation pour chaque activité que vous enregistrez comme point d'entrée dans le manifeste de votre application.

  • 5. Fichier de configuration personnalisé

Emplacement : par défaut, lorsque vous créez un nouveau module à l'aide d'Android Studio, l'EDI crée /proguard-rules.pro afin que vous puissiez ajouter vos propres règles. illustrer:

Vous pouvez ajouter des configurations supplémentaires et R8 les appliquera au moment de la compilation. Si vous définissez l'attribut minifyEnabled sur true, R8 combinera les règles de toutes les sources disponibles ci-dessus, mais vous devez faire attention aux conflits de règles provoqués par l'introduction de bibliothèques dépendantes.

Si vous devez générer un rapport complet de toutes les règles appliquées par R8 lors de la construction du projet : Vous pouvez ajouter l'instruction suivante à proguard-rules.pro.

// You can specify any path and filename.
-printconfiguration ~/tmp/full-r8-config.txt

Si vous devez ajouter des fichiers proguad supplémentaires : Vous pouvez ajouter les fichiers correspondants à l'attribut proguardFiles du fichier build.gradle du module : Si vous ajoutez des règles à chaque saveur de produit séparément, vous pouvez utiliser la méthode suivante :

android {
    ...
    buildTypes {
        release {
            minifyEnabled true
            proguardFiles
                getDefaultProguardFile('proguard-android-optimize.txt'),
                // List additional ProGuard rules for the given build type here. By default,
                // Android Studio creates and includes an empty rules file for you (located
                // at the root directory of each module).
                'proguard-rules.pro'
        }
    }
    flavorDimensions "version"
    productFlavors {
        flavor1 {
            ...
        }
        flavor2 {
            proguardFile 'flavor2-rules.pro'
        }
    }
}
2. Réduction des ressources

La réduction des ressources est effectuée après la réduction du code . Ce n'est qu'après avoir supprimé le code inutile que nous pouvons savoir quelles ressources du code ne sont pas introduites et peuvent être supprimées. Pour réduire les ressources, ajoutez simplement l'attribut ShrinkResources sous le module Gradle :

android {
    ...
    buildTypes {
        release {
            shrinkResources true
            minifyEnabled true
            ...
        }
    }
}

Remarque : La réduction des ressources nécessite l'activation préalable de la réduction de code minifyEnabled. Bien entendu, vous pouvez également ajouter une liste blanche aux fichiers ressources :

<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools"
    tools:keep="@layout/l_used*_c,@layout/l_used_a,@layout/l_used_b*"
    tools:discard="@layout/unused2" />

Utilisez tools:keep pour spécifier les fichiers de ressources qui doivent être conservés et tools:discard pour spécifier les fichiers de ressources qui peuvent être supprimés.

3. Obscurcissement du code

L'obscurcissement fait référence à l'utilisation de caractères dénués de sens pour représenter les noms de classe, les noms de méthodes et les noms d'attributs : voir la figure ci-dessous :

Comment obscurcir ?

Quelques règles de base pour l'obscurcissement :

  • Une étoile : indique que le nom de la classe sous le package actuel est conservé. S'il existe un sous-package, les classes du sous-package seront confondues.
-keep class cn.hadcn.test.*
  • Deux étoiles : indique que le nom de classe sous le package actuel est conservé. S'il existe un sous-package, le nom de classe dans le sous-package sera également conservé.
-keep class cn.hadcn.test.**
  • Bien que la méthode ci-dessus conserve le nom de la classe, le contenu sera toujours confus. Utilisez la méthode suivante pour conserver le contenu :
-keep class cn.hadcn.test.* {*;}
  • Sur cette base, nous pouvons également utiliser les règles de base de Java pour empêcher la confusion de classes spécifiques. Par exemple, nous pouvons utiliser des règles Java telles que extends et Implements. L'exemple suivant évite la confusion de toutes les classes qui héritent d'Activity :
-keep public class * extends android.app.Activity
  • Si nous voulons éviter que les classes internes d'une classe ne soient confuses, nous devons utiliser le symbole $. L'exemple suivant montre que tout le contenu public de la classe interne ScriptFragment JavaScriptInterface ne peut pas être confondu.
-keepclassmembers class cc.ninty.chat.ui.fragment.ScriptFragment$JavaScriptInterface {
   public *;
}
  • De plus, si vous ne souhaitez pas empêcher l'obscurcissement de l'intégralité du contenu d'une classe, mais souhaitez simplement protéger un contenu spécifique de la classe, vous pouvez utiliser :
<init>;     //匹配所有构造器
<fields>;   //匹配所有域
<methods>;  //匹配所有方法方法
  • privateVous pouvez également ajouter , public, etc. avant ou nativepour préciser davantage le contenu qui ne sera pas confondu, comme
-keep class cn.hadcn.test.One {
    public <methods>;
}
  • Bien sûr, vous pouvez également ajouter des paramètres. Par exemple, ce qui suit signifie que le constructeur qui utilise JSONObject comme paramètre d'entrée ne sera pas confondu :
-keep class cn.hadcn.test.One {
   public <init>(org.json.JSONObject);
}
  • Parfois, vous pensez encore : je n'ai pas besoin de conserver le nom de la classe, j'ai juste besoin d'éviter que les méthodes spécifiques de la classe ne soient confondues. Ensuite, vous ne pouvez pas utiliser la méthode keep. La méthode keep conservera le nom de la classe, et Vous devez utiliser keepclassmembers, afin que ces noms ne soient pas conservés. Afin de faciliter la compréhension de ces règles, le site officiel fournit le tableau suivant :

Explication détaillée de la confusion dans l'emballage Android et des règles de syntaxe_android

# -keep关键字
# keep:包留类和类中的成员,防止他们被混淆
# keepnames:保留类和类中的成员防止被混淆,但成员如果没有被引用将被删除
# keepclassmembers :只保留类中的成员,防止被混淆和移除。
# keepclassmembernames:只保留类中的成员,但如果成员没有被引用将被删除。
# keepclasseswithmembers:如果当前类中包含指定的方法,则保留类和类成员,否则将被混淆。
# keepclasseswithmembernames:如果当前类中包含指定的方法,则保留类和类成员,如果类成员没有被引用,则会被移除。
Précautions
  • 1. Après avoir utilisé la version AS4.1.1 pour une compilation obscurcie, les quatre fichiers suivants seront générés sous /build/outputs/mapping/release/ :

  • utilisation : Fichiers inutilisés, c’est-à-dire fichiers après suppression.

  • graines : classes et membres non obscurcis.

  • mapping : Mappage des fichiers avant et après obfuscation. Ce fichier est utile lors de l'utilisation de l'anti-obscurcissement.

  • configuration : le fichier de règles après avoir intégré tous les fichiers ProGuard :

  • 2. Lorsque jni est utilisé, la méthode jni ne peut pas être confondue.

-keepclasseswithmembernames class * {    
    native <methods>;
}
  • 3. Les classes utilisées pour la réflexion ne sont pas confondues.

  • 4. Les classes d'AndroidMainfest ne sont pas confondues. Les quatre composants principaux, les sous-classes d'Application et toutes les classes sous la couche Framework ne seront pas confondues par défaut, et les vues personnalisées ne seront pas confondues par défaut. Par conséquent, il n'est pas nécessaire d'ajouter des règles dans Android Studio qui excluent les vues personnalisées ou confondent les quatre composants principaux comme beaucoup sont publiés en ligne.

  • 5. Lors de l'interaction avec le serveur, lors de l'utilisation de frameworks tels que GSON et fastjson pour analyser les données du serveur, la classe d'objet JSON écrite ne doit pas être confondue, sinon le JSON ne peut pas être analysé dans l'objet correspondant ;

  • 6. Lors de l'utilisation de bibliothèques open source tierces ou du référencement d'autres packages SDK tiers, s'il existe des exigences particulières, les règles d'obscurcissement correspondantes doivent être ajoutées au fichier d'obfuscation ;

  • 7. Les appels JS qui utilisent WebView doivent également garantir que les méthodes d'interface écrites ne prêtent pas à confusion.La raison est la même que la première ;

  • 8. Ne confondez pas les sous-classes de Parcelable avec les variables membres statiques de Creator, sinon Android.os.BadParcelableException se produira ;

# 保持Parcelable不被混淆    
-keep class * implements Android.os.Parcelable {         
    public static final Android.os.Parcelable$Creator *;
}
  • 9. Lorsque vous utilisez le type enum, vous devez faire attention à éviter toute confusion entre les deux méthodes suivantes. En raison de la particularité de la classe enum, les deux méthodes suivantes seront appelées par réflexion. Voir la deuxième règle.
-keepclassmembers enum * {  
    public static **[] values();  
    public static ** valueOf(java.lang.String);  
}
modèle obscurci

Ce qui suit est un modèle de confusion : vous pouvez ajouter et supprimer selon vos propres besoins :

#--------------------------1.实体类---------------------------------
# 如果使用了Gson之类的工具要使被它解析的JavaBean类即实体类不被混淆。(这里填写自己项目中存放bean对象的具体路径)
-keep class com.php.soldout.bean.**{*;}

#--------------------------2.第三方包-------------------------------

#Gson
-keepattributes Signature
-keepattributes *Annotation*
-keep class sun.misc.Unsafe { *; }
-keep class com.google.gson.stream.** { *; }
-keep class com.google.gson.examples.android.model.** { *; }
-keep class com.google.gson.* { *;}
-dontwarn com.google.gson.**

#butterknife
-keep class butterknife.** { *; }
-dontwarn butterknife.internal.**
-keep class **$$ViewBinder { *; }

#-------------------------3.与js互相调用的类------------------------


#-------------------------4.反射相关的类和方法----------------------


#-------------------------5.基本不用动区域--------------------------
#指定代码的压缩级别
-optimizationpasses 5

#包明不混合大小写
-dontusemixedcaseclassnames

#不去忽略非公共的库类
-dontskipnonpubliclibraryclasses
-dontskipnonpubliclibraryclassmembers

#混淆时是否记录日志
-verbose

#优化  不优化输入的类文件
-dontoptimize

#预校验
-dontpreverify

# 保留sdk系统自带的一些内容 【例如:-keepattributes *Annotation* 会保留Activity的被@override注释的onCreate、onDestroy方法等】
-keepattributes Exceptions,InnerClasses,Signature,Deprecated,SourceFile,LineNumberTable,*Annotation*,EnclosingMethod

# 记录生成的日志数据,gradle build时在本项根目录输出
# apk 包内所有 class 的内部结构
-dump proguard/class_files.txt
# 未混淆的类和成员
-printseeds proguard/seeds.txt
# 列出从 apk 中删除的代码
-printusage proguard/unused.txt
# 混淆前后的映射
-printmapping proguard/mapping.txt


# 避免混淆泛型
-keepattributes Signature
# 抛出异常时保留代码行号,保持源文件以及行号
-keepattributes SourceFile,LineNumberTable

#-----------------------------6.默认保留区-----------------------
# 保持 native 方法不被混淆
-keepclasseswithmembernames class * {
    native <methods>;
}

-keepclassmembers public class * extends android.view.View {
 public <init>(android.content.Context);
 public <init>(android.content.Context, android.util.AttributeSet);
 public <init>(android.content.Context, android.util.AttributeSet, int);
 public void set*(***);
}

#保持 Serializable 不被混淆
-keepclassmembers class * implements java.io.Serializable {
    static final long serialVersionUID;
    private static final java.io.ObjectStreamField[] serialPersistentFields;
    !static !transient <fields>;
    !private <fields>;
    !private <methods>;
    private void writeObject(java.io.ObjectOutputStream);
    private void readObject(java.io.ObjectInputStream);
    java.lang.Object writeReplace();
    java.lang.Object readResolve();
}

# 保持自定义控件类不被混淆
-keepclasseswithmembers class * {
    public <init>(android.content.Context,android.util.AttributeSet);
}
# 保持自定义控件类不被混淆
-keepclasseswithmembers class * {
    public <init>(android.content.Context,android.util.AttributeSet,int);
}
# 保持自定义控件类不被混淆
-keepclassmembers class * extends android.app.Activity {
    public void *(android.view.View);
}

# 保持枚举 enum 类不被混淆
-keepclassmembers enum * {
    public static **[] values();
    public static ** valueOf(java.lang.String);
}

# 保持 Parcelable 不被混淆
-keep class * implements android.os.Parcelable {
  public static final android.os.Parcelable$Creator *;
}

# 不混淆R文件中的所有静态字段,我们都知道R文件是通过字段来记录每个资源的id的,字段名要是被混淆了,id也就找不着了。
-keepclassmembers class **.R$* {
    public static <fields>;
}

#如果引用了v4或者v7包
-dontwarn android.support.**

# 保持哪些类不被混淆
-keep public class * extends android.app.Appliction
-keep public class * extends android.app.Activity
-keep public class * extends android.app.Fragment
-keep public class * extends android.app.Service
-keep public class * extends android.content.BroadcastReceiver
-keep public class * extends android.content.ContentProvider
-keep public class * extends android.preference.Preference

-keep class com.zhy.http.okhttp.**{*;}
-keep class com.wiwide.util.** {*;}

# ============忽略警告,否则打包可能会不成功=============
-ignorewarnings
4. Optimisation du code

Pour réduire davantage votre application, R8 examine le code à un niveau plus approfondi pour supprimer davantage de code inutilisé ou, si possible, réécrire le code pour le rendre plus propre. Voici quelques exemples de telles optimisations :

  • Si votre code ne prend jamais la branche else {} d'une instruction if/else donnée, R8 peut supprimer le code de la branche else {}.
  • Si votre code appelle une méthode à un seul endroit, R8 peut supprimer la méthode et l'intégrer sur ce site d'appel.
  • Si R8 détermine qu'une classe n'a qu'une seule sous-classe et que cette classe elle-même n'est pas instanciée (par exemple, une classe de base abstraite utilisée uniquement par une classe d'implémentation concrète), il peut combiner les deux classes et les supprimer de l'application Supprimer une classe .

Pour plus de points d'optimisation, vous pouvez consulter : l'article de blog de Jake Wharton sur l'optimisation R8.

3 Résumé de cet article

L'article explique principalement certaines opérations d'optimisation du compilateur R8 sur le code apk et les ressources pendant tout le processus de compilation, en se concentrant principalement sur la réduction du code, la réduction des ressources, l'obscurcissement du code et l'optimisation du code . Parmi eux, un examen plus complet de l'obscurcissement du code est donné .analyse.

Afin d'aider chacun à comprendre l'optimisation des performances de manière plus complète et plus claire, nous avons préparé des notes de base pertinentes (y compris la logique sous-jacente) :https://qr18.cn/FVlo89

Notes de base sur l’optimisation des performances :https://qr18.cn/FVlo89

Optimisation du démarrage

, optimisation de la mémoire,

optimisation de l'interface utilisateur,

optimisation du réseau,

optimisation Bitmap et optimisation de la compression d'image : optimisation de la concurrence multithread et optimisation de l'efficacité de la transmission de données, optimisation des packages de volumeshttps://qr18.cn/FVlo89




« Cadre de surveillance des performances Android » :https://qr18.cn/FVlo89

"Manuel d'apprentissage du framework Android":https://qr18.cn/AQpN4J

  1. Processus d'initialisation du démarrage
  2. Démarrez le processus Zygote au démarrage
  3. Démarrez le processus SystemServer au démarrage
  4. Pilote de reliure
  5. Processus de démarrage d'AMS
  6. Processus de démarrage du PMS
  7. Processus de démarrage du lanceur
  8. Android quatre composants principaux
  9. Service système Android - Processus de distribution des événements d'entrée
  10. Rendu sous-jacent Android - Analyse du code source du mécanisme de rafraîchissement de l'écran
  11. Analyse du code source Android en pratique

Je suppose que tu aimes

Origine blog.csdn.net/weixin_61845324/article/details/133274686
conseillé
Classement