Android混淆心得

最近在做Android应用的混淆,踩了一些坑,这里记录分享下个人的心得。

混淆介绍

首先先简单说一下什么是混淆和混淆的作用,其实这个搜索下可以找到一堆官方的说法等等,这里简单口语叙述一下,混淆就是把代码替换成a、b、c基本字母组成的代码,比如一个方法名为:function(),混淆后可能会被替换成a()。

混淆的好处:

  • 代码混淆后阅读性降低,反编译后破译程序难度提高
  • 混淆后字节数减少,减少了应用了体积

前者只能说有一点作用,后者则需要看代码的数量
当然不能忽视混淆的缺点:

  • 混淆后,测试不充分可能导致某些功能不能使用

开启混淆

混淆在Android Studio的项目中默认是关闭的,其中控制开关和规则配置文件分别由项目moudle中的build.gradleproguard-rules.pro控制,如下图所示:

其中build.gradle中

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

其中

minifyEnabled false

表示不开启混淆,可以改为

minifyEnabled true

开启混淆,开启混淆后可以添加一句:

shrinkResources true

表示去掉没有引用的资源,可以减少应用的体积,但这个只有在混淆开启后才有效。

混淆规则文件

可以从上述的代码看出,Android自带一个混淆规则文件:

proguard-android.txt

这个文件在SDK目录下,里面有一些默认自带的规则,而我们今天需要配置的自定义配置的文件,即上面所述的

proguard-rules.pro

混淆规则基本语法

混淆文件采用白名单法,意思是不在白名单里面的都要混淆。
混淆规则的基本符号:

# 代表行注释符
- 表示一条规则的开始

一般规则用连起来单词表示,主要有:

keep 保留,例如keepattributes:表示保留属性
dont 不要,例如dontwarn:表示不要提示警告
ignore 忽略,例如ignorewarning:表示忽略警告

混淆配置文件不检查规则是否重复,如果两条规则冲突,则采用白名单的,比如设置了开启优化和不优化两个选项后,无论顺序,最终都会执行不优化的操作。

优化控制

这个是用于控制混淆是否开启优化代码,例如一些if/else语句可以被简化等这些操作:

# 不优化
-dontoptimize

# 代码循环优化次数,0-7,默认为5
-optimizationpasses 5

值得注意的是默认混淆配置文件开启了-dontoptimize

优化进阶

开启优化后可以设置下面的规则,assumenosideeffects表示指定的代码无效,可以优化,最终效果表现为不执行。

# 混淆时所采用的优化规则
-optimizations !code/simplification/arithmetic,!field/*,!class/merging/*

# 关闭log
-assumenosideeffects class android.util.Log {
    public static boolean isLoggable(java.lang.String, int);
    public static int v(...);
    public static int i(...);
    public static int w(...);
    public static int d(...);
    public static int e(...);
}

基本混淆规则

下面这些一般混淆规则都要加入,其中前两个在默认文件中已经配置:

# 包名不使用大小写混合 aA Aa
-dontusemixedcaseclassnames

# 不混淆第三方引用的库
-dontskipnonpubliclibraryclasses

# 不做预校验
-dontpreverify

# 忽略警告
-ignorewarning

输出混淆记录

混淆后由于阅读困难性提高,所以为了方便自己查阅,可以输出mapping对应文件,可以利用AndroidSDK\tools\proguard\bin中的proguardgui.bat打开混淆工具,利用retrace结合mapping和stacktrace调试遇到的错误

# 混淆后生产映射文件 map 类名->转化后类名的映射
# 存放在app\build\outputs\mapping\release中
-verbose

# 混淆前后的映射
-printmapping mapping.txt

# apk 包内所有 class 的内部结构
-dump class_files.txt

# 未混淆的类和成员
-printseeds seeds.txt

# 列出从 apk 中删除的代码
-printusage unused.txt

保留源代码行号

即使使用retrace工具,还是很难定位到错误的时候,可以暂时先保留行号,观察错误修改后再关闭掉

# 抛出异常时保留代码行号
# 这个最后release的时候关闭掉
-keepattributes SourceFile,LineNumberTable

基本组件白名单

Android中的基本组件不能混淆,为了方便,下面提供了兼容性比较高的规则:

-keep public class * extends android.app.Fragment
-keep public class * extends android.app.Activity
-keep public class * extends android.app.Application
-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.app.backup.BackupAgentHelper
-keep public class * extends android.preference.Preference

Support包规则

# 如果有引用v4包可以添加下面这行
-keep public class * extends android.support.v4.app.Fragment

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

不混淆本地方法

本地方法不能混淆,这个规则在默认配置文件中有:

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

WebView混淆规则

使用了WebView的JS功能则开启下面规则,这个规则在自定义规则文件中已经用注释说明了:

# WebView使用javascript功能则需要开启
-keepclassmembers class fqcn.of.javascript.interface.for.webview {
    public *;
}

注解、泛型和反射混淆

下面是混淆规则:

# 保护注解
-keepattributes *Annotation*
-keep class * extends java.lang.annotation.Annotation {*;}

# 泛型与反射
-keepattributes Signature
-keepattributes EnclosingMethod

有些注解可能不能被混淆,需要手动混淆一下

内部类混淆

# 不混淆内部类
-keepattributes InnerClasses

第三方混淆参考规则

Gson

# gson
-dontwarn com.google.**
-keep class com.google.gson.** {*;}

otto

# otto混淆规则
-keepattributes *Annotation*
-keepclassmembers class ** {
    @com.squareup.otto.Subscribe public *;
    @com.squareup.otto.Produce public *;
}

universal-image-loader

-dontwarn com.nostra13.universalimageloader.**
-keep class com.nostra13.universalimageloader.** {*;}

友盟统计

# 友盟统计
-keepclassmembers class * {
    public <init> (org.json.JSONObject);
}

# 友盟统计5.0.0以上SDK需要
-keepclassmembers enum * {
    public static **[] values();
    public static ** valueOf(java.lang.String);
}

# 友盟统计R.java删除问题
-keep public class com.gdhbgh.activity.R$*{
    public static final int *;
}

OkHttp

# OkHttp
-dontwarn com.squareup.okhttp.**
-keep class com.squareup.okhttp.** {*;}
-keep interface com.squareup.okhttp.** {*;}
-dontwarn okio.**

nineoldandroids

-dontwarn com.nineoldandroids.*
-keep class com.nineoldandroids.** {*;}

支付宝

# 支付宝
-keep class com.alipay.android.app.IAlixPay{*;}
-keep class com.alipay.android.app.IAlixPay$Stub{*;}
-keep class com.alipay.android.app.IRemoteServiceCallback{*;}
-keep class com.alipay.android.app.IRemoteServiceCallback$Stub{*;}
-keep class com.alipay.sdk.app.PayTask{
    public *;
}
-keep class com.alipay.sdk.app.AuthTask{
    public *;
}

Socket.io

# socket.io
-keep class socket.io-client.
-keepclasseswithmembers,allowshrinking class socket.io-client.* {*;}
-keep class io.socket.
-keepclasseswithmembers,allowshrinking class io.socket.* {*;}

JPUSH

# jpush
-dontwarn cn.jpush.**
-keep class cn.jpush.** {*;}

# protobuf(jpush依赖)
-dontwarn com.google.**
-keep class com.google.protobuf.** {*;}

友盟分享

这个只有部分热门的SDK,具体可以参考分享文档:

# 友盟分享
-dontwarn com.umeng.**
-dontwarn com.tencent.weibo.sdk.**

-keep public interface com.tencent.**
-keep public interface com.umeng.socialize.**
-keep public interface com.umeng.socialize.sensor.**
-keep public interface com.umeng.scrshot.**

-keep public class com.umeng.socialize.* {*;}

-keep class com.umeng.scrshot.**
-keep public class com.tencent.** {*;}
-keep class com.umeng.socialize.sensor.**
-keep class com.umeng.socialize.handler.**
-keep class com.umeng.socialize.handler.*
-keep class com.tencent.mm.sdk.modelmsg.WXMediaMessage {*;}
-keep class com.tencent.mm.sdk.modelmsg.** implements com.tencent.mm.sdk.modelmsg.WXMediaMessage$IMediaObject {*;}

-keep class im.yixin.sdk.api.YXMessage {*;}
-keep class im.yixin.sdk.api.** implements im.yixin.sdk.api.YXMessage$YXMessageData{*;}

-keep class com.tencent.** {*;}
-dontwarn com.tencent.**
-keep public class com.umeng.soexample.R$*{
    public static final int *;
}
-keep class com.tencent.open.TDialog$*
-keep class com.tencent.open.TDialog$* {*;}
-keep class com.tencent.open.PKDialog
-keep class com.tencent.open.PKDialog {*;}
-keep class com.tencent.open.PKDialog$*
-keep class com.tencent.open.PKDialog$* {*;}

-keep class com.sina.** {*;}
-dontwarn com.sina.**
-keep class  com.alipay.share.sdk.** {*;}

个人遇到的一些坑

网络层混淆

混淆要注意,一般网络层都不进行混淆,可以经过划分包后直接不混淆网络层的包:

-keep class com.xxx.xxx.http.** {*;}

数据模型混淆

所有bean都不要混淆,可以使用下面的:

-keep class * implements java.io.Serializable {*;}
-keepclassmembers class * implements java.io.Serializable {*;}

但是有时候上述代码可能导致应用卡住,没用任何错误提示,所以我建议采用分包模式,把所有bean放在一个包中,直接对该包加白名单:

-keep class com.xxx.xxx.domain.** {*;}

XML映射问题

如果你遇到一些控件无法Inflate,报NullPointException,比如ListView,NavigationView等等,这个问题花了我几个小时自己研究出了规则:

-keep class org.xmlpull.v1.** {*;}

混淆规则编写方法

如果混淆后报错,通过retrace后找到错误的问题后可以直接编写规则来去掉混淆,但是如果报的错误莫名其妙,而且报错的类没有混淆,那么你可以采用极端的方法:

加入下面规则:

-keep class *.** {*;}

这条规则表示不混淆所有类及其中所有代码,加了这条规则之后,
还不能运行表示是其他问题,例如注解,内部类等等,
可以运行后,可以通过反编译,寻找所有包名,记录下来,把上述规则改为:

-keep class android.** {*;}
-keep class com.** {*;}
-keep class io.** {*;}
-keep class org.** {*;}
...

一个个去掉检查是否有报错,例如查到

-keep class com.** {*;}

加了就不报错,则可以继续一级级往下检查。
但要注意,有时候可能是几个包混合问题。

声明

原创文章,欢迎转载,请保留出处。
有任何错误、疑问或者建议,欢迎指出。
我的邮箱:Maxwell_nc@163.com

本页内容版权归属为原作者,如有侵犯您的权益,请通知我们删除。
[编写高质量iOS代码的52个有效方法](二)对象 参考书籍:《Effective Objective-C 2.0》 【英】 Matt Galloway 先睹为快 6.理解“属性”这一概念 7.在对象内部尽量直接访问实例变量 8.理解“对象等同性”这一概念 9.以“类簇模式”隐藏实现细节 10.在既有类中使用关联对象存放自定义数据 目录 编写高质量iOS代码的52个有效方法二对象 先睹为快 目录 第6条理解属性这一概念 第7条在对象内部尽量直接访问实例变量 第8条理解对象等同性这一概念 第9条以类簇模式隐
插件其实是Apk安装包,如果要使用必须先要安装和解析,以便知道插件Apk的相关信息。而从Demo中我们知道插件的安装和卸载是通过调用PluginManager的installPackage()和deletePackage()来实现的。就先从PluginManager.installPackage()开始分析插件Apk的安装过程。 第一步:PluginManager. getInstance().installPackage(apkPath,flag); 此函数中只是调用了mPluginManager.in
分享截屏已经是很多游戏应用必备的功能了,找到了一个国内的插件,虽然用起来还行,但是,还是想吐槽下,跟老外的插件比,真的有差距啊有差距啊有差距啊,啊啊啊。 ShareSDK的官方网站:http://www.mob.com/,使用插件需要注册账号获得key,不过,至少现在是免费的。 我的Unity版本是5.3,xcode版本是7.2,ShareSDK版本是2.7.4 Unity sdk下载地址:https://github.com/MobClub/New-Unity-For-ShareSDK 新建一个简单的工
正常情况下启动一个Activity,首先需要在AndroidManifest文件中声明,其次需要把该应用安装到手机系统中。 而插件apk是没有正在安装到手机系统中的,也就按照正常的启动流程插件Activity是不能启动的。另外插件apk的类需要加载进来是需要指定ClassLoader。前面的文章也大概讲过,当启动一个插件Activity时,先是用预定义的代理Activity替换目标Activity(及插件Activity)去启动,当AMS处理完回调到应用空间时(及回到运行Activity的进程空间时)再用
从DroidPlugin的官方文档中我们知道。 2 在AndroidManifest.xml中使用插件的com.morgoo.droidplugin.PluginApplication: 或者在自定义的Application的onCreate()函数中,调用PluginHelper.getInstance().applicationOnCreate(getBaseContext()); 在Application的attachBaseContext()函数中,调用 PluginHelper.getInsta

字母雨的实现 - 2016-07-23 14:07:06

有段时间没写博文了,前段时间比较忙,这几天闲下来,想着写点东西,脑袋一下就闪过以前学习Android的时候见到的别人实现的黑客帝国的字母雨效果,当时对于小菜鸟的自己,那叫一个膜拜啊,时隔几年,自己实现一下,算是对以前的自己一个交代吧。 先看效果: 一、实现原理 在实现过程中,主要考虑整个界面由若干个字母组成的子母线条组成,这样的话把固定数量的字母封装成一个字母线条,而每个字母又封装成一个对象,这样的话,就形成了如下组成效果: 字母对象--》字母线条对象--》界面效果 每个字母都应该知道自己的位置坐标,自己

DroidPlugin源码分析Hook过程 - 2016-07-23 14:07:06

插件运行环境初始化过程中我们知道,Hook的初始化是在PluginHelper的initPlugin函数中通过调用PluginProcessManager.installHook来实现的。而在分析DroidPlugin Hook过程之前需要先简单了解一下Java的动态代理。 Java动态代理与之相关的一个类Proxy,一个接口InvocationHandler,一个函数invoke他们之间的关系。就通过DroidPlugin 的BinderHook类的部分代码来解释一下他们的关系; abstract cl
大概半年之前,看过鸿洋大神的一篇博客 Android 自定义控件玩转字体变色 打造炫酷ViewPager指示器 他说大概想了32秒就知道了实现思路,这深深的刺痛了我。最近又看了一遍,决定做点什么 我要自定义的控件是一个盖世英雄, 它不仅仅是一个 Loading控件 ,同时还支持 进度条 (ProgressBar) 功能 。 它会在你需要的时候出现, 它支持 left , top , right , bottom 四个方向加载(变色),最重要的是,它可以是 文字 ,也可以是 图片 ,能够满足开发者一切需求。
首先为权限: uses-permission android:name="android.permission.INTERNET" / uses-permission android:name="com.android.vending.BILLING" / uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" / uses-permission android:name="android.permission.AC
Day01 html与css基础入门 1.html的常见标签和实战 1.1 a标签 a href=#> 1.2 img标签 img src="plmm.jpg" width="100px" height="200px" alt="图片信息丢失!"/// alt属性的值表示当图片找不到时显示的文字信息 1.3 列表标签 ol type="I" start="1" li我是天才1号/li li我是天才2号/li li我是天才3号/li li我是天才4号/li/olul type="circle" li我是逗逼