Android 混淆详解

Android 混淆详解

转载请标明出处:http://blog.csdn.net/zhaoyanjun6/article/details/69388246
本文出自【赵彦军的博客】

混淆的基本概念

  • 什么是混淆?

代码混淆亦称花指令,是将计算机程序的代码,转换成一种功能上等价,但是难于阅读和理解的形式的行为。

  • 混淆的目的

1、混淆的目的是为了加大反编译的成本,但是并不能彻底防止反编译.

2、压缩apk 资源文件

开启混淆

一般我们做项目的时候,都是分为 release 和 debug 版本,release 版本混淆,debug 版本不混淆。设置如下:

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

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

混淆规则理解

1、有一些固定的混淆规则不需要更改:

#指定代码的压缩级别
-optimizationpasses 5

#包明不混合大小写
-dontusemixedcaseclassnames

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

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

 #预校验
-dontpreverify

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

 # 混淆时所采用的算法
-optimizations !code/simplification/arithmetic,!field/*,!class/merging/*

#保护注解
-keepattributes *Annotation*

2、理解通配符

-keep class cn.hadcn.test.**
-keep class cn.hadcn.test.*

很多同学都搞不懂下面两个的区别,一颗星表示只是保持该包下的类名,而子包下的类名还是会被混淆;两颗星表示把本包和所含子包下的类名都保持;用以上方法保持类后,你会发现类名虽然未混淆,但里面的具体方法和变量命名还是变了,这时如果既想保持类名,又想保持里面的内容不被混淆,我们就需要以下方法了:

-keep class cn.hadcn.test.* {*;}

3、保证指定包名下的所有类及子包中所有的类不被混淆

  • 例子1:com.lib.manager 包下面的类不混淆
-keep class com.lib.manager.**{*;} 
  • 例子2:v4包下面的所有类不混淆
-keep class android.support.v4.** { *; }

4、保证指定的类不被混淆

  • 例子1:YiBaWiFiActivity 类不被混淆, YiBaWiFiActivity 完整包名:com.yiba.wifi.sdk.lib.activity.YiBaWiFiActivity
-keep class com.yiba.wifi.sdk.lib.activity.YiBaWiFiActivity{*;}
  • 例子2:v4 包下面的 ActivityCompat 类不被混淆
-keep class android.support.v4.app.ActivityCompat{*;}

5、不混淆指定类的子类

-keep class * extends pp.lib.User { *; }

User 类的子类不混淆

6、指定接口不混淆

Callback 是一个接口,完整包名为:com.lib.impl.Callback

-keep interface com.lib.impl.Callback{ * ; }

7、指定接口的实现类不混淆

Callback 是一个接口,完整包名为:com.lib.impl.Callback . 所有的实现类都不会混淆。

-keep class * implements com.lib.impl.Callback { *; }

8、指定类的内部类不混淆

PhoneUtil 的源码如下:

package com.lib.manager;
import android.content.Context;
import android.os.AsyncTask;

/**
 * Created by yiba_zyj on 2017/4/5.
 */

public class PhoneUtil {

    public final static int APPID = 100 ;

    public PhoneUtil( Context context ){

    }

    class MyTask extends AsyncTask {

        @Override
        protected Object doInBackground(Object[] params) {
            return null;
        }
    }
}

PhoneUtil 有一个常量、一个构造函数、一个内部类.

混淆规则:

-keep class com.lib.manager.PhoneUtil$* {
  * ;
}

混淆后的结果如下:

可以看到混淆后的 PhoneUtil ,常量和 构造函数 都被移除了,只留下了 内部类 MyTask 。

9、构造函数不混淆

PhoneUtil 的源码如所示:


package com.lib;

/**
 * Created by yiba_zyj on 2017/4/7.
 */

public class PhoneUtil {

    public static final String aa = "1222" ;

    public void run(){

    }

}

混淆规则:PhoneUtil 的无参构造函数不混淆 。PhoneUtil 里面没有用到的 属性和方法将会被移除。

-keep class com.lib.PhoneUtil {
  public <init>();
}

那么有参的构造函数的混淆规则是怎么样的?

混淆规则:PhoneUtil 的 参数为 Context 的构造函数不混淆

-keep class com.lib.PhoneUtil {
public <init>( android.content.Context );
}

10、指定类的属性和方法不被混淆

  • 原始类 PhoneUtil 代码如下,完整的包名为:com.lib.manager.PhoneUtil
package com.lib.manager;

import android.content.Context;
import android.util.DisplayMetrics;
import android.view.WindowManager;

/**
 * Created by yiba_zyj on 2017/4/5.
 */

public class PhoneUtil {

     public static final int AppID = 100 ;

    /**
     * 获得屏幕高度
     * @param context
     * @return
     */
    public static int getScreenWidth(Context context) {
        WindowManager wm = (WindowManager) context
                .getSystemService(Context.WINDOW_SERVICE);
        DisplayMetrics outMetrics = new DisplayMetrics();
        wm.getDefaultDisplay().getMetrics(outMetrics);
        return  outMetrics.widthPixels;
    }

    /**
     * 获得屏幕宽度
     * @param context
     * @return
     */
    public static int getScreenHeight(Context context) {
        WindowManager wm = (WindowManager) context
                .getSystemService(Context.WINDOW_SERVICE);
        DisplayMetrics outMetrics = new DisplayMetrics();
        wm.getDefaultDisplay().getMetrics(outMetrics);
        return  outMetrics.heightPixels;
    }

}

PhoneUtil 类中包含1个常量及2个方法.

  • 类中的变量不被混淆:
-keep class com.lib.manager.PhoneUtil{
public static final int AppID ;
}
  • 混淆后的效果如下:
package com.lib.manager;

public class PhoneUtil {
    public static final int AppID = 100;

    public PhoneUtil() {
    }
}

可以看到混淆后的类中,AppID 常量被保存下来了,getScreenWidthgetScreenHeight 这两个方法被移除了。 为什么会有方法被移除? 因为在混淆时,Android 会默认移除没有使用的的方法和资源,保证apk 合理的被压缩。

  • 保留类中的方法不被混淆
-keep class com.lib.manager.PhoneUtil{
 public static int getScreenWidth(android.content.Context);
 }
  • 混淆后的方法如下:
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package com.lib.manager;

import android.content.Context;
import android.util.DisplayMetrics;
import android.view.WindowManager;

public class PhoneUtil {
    public PhoneUtil() {
    }

    public static int getScreenWidth(Context var0) {
        WindowManager var1 = (WindowManager)var0.getSystemService("window");
        DisplayMetrics var2 = new DisplayMetrics();
        var1.getDefaultDisplay().getMetrics(var2);
        return var2.widthPixels;
    }
}

11、不混淆类中所有的 public 方法

# <methods> 匹配所有的方法

-keep class cn.hadcn.test.One {
    public <methods>;
}

表示One类下的所有public方法都不会被混淆

12、不混淆类中所有的 public 字段

-keep class pp.lib.PhoneUtil{
  public <fields> ;
}

表示 PhoneUtil 类中所有的 public 属性将保留,其他类型的字段

13、不混淆构造函数


-keep class pp.lib.PhoneUtil{
   <init>(***) ;
   <init>(*** , *** ) ;
}

不混淆 PhoneUtil 类中所有 一个参数 和 两个参数的 的构造函数。

13、不混淆 bean 对象里面的 set 、get 方法

User 对象如下图所示:

package pp.lib;

/**
 * Created by ${zhaoyanjun} on 2017/4/10.
 */

public class User {

    private String name ;
    private String age ;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getAge() {
        return age;
    }

    public void setAge(String age) {
        this.age = age;
    }
}

混淆规则:不混淆 User 类中所有的 set 和 get 方法,*** 代表 通配符

-keep class pp.lib.User{
  void set*( *** ) ;
   *** get*() ;
}

常见不混淆的类和属性

  • 不混淆四大组件 和 Application

因为四大组件和 Application 需要在 AndroidManifest.xml 中注册,不能混淆,否则就会报类找不到异常。

-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 

特殊情况:

如果 BroadcastReceiver 是动态注册的,则是可以加入混淆的。

  • 不混淆任何包含native方法的类的类名以及native方法名
-keepclasseswithmembernames class * {
    native <methods>;
}
  • 不混淆任何一个View中的setXxx()和getXxx()方法,因为属性动画需要有相应的setter和getter的方法实现,混淆了就无法工作了。
-keepclassmembers public class * extends android.view.View {
   void set*(***);
   *** get*();
}
  • 不混淆Activity中参数是View的方法,因为有这样一种用法,在XML中配置android:onClick=”buttonClick”属性,当用户点击该按钮时就会调用Activity中的buttonClick(View view)方法,如果这个方法被混淆的话就找不到了。
-keepclassmembers class * extends android.app.Activity {
   public void *(android.view.View);
}
  • 不混淆枚举中的values()和valueOf()方法
-keepclassmembers enum * {
    public static **[] values();
    public static ** valueOf(java.lang.String);
}
  • 不混淆Parcelable实现类中的CREATOR字段,毫无疑问,CREATOR字段是绝对不能改变的,包括大小写都不能变,不然整个Parcelable工作机制都会失败。
-keepclassmembers class * implements android.os.Parcelable {
  public static final android.os.Parcelable$Creator CREATOR;
}
  • 不混淆R文件中的所有静态字段,我们都知道R文件是通过字段来记录每个资源的id的,字段名要是被混淆了,id也就找不着了。
-keepclassmembers class **.R$* {
    public static <fields>;
}

混淆后的项目目录资源

混淆完以后会在 build/outputs/mapping/release 目录下生成4个文件。如下图所示:

  • mapping.txt :代表源码的 包名/类名/变量名/方法名 和混淆后 的转换关系。

混淆规则

-keep class com.lib.manager.PhoneUtil{
 public static int getScreenWidth(android.content.Context);
 }

混淆后的 mapping 文件如下图所示:

从上面的 mapping 文件可以看出:

PhoneUtil 转换为 PhoneUtil , 名字没有发生变化,相当于没有混淆。

getScreenWidth 转换为 getScreenWidth , 名字没有发生变换,相当于没有混淆。

getScreenHeight 转换位 a , 名字已经变换了,增加了反编译的难度。

混淆后 PhontUtil 源码如下图所示:

  • usage.txt : 代表本次混淆过程中被移除的类或者方法。

举例说明

从上图中可以看出,本次混淆过程中 , 移除了很多没用的类。

com.lib.BuildConfig   //移除了 com.lib 包下的 BuildConfig 类
com.lib.activity.A    //移除了 com.lib.activity 包下的 A 类
com.lib.manager.PhoneUtil:
    public static final int AppID  //移除了 com.lib.manager 包下的 PhoneUtil 类 中的 AppID 常量
    com.lib.manager 包下的 PhoneUtil 类 中的 getScreenHeight 方法 。
com.lib.manager.SS       //移除了 com.lib.manager 包下的 SS 类
com.lib.manager.Util     //移除了 com.lib.manager 包下的 Util 类
com.lib.manager.hj.PP    //移除了 com.lib.manager.hj 包下的 PP 类
com.lib.manager.hj.PP$1 

实战1

User 类源码:

package pp.lib;

/**
 * Created by ${zhaoyanjun} on 2017/4/10.
 */

public class User {

    private String name ;
    private String age ;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getAge() {
        return age;
    }

    public void setAge(String age) {
        this.age = age;
    }
}

User1 类源码:

package pp.lib;

/**
 * Created by ${zhaoyanjun} on 2017/4/10.
 */

public class User1 extends  User {

    private String id ;

    public void run(){

    }

}

混淆规则:

-keep class * extends pp.lib.User { *; }

混淆结果: User 类的 类名已经被混淆了,字段没有混淆

User1 类,类名和字段都没有混淆

参考资料

时间: 2024-09-30 21:38:29

Android 混淆详解的相关文章

Android Menu详解及示例代码_Android

Android Menu 详细介绍: 1.选项菜单 OptionsMenu 2.上下文菜单 ContextMenu 3.子菜单 SubMenu 组成Android用户界面的除了View以外,还有菜单和对话框,这一讲我们就共同学习一下菜单的使用. 菜单是用户界面中最常见的元素,使用也非常频繁,在Android中,菜单被分为如下三种,选项菜单(OptionsMenu).上下文菜单(ContextMenu)和子菜单(SubMenu),下面分别举例说明. 一.选项菜单 OptionsMenu Andro

Android签名详解(debug和release)

Android签名详解(debug和release) 1. 为什么要签名 1) 发送者的身份认证 由于开发商可能通过使用相同的Package Name来混淆替换已经安装的程序,以此保证签名不同的包不被替换 2) 保证信息传输的完整性 签名对于包中的每个文件进行处理,以此确保包中内容不被替换 3) 防止交易中的抵赖发生,Market对软件的要求 2. 签名的说明 1) 所有的应用程序都必须有数字证书,Android系统不会安装一个没有数字证书的应用程序 2) Android程序包使用的数字证书可以

Android GPS详解及示例代码_Android

LBS(Location Based Services)直译的话就是基于地理位置的服务,这里面至少有两层意思,第一要能轻易的获取当前的地理位置,譬如经纬度海拔等,另一个就是在当前位置的基础上提供增值服务,譬如找附近的加油站.餐馆.酒店等.这里面的第一步:获取用户当前位置,我们就可以用Android的GPS定位服务来得到.Android提供了基于网络的定位服务和基于卫星的定位服务两种.在设置->位置和安全设置里面的前三项就是,最后一个增强型GPS是为了辅助快速找卫星的.  那么我们现在就写一个简单

Android Service详解及示例代码_Android

Android Service 详细介绍: 1.Service的概念 2.Service的生命周期 3.实例:控制音乐播放的Service 一.Service的概念 Service是Android程序中四大基础组件之一,它和Activity一样都是Context的子类,只不过它没有UI界面,是在后台运行的组件. 二.Service的生命周期 Service对象不能自己启动,需要通过某个Activity.Service或者其他Context对象来启动.启动的方法有两种,Context.startS

使用OpenGL开发Android应用详解系列三

注:近三篇转载中的视锥体部分结合着来看,再参照老罗的3d变换,基本可以初步理解和完成相关视锥体调整. 使用OpenGL开发Android应用详解系列三 [原创]转载请注明出处 我一家网 http://www.5yijia.com 前面两节主要介绍了一下OpenGL的基本概念,以及在Android开发中引入OpenGL时,Android项目的基本构成情况.这一节开始,我们通过具体的实例,来进行简单3D图形的描画. 注:代码基础还是采用上一节: 使用OpenGL开发Android应用详解系列二中使用

Android CardView详解及使用方法和实例_Android

Android  CardView详解 Android5.0中向我们介绍了一个全新的控件–CardView,从本质上看,可以将CardView看做是FrameLayout在自身之上添加了圆角和阴影效果.请注意:CardView被包装为一种布局,并且经常在ListView和RecyclerView的Item布局中,作为一种容器使用. 发现个好看的东东 CardView,他在support v7包中~~ 顾名思义就是卡片view,可以设置阴影,圆角,等等.. 样子是这样的: 或者你还可以放到list

如何正确使用Android线程详解_Android

前言 对于移动开发者来说,"将耗时的任务放到子线程去执行,以保证UI线程的流畅性"是线程编程的第一金科玉律,但这条铁则往往也是UI线程不怎么流畅的主因.我们在督促自己更多的使用线程的同时,还需要时刻提醒自己怎么避免线程失控. 多线程编程之所以复杂原因之一在于其并行的特性,人脑的工作方式更符合单线程串行的特点.一个接着一个的处理任务是大脑最舒服的状态,频繁的在任务之间切换会产生"头痛"这类系统异常.人脑的多任务和计算机的多任务性能差异太大导致我们在设计并行的业务逻辑之

Android CoordinatorLayout详解及实例代码_Android

Android CoordinatorLayout详解 一.CoordinatorLayout有什么作用 CoordinatorLayout作为"super-powered FrameLayout"基本实现两个功能: 1.作为顶层布局 2.调度协调子布局 CoordinatorLayout使用新的思路通过协调调度子布局的形式实现触摸影响布局的形式产生动画效果.CoordinatorLayout通过设置子View的 Behaviors来调度子View.系统(Support V7)提供了A

Android RecyclerView详解之实现 ListView GridView瀑布流效果_Android

 什么是RecyclerView RecyclerView 是Google推出的最新的 替代ListView.GridView的组件,RecyclerView是用来显示大量数据的容器,并通过有限数量的子View,来提高滚动时的性能. 与ListView不同,RecyclerView 不再负责布局,而是专注于布局复用.布局主要通过 LayoutManager来管理,目前提供了3种常用的布局管理: LinearLayoutManager 线性布局管理器 (ListView效果) GridLayout