标签:
前言:该文接上篇博文App的打磨之路(上),继续描述混淆及APK瘦身。
Java 是一种跨平台的、解释型语言,Java 源代码编译成中间”字节码”存储于 class 文件中。由于跨平台的需要,Java 字节码中包括了很多源代码信息,如变量名、方法名,并且通过这些名称来访问变量和方法,这些符号带有许多语义信息,很容易被反编译成 Java 源代码。为了防止这种现象,我们可以使用 Java 混淆器对 Java 字节码进行混淆。
混淆就是对发布出去的程序进行重新组织和处理,使得处理后的代码与处理前代码完成相同的功能,而混淆后的代码很难被反编译,即使反编译成功也很难得出程序的真正语义。被混淆过的程序代码,仍然遵照原来的档案格式和指令集,执行结果也与混淆前一样,只是混淆器将代码中的所有变量、函数、类的名称变为简短的英文字母代号,在缺乏相应的函数名和程序注释的况下,即使被反编译,也将难以阅读。同时混淆是不可逆的,在混淆的过程中一些不影响正常运行的信息将永久丢失,这些信息的丢失使程序变得更加难以理解。
-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) 这些方法。 |
混淆是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为例,描述第三方库的保留方式,更多的第三方库保留方式已提交到github上。
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>; }
到此混淆相关介绍就已完成,完整的混淆文件已提交到github上,路径如下:https://github.com/xiaoyaoyou1212/Note/blob/master/app/proguard-rules.pro.其中包含了大部分第三方库的混淆方式。
注:混淆后生成的相关信息文件在当前module的build/outputs/mapping下,目录下可能会有渠道名,下面再分为debug和release,与你的配置相关,其中的mapping.txt文件需要重点关注,该文件表示混淆前后代码的对照表,这个文件非常重要,如果你的代码混淆后会产生bug的话,log提示中是混淆后的代码,希望定位到源代码的话就可以根据mapping.txt反推,每次发布都要保留它方便该版本出现问题时调出日志进行排查,它可以根据版本号或是发布时间命名来保存或是放进代码版本控制中。
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.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.4、限制支持CPU架构
一般说来Android使用Java代码即可以满足大部分需求,不过还是有一小部分案例需要使用一些native code。CPU的架构主要分为以下几种:ARM架构,ARM-V7架构,MIPS架构和X86架构,目前市场上使用的移动终端大多是基于ARM或者ARM-V7a架构的,X86和MIPS架构的移动智能终端比较少,所以有些应用程序lib目录下只包含armeabi目录或者armeabi-v7a目录,也就是说lib目录要根据CPU的架构来选,而市面上ARM架构的手机占大多数,所以一般的APK只包含ARM和ARM-V7a的so。
2.5、其他优化技巧
defaultConfig {
resConfigs "en", "de", "fr", "it"
resConfigs "nodpi", "hdpi", "xhdpi", "xxhdpi", "xxxhdpi"
}
1、Android 项目的代码混淆,Android proguard 使用说明
2、Android安全攻防战,反编译与混淆技术完全解析
3、如何给你的Android 安装文件(APK)瘦身
4、关于APK瘦身值得分享的一些经验
个人网站:http://www.xiaoyaoyou1212.com 欢迎吐槽围观!
标签:
原文地址:http://blog.csdn.net/xiaoyaoyou1212/article/details/51934384