Android开发之混淆高级教程02

2017-09-11 16:50 阅读 4,923 次 评论 2 条

摘要:

学习混淆的最终目的:希望运用到当前开发的Android项目中,那么Android项目需要添加的混淆规则包括哪些呢?在《Android开发之混淆基础教程》介绍了混淆指定包名、指定类名和指定方法的基础知识,《Android开发之混淆高级教程(一)》介绍了保留关键字-keep之间的区别,本篇文章主要介绍第三方类库注解序列化对象实体自定义View枚举native方法callback方法JS交互方法等混淆规则的写法,最后总结成一份可以通用的Android项目混淆规则。

一、Android项目混淆规则分类

在学习了Android开发之混淆基础教程,知道怎么保留指定的包名、类名、方法名以及字段后,其实已经掌握了混淆的大部分内容。为了更好地运用、记忆混淆的内容,将混淆规则划分成如下几类:

1.1 混淆第三方类库

怎么混淆第三方类库,这是一个很多同学可能会问的问题,因为我们并不是类库的开发者,对于类库的代码、结构不熟悉,自然不知道该添加什么样的混淆规则,这是比较头疼的事情。对于我个人来说,能够添加混淆规则的第三方类库,尽量添加,混淆其中一个好处压缩apk的大小,减少内存的消耗。

一个优秀的开源项目,开发者可能会提供对应的混淆规则,所以,添加第三方类库的混淆规则,第一种方法:登录GitHub或开源库官网,查找开源库的混淆规则,直接复制到当前Android项目中即可,查看当前项目添加的第三方类库,如下图:

混淆第三方类库

com.squareup.okhttp3:okhttp:3.8.1类库为例,在GitHub搜索开源库okhttp,查看混淆规则说明,如下所示:

-dontwarn okhttp3.**
-dontwarn okio.**
-dontwarn javax.annotation.**

直接复制上述规则,到我们当前开发的Android项目中即可

如果第三方类库,开发者没有提供对应的混淆规则,哪该怎么办?以com.nineoldandroids:library:2.4.0为例,在GitHub查看开源库,没有找到混淆规则的代码,如果比较熟悉开源库的源码,可以自己定义开源库的混淆规则,但如果你还没有阅读过源码,不知道怎么写混淆规则,建议可以保留开源库,不混淆包名下(二级、三级及其以下)的所有文件,如下图:

保留第三方类库

在当前Android项目中,添加如下规则:

-keepnames com.nineoldandroids.**{*;}

混淆第三方类库,第一种方法:复制开源库的混淆规则到项目中,第二种方法:保留开源库包名及其以下所有类、方法,第三种方法:自己定义开源库的混淆规则,前提是熟悉开源库源码

1.2 混淆注解相关代码

这里所说的注解,不包括第三方类库使用到注解,使用注解的第三方类库,可以参考混淆第三方类库的方法。

保留注解相关代码,需要使用关键字-keepattributes [attribute_filter],该关键字的作用:指定需要被保留的任一可选项属性,属性可以指定一项或多项,可以选择的属性attribute_filter包括如下几种类型:

  1. Exceptions
  2. Signature
  3. Deprecated
  4. SourceFile
  5. SourceDir
  6. LineNumberTable
  7. LocalVariableTable
  8. LocalVariableTypeTable
  9. Synthetic
  10. EnclosingMethod
  11. RuntimeVisibleAnnotations
  12. RuntimeInvisibleAnnotations
  13. RuntimeVisibleParameterAnnotations
  14. RuntimeInvisibleParameterAnnotations
  15. AnnotationDefault

混淆注解的相关代码,需要在Android项目中,添加如下规则:

-keepattributes *Annotation*

1.3 混淆序列化对象(Serializable、Parcelable)

继承自SerializableParcelable接口的类创建的对象称为序列化对象,为什么需要保留序列化对象的某些字段、方法,需要深入了解序列化的特点。

序列化对象以字节的形式保留到本地或在网络中传输,再通过反序列化将获取的字节重新组装成对象,为了保证组装的成功

1.3.1 混淆实现Serializable接口的类

  1. 需要保留的字段包括:非static、非transient、非private修饰的字段
  2. 需要保留的方法包括:非private修饰的方法,writeObject()方法、readObject()方法、writeReplace()方法、readResolve()方法

混淆实现Serializable接口的类,在当前Android项目中,添加如下规则:

-keepnames class * implements java.io.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();
}

1.3.2 混淆实现Parcelable接口的类

混淆实现Parcelable接口的类,在当前Android项目中,添加如下规则:

-keepclassmembers class * implements android.os.Parcelable {
    static android.os.Parcelable$Creator CREATOR;
}

1.4 混淆实体类

我刚开始学习Android的时候,容易把实体对象、序列化对象弄错,实体类和序列化类的一个重要区别是:前者没有实现Serializable接口,无法持久化对象。

实体对象和序列化对象的相同特点:

  1. 使用private定义属性字段
  2. 提供setget方法设置、访问属性字段
  3. 提供空的构造方法

1.4.1 实体类

/**
 * Created by https://www.teachcourse.cn on 2017/8/29.
 */
public class City {
    private double latitude;
    private double longitude;
    private String cityName;

    public City(double latitude, double longitude, String cityName) {
        this.latitude = latitude;
        this.longitude = longitude;
        this.cityName = cityName;
    }

    public double getLatitude() {
        return latitude;
    }

    public double getLongitude() {
        return longitude;
    }

    public String getCityName() {
        return cityName;
    }

    public static String from(){
        return "Chinese";
    }
}

1.4.2 序列化类

/**
 * Created by https://www.teachcourse.cn on 2017/8/30.
 */

public class CityBean implements Serializable {
    private double latitude;
    private double longitude;
    private String cityName;

    public CityBean(double latitude, double longitude, String cityName) {
        this.latitude = latitude;
        this.longitude = longitude;
        this.cityName = cityName;
    }

    public double getLatitude() {
        return latitude;
    }

    public double getLongitude() {
        return longitude;
    }

    public String getCityName() {
        return cityName;
    }
}

混淆实体类,在当前Android项目中添加如下规则:

-keep class cn.teachcourse.bean.** {
    void set*(***);
    void set*(int, ***);

    boolean is*(); 
    boolean is*(int);

    *** get*();
    *** get*(int);
}

PS:在你的Android项目中,需要将cn.teachcourse.bean目录改为你存放实体的路径。

1.5 混淆自定义View

重写继承自View或ViewGroup的方法,实现需要的效果,为了保证自定义的View可以布局文件中正常引用,不运行混淆自定义View:构造方法set方法、get方法等

混淆自定义View,在当前Android项目中添加如下规则:

-keep 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*(...);
}

1.6 混淆枚举类

什么是枚举?什么怎样在实际项目中应用?如果你不是很熟悉,可以先花两分钟大略阅读《Android开发之枚举(Enum)在实际项目中的应用

混淆枚举类,在当前Android项目中添加如下规则:

-keepclassmembers enum * {
    public static **[] values();
    public static ** valueOf(java.lang.String);
}

1.7 混淆native方法

一个native关键字修饰的方法,称为native方法,一个native方法长得很像abstract方法:只有方法签名,没有方法体,与抽象方法不同的是:native方法是由非Java语言实现的,比如C/C++语言实现;abstract方法是由Java语言实现的,下面是一个包含native方法的例子:

public class NativeDemo {  
    /**
     *native方法,不包含方法体
     */
    native public int cal(int a,int b);  
    static   
    {  
        System.loadLibrary("test");   
    }  
    public static void main(String[] args) {  
        NativeDemo nd = new NativeDemo();  
        System.out.println(nd.cal(3,5));  

    }  
}

1.7.1 native方法实现的过程:

  • 编写Java程序,javac编译生成.class文件;
  • 用javah编译生成的class文件,生成.h文件;
  • 编写.cpp文件实现native方法,其中需要包含上述生成.h文件(.h文件包含了JDK自带的jni.h文件);
  • .cpp文件变异成动态链接库.dll文件;
  • 在Java中调用System.loadLibrary()方法或Runtime.loafLibrary()方法加载动态链接库文件,实现在Java中调用这个native方法;
  • 运行Java文件:java Djava.library.path=[dll存放的路径]。

混淆native方法,在当前Android项目中添加如下规则:

-keepclasseswithmembernames class * {
    native <methods>;
}

1.8 混淆callback方法

什么是callback方法呢?简单地说小明想要计算1024+1024等于多少的填空题,但因为小明还没学过四位数的加法,于是借助计算器的帮助,最终,小明得出1024+1024的答案,完成填空题,代码如下:

public class Student
{
    private String name = null;

    public Student(String name)
    {
        this.name = name;
    }

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

    public void callHelp (int a, int b)
    {
        new SuperCalculator().add(a, b, this);
    }

    public void fillBlank(int a, int b, int result)
    {
        System.out.println(name + "借助计算器计算:" + a + " + " + b + " = " + result);
    }
}

1.8.1 callback方法

public class SuperCalculator
{
    public void add(int a, int b, Student  xiaoming)
    {
        int result = a + b;
        xiaoming.fillBlank(a, b, result);
    }
}

Student持有SuperCalculator的引用,在A类中调用B类的方法;同时SuperCalculator也持有Student的引用,B类将计算结果通过A类的方法,回调给A类,在上面的例子中,fillBlank()就是一个callback方法。

1.8.2 不陌生的callback方法

有接口的地方,就用到callback方法,这样的例子有很多,比如:列表选择、手势拖拽、焦点改变、按钮点击,这里看一下按钮点击的回调方法,代码如下:

btn.setOnClickListener(new OnClickListener(){
         @Override
         public void onClick(View view){
                Button btn=(Button)view;
                       btn.setTextSize(25);
         }
});

在上面点击按钮的例子中,我们想要保留callback方法setTextSieze(),在当前Android项目中添加如下规则:

-keep class android.widget.Button {
    public void setTextSize(...);
}

1.9 混淆JS交互方法

为什么需要JS交互方法?在需要与H5进行JS交互的界面,前端根据Android客户端传递的对象,调用相关的方法,比如:页面开始加载方法onPageStarted()、页面加载结束方法onPageFinished()以及页面重载方法shouldOverrideUrlLoading(),上述方法来自android.webkit.WebViewClient类,也就是说为了保证WebView加载H5界面可以正确WebViewClient的方法,在proguard-rules.pro添加如下规则:

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

除此之外,我们会自定义JS可以调用的方法,如下图:

保留JS交互代码

我们在当前加载H5网页的activity中,定义isLogin()方法,在点击H5按钮,兑换积分礼品,这时通过JS代码,在H5界面调用Java代码中的isLogin(),判断用户是否登录,我们需要保留所有JS会调用的Java代码,在proguard-rules.pro添加如下规则:

-keepclassmembers class cn.teachcourse.LoginInterface {
    public *;
}

PS:LoginInterface是定义的一个接口,声明了H5界面会调用的方法,在你的Android项目中,需要修改成你自己的类。

二、总结

本篇文章通过例子的方式说明:第三方类库注解序列化对象实体自定义View枚举native方法callback方法JS交互方法规则的写法,当然在Android项目中,需要混淆的内容还不止这些分类,在熟悉了混淆基础知识的前提下,想要保留任意的包名、类名、方法名以及字段,都不是问题,将上面的规则总结如下:

# -----------------1.1 混淆第三方类库------------------------------------------

# 添加okhttp混淆规则
-dontwarn okhttp3.**
-dontwarn okio.**
-dontwarn javax.annotation.**

#添加nineoldandroids混淆规则
-keepnames com.nineoldandroids.**{*;}

# -----------------1.2 混淆注解相关代码------------------------------------------

-keepattributes *Annotation*

# -----------------1.3 混淆序列化对象------------------------------------------

# 混淆实现Serializable的类
-keepnames class * implements java.io.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();
}

#混淆实现Parcelable的类
-keepclassmembers class * implements android.os.Parcelable {
    static android.os.Parcelable$Creator CREATOR;
}

# -----------------1.4 混淆实体类------------------------------------------

# 注意将cn.teachcourse.bean更改为您项目存放实体类的路径
-keep class cn.teachcourse.bean.** {
    void set*(***);
    void set*(int, ***);

    boolean is*();
    boolean is*(int);

    *** get*();
    *** get*(int);
}

# -----------------1.5 混淆自定义View------------------------------------------

-keep 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*(...);
}

# -----------------1.6 混淆枚举------------------------------------------

-keepclassmembers enum * {
    public static **[] values();
    public static ** valueOf(java.lang.String);
}

# -----------------1.7 混淆native方法------------------------------------------

-keepclasseswithmembernames class * {
    native <methods>;
}

# -----------------1.8 混淆callback方法------------------------------------------

-keep class android.widget.Button {
    public void setTextSize(...);
}

# -----------------1.8 混淆JS交互方法------------------------------------------

-keepclassmembers class * extends android.webkit.WebViewClient {
    public void *(android.webkit.WebView, java.lang.String, android.graphics.Bitmap);
    public boolean *(android.webkit.WebView, java.lang.String);
}
-keepclassmembers class * extends android.webkit.WebViewClient {
    public void *(android.webkit.WebView, jav.lang.String);
}
# 将cn.teachcourse.LoginInterface替换成您定义的接口
-keepclassmembers class cn.teachcourse.LoginInterface {
    public *;
}

你可能感兴趣的文章

来源:TeachCourse每周一次,深入学习Android教程,关注(QQ158#9359$239或公众号TeachCourse)
转载请注明出处: https://www.teachcourse.cn/2494.html ,谢谢支持!

资源分享

Android应用微信分享与收藏功能实现 Android应用微信分享与收藏功能
避孕常见的误区 避孕常见的误区
搭建独立网站不得不读的第一篇WordPress安装指南 搭建独立网站不得不读的第一篇W
Handler方法解析 Handler方法解析