精华内容
下载资源
问答
  • 5、对Gson动手,添加统一的null值转换 默认Gson中反序列化的Null值替换 先看看我们什么都不做,Gson默认情况下会对null值做什么处理? 先创建一个数据对象,包含我们常用的数据类型。 public class UserBean { ...

    相信很多移动开发在后台开发接口时,曾一遍遍的发生过如下场景~

    移动端:

    不要传null!不要传null!不要传null!

    后台:

    放心吧,大兄弟,我怎么会传null给你呢(手动滑稽)

    结果在对接口时才发现,曾经的誓言都喂了狗,这些null就像垃圾短信一样,时不时的蹦出来骚扰你,轻则影响UI展示,重则导致应用崩溃。于是你怒气冲冲的跑去质问后台,发生如下场景。。

    移动端:

    为什么要骗我?说好的永不传null呢?难道我们不是最好的朋友吗?

    后台:

    不能啊,怎么会传null呢,我明明改过了啊,你先别急,大兄弟,我们当然是最好的朋友啦(滑稽),我看看哪里出了问题。

    ---------------------------------------------------------------------------------------------------------十年后

    后台:哦哦,我漏掉了一个东西,balabala......,放心吧,大兄弟,不会传null了,再传null,你拿刀来见我!

    移动端:

    哦(我信你个鬼,你个糟老头子坏得很)。

    那么,问题来了,如何在和一个不靠谱的后台开发合作时,保证自己的应用不会出现null值,因为null值产生应用崩溃这种问题呢?

    1、万能的百度啊,赐我一个答案吧!结果查到都是这个答案。

    gson.serializeNulls();

    可以明确的告诉你,不管用,这是序列化用的方法,而我们是反序列化。

    2、他给null就给null吧,大不了我在用的时候判断是否为null就好了。

    这个方法,应该是最普遍采用的了,看起来是能解决因为null产生崩溃的问题,但是操作起来费时费力,几乎每次在用到值得地方都要判断一次是否为null,要是那个地方忘记判断了,还是避免不了会出现因为null崩溃的问题。

    3、做一个日志采集,把因为null崩溃的日志记录下来,等记录到一定的数量后拿去给上级看,强逼后台严禁null值。

    emmm,有理有据,可能会起作用,但是毕竟应用是自己写的,崩溃那么多太影响用户体验,而且应用崩溃,人们能想到的第一个背锅的肯定是开发应用的自己,而且可能上级还会质问为什么移动端不加校验。

    4、我自己用JsonObject、JsonArray去解析json,如果碰到null值的,手动给他设置一个默认值。

    这个想法不错,在自己能触碰到的根源处解决问题,但还是太啰嗦了,每次都要手动转换json,岂不是浪费了Gson这类神器,不过都想到这个方法了,那么能不能在Gson上做做文章,在统一的地方对null值做转换呢?

    5、对Gson动手,添加统一的null值转换

    默认Gson中反序列化的Null值替换

    先看看我们什么都不做,Gson默认情况下会对null值做什么处理?

    先创建一个数据对象,包含我们常用的数据类型。

    public class UserBean {
    
        String name;
        String gender;
        String email;
        String address;
        int age;
        boolean hasMarry;
        float weight;
        double height;
    
        public UserBean(String name, String gender, String email, String address, int age, boolean hasMarry, float weight, double height) {
            this.name = name;
            this.gender = gender;
            this.email = email;
            this.address = address;
            this.age = age;
            this.hasMarry = hasMarry;
            this.weight = weight;
            this.height = height;
        }
    
        @Override
        public String toString() {
            return "name:" + name + ",gender:" + gender + ",email:" + email + ",address:" + address + ",age:" + age + ",hasMarry:" + hasMarry + ",weight:" + weight + ",height:" + height;
        }
    }

    接着再创建一个默认的Gson对象出来,反序列化一个字段全是null的Userbean,再toString打印出来。

    private void defaultGson(){
            Gson gson = new Gson();
            UserBean bean = gson.fromJson("{\"address\":null,\"age\":null,\"email\":null,\"gender\":null,\"hasMarry\":null,\"name\":null,\"weight\":null,\"height\":null}", UserBean.class);
            Log.i("kkk","默认的Gson:\n");
            Log.i("kkk",bean.toString());
        }
    01-14 14:25:55.914 12966-12966/wowo.kjt.app4 I/kkk: 默认的Gson:
        name:null,gender:null,email:null,address:null,age:0,hasMarry:false,weight:0.0,height:0.0

    可以看到除了String类型,其他都有默认值,这是Gson为我们做的吗?其实是Java做的,只要是基本数据类型,不管你赋不赋值,都会有默认值,只有对象才会为null,所以在这Gson除了帮我们自动反序列化,其他啥也没帮我们做。

    在开发中,最常用的字段类型就是String了,这么重要的类型可不能为null。

    自定义Gson反序列化Null值替换

    扯了这么多,终于到实践的地步了,首先我要介绍一个Gson中的接口。

    /*
     * @since 2.1
     */
    public interface TypeAdapterFactory {
    
      /**
       * Returns a type adapter for {@code type}, or null if this factory doesn't
       * support {@code type}.
       * 返回{@code type}的类型适配器,如果此工厂没有,则返回null
       * 支持{@code type}。
       */
      <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type);
    }

    其实这个接口的注释有很长很长一大段,包含示例和解释,想看的可以去源码里看一下,这里我只挑重点放上。

    首先这个接口是在Gson2.1才开始有的,所以Gson版本低于2.1的赶紧升级一下版本,这个接口是Gson的类型适配器工厂,里面的方法作用是”返回对应类型的适配器“,如果我们想在反序列化其他对象类型时做一些操作,可以通过这个适配器来完成。比如把字符串全部转换小写,对列表元素做一些预处理等等,所以,我们是不是可以通过它对类型为String的null值做一些操作呢?来试试!

    仿照示例代码编写一个StringAdapterFactory和StringAdapter

    public static class StringAdapterFactory implements TypeAdapterFactory {
            public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
    
                Class<T> rawType = (Class<T>) type.getRawType();
                //如果对象类型为String,返回自己实现的StringAdapter
                if (rawType != String.class) {
                    return null;
                }
                return (TypeAdapter<T>) new StringAdapter();
            }
        }
    
        public static class StringAdapter extends TypeAdapter<String> {
            public String read(JsonReader reader) throws IOException {
                //如果值为null,返回空字符串
                if (reader.peek() == JsonToken.NULL) {
                    reader.nextNull();
                    return "";
                }
                return reader.nextString();
            }
            //序列化用到的,这里我们实现默认的代码就行
            public void write(JsonWriter writer, String value) throws IOException {
               
                writer.value(value);
            }
        }

    好了,把上面的Gson代码改一下,加上我们自己实现的StringAdapterFactory试试,通过GsonBuilder添加,构建Gson。

    private void customGson(){
            GsonBuilder builder = new GsonBuilder();
            Gson gson = builder.registerTypeAdapterFactory(new StringAdapterFactory()).create();
            UserBean bean = gson.fromJson("{\"address\":null,\"age\":null,\"email\":null,\"gender\":null,\"hasMarry\":null,\"name\":null,\"weight\":null,\"height\":null}", UserBean.class);
            Log.i("kkk","自定义的Gson:\n");
            Log.i("kkk",bean.toString());
        }
    01-14 16:25:13.364 25301-25301/wowo.kjt.app4 I/kkk: 自定义的Gson:
        name:,gender:,email:,address:,age:0,hasMarry:false,weight:0.0,height:0.0

    哈哈,大功告成!讨厌的null值不见了,取而代之的是我们定义的空字符串```

    这下不会再有对null字符串操作导致的异常崩溃了✌️

    Gson反序列化错误类型处理

    本以为高高兴兴,解决了后台传null的问题,没想到刚过几天安稳日子,程序又崩了!一查原因,好嘛,魔高一尺道高一丈,我升级,后台也升级,这下不光传null了,类型都传错了,我要int,他却传给我一个string,我要string,他给我传过来一个boolean。于是拿着这个问题又和后台人员亲切交流一番,本着防患于未然,我觉得还是在App里加一个处理比较稳妥,这样,下次再有这种情况,起码不影响App的正常使用,防止崩溃。

    怎么做呢?还是通过那个Adapter,除了对null判断,再加上对其他类型的判断,比如对string和int类型的值校验,其他基本类型的校验一样,可以照着这两个写:

    public static class StringAdapter extends TypeAdapter<String> {
            public String read(JsonReader reader) throws IOException {
                if (reader.peek() == JsonToken.NULL) {
                    reader.nextNull();
                    return "";
                }
                if (reader.peek() == JsonToken.BOOLEAN) {
                    reader.nextBoolean();
                    return "";
                }
                return reader.nextString();
            }
    
            public void write(JsonWriter writer, String value) throws IOException {
               
            }
        }
    public static class IntegerAdapter extends TypeAdapter<Integer> {
    
            @Override
            public void write(JsonWriter out, Integer value) throws IOException {
    
            }
    
            @Override
            public Integer read(JsonReader in) throws IOException {
                if (in.peek() == JsonToken.NULL) {
                    in.nextNull();
                    return -1;
                }
                //默认的gson其实可以对string转换int,不过仅限string内容为数字,
                //为了保证安全,这里统一处理
                if (in.peek() == JsonToken.STRING){
                    in.nextString();
                    return -1;
                }
                if (in.peek() == JsonToken.BOOLEAN) {
                    in.nextBoolean();
                    return -1;
                }
                return in.nextInt();
            }
        }

    这里返回-1只是为了验证我们转换是否有作用,然后将这些Adapter添加到TypeAdapterFactory再设置给gson。

    public static class MyTypeAdapterFactory implements TypeAdapterFactory {
            public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
    
                Class<T> rawType = (Class<T>) type.getRawType();
                if (rawType == String.class) 
                    return (TypeAdapter<T>) new StringAdapter();
                if (rawType == int.class) 
                    return (TypeAdapter<T>) new IntegerAdapter();
                if (rawType == boolean.class)
                    return (TypeAdapter<T>)new BooleanAdapter();
                if (rawType == double.class)
                    return (TypeAdapter<T>)new DoubleAdapter();
                if (rawType == float.class)
                    return (TypeAdapter<T>)new FloatAdapter();
                return null;
            }
        }

    验证一下

    private void customGson() {
            GsonBuilder builder = new GsonBuilder();
            Gson gson = builder.registerTypeAdapterFactory(new MyTypeAdapterFactory()).create();
            UserBean bean = gson.fromJson("{\"address\":false,\"age\":\"sss\",\"email\":null,\"gender\":null,\"hasMarry\":1.5,\"name\":null,\"weight\":false,\"height\":\"188\"}", UserBean.class);
            Log.i("kkk", "错误类型:\n");
            Log.i("kkk", bean.toString());
            UserBean bean1 = gson.fromJson("{\"address\":\"北京\",\"age\":18,\"email\":\"126.com\",\"gender\":\"男\",\"hasMarry\":true,\"name\":\"kjt\",\"weight\":150.0,\"height\":188}", UserBean.class);
            Log.i("kkk", "正常类型:\n");
            Log.i("kkk", bean1.toString());
        }

     

    错误类型:
        name:,gender:,email:,address:,age:-1,hasMarry:false,weight:0.0,height:0.0
    正常类型:
        name:kjt,gender:男,email:126.com,address:北京,age:18,hasMarry:true,weight:150.0,height:188.0

    ok,可以看到错误的类型都按照我们约束的值返回了~开心~

    如果文中有错误的地方或者想提的意见,希望大家踊跃留言评论~

    如果对你有用的话点个赞呗~

    展开全文
  • GSON序列化与反序列化 GSON 是一个很好的工具, 使用它我们可以轻松的实现序列化和反序列化. 但是当它一旦遇到混淆, 就需要我们注意了. 一个简单的类 Item, 用来处理序列化和反序列化 public class Item { public...

    GSON 的序列化与反序列化

    GSON 是一个很好的工具, 使用它我们可以轻松的实现序列化和反序列化. 但是当它一旦遇到混淆, 就需要我们注意了.

    一个简单的类 Item, 用来处理序列化和反序列化

    public class Item {
        public String name;
        public int id;
    }
    

    序列化的代码

    Item toSerializeItem = new Item();
    toSerializeItem.id = 2;
    toSerializeItem.name = "Apple";
    String serializedText = gson.toJson(toSerializeItem);
    Log.i(LOGTAG, "testGson serializedText=" + serializedText);
    

    开启混淆之后的日志输出结果

    I/MainActivity: testGson serializedText={"a":"Apple","b":2}
    

    属性名已经改变了, 变成了没有意思的名称, 对我们后续的某些处理是很麻烦的.

    反序列化的代码

    Gson gson = new Gson();
    Item item = gson.fromJson("{\"id\":1, \"name\":\"Orange\"}", Item.class);
    Log.i(LOGTAG, "testGson item.id=" + item.id + ";item.name=" + item.name);
    

    对应的日志结果是

    I/MainActivity: testGson item.id=0;item.name=null
    

    可见, 混淆之后, 反序列化的属性值设置都失败了.

    为什么呢?

    • 因为反序列化创建对象本质还是利用反射, 会根据 json 字符串的 key 作为属性名称, value 则对应属性值.

    如何解决

    • 将序列化和反序列化的类排除混淆
    • 使用 @SerializedName 注解字段

    @SerializedName(parameter) 通过注解属性实现了

    • 序列化的结果中, 指定该属性 key 为 parameter 的值.
    • 反序列化生成的对象中, 用来匹配 key 与 parameter 并赋予属性值.

    一个简单的用法为

    public class Item {
        @SerializedName("name")
        public String name;
        @SerializedName("id")
        public int id;
    }
    

    当然也可以直接这么写

    public class Item implements Serializable{
        public String name;
        public int id;
    }
    

    上面是通过添加 @SerializedName 注解实现混淆之后反序列化出现的问题,下面我们说下将将序列化和反序列化的类排除混淆该怎么做。

    package com.baidu.bean;
    
    public class Item {
        public String name;
        public int id;
    
        public static class PageConfig {
            public String type;
        }
    }
    

    Item 中增加了一个内部类 PageConfig。

    这里敲黑板了

    1.Item 里面的字段、Item 里面引用到的类和 Item 里面的内部类 PageConfig 都需要实现序列化 (implements Serializable);

    1. 如果不是 implements Serializable 实现序列化,而是给每个字段加上 @SerializedName 注解,那么务必注意:Item 里面的字段、Item 里面引用到的类的和 Item 里面的内部类的字段都需要加上 @SerializedName 注解,否则会出现莫名其妙的问题:不会崩溃,就是各种奇怪现象,而在 debug 下又不出现这个问题。

    最常见的做法是:

    -keep class com.baidu.bean.** { *; }
    

    含义是:将 bean 目录下包括子目录下的类排除不被混淆

    单独排除某个类可以这么写:

    -keep class com.baidu.bean {*;}

    单独排除某个类的内部类需要这么写:

    -keep class class com.baidu.bean.Item$PageConfig {*;}

    如果很多实体类里面有内部类,建议组合起来写:

    -keep class com.baidu.bean.**{ *;}
    
    -keep class com.baidu.bean.**$*{ *;}
    

    另外,下面的写法也是可以的,主要以上面的写法为主。具体要使用哪种,读者可以自己根据需要使用。

    -keep class com.baidu.bean.$** {;}

    -keep class com.baidu.bean.$ {*;}

    -keep class com.baidu.bean.**$* {*;}

    上面出现了 * 和 ** 通配符的配置,为了便于加深印象,这里延伸阅读下:

    Android 混淆最佳实践

    1. 混淆配置

    android{
    
    buildTypes {
            release {
                buildConfigField "boolean", "LOG_DEBUG", "false" //不显示log
                minifyEnabled true
                shrinkResources true
                proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
                signingConfig signingConfigs.config
                }
            }
    }
    

    因为开启混淆会使编译时间变长,所以 debug 模式下不开启。我们需要做的是:

    1. release下minifyEnabled的值改为true,打开混淆;
    2. 加上shrinkResources true,打开资源压缩。

    3.buildConfigField 不显示 log 日志
    4.signingConfig signingConfigs.config配置签名文件文件

    自定义混淆规则

    自定义混淆方案适用于大部分的项目

    #指定压缩级别
    -optimizationpasses 5
    
    #不跳过非公共的库的类成员
    -dontskipnonpubliclibraryclassmembers
    
    #混淆时采用的算法
    -optimizations !code/simplification/arithmetic,!field/*,!class/merging/*
    
    #把混淆类中的方法名也混淆了
    -useuniqueclassmembernames
    
    #优化时允许访问并修改有修饰符的类和类的成员 
    -allowaccessmodification
    
    #将文件来源重命名为“SourceFile”字符串
    -renamesourcefileattribute SourceFile
    #保留行号
    -keepattributes SourceFile,LineNumberTable
    #保持泛型
    -keepattributes Signature
    
    #保持所有实现 Serializable 接口的类成员
    -keepclassmembers class * implements java.io.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();
    }
    
    #Fragment不需要在AndroidManifest.xml中注册,需要额外保护下
    -keep public class * extends android.support.v4.app.Fragment
    -keep public class * extends android.app.Fragment
    
    # 保持测试相关的代码
    -dontnote junit.framework.**
    -dontnote junit.runner.**
    -dontwarn android.test.**
    -dontwarn android.support.test.**
    -dontwarn org.junit.**
    

    真正通用的、需要添加的就是上面这些,除此之外,需要每个项目根据自身的需求添加一些混淆规则:

    第三方库所需的混淆规则。正规的第三方库一般都会在接入文档中写好所需混淆规则,使用时注意添加。

    在运行时动态改变的代码,例如反射。比较典型的例子就是会与 json 相互转换的实体类。假如项目命名规范要求实体类都要放在 model 包下的话,可以添加类似这样的代码把所有实体类都保持住:-keep public class **.*Model*.** {*;}

    JNI 中调用的类。

    WebView 中JavaScript调用的方法

    Layout 布局使用的 View 构造函数、android:onClick等。

    检查混淆结果

    混淆过的包必须进行检查,避免因混淆引入的 bug。

    一方面,需要从代码层面检查。使用上文的配置进行混淆打包后在<module-name>/build/outputs/mapping/release/目录下会输出以下文件:

    dump.txt

    描述 APK 文件中所有类的内部结构

    mapping.txt

    提供混淆前后类、方法、类成员等的对照表

    seeds.txt

    列出没有被混淆的类和成员

    usage.txt

    列出被移除的代码

    我们可以根据 seeds.txt 文件检查未被混淆的类和成员中是否已包含所有期望保留的,再根据 usage.txt文件查看是否有被误移除的代码。

    另一方面,需要从测试方面检查。将混淆过的包进行全方面测试,检查是否有 bug 产生。

    解出混淆栈

    混淆后的类、方法名等等难以阅读,这固然会增加逆向工程的难度,但对追踪线上 crash 也造成了阻碍。我们拿到 crash 的堆栈信息后会发现很难定位,这时需要将混淆反解。

    <sdk-root>/tools/proguard/路径下有附带的的反解工具(Window 系统为proguardgui.bat,Mac 或 Linux 系统为proguardgui.sh)。

    这里以 Window 平台为例。双击运行 proguardgui.bat 后,可以看到左侧的一行菜单。点击 ReTrace,选择该混淆包对应的 mapping 文件(混淆后在 /build/outputs/mapping/release/ 路径下会生成 mapping.txt 文件,它的作用是提供混淆前后类、方法、类成员等的对照表),再将 crash 的 stack trace 黏贴进输入框中,点击右下角的 ReTrace ,混淆后的堆栈信息就显示出来了。

    以上使用 GUI 程序进行操作,另一种方式是利用该路径下的 retrace 工具通过命令行进行反解,命令是

    retrace.bat|retrace.sh [-verbose] mapping.txt [<stacktrace_file>]

    例如:

    retrace.bat -verbose mapping.txt obfuscated_trace.txt

    注意事项:

    所有在 AndroidManifest.xml 涉及到的类已经自动被保持,因此不用特意去添加这块混淆规则。(很多老的混淆文件里会加,现在已经没必要)

    proguard-android.txt已经存在一些默认混淆规则,没必要在 proguard-rules.pro 重复添加

    混淆简介

    Android 中的 “混淆” 可以分为两部分,一部分是Java 代码的优化与混淆,依靠 proguard混淆器来实现;另一部分是资源压缩,将移除项目及依赖的库中未被使用的资源 (资源压缩严格意义上跟混淆没啥关系,但一般我们都会放一起用)。

    代码压缩

    代码压缩流程

    代码混淆是包含了代码压缩、优化、混淆等一系列行为的过程。如上图所示,混淆过程会有如下几个功能:

    压缩。移除无效的类、类成员、方法、属性等;
    优化。分析和优化方法的二进制代码;根据proguard-android-optimize.txt中的描述,优化可能会造成一些潜在风险,不能保证在所有版本的 Dalvik 上都正常运行。
    混淆。把类名、属性名、方法名替换为简短且无意义的名称;
    预校验。添加预校验信息。这个预校验是作用在 Java 平台上的,Android 平台上不需要这项功能,去掉之后还可以加快混淆速度。
    这四个流程默认开启。

    Android项目中我们可以选择将 “优化” 和“预校验”关闭,对应命令是-dontoptimize、-dontpreverify(当然,默认的 proguard-android.txt文件已包含这两条混淆命令,不需要开发者额外配置)。

    资源压缩

    资源压缩将移除项目及依赖的库中未被使用的资源,这在减少apk 包体积上会有不错的效果,一般建议开启。具体做法是在 build.grade文件中,将shrinkResources属性设置为true。需要注意的是,只有在用minifyEnabled true开启了代码压缩后,资源压缩才会生效。

    资源压缩包含了 “合并资源” 和“移除资源”两个流程。

    “合并资源” 流程中,名称相同的资源被视为重复资源会被合并。需要注意的是,这一流程不受shrinkResources属性控制,也无法被禁止,gradle必然会做这项工作,因为假如不同项目中存在相同名称的资源将导致错误。gradle 在四处地方寻找重复资源:

    src/main/res/ 路径
    不同的构建类型(debugrelease等等)
    不同的构建渠道
    项目依赖的第三方库
    合并资源时按照如下优先级顺序

    依赖 -> main -> 渠道 -> 构建类型
    

    假如重复资源同时存在于 main 文件夹和不同渠道中,gradle 会选择保留渠道中的资源。

    同时,如果重复资源在同一层次出现,比如src/main/res/ 和 src/main/res2/,则 gradle无法完成资源合并,这时会报资源合并错误。

    “移除资源” 流程则见名知意,需要注意的是,类似代码,混淆资源移除也可以定义哪些资源需要被保留,这点在下文给出。

    自定义混淆规则

    在上文 “混淆配置” 中有这样一行代码

    proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
    

    这行代码定义了混淆规则由两部分构成:位于 SDK 的 tools/proguard/ 文件夹中的 proguard-android.txt 的内容以及默认放置于模块根目录的 proguard-rules.pro 的内容。前者是 SDK 提供的默认混淆文件,后者是开发者自定义混淆规则的地方。

    常见的混淆指令

    • optimizationpasses
    • dontoptimize
    • dontusemixedcaseclassnames
    • dontskipnonpubliclibraryclasses
    • dontpreverify
    • dontwarn
    • verbose
    • optimizations
    • keep
    • keepnames
    • keepclassmembers
    • keepclassmembernames
    • keepclasseswithmembers
    • keepclasseswithmembernames
      更多详细的请到官网

    需要特别介绍的是与保持相关元素不参与混淆的规则相关的几种命令:

    命令作用
    -keep防止类和成员被移除或者被重命名
    -keepnames防止类和成员被重命名
    -keepclassmembers防止成员被移除或者被重命名
    -keepnames防止成员被重命名
    -keepclasseswithmembers防止拥有该成员的类和成员被移除或者被重命名
    -keepclasseswithmembernames防止拥有该成员的类和成员被重命名

    保持元素不参与混淆的规则

      [保持命令] [类] {
        [成员] 
    }
    

    “类” 代表类相关的限定条件,它将最终定位到某些符合该限定条件的类。它的内容可以使用:

    • 具体的类
    • 访问修饰符(public、protected、private)
    • 通配符 *,匹配任意长度字符,但不含包名分隔符 (.)
    • 通配符 **,匹配任意长度字符,并且包含包名分隔符 (.)
    • extends,即可以指定类的基类
    • implement,匹配实现了某接口的类
    • $,内部类

    “成员” 代表类成员相关的限定条件,它将最终定位到某些符合该限定条件的类成员。它的内容可以使用:

    • 匹配所有构造器
    • 匹配所有域
    • 匹配所有方法
    • 通配符 *,匹配任意长度字符,但不含包名分隔符 (.)
    • 通配符 **,匹配任意长度字符,并且包含包名分隔符 (.)
    • 通配符 ***,匹配任意参数类型
    • …,匹配任意长度的任意类型参数。比如 void test(…) 就能匹配任意 void test(String a) 或者是 void test(int a, String b) 这些方法。
    • 访问修饰符(public、protected、private)
      举个例子,假如需要将 com.biaobiao.test 包下所有继承 Activity 的 public 类及其构造函数都保持住,可以这样写:
     -keep public class com.biaobiao.test.** extends Android.app.Activity {
        <init>
    }
    

    常用自定义混淆规则

    • 不混淆某个类
    -keep public class com.biaobiao.example.Test { *; }
    

    不混淆某个包所有的类

    -keep class com.biaobiao.test.** { *; }
    }
    

    不混淆某个类的子类

    -keep public class * extends com.biaobiao.example.Test { *; }
    

    不混淆所有类名中包含了 “model” 的类及其成员

    -keep public class * extends com.biaobiao.example.Test { *; }
    

    不混淆某个接口的实现

    -keep class * implements com.biaobiao.example.TestInterface { *; }
    

    不混淆某个类的构造方法

    -keepclassmembers class com.biaobiao.example.Test { 
        public <init>(); 
    }
    

    不混淆某个类的特定的方法

    -keepclassmembers class com.biaobiao.example.Test { 
        public void test(java.lang.String); 
    }
    }
    

    不混淆某个类的内部类

    -keep class com.biaobiao.example.Test$* {
            *;
     }
    

    自定义资源保持规则

    1. keep.xml

    shrinkResources true开启资源压缩后,所有未被使用的资源默认被移除。假如你需要定义哪些资源必须被保留,在 res/raw/ 路径下创建一个 xml 文件,例如keep.xml

    通过一些属性的设置可以实现定义资源保持的需求,可配置的属性有:

    • keep 定义哪些资源需要被保留(资源之间用 “,” 隔开)
    • discard 定义哪些资源需要被移除(资源之间用 “,” 隔开)
    • shrinkMode 开启严格模式
    • 当代码中通过Resources.getIdentifier() 用动态的字符串来获取并使用资源时,普通的资源引用检查就可能会有问题。例如,如下代码会导致所有以 “img_” 开头的资源都被标记为已使用。
      当代码中通过 Resources.getIdentifier() 用动态的字符串来获取并使用资源时,普通的资源引用检查就可能会有问题。例如,如下代码会导致所有以 “img_” 开头的资源都被标记为已使用。
    String name = String.format("img_%1d", angle + 1);
    res = getResources().getIdentifier(name, "drawable", getPackageName());
    

    我们可以设置 tools:shrinkModestrict来开启严格模式,使只有确实被使用的资源被保留。

    以上就是自定义资源保持规则相关的配置,举个例子:

    <?xml version="1.0" encoding="utf-8"?>
    <resources xmlns:tools="http://schemas.android.com/tools"
        tools:keep="@layout/l_used*_c,@layout/l_used_a,@layout/l_used_b*"
        tools:discard="@layout/unused2"
        tools:shrinkMode="strict"/>
    

    移除替代资源

    一些替代资源,例如多语言支持的 strings.xml,多分辨率支持的 layout.xml 等,在我们不需要使用又不想删除掉时,可以使用资源压缩将它们移除。

    我们使用 resConfig 属性来指定需要支持的属性,例如
    一些替代资源,例如多语言支持的strings.xml,多分辨率支持的 layout.xml等,在我们不需要使用又不想删除掉时,可以使用资源压缩将它们移除。

    我们使用 resConfig属性来指定需要支持的属性,例如

    android {
        defaultConfig {
            ...
            resConfigs "en", "fr"
        }
    }
    

    其他未显式声明的语言资源将被移除。

    最后附上一个我在实际项目中的混淆方案

    proguard-android.txt文件内容

    # 代码混淆压缩比,在0~7之间
    -optimizationpasses 5
    # 混合时不使用大小写混合,混合后的类名为小写
    -dontusemixedcaseclassnames
    # 指定不去忽略非公共库的类
    -dontskipnonpubliclibraryclasses
    # 不做预校验,preverify是proguard的四个步骤之一,Android不需要preverify,去掉这一步能够加快混淆速度。
    -dontpreverify
    -verbose
    # 避免混淆泛型
    -keepattributes Signature
    
    # 保留Annotation不混淆
    -keepattributes *Annotation*,InnerClasses
    #google推荐算法
    -optimizations !code/simplification/arithmetic,!code/simplification/cast,!field/*,!class/merging/*
    # 避免混淆Annotation、内部类、泛型、匿名类
    -keepattributes *Annotation*,InnerClasses,Signature,EnclosingMethod
    # 重命名抛出异常时的文件名称
    -renamesourcefileattribute SourceFile
    # 抛出异常时保留代码行号
    -keepattributes SourceFile,LineNumberTable
    # 处理support包
    -dontnote android.support.**
    -dontwarn android.support.**
    # 保留继承的
    -keep public class * extends android.support.v4.**
    -keep public class * extends android.support.v7.**
    -keep public class * extends android.support.annotation.**
    
    # 保留R下面的资源
    -keep class **.R$* {*;}
    # 保留四大组件,自定义的Application等这些类不被混淆
    -keep public class * extends android.app.Activity
    -keep public class * extends android.app.Appliction
    -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.preference.Preference
    -keep public class com.android.vending.licensing.ILicensingService
    
    # 保留在Activity中的方法参数是view的方法,
    # 这样以来我们在layout中写的onClick就不会被影响
    -keepclassmembers class * extends android.app.Activity{
        public void *(android.view.View);
    }
    # 对于带有回调函数的onXXEvent、**On*Listener的,不能被混淆
    -keepclassmembers class * {
        void *(**On*Event);
        void *(**On*Listener);
    }
    # 保留本地native方法不被混淆
    -keepclasseswithmembernames class * {
        native <methods>;
    }
    
    # 保留枚举类不被混淆
    -keepclassmembers enum * {
        public static **[] values();
        public static ** valueOf(java.lang.String);
    }
    
    # 保留Parcelable序列化类不被混淆
    -keep class * implements android.os.Parcelable {
        public static final android.os.Parcelable$Creator *;
    }
    
    -keepclassmembers class * implements java.io.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();
    }
    #assume no side effects:删除android.util.Log输出的日志
    -assumenosideeffects class android.util.Log {
        public static *** v(...);
        public static *** d(...);
        public static *** i(...);
        public static *** w(...);
        public static *** e(...);
    }
    #保留Keep注解的类名和方法
    -keep,allowobfuscation @interface android.support.annotation.Keep
    -keep @android.support.annotation.Keep class *
    -keepclassmembers class * {
        @android.support.annotation.Keep *;
    }
    #3D 地图 V5.0.0之前:
    
    -dontwarn com.amap.api.**
    -dontwarn com.autonavi.**
    -keep class com.amap.api.**{*;}
    -keep class com.autonavi.**{*;}
    
    -keep   class com.amap.api.maps.**{*;}
    -keep   class com.autonavi.amap.mapcore.*{*;}
    -keep   class com.amap.api.trace.**{*;}
    
    #3D 地图 V5.0.0之后:
    -keep   class com.amap.api.maps.**{*;}
    -keep   class com.autonavi.**{*;}
    -keep   class com.amap.api.trace.**{*;}
    
    #定位
    -keep class com.amap.api.location.**{*;}
    -keep class com.amap.api.fence.**{*;}
    -keep class com.autonavi.aps.amapapi.model.**{*;}
    
    #搜索
    -keep   class com.amap.api.services.**{*;}
    
    #2D地图
    -keep class com.amap.api.maps2d.**{*;}
    -keep class com.amap.api.mapcore2d.**{*;}
    
    #导航
    -keep class com.amap.api.navi.**{*;}
    -keep class com.autonavi.**{*;}
    # Retain generic type information for use by reflection by converters and adapters.
    -keepattributes Signature
    
    # Retain service method parameters when optimizing.
    -keepclassmembers,allowshrinking,allowobfuscation interface * {
        @retrofit2.http.* <methods>;
    }
    
    # Ignore annotation used for build tooling.
    -dontwarn org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement
    
    # Ignore JSR 305 annotations for embedding nullability information.
    -dontwarn javax.annotation.**
    
    # JSR 305 annotations are for embedding nullability information.
    -dontwarn javax.annotation.**
    
    # A resource is loaded with a relative path so the package of this class must be preserved.
    -keepnames class okhttp3.internal.publicsuffix.PublicSuffixDatabase
    
    # Animal Sniffer compileOnly dependency to ensure APIs are compatible with older versions of Java.
    -dontwarn org.codehaus.mojo.animal_sniffer.*
    
    # OkHttp platform used only on JVM and when Conscrypt dependency is available.
    -dontwarn okhttp3.internal.platform.ConscryptPlatform
    
    #fastjson混淆
    -keepattributes Signature
    -dontwarn com.alibaba.fastjson.**
    -keep class com.alibaba.**{*;}
    -keep class com.alibaba.fastjson.**{*; }
    -keep public class com.ninstarscf.ld.model.entity.**{*;}
    

    最后

    笔者在面试前,从网上收集了一些 Android 开发相关的学习文档、面试题、Android 核心笔记等等文档,进行了复习,在此分享给大家,希望能帮助到大家学习提升,如有需要参考的可以直接去我 GitHub地址:https://codechina.csdn.net/u012165769/Android-T3/-/blob/master/README.md 访问查阅。

    展开全文
  • GSON序列化与反序列化 GSON是一个很好的工具,使用它我们可以轻松的实现序列化和反序列化.但是当它一旦遇到混淆,就需要我们注意了. 一个简单的类Item,用来处理序列化和反序列化 public class Item { public ...

    GSON的序列化与反序列化

    GSON是一个很好的工具,使用它我们可以轻松的实现序列化和反序列化.但是当它一旦遇到混淆,就需要我们注意了.

    一个简单的类Item,用来处理序列化和反序列化

    public class Item {
        public String name;
        public int id;
    }
    

    序列化的代码

    Item toSerializeItem = new Item();
    toSerializeItem.id = 2;
    toSerializeItem.name = "Apple";
    String serializedText = gson.toJson(toSerializeItem);
    Log.i(LOGTAG, "testGson serializedText=" + serializedText);
    

    开启混淆之后的日志输出结果

    I/MainActivity: testGson serializedText={"a":"Apple","b":2}
    

    属性名已经改变了,变成了没有意思的名称,对我们后续的某些处理是很麻烦的.

    反序列化的代码

    Gson gson = new Gson();
    Item item = gson.fromJson("{\"id\":1, \"name\":\"Orange\"}", Item.class);
    Log.i(LOGTAG, "testGson item.id=" + item.id + ";item.name=" + item.name);
    

    对应的日志结果是

    I/MainActivity: testGson item.id=0;item.name=null
    

    可见,混淆之后,反序列化的属性值设置都失败了.

    为什么呢?

    • 因为反序列化创建对象本质还是利用反射,会根据json字符串的key作为属性名称,value则对应属性值.

    如何解决

    • 将序列化和反序列化的类排除混淆
    • 使用@SerializedName注解字段

    @SerializedName(parameter)通过注解属性实现了

    • 序列化的结果中,指定该属性key为parameter的值.
    • 反序列化生成的对象中,用来匹配key与parameter并赋予属性值.

    一个简单的用法为

    public class Item {
        @SerializedName("name")
        public String name;
        @SerializedName("id")
        public int id;
    }

    当然也可以直接这么写

    public class Item implements Serializable{
        public String name;
        public int id;
    }

    上面是通过添加@SerializedName注解实现混淆之后反序列化出现的问题,下面我们说下将将序列化和反序列化的类排除混淆该怎么做。

    package com.baidu.bean;
    
    public class Item {
        public String name;
        public int id;
    
        public static class PageConfig {
            public String type;
        }
    }

    Item中增加了一个内部类PageConfig。

    这里敲黑板了:

    1.Item里面的字段、Item里面引用到的类和Item里面的内部类PageConfig都需要实现序列化(implements Serializable);

    2.如果不是implements Serializable实现序列化,而是给每个字段加上@SerializedName注解,那么务必注意:Item里面的字段、Item里面引用到的类的和Item里面的内部类的字段都需要加上@SerializedName注解,否则会出现莫名其妙的问题:不会崩溃,就是各种奇怪现象,而在debug下又不出现这个问题。

    最常见的做法是:

    -keep class com.baidu.bean.** { *; }

    含义是:将bean目录下包括子目录下的类排除不被混淆

    单独排除某个类可以这么写:

    -keep class com.baidu.bean {*;}

    单独排除某个类的内部类需要这么写:

    -keep class class com.baidu.bean.Item$PageConfig {*;}

    如果很多实体类里面有内部类,建议组合起来写:

    -keep class com.baidu.bean.**{ *;}
    
    -keep class com.baidu.bean.**$*{ *;}

    另外,下面的写法也是可以的,主要以上面的写法为主。具体要使用哪种,读者可以自己根据需要使用。

    -keep class com.baidu.bean.*$** {*;}

    -keep class com.baidu.bean.*$* {*;}

    -keep class com.baidu.bean.**$* {*;}

     


    上面出现了*和**通配符的配置,为了便于加深印象,这里延伸阅读下:

    Android混淆最佳实践

    1. 混淆配置

     

    android{
    
    buildTypes {
            release {
                buildConfigField "boolean", "LOG_DEBUG", "false" //不显示log
                minifyEnabled true
                shrinkResources true
                proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
                signingConfig signingConfigs.config
                }
            }
    }
    

    因为开启混淆会使编译时间变长,所以debug模式下不开启。我们需要做的是:
    1.将release下minifyEnabled的值改为true,打开混淆;
    2.加上shrinkResources true,打开资源压缩。
    3.buildConfigField 不显示log日志
    4.signingConfig signingConfigs.config配置签名文件文件

    自定义混淆规则

    自定义混淆方案适用于大部分的项目

     

    #指定压缩级别
    -optimizationpasses 5
    
    #不跳过非公共的库的类成员
    -dontskipnonpubliclibraryclassmembers
    
    #混淆时采用的算法
    -optimizations !code/simplification/arithmetic,!field/*,!class/merging/*
    
    #把混淆类中的方法名也混淆了
    -useuniqueclassmembernames
    
    #优化时允许访问并修改有修饰符的类和类的成员 
    -allowaccessmodification
    
    #将文件来源重命名为“SourceFile”字符串
    -renamesourcefileattribute SourceFile
    #保留行号
    -keepattributes SourceFile,LineNumberTable
    #保持泛型
    -keepattributes Signature
    
    #保持所有实现 Serializable 接口的类成员
    -keepclassmembers class * implements java.io.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();
    }
    
    #Fragment不需要在AndroidManifest.xml中注册,需要额外保护下
    -keep public class * extends android.support.v4.app.Fragment
    -keep public class * extends android.app.Fragment
    
    # 保持测试相关的代码
    -dontnote junit.framework.**
    -dontnote junit.runner.**
    -dontwarn android.test.**
    -dontwarn android.support.test.**
    -dontwarn org.junit.**
    

    真正通用的、需要添加的就是上面这些,除此之外,需要每个项目根据自身的需求添加一些混淆规则:

    第三方库所需的混淆规则。正规的第三方库一般都会在接入文档中写好所需混淆规则,使用时注意添加。

    在运行时动态改变的代码,例如反射。比较典型的例子就是会与 json 相互转换的实体类。假如项目命名规范要求实体类都要放在model包下的话,可以添加类似这样的代码把所有实体类都保持住:-keep public class **.*Model*.** {*;}

    JNI中调用的类。

    WebView中JavaScript调用的方法

    Layout布局使用的View构造函数、android:onClick等。

    检查混淆结果

    混淆过的包必须进行检查,避免因混淆引入的bug。

    一方面,需要从代码层面检查。使用上文的配置进行混淆打包后在<module-name>/build/outputs/mapping/release/目录下会输出以下文件:

    dump.txt

    描述APK文件中所有类的内部结构

    mapping.txt

    提供混淆前后类、方法、类成员等的对照表

    seeds.txt

    列出没有被混淆的类和成员

    usage.txt

    列出被移除的代码

    我们可以根据 seeds.txt 文件检查未被混淆的类和成员中是否已包含所有期望保留的,再根据 usage.txt文件查看是否有被误移除的代码。

    另一方面,需要从测试方面检查。将混淆过的包进行全方面测试,检查是否有 bug 产生。

    解出混淆栈

    混淆后的类、方法名等等难以阅读,这固然会增加逆向工程的难度,但对追踪线上 crash 也造成了阻碍。我们拿到 crash 的堆栈信息后会发现很难定位,这时需要将混淆反解。

    在 <sdk-root>/tools/proguard/路径下有附带的的反解工具(Window 系统为proguardgui.bat,Mac 或 Linux 系统为proguardgui.sh)。

    这里以 Window 平台为例。双击运行 proguardgui.bat 后,可以看到左侧的一行菜单。点击 ReTrace,选择该混淆包对应的 mapping 文件(混淆后在 <module-name>/build/outputs/mapping/release/ 路径下会生成 mapping.txt 文件,它的作用是提供混淆前后类、方法、类成员等的对照表),再将 crash 的 stack trace 黏贴进输入框中,点击右下角的 ReTrace ,混淆后的堆栈信息就显示出来了。

    以上使用 GUI 程序进行操作,另一种方式是利用该路径下的 retrace 工具通过命令行进行反解,命令是

    retrace.bat|retrace.sh [-verbose] mapping.txt [<stacktrace_file>]

    例如:

    retrace.bat -verbose mapping.txt obfuscated_trace.txt

    注意事项:

    所有在 AndroidManifest.xml 涉及到的类已经自动被保持,因此不用特意去添加这块混淆规则。(很多老的混淆文件里会加,现在已经没必要)

    proguard-android.txt已经存在一些默认混淆规则,没必要在 proguard-rules.pro 重复添加

    混淆简介

    Android中的“混淆”可以分为两部分,一部分是Java 代码的优化与混淆,依靠 proguard混淆器来实现;另一部分是资源压缩,将移除项目及依赖的库中未被使用的资源(资源压缩严格意义上跟混淆没啥关系,但一般我们都会放一起用)。

    代码压缩

    代码压缩流程

     

    代码混淆是包含了代码压缩、优化、混淆等一系列行为的过程。如上图所示,混淆过程会有如下几个功能:

    压缩。移除无效的类、类成员、方法、属性等;
    优化。分析和优化方法的二进制代码;根据proguard-android-optimize.txt中的描述,优化可能会造成一些潜在风险,不能保证在所有版本的Dalvik上都正常运行。
    混淆。把类名、属性名、方法名替换为简短且无意义的名称;
    预校验。添加预校验信息。这个预校验是作用在Java平台上的,Android平台上不需要这项功能,去掉之后还可以加快混淆速度。
    这四个流程默认开启。

    Android项目中我们可以选择将“优化”和“预校验”关闭,对应命令是-dontoptimize、-dontpreverify(当然,默认的 proguard-android.txt文件已包含这两条混淆命令,不需要开发者额外配置)。

    资源压缩

    资源压缩将移除项目及依赖的库中未被使用的资源,这在减少apk 包体积上会有不错的效果,一般建议开启。具体做法是在 build.grade文件中,将shrinkResources属性设置为true。需要注意的是,只有在用minifyEnabled true开启了代码压缩后,资源压缩才会生效。

    资源压缩包含了“合并资源”和“移除资源”两个流程。

    “合并资源”流程中,名称相同的资源被视为重复资源会被合并。需要注意的是,这一流程不受shrinkResources属性控制,也无法被禁止,gradle必然会做这项工作,因为假如不同项目中存在相同名称的资源将导致错误。gradle 在四处地方寻找重复资源:

    src/main/res/ 路径
    不同的构建类型(debugrelease等等)
    不同的构建渠道
    项目依赖的第三方库
    合并资源时按照如下优先级顺序

     

    依赖 -> main -> 渠道 -> 构建类型
    

    假如重复资源同时存在于main文件夹和不同渠道中,gradle 会选择保留渠道中的资源。

    同时,如果重复资源在同一层次出现,比如src/main/res/ 和 src/main/res2/,则 gradle无法完成资源合并,这时会报资源合并错误。

    “移除资源”流程则见名知意,需要注意的是,类似代码,混淆资源移除也可以定义哪些资源需要被保留,这点在下文给出。

    自定义混淆规则

    在上文“混淆配置”中有这样一行代码

     

    proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
    

    这行代码定义了混淆规则由两部分构成:位于 SDK 的 tools/proguard/ 文件夹中的 proguard-android.txt 的内容以及默认放置于模块根目录的 proguard-rules.pro 的内容。前者是 SDK 提供的默认混淆文件,后者是开发者自定义混淆规则的地方。

    常见的混淆指令

    • optimizationpasses
    • dontoptimize
    • dontusemixedcaseclassnames
    • dontskipnonpubliclibraryclasses
    • dontpreverify
    • dontwarn
    • verbose
    • optimizations
    • keep
    • keepnames
    • keepclassmembers
    • keepclassmembernames
    • keepclasseswithmembers
    • keepclasseswithmembernames
      更多详细的请到官网

    需要特别介绍的是与保持相关元素不参与混淆的规则相关的几种命令:

    命令作用
    -keep防止类和成员被移除或者被重命名
    -keepnames防止类和成员被重命名
    -keepclassmembers防止成员被移除或者被重命名
    -keepnames防止成员被重命名
    -keepclasseswithmembers防止拥有该成员的类和成员被移除或者被重命名
    -keepclasseswithmembernames防止拥有该成员的类和成员被重命名

    保持元素不参与混淆的规则

     

      [保持命令] [类] {
        [成员] 
    }
    

    “类”代表类相关的限定条件,它将最终定位到某些符合该限定条件的类。它的内容可以使用:

    • 具体的类
    • 访问修饰符(public、protected、private)
    • 通配符*,匹配任意长度字符,但不含包名分隔符(.)
    • 通配符**,匹配任意长度字符,并且包含包名分隔符(.)
    • extends,即可以指定类的基类
    • implement,匹配实现了某接口的类
    • $,内部类

    “成员”代表类成员相关的限定条件,它将最终定位到某些符合该限定条件的类成员。它的内容可以使用:

    • <init> 匹配所有构造器
    • <fields> 匹配所有域
    • <methods> 匹配所有方法
    • 通配符*,匹配任意长度字符,但不含包名分隔符(.)
    • 通配符**,匹配任意长度字符,并且包含包名分隔符(.)
    • 通配符***,匹配任意参数类型
    • …,匹配任意长度的任意类型参数。比如void test(…)就能匹配任意 void test(String a) 或者是 void test(int a, String b) 这些方法。
    • 访问修饰符(public、protected、private)
      举个例子,假如需要将com.biaobiao.test包下所有继承Activity的public类及其构造函数都保持住,可以这样写:

     

     -keep public class com.biaobiao.test.** extends Android.app.Activity {
        <init>
    }
    

    常用自定义混淆规则

    • 不混淆某个类

     

    -keep public class com.biaobiao.example.Test { *; }
    

    不混淆某个包所有的类

     

    -keep class com.biaobiao.test.** { *; }
    }
    

    不混淆某个类的子类

     

    -keep public class * extends com.biaobiao.example.Test { *; }
    
    

    不混淆所有类名中包含了“model”的类及其成员

     

    -keep public class * extends com.biaobiao.example.Test { *; }
    

    不混淆某个接口的实现

     

    -keep class * implements com.biaobiao.example.TestInterface { *; }
    

    不混淆某个类的构造方法

     

    -keepclassmembers class com.biaobiao.example.Test { 
        public <init>(); 
    }
    

    不混淆某个类的特定的方法

     

    -keepclassmembers class com.biaobiao.example.Test { 
        public void test(java.lang.String); 
    }
    }
    

    不混淆某个类的内部类

     

    -keep class com.biaobiao.example.Test$* {
            *;
     }
    

    自定义资源保持规则

    1. keep.xml

    shrinkResources true开启资源压缩后,所有未被使用的资源默认被移除。假如你需要定义哪些资源必须被保留,在 res/raw/ 路径下创建一个 xml 文件,例如keep.xml 。

    通过一些属性的设置可以实现定义资源保持的需求,可配置的属性有:

    • keep 定义哪些资源需要被保留(资源之间用“,”隔开)
    • discard 定义哪些资源需要被移除(资源之间用“,”隔开)
    • shrinkMode 开启严格模式
    • 当代码中通过Resources.getIdentifier() 用动态的字符串来获取并使用资源时,普通的资源引用检查就可能会有问题。例如,如下代码会导致所有以“img_”开头的资源都被标记为已使用。
      当代码中通过 Resources.getIdentifier() 用动态的字符串来获取并使用资源时,普通的资源引用检查就可能会有问题。例如,如下代码会导致所有以“img_”开头的资源都被标记为已使用。

     

    String name = String.format("img_%1d", angle + 1);
    res = getResources().getIdentifier(name, "drawable", getPackageName());
    

    我们可以设置 tools:shrinkMode 为strict来开启严格模式,使只有确实被使用的资源被保留。

    以上就是自定义资源保持规则相关的配置,举个例子:

    <?xml version="1.0" encoding="utf-8"?>
    <resources xmlns:tools="http://schemas.android.com/tools"
        tools:keep="@layout/l_used*_c,@layout/l_used_a,@layout/l_used_b*"
        tools:discard="@layout/unused2"
        tools:shrinkMode="strict"/>
    

    移除替代资源

    一些替代资源,例如多语言支持的 strings.xml,多分辨率支持的 layout.xml 等,在我们不需要使用又不想删除掉时,可以使用资源压缩将它们移除。

    我们使用 resConfig 属性来指定需要支持的属性,例如
    一些替代资源,例如多语言支持的strings.xml,多分辨率支持的 layout.xml等,在我们不需要使用又不想删除掉时,可以使用资源压缩将它们移除。

    我们使用 resConfig属性来指定需要支持的属性,例如

    android {
        defaultConfig {
            ...
            resConfigs "en", "fr"
        }
    }
    

    其他未显式声明的语言资源将被移除。

    最后附上一个我在实际项目中的混淆方案

    proguard-android.txt文件内容

    # 代码混淆压缩比,在0~7之间
    -optimizationpasses 5
    # 混合时不使用大小写混合,混合后的类名为小写
    -dontusemixedcaseclassnames
    # 指定不去忽略非公共库的类
    -dontskipnonpubliclibraryclasses
    # 不做预校验,preverify是proguard的四个步骤之一,Android不需要preverify,去掉这一步能够加快混淆速度。
    -dontpreverify
    -verbose
    # 避免混淆泛型
    -keepattributes Signature
    
    # 保留Annotation不混淆
    -keepattributes *Annotation*,InnerClasses
    #google推荐算法
    -optimizations !code/simplification/arithmetic,!code/simplification/cast,!field/*,!class/merging/*
    # 避免混淆Annotation、内部类、泛型、匿名类
    -keepattributes *Annotation*,InnerClasses,Signature,EnclosingMethod
    # 重命名抛出异常时的文件名称
    -renamesourcefileattribute SourceFile
    # 抛出异常时保留代码行号
    -keepattributes SourceFile,LineNumberTable
    # 处理support包
    -dontnote android.support.**
    -dontwarn android.support.**
    # 保留继承的
    -keep public class * extends android.support.v4.**
    -keep public class * extends android.support.v7.**
    -keep public class * extends android.support.annotation.**
    
    # 保留R下面的资源
    -keep class **.R$* {*;}
    # 保留四大组件,自定义的Application等这些类不被混淆
    -keep public class * extends android.app.Activity
    -keep public class * extends android.app.Appliction
    -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.preference.Preference
    -keep public class com.android.vending.licensing.ILicensingService
    
    # 保留在Activity中的方法参数是view的方法,
    # 这样以来我们在layout中写的onClick就不会被影响
    -keepclassmembers class * extends android.app.Activity{
        public void *(android.view.View);
    }
    # 对于带有回调函数的onXXEvent、**On*Listener的,不能被混淆
    -keepclassmembers class * {
        void *(**On*Event);
        void *(**On*Listener);
    }
    # 保留本地native方法不被混淆
    -keepclasseswithmembernames class * {
        native <methods>;
    }
    
    # 保留枚举类不被混淆
    -keepclassmembers enum * {
        public static **[] values();
        public static ** valueOf(java.lang.String);
    }
    
    # 保留Parcelable序列化类不被混淆
    -keep class * implements android.os.Parcelable {
        public static final android.os.Parcelable$Creator *;
    }
    
    -keepclassmembers class * implements java.io.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();
    }
    #assume no side effects:删除android.util.Log输出的日志
    -assumenosideeffects class android.util.Log {
        public static *** v(...);
        public static *** d(...);
        public static *** i(...);
        public static *** w(...);
        public static *** e(...);
    }
    #保留Keep注解的类名和方法
    -keep,allowobfuscation @interface android.support.annotation.Keep
    -keep @android.support.annotation.Keep class *
    -keepclassmembers class * {
        @android.support.annotation.Keep *;
    }
    #3D 地图 V5.0.0之前:
    
    -dontwarn com.amap.api.**
    -dontwarn com.autonavi.**
    -keep class com.amap.api.**{*;}
    -keep class com.autonavi.**{*;}
    
    -keep   class com.amap.api.maps.**{*;}
    -keep   class com.autonavi.amap.mapcore.*{*;}
    -keep   class com.amap.api.trace.**{*;}
    
    #3D 地图 V5.0.0之后:
    -keep   class com.amap.api.maps.**{*;}
    -keep   class com.autonavi.**{*;}
    -keep   class com.amap.api.trace.**{*;}
    
    #定位
    -keep class com.amap.api.location.**{*;}
    -keep class com.amap.api.fence.**{*;}
    -keep class com.autonavi.aps.amapapi.model.**{*;}
    
    #搜索
    -keep   class com.amap.api.services.**{*;}
    
    #2D地图
    -keep class com.amap.api.maps2d.**{*;}
    -keep class com.amap.api.mapcore2d.**{*;}
    
    #导航
    -keep class com.amap.api.navi.**{*;}
    -keep class com.autonavi.**{*;}
    # Retain generic type information for use by reflection by converters and adapters.
    -keepattributes Signature
    
    # Retain service method parameters when optimizing.
    -keepclassmembers,allowshrinking,allowobfuscation interface * {
        @retrofit2.http.* <methods>;
    }
    
    # Ignore annotation used for build tooling.
    -dontwarn org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement
    
    # Ignore JSR 305 annotations for embedding nullability information.
    -dontwarn javax.annotation.**
    
    # JSR 305 annotations are for embedding nullability information.
    -dontwarn javax.annotation.**
    
    # A resource is loaded with a relative path so the package of this class must be preserved.
    -keepnames class okhttp3.internal.publicsuffix.PublicSuffixDatabase
    
    # Animal Sniffer compileOnly dependency to ensure APIs are compatible with older versions of Java.
    -dontwarn org.codehaus.mojo.animal_sniffer.*
    
    # OkHttp platform used only on JVM and when Conscrypt dependency is available.
    -dontwarn okhttp3.internal.platform.ConscryptPlatform
    
    #fastjson混淆
    -keepattributes Signature
    -dontwarn com.alibaba.fastjson.**
    -keep class com.alibaba.**{*;}
    -keep class com.alibaba.fastjson.**{*; }
    -keep public class com.ninstarscf.ld.model.entity.**{*;}
    

    以上内容来自:https://www.jianshu.com/p/e9d3c57ab92f

     

    展开全文
  • gson序列化出现double与int 类型出现错误简介如何使用registerTypeAdapter(注册类型适配器)方法进行解决转换问题gson.jar第一步:书写自己的类型适配器第二步:gosn 实例化的时候进行选择上述的注册类型适配器结尾 ...

    简介如何使用registerTypeAdapter(注册类型适配器)方法进行解决转换问题

    下列使用com.google.gson-2.2.4.jar 版本进行操作,下方会提供本人使用的的jar包以及对应的适配器类。

    gson.jar

    1.maven:
    链接:https://mvnrepository.com/artifact/com.google.code.gson/gson

    第一步:书写自己的类型适配器

    需要书写自己的适配器

    import com.google.gson.TypeAdapter;
    import com.google.gson.internal.LinkedTreeMap;
    import com.google.gson.stream.JsonReader;
    import com.google.gson.stream.JsonToken;
    import com.google.gson.stream.JsonWriter;
    
    import java.io.IOException;
    import java.util.ArrayList;
    import java.util.List;
    import java.util.Map;
    
    /**
     * @author yuanbo
     * @ClassName MapTypeAdapter
     * @Description TODO 重写gson adapter 解决转换时 int 转换为double
     * * @date 2020-11-02
     * @Version 1.0
     **/
    public class MapTypeAdapter extends TypeAdapter<Object> {
        @Override
        public Object read(JsonReader in) throws IOException {
            JsonToken token = in.peek();
            switch (token) {
                case BEGIN_ARRAY:
                    List<Object> list = new ArrayList<Object>();
                    in.beginArray();
                    while (in.hasNext()) {
                        list.add(read(in));
                    }
                    in.endArray();
                    return list;
    
                case BEGIN_OBJECT:
                    Map<String, Object> map = new LinkedTreeMap<String, Object>();
                    in.beginObject();
                    while (in.hasNext()) {
                        map.put(in.nextName(), read(in));
                    }
                    in.endObject();
                    return map;
    
                case STRING:
                    return in.nextString();
    
                case NUMBER:
                    /**
                     * 改写数字的处理逻辑,将数字值分为整型与浮点型。
                     */
                    double dbNum = in.nextDouble();
    
                    // 数字超过long的最大值,返回浮点类型
                    if (dbNum > Long.MAX_VALUE) {
                        return dbNum;
                    }
    
                    // 判断数字是否为整数值
                    long lngNum = (long) dbNum;
                    if (dbNum == lngNum) {
                        return lngNum;
                    } else {
                        return dbNum;
                    }
    
                case BOOLEAN:
                    return in.nextBoolean();
    
                case NULL:
                    in.nextNull();
                    return null;
    
                default:
                    throw new IllegalStateException();
            }
        }
    
        @Override
        public void write(JsonWriter out, Object value) throws IOException {
            // 序列化无需实现
        }
    

    第二步:gosn 实例化的时候进行选择上述的注册类型适配器

     //使用typetoken 防止int转为double
        private static Gson gson = new GsonBuilder()
                .registerTypeAdapter(new TypeToken<Map<String, Object>>() {
                }.getType(), new MapTypeAdapter()).create();
         //伪代码举例
            Object attachJson = returnOrder.get("attach");
           Map<String, Object> attachMap = gson.fromJson(attachJson.toString(), Map.class);
    

    结尾

    1.此文章本人调用gson 的笔记,如有错误请各位大佬指导改正
    2.对于本人引用的链接和demo代码,如有遇到原始的希望告知,方便及时贴上其原帖
    3.如果对上述的问题存在解答方法,欢迎在帖子下评论。

    展开全文
  • 1.Item 里面的字段、Item 里面引用到的类和 Item 里面的内部类 PageConfig 都需要实现序列化 (implements Serializable); 如果不是 implements Serializable 实现序列化,而是给每个字段加上 @SerializedName 注解...
  • 1.Gson的构造函数主要是初始化factories (TypeAdapterFactory 集合),便于在getAapter()根据数据类型返回对应的TypeAdapterFactory,从而获取对应的TypeAdapter进行序列化和反序列化。 toJson()是序列化入口,首先会...
  • 1.Gson的构造函数主要是初始化factories (TypeAdapterFactory 集合),便于在getAapter()根据数据类型返回对应的TypeAdapterFactory,从而获取对应的TypeAdapter进行序列化和反序列化。 toJson()是序列化入口,首先会...
  • /** 涉及所需要字段的方法,排除字段的方法,并调用相对应的方法输出json格式的数据:如下所示 ...import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.JsonObject; i
  • Gson解析之自定义序列化和反序列化

    千次阅读 2015-10-19 18:12:41
    最近在解析Gson过程正经常遇到这样的问题: 在后端给的接口文档中,对于给定的字段A其数据类型是X,但是在实际情况下,后端有可能返回的字段A是数据类型Y,这个时候就hehe了,gson抛异常,如果不捕获程序就直接奔溃...
  • GSON序列化时排除字段的几种方式

    千次阅读 2015-03-17 22:27:27
    在使用GSON将Java对象转成JSON时,如何排除某些字段,有以下几种方法: 给排除字段加上 transient 修饰符; 排除Modifier为指定类型的字段:Gson gson = new GsonBuilder() .excludeFieldsWithModifiers(Modifier....
  • 使用JSON序列化以及GSON工具使用JSON序列化以及GSON工具创建项目创建JSON草稿用作之后的对比创建student类建立二者之间的对应联系——使用GSON工具当对象有嵌套时,如果进行序列化与反序列化?当对象有多个时List...
  • Json序列化框架之Gson Api详解

    千次阅读 2017-04-18 15:08:13
    Gson框架使用其实很简单,但大部分人可能只用了其中的部分功能,由于工作或功能的限制,没有太多时间去整体看看Gson到底能帮我们在数据序列化上做哪些事情,这篇文章准备根据Gson官方的API文档,同时结合项目中使用...
  • 首先我们看Gson的构造函数,在构造函数中添加了很多对应数据类型的TypeAdapterFactory,而每一个TypeAdapterFactory都对应有一个TypeAdapter,而TypeAdapter就是用来对对应类型的数据进行序列化/反序列操作 public...
  • 首先我们看Gson的构造函数,在构造函数中添加了很多对应数据类型的TypeAdapterFactory,而每一个TypeAdapterFactory都对应有一个TypeAdapter,而TypeAdapter就是用来对对应类型的数据进行序列化/反序列操作 public...
  • 序列化&反序列化

    2018-11-26 22:23:28
    Java对象的序列化 SerializablJava平台允许我们在内存中创建可复用的Java对象,但一般情况下,只有当JVM处于运行时,这些对象才可能存在,即,这些对象的生命周期不会比JVM的生命周期更长。但在现实应用中,就可能...
  • Java序列化与反序列化

    千次阅读 2016-05-11 12:21:36
    原文地址:... 目录[-] 序列化是干什么的 什么情况下需要序列化 序列化的几种方式 Object Serialize JSON化 Google ProtoBuf 序列化是干什么的 简单说就是为了保存在内存中的各种对象的状态
  • 很多商业项目用到数据库、内存映射文件和普通文件来完成项目中的序列化处理的需求,但是这些方法很少会依靠于Java序列化。本文也不是用来解释序列化的,而是一起来看看面试中有关序列化的问题,这些问题你很有可能不...
  • Gson解析要防止Bean混淆的原因

    千次阅读 2018-03-07 19:33:17
    大多数在这篇文章已经写的很清楚了补充1...返回一个Map,key:bean的字段名 value:bean的字段field补充2:json解析出来后怎么与bean的字段对应上补充3:Gson解析出来一个字段的值后,给bean对应字段赋值Gson序列化...
  • 为什么需要序列化 当需要将对象保存在磁盘,通常来讲对象的生命周期不大于JVM的生命周期,保存在磁盘为了防止意外宕机,引起数据丢失 当需要在网络传输对象,在分布式架构中对象传输非常普遍,选择好的序列化方式...
  • java序列化深克隆 开发人员经常依靠3d方库来避免重新发明轮子,尤其是在Java世界中,Apache和Spring这样的项目如此盛行。 在处理这些框架时,我们通常很少或根本无法控制其类的行为。 这有时会导致问题。 例如,...
  • Gson

    2018-11-28 10:11:14
    Gson介绍:GSON是Google提供的用来在Java对象和JSON数据之间进行映射的Java类库。可以将一个Json字符转成一个Java对象,或者将一个Java转化为Json字符串。特点: a...
  • 几种常用序列化和反序列化方法

    千次阅读 2019-12-30 21:48:38
    序列化和反序列化几乎是工程师们每天都要面对的事情,但是要精确掌握这两个概念并不容易:一方面,它们往往作为框架的一部分出现而湮没在框架之中;另一方面,它们会以其他更容易理解的概念出现,例如加密、持久化。...
  • Gson使用

    2018-12-04 16:52:01
    Gson使用 ... Serialization:序列化,使Java对象到Json字符串的过程。  Deserialization:反序列化,字符串转换成Java对象   使用Maven管理Gson,pom.xml导入gson的依赖 &lt;depen...
  • Java序列化、反序列化

    千次阅读 2016-04-25 17:46:41
    序列化是干什么的 简单说就是为了保存在内存中的各种对象的状态(也就是实例变量,不是方法),并且可以把保存的对象状态再读出来。虽然你可以用你自己的各种各样的方法来保 存object states,但是Java给你提供一...
  • 文章目录什么是序列化为什么需要序列化Java的序列化Android的序列化 什么是序列化 为什么需要序列化 Java的序列化 Android的序列化 高性能rpc 的关键技术之一 就是高效的对象序列化机制。 传统java的序列化机制。有...
  • 如何防止GSON的表达整数作为浮点数

    千次阅读 2016-08-18 17:30:36
    GSON有怪异的行为,当我...有没有一种方法,以防止GSON从加入“0.0所有整数值? ArrayList> responses; Type ResponseList = new TypeToken>>() {}.getType(); responses = new Gson().fromJson(draft, ResponseLis
  • 为什么要序列化?  有的时候我们想把一个java对象变成字节流的形式进行传输(序列化),或者把一个字节流变成对象(反序列化) 序列化常用的两种方式:  1️⃣ 实现Serializable接口,也是经常使用的方式,用法很简单,只...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 1,456
精华内容 582
关键字:

gson防止序列化