优化

App混淆和APK瘦身

2018-09-14  本文已影响118人  Zane_Samuel

混淆

1.混淆原理

Java 是一种跨平台的、解释型语言,Java 源代码编译成中间”字节码”存储于 class 文件中。由于跨平台的需要,Java 字节码中包括了很多源代码信息,如变量名、方法名,并且通过这些名称来访问变量和方法,这些符号带有许多语义信息,很容易被反编译成 Java 源代码。为了防止这种现象,我们可以使用 Java 混淆器对 Java 字节码进行混淆。
混淆就是对发布出去的程序进行重新组织和处理,使得处理后的代码与处理前代码完成相同的功能,而混淆后的代码很难被反编译,即使反编译成功也很难得出程序的真正语义。被混淆过的程序代码,仍然遵照原来的档案格式和指令集,执行结果也与混淆前一样,只是混淆器将代码中的所有变量、函数、类的名称变为简短的英文字母代号,在缺乏相应的函数名和程序注释的况下,即使被反编译,也将难以阅读。同时混淆是不可逆的,在混淆的过程中一些不影响正常运行的信息将永久丢失,这些信息的丢失使程序变得更加难以理解。

2、混淆语法

-include {filename}    从给定的文件中读取配置参数
-basedirectory {directoryname}    指定基础目录为以后相对的档案名称
-injars {class_path}    指定要处理的应用程序jar,war,ear和目录
-outjars {class_path}    指定处理完后要输出的jar,war,ear和目录的名称
-libraryjars {classpath}    指定要处理的应用程序jar,war,ear和目录所需要的程序库文件
-dontskipnonpubliclibraryclasses    指定不去忽略非公共的库类
-dontskipnonpubliclibraryclassmembers    指定不去忽略包可见的库类的成员

保留选项
-keep {Modifier} {class_specification}    保护指定的类文件和类的成员
-keepnames {class_specification}    保护指定的类和类的成员的名称(如果他们不会在压缩步骤中删除)
-keepclassmembers {modifier} {class_specification}    保护指定类的成员,如果此类受到保护他们会保护的更好
-keepclassmembernames {class_specification}    保护指定的类的成员的名称(如果他们不会在压缩步骤中删除)
-keepclasseswithmembers {class_specification}    保护指定的类和类的成员,但条件是所有指定的类和类成员是要存在
-keepclasseswithmembernames {class_specification}    保护指定的类和类的成员的名称,如果所有指定的类成员出席(在压缩步骤之后)
-printseeds {filename}    列出类和类的成员-keep选项的清单,标准输出到给定的文件
-keepattributes {attribute_name,...}    保护给定的可选属性,例如LineNumberTable, LocalVariableTable, SourceFile, Deprecated, Synthetic, Signature, and InnerClasses.

压缩相关
-dontshrink    不压缩输入的类文件
-printusage {filename}
-dontwarn   如果有警告也不终止
-whyareyoukeeping {class_specification}

优化相关
-dontoptimize    不优化输入的类文件
-assumenosideeffects {class_specification}    优化时假设指定的方法,没有任何副作用
-allowaccessmodification    优化时允许访问并修改有修饰符的类和类的成员

混淆相关
-dontobfuscate    不混淆输入的类文件
-printmapping {filename}
-applymapping {filename}    重用映射增加混淆
-obfuscationdictionary {filename}    使用给定文件中的关键字作为要混淆方法的名称
-overloadaggressively    混淆时应用侵入式重载
-useuniqueclassmembernames    确定统一的混淆类的成员名称来增加混淆
-flattenpackagehierarchy {package_name}    重新包装所有重命名的包并放在给定的单一包中
-repackageclass {package_name}    重新包装所有重命名的类文件中放在给定的单一包中
-dontusemixedcaseclassnames    混淆时不会产生形形色色的类名
-renamesourcefileattribute {string}    设置源文件中给定的字符串常量
关键字 描述
keep 保留类和类中的成员,防止它们被混淆或移除。
keepnames 保留类和类中的成员,防止它们被混淆,但当成员没有被引用时会被移除。
keepclassmembers 只保留类中的成员,防止它们被混淆或移除。
keepclassmembernames 只保留类中的成员,防止它们被混淆,但当成员没有被引用时会被移除。
keepclasseswithmembers 保留类和类中的成员,防止它们被混淆或移除,前提是指名的类中的成员必须存在,如果不存在则还是会混淆。
keepclasseswithmembernames 保留类和类中的成员,防止它们被混淆,但当成员没有被引用时会被移除,前提是指名的类中的成员必须存在,如果不存在则还是会混淆。
通配符 描述
<field> 匹配类中的所有字段
<method> 匹配类中的所有方法
<init> 匹配类中的所有构造函数
* 匹配任意长度字符,但不含包名分隔符(.)。比如说我们的完整类名是com.vise.note.MainActivity,使用com.,或者com.vise.都是无法匹配的,因为无法匹配包名中的分隔符,正确的匹配方式是com.vise..,或者com.vise.note.,这些都是可以的。但如果你不写任何其它内容,只有一个*,那就表示匹配所有的东西。
** 匹配任意长度字符,并且包含包名分隔符(.)。比如proguard-android.txt中使用的-dontwarn android.support.**就可以匹配android.support包下的所有内容,包括任意长度的子包。
*** 匹配任意参数类型。比如void set()就能匹配任意传入的参数类型,* get*()就能匹配任意返回值的类型。
匹配任意长度的任意类型参数。比如void test(…)就能匹配任意void test(String a)或者是void test(int a, String b)这些方法。

3、混淆文件编写

混淆是apk上线前挺重要的一个环节,Android使用的是ProGuard,可以起到压缩,混淆,预检,优化的作用。纵观大部分项目的混淆文件,其大部分内容都是固定的,从中可以整理出一个通用的模板,模板内容大致分为以下几个部分:基本指令、公共组件、第三方库、实体类、反射相关及JS调用相关。其中前两部分基本不会有太大变化,第三方库网上基本都会提供混淆方式,下文也会依据网上资源整理出大部分的三方库保留方式,而后面几个部分就与具体项目相关了,掌握思路后依照具体项目定制就行。
3.1、基本指令

-optimizationpasses 5   # 指定代码的压缩级别
-dontusemixedcaseclassnames # 表示混淆时不使用大小写混合类名
-dontskipnonpubliclibraryclasses    # 表示不跳过library中的非public的类
-dontskipnonpubliclibraryclassmembers   # 指定不去忽略包可见的库类的成员
-dontoptimize   # 表示不进行优化,建议使用此选项,因为根据proguard-android-optimize.txt中的描述,优化可能会造成一些潜在风险,不能保证在所有版本的Dalvik上都正常运行
-dontpreverify  # 表示不进行预校验,这个预校验是作用在Java平台上的,Android平台上不需要这项功能,去掉之后还可以加快混淆速度
-verbose    # 表示打印混淆的详细信息
-optimizations !code/simplification/arithmetic,!field/*,!class/merging/*    # 混淆时所采用的算法

-dump dump.txt  # 描述apk内所有class文件的内部结构
-printseeds seeds.txt   # 列出了没有被混淆的类和成员
-printusage unused.txt  # 列出了源代码中被删除在apk中不存在的代码
-printmapping mapping.txt   # 表示混淆前后代码的对照表

3.2、公共组件

-keep public class * extends android.app.Activity   # 保留继承自Activity类不被混淆
-keep public class * extends android.app.Application    # 保留继承自Application类不被混淆
-keep public class * extends android.support.multidex.MultiDexApplication   # 保留继承自MultiDexApplication类不被混淆
-keep public class * extends android.app.Service    # 保留继承自Service类不被混淆
-keep public class * extends android.content.BroadcastReceiver  # 保留继承自BroadcastReceiver类不被混淆
-keep public class * extends android.content.ContentProvider    # 保留继承自ContentProvider类不被混淆
-keep public class * extends android.app.backup.BackupAgentHelper   # 保留继承自BackupAgentHelper类不被混淆
-keep public class * extends android.preference.Preference  # 保留继承自Preference类不被混淆
-keep public class com.google.vending.licensing.ILicensingService   # 保留Google包下ILicensingService类不被混淆
-keep public class com.android.vending.licensing.ILicensingService  # 保留Android包下ILicensingService类不被混淆

-keepattributes *Annotation*,InnerClasses,Signature,SourceFile,LineNumberTable  # 保留相关属性

-keepclasseswithmembernames class * {   # 保持native方法不被混淆
    native <methods>;
}

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

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

-keepclassmembers class * extends android.app.Activity {    # 表示不混淆Activity中参数是View的方法,主要针对在xml中配置onClick事件
   public void *(android.view.View);
}

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

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

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

-keepclassmembers class **.R$* {   # 表示不混淆R文件下的静态字段
    public static <fields>;
}

-keepclassmembers class * extends android.webkit.webViewClient {    # 保留WebView
    public void *(android.webkit.webView, jav.lang.String);
    public void *(android.webkit.WebView, java.lang.String, android.graphics.Bitmap);
    public boolean *(android.webkit.WebView, java.lang.String);
}

3.3、第三方库

#EventBus
-keepattributes *Annotation*
-keepclassmembers class ** {
    @org.greenrobot.eventbus.Subscribe <methods>;
}
-keep enum org.greenrobot.eventbus.ThreadMode { *; }

# Only required if you use AsyncExecutor
-keepclassmembers class * extends org.greenrobot.eventbus.util.ThrowableFailureEvent {
    <init>(java.lang.Throwable);
}
-keepclassmembers class * { # 保持EventBus中Event事件接收
    void *(**On*Event);
}
#注:此处以EventBus为例,描述第三方库的保留方式

3.4、实体类

-keep class 你的实体类包名.** { *; }

3.5、反射相关

-keep class 你的类所在的包.** { *; }

3.6、JS调用相关

-keepattributes *JavascriptInterface*
-keep class **.Webview2JsInterface { *; }  # 保持WebView对HTML页面的API不被混淆
-keepclassmembers class fqcn.of.javascript.interface.for.webview {  # 保留WebView
   public *;
}
-keep class 你的类所在的包.** { *; }
#如果是内部类则使用如下方式
-keepclasseswithmembers class 你的类所在的包.父类$子类 { <methods>; }

二、APK瘦身

1、APK文件结构

Android应用是用Java编写的,利用Android SDK编译代码,并且把所有的数据和资源文件打包成一个APK (Android Package)文件,这是一个后缀名为.apk的压缩文件,APK文件中包含了一个Android应用程序的所有内容,是Android平台用于安装应用程序的文件。APK就是一个zip压缩包,解开这个APK包我们可以看到以下的结构:

目录 描述
assets目录 存放需要打包到apk中的静态文件
lib目录 程序依赖的native库
res目录 存放应用程序的资源
META-INF目录 存放应用程序签名和证书的目录
AndroidManifest.xml 应用程序的配置文件
classes.dex dex可执行文件
resources.arsc 资源配置文件

从以上目录结构中可以看出,如果需要缩小apk的大小,主要针对的是assets目录、lib目录、res目录及classes.dex文件。其中的assets目录主要是静态加载到apk中的文件,这个可以根据是否需要来进行手动管理,没什么优化的空间,下文主要针对classes.dex文件、res目录及lib目录来进行优化讲解。

2、Apk瘦身方式

2.1、Proguard混淆优化代码
Proguard是一个很强悍的工具,它可以帮你在代码编译时对代码进行混淆,优化和压缩。它有一个专门用来减少apk文件大小的功能叫做tree-shaking。Proguard 会遍历你的所有代码然后找出无用处的代码。所有这些不可达(或者不需要)的代码都会在生成最终的apk文件之前被清除掉。Proguard 也会重命名你的类属性,类和接口,然整个代码尽可能地保持轻量级水平。

2.2、Lint检查优化资源
混淆只能优化java代码,不能对无用资源进行清理,而Lint则可以检查所有无用的资源文件,只要使用命令./gradlew lint或者在Android Studio工程中点击Analyze->Inspect Code,选择Whole Project点击ok就行。它在检测完之后会提供一份详细的资源文件清单,并将无用的资源列在“UnusedResources: Unused resources” 区域之下。只要你不通过反射来反问这些无用资源,你就可以放心地移除这些文件了。

2.3、压缩图片
图片资源的优化原则是:在不降低图片效果、保证APK显示效果的前提下缩小图片文件的大小。

2.5、其他优化技巧

defaultConfig {
    resConfigs "en", "de", "fr", "it"
    resConfigs "nodpi", "hdpi", "xhdpi", "xxhdpi", "xxxhdpi"
}
上一篇 下一篇

猜你喜欢

热点阅读