1 Star 0 Fork 0

于旭昊 / GsonFactory

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
克隆/下载
贡献代码
同步代码
取消
提示: 由于 Git 不支持空文件夾,创建文件夹后会生成空的 .keep 文件
Loading...
README
Apache-2.0

Gson 解析容错框架

集成步骤

  • 如果你的项目 Gradle 配置是在 7.0 以下,需要在 build.gradle 文件中加入
allprojects {
    repositories {
        // JitPack 远程仓库:https://jitpack.io
        maven { url 'https://jitpack.io' }
    }
}
  • 如果你的 Gradle 配置是 7.0 及以上,则需要在 settings.gradle 文件中加入
dependencyResolutionManagement {
    repositories {
        // JitPack 远程仓库:https://jitpack.io[NameThatColor-1.7.4-fix.jar](..%2FStudioPlugins%2Fplugin%2FNameThatColor-1.7.4-fix.jar)
        maven { url 'https://jitpack.io' }
    }
}
  • 配置完远程仓库后,在项目 app 模块下的 build.gradle 文件中加入远程依赖
android {
    // 支持 JDK 1.8
    compileOptions {
        targetCompatibility JavaVersion.VERSION_1_8
        sourceCompatibility JavaVersion.VERSION_1_8
    }
}

dependencies {
    // Gson 解析容错:https://github.com/getActivity/GsonFactory
    implementation 'com.github.getActivity:GsonFactory:9.0'
    // Json 解析框架:https://github.com/google/gson
    implementation 'com.google.code.gson:gson:2.10.1'
}
  • 需要注意的是:Gson 框架必须使用 2.9.0 及以上版本,否则将会出现版本兼容问题

使用文档

  • 请使用框架返回的 Gson 对象来代替项目中的 Gson 对象
// 获取单例的 Gson 对象(已处理容错)
Gson gson = GsonFactory.getSingletonGson();
  • 因为框架中的 Gson 对象已经对解析规则进行了容错处理

其他 API

// 设置自定义的 Gson 对象
GsonFactory.setSingletonGson(Gson gson);

// 创建一个 Gson 构建器(已处理容错)
GsonBuilder gsonBuilder = GsonFactory.newGsonBuilder();

// 注册类型适配器
GsonFactory.registerTypeAdapterFactory(TypeAdapterFactory factory);

// 注册构造函数创建器
GsonFactory.registerInstanceCreator(Type type, InstanceCreator<?> creator);

// 添加反射访问过滤器
GsonFactory.addReflectionAccessFilter(ReflectionAccessFilter filter);

// 设置 Json 解析容错回调对象
GsonFactory.setParseExceptionCallback(ParseExceptionCallback callback);

框架混淆规则

  • 在混淆规则文件 proguard-rules.pro 中加入
-keep class com.hjq.gson.factory.** {*;}

数据类型容错介绍

  • 目前支持容错的数据类型有:

    • Bean 类

    • 数组集合

    • Map 集合

    • JSONArray

    • JSONObject

    • String(字符串)

    • boolean / Boolean(布尔值)

    • int / Integer(整数,属于数值类)

    • long / Long(长整数,属于数值类)

    • float / Float(单精度浮点数,属于数值类)

    • double / Double(双精度浮点数,属于数值类)

    • BigDecimal(精度更高的浮点数,属于数值类)

  • 基本涵盖 99.99% 的开发场景,可以运行 Demo 中的单元测试用例来查看效果:

数据类型 容错的范围 数据示例
bean 集合、字符串、布尔值、数值 []""false0
集合 bean、字符串、布尔值、数值 {}""false0
字符串 bean、集合、布尔值、数值 {}[]false0
布尔值 bean、集合、字符串、数值 {}[]""0
数值 bean、集合、字符串、布尔值 {}[]""false
  • 大家可能觉得 Gson 解析容错没什么,那是因为我们对 Gson 解析失败的场景没有了解过:

    • 惊吓不断:后台有数据时返回 JsonObject 类型,没数据返回 [],Gson 会直接抛出异常

    • 意想不到:如果客户端定义的是 boolean 类型,但是后台返回的是 0 或者 1,Gson 会直接抛出异常

    • 措手不及:如果客户端定义的是 int 或者 long 类型,但是后台返回的是 float 或者 double 类型,Gson 会直接抛出异常

  • 以上情况框架已经做了容错处理,具体处理的规则如下:

    • 如果后台返回的类型和客户端定义的类型不匹配,框架就跳过解析这个字段,不影响其他字段正常解析

    • 如果客户端定义的是 boolean 类型,但是后台返回整数,框架则将非 0 的数值则赋值为 true,否则为 false

    • 如果客户端定义的是 int 或者 long 类型,但后台返回浮点数,框架就对数值进行直接取整并赋值给字段

适配 Kotlin 空值介绍

  • 如果你在 Kotlin 中定义了以下内容的 Bean 类
class XxxBean {
    
    val age: Int = 18
}
  • 大家是不是以为在后台返回 { "age" : null }age 字段的值会等于 18 ?我帮大家测试过了,不会等于 18,会等于空。

  • 那么这到底是为什么呢?聊到这个就不得不先说一下 Gson 解析的机制,我们都知道 Gson 在解析一个 Bean 类的时候,会反射创建一个对象出来,但是大家不知道的是,Gson 会根据 Bean 类的字段名去解析 Json 串中对应的值,然后简单粗暴进行反射赋值,你没有听错,简单粗暴,如果后台返回这个 age 字段的值为空,那么 age 就会被赋值为空,但是你又在 Kotlin 中声明了 age 变量不为空,外层一调用,触发 NullPointerException 也是在预料之中。

  • 另外针对 List 和 Map 类型的对象,后台如果有返回 null 或者错误类型数据的时候,框架也会返回一个不为空但是集合大小为 0 的 List 对象或者 Map 对象,避免在 Kotlin 字段上面自定义字段不为空,但是后台返回空的情况导致出现的空指针异常。

  • 框架目前的处理方案是,如果后台没有返回这个字段的值,又或者返回这个值为空,则不会赋值给类的字段,因为 Gson 那样做是不合理的,会导致我在 Kotlin 上面使用 Gson 是有问题,变量不定义成可空,每次用基本数据类型还得去做判空,定义成非空,一用还会触发 NullPointerException,前后夹击,腹背受敌。

适配 Kotlin 默认值介绍

  • 如果你在 Kotlin 中定义了以下内容的 Bean 类
data class DataClassBean(val name: String?, val age: Int = 18)
  • 如果丢给 Gson 解析,最终会得到以下结果
name = null
age = 0
  • age 为什么不等于 18?为什么会等于 0 呢?要知道这个问题的原因,我们需要反编译看一下 DataClassBean 的源码
public final class DataClassBean {
    private final int age;
    private final String name;

    public static /* synthetic */ DataClassBean copy$default(DataClassBean bean, String str, int i, int i2, Object obj) {
        if ((i2 & 1) != 0) {
            str = bean.name;
        }
        if ((i2 & 2) != 0) {
            i = bean.age;
        }
        return bean.copy(str, i);
    }

    public final String component1() {
        return this.name;
    }

    public final int component2() {
        return this.age;
    }

    public final DataClassBean copy(String str, int i) {
        return new DataClassBean(str, i);
    }

    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (obj instanceof DataClassBean) {
            DataClassBean bean = (DataClassBean) obj;
            return Intrinsics.areEqual(this.name, bean.name) && this.age == bean.age;
        }
        return false;
    }

    public int hashCode() {
        String str = this.name;
        return ((str == null ? 0 : str.hashCode()) * 31) + this.age;
    }

    public String toString() {
        return "DataClassBean(name=" + ((Object) this.name) + ", age=" + this.age + ')';
    }

    public DataClassBean(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public /* synthetic */ DataClassBean(String str, int i, int i2, DefaultConstructorMarker defaultConstructorMarker) {
        this(str, (i2 & 2) != 0 ? 18 : i);
    }

    public final int getAge() {
        return this.age;
    }

    public final String getName() {
        return this.name;
    }
}
  • 不知道大家发现问题没有?DataClassBean 类里面并没有空参构造函数,那 Gson 到底是怎么创建对象的呢?让我们看一段源码
package com.google.gson.internal;

public final class ConstructorConstructor {

  public <T> ObjectConstructor<T> get(TypeToken<T> typeToken) {
  
    ......

    ObjectConstructor<T> defaultConstructor = newDefaultConstructor(rawType, filterResult);
    if (defaultConstructor != null) {
      return defaultConstructor;
    }
  
    ......
  
    if (filterResult == FilterResult.ALLOW) {
      // finally try unsafe
      return newUnsafeAllocator(rawType);
    } else {
      ........
    }
  }
  
  private <T> ObjectConstructor<T> newUnsafeAllocator(final Class<? super T> rawType) {

    ......

    ObjectConstructor<T> defaultConstructor = newDefaultConstructor(rawType, filterResult);
    if (defaultConstructor != null) {
      return defaultConstructor;
    }
  
    ......

    if (useJdkUnsafe) {
      return new ObjectConstructor<T>() {
        @Override public T construct() {
          try {
            @SuppressWarnings("unchecked")
            T newInstance = (T) UnsafeAllocator.INSTANCE.newInstance(rawType);
            return newInstance;
          } catch (Exception e) {
            throw new RuntimeException(("Unable to create instance of " + rawType + "."
                + " Registering an InstanceCreator or a TypeAdapter for this type, or adding a no-args"
                + " constructor may fix this problem."), e);
          }
        }
      };
    } else {
      ......
    }
  }
  
  private static <T> ObjectConstructor<T> newDefaultConstructor(Class<? super T> rawType, FilterResult filterResult) {

    ......

    final Constructor<? super T> constructor;
    try {
      constructor = rawType.getDeclaredConstructor();
    } catch (NoSuchMethodException e) {
      return null;
    }

    ......

    return new ObjectConstructor<T>() {
      @Override public T construct() {
        try {
          @SuppressWarnings("unchecked") // T is the same raw type as is requested
          T newInstance = (T) constructor.newInstance();
          return newInstance;
        }
        // Note: InstantiationException should be impossible because check at start of method made sure
        //   that class is not abstract
        catch (InstantiationException e) {
          throw new RuntimeException("Failed to invoke constructor '" + ReflectionHelper.constructorToString(constructor) + "'"
              + " with no args", e);
        } catch (InvocationTargetException e) {
          // TODO: don't wrap if cause is unchecked?
          // TODO: JsonParseException ?
          throw new RuntimeException("Failed to invoke constructor '" + ReflectionHelper.constructorToString(constructor) + "'"
              + " with no args", e.getCause());
        } catch (IllegalAccessException e) {
          throw ReflectionHelper.createExceptionForUnexpectedIllegalAccess(e);
        }
      }
    };
  }
}
package com.google.gson.internal;

public abstract class UnsafeAllocator {

  public abstract <T> T newInstance(Class<T> c) throws Exception;

  public static final UnsafeAllocator INSTANCE = create();

  private static UnsafeAllocator create() {
    // try JVM
    // public class Unsafe {
    //   public Object allocateInstance(Class<?> type);
    // }
    try {
      Class<?> unsafeClass = Class.forName("sun.misc.Unsafe");
      Field f = unsafeClass.getDeclaredField("theUnsafe");
      f.setAccessible(true);
      final Object unsafe = f.get(null);
      final Method allocateInstance = unsafeClass.getMethod("allocateInstance", Class.class);
      return new UnsafeAllocator() {
        @Override
        @SuppressWarnings("unchecked")
        public <T> T newInstance(Class<T> c) throws Exception {
          assertInstantiable(c);
          return (T) allocateInstance.invoke(unsafe, c);
        }
      };
    } catch (Exception ignored) {
      // OK: try the next way
    }

    ......
  }
}
  • 相信你看完就懂了,Gson 确实是反射创建无参构造函数来创建对象,但是如果没有空参构造函数的情况下,它也会通过另外的手段创建对象,借助 sun.misc.Unsafe 创建对象,这样会有一个问题,这样创建出来的对象它不会走任何构造函数,通过查看刚刚反编译出来的 DataClassBean 类,就知道为什么这样 Kotlin 默认值都不会生效了

  • 框架的做法很简单,既然没有无参构造函数,那我就通过其他构造函数来创建,就拿 Kotlin 生成的 DataClassBean(String str, int i, int i2, DefaultConstructorMarker defaultConstructorMarker) 来创建对象

这个构造函数特别有意思,最后第一个参数是 DefaultConstructorMarker 类,里面啥也没有

public final class DefaultConstructorMarker {
    private DefaultConstructorMarker() {
    }
}
  • 最后第二个参数是参数标记,标记是否使用 data class 定义的默认值
public final class DataClassBean {

    public /* synthetic */ DataClassBean(String str, int i, int i2, DefaultConstructorMarker defaultConstructorMarker) {
        this(str, (i2 & 2) != 0 ? 18 : i);
    }

    public DataClassBean(String name, int age) {
        this.name = name;
        this.age = age;
    }
}
  • 框架的解决方案是:反射最后第一个参数类型为 DefaultConstructorMarker,然后传入空对象即可,最后第二个参数类型为 int 的构造函数,并且让最后第二个参数的位运算逻辑为 true,让它走到默认值赋值那里,这样可以选择传入 Integer.MAX_VALUE,这样每次使用它去 & 不大于 0 的某个值,都会等于某个值,也就是不会等于 0,这样就能保证它的运算条件一直为 true,也就是使用默认值,其他参数传值的话,如果是基本数据类型,就传入基本数据类型的默认值,如果是对象类型,则直接传入 null。这样就解决了 Gson 反射 Kotlin Data Class 类出现字段默认值不生效的问题。

常见疑问解答

Retrofit 怎么替换 Gson?

Retrofit retrofit = new Retrofit.Builder()
        .addConverterFactory(GsonConverterFactory.create(GsonFactory.getSingletonGson()))
        .build();

如何替换项目中已有的原生 Gson ?

// 替换调用
new Gson()
GsonFactory.getSingletonGson()
// 替换导包
import com.google.gson.Gson
import com.hjq.gson.factory.GsonFactory
// 再手动处理一些没有替换成功的
new GsonBuilder()

有没有必要处理 Json 解析容错?

  • 我觉得非常有必要,因为后台返回的数据结构是什么样我们把控不了,但是有一点是肯定的,我们都不希望它崩,因为一个接口的失败导致整个 App 崩溃退出实属不值得,但是 Gson 很敏感,动不动就崩。

我们后台用的是 Java,有必要处理容错吗?

  • 如果你们的后台用的是 PHP,那我十分推荐你使用这个框架,因为 PHP 返回的数据结构很乱,这块经历过的人都懂,说多了都是泪,没经历过的人怎么说都不懂。

  • 如果你们的后台用的是 Java,那么可以根据实际情况而定,可用可不用,但是最好用,作为一种兜底方案,这样就能防止后台突然某一天不讲码德,例如我现在的公司的后台全是用 Java 开发的,但是 Bugly 还是有上报关于 Gson 解析的异常,下面是通过 GsonFactory.setParseExceptionCallback 采集到的数据,大家可以参考参考:

  • 粗略估算了一下,总共上报了三千万多次错误,影响设备数三百多万,看到这里你还相信 Java 开发的后台不会有数据容错的问题么?事在人为,Java 只是一种开发语言,并不能担保不会有数据容错的问题,如果后续真的出现了这种问题,主要分为两种情况:

    • 如果 iOS 没有做数据容错,那么锅是后台的,这点是毫无疑问的,后台想甩也甩不开

    • 如果 iOS 做了数据容错,那么很可能的处理结果是,后台和 Android 两端的人要被拉出去各挨五十大板:

      • CTO:后台有问题,为什么 iOS 没事,就 Android 有事?

      • Android:iOS 那边做了容错,但是 Android 这边没有做。

      • CTO:你们为什么不做?这种兜底机制本来不应该就得有的吗?

      • Android:我们用了 Gson 框架,它的机制本身就是这样的。

      • CTO:我不管你们用了什么框架,在这点上崩溃了就是你们不对,后台返回错误的数据结构你们不解析就好了,你为什么还崩溃?同样的数据结构咋 iOS 就没有事呢?这个责任应该有你们 Android 一份子。

      • Android:。。。。。。(哑巴吃黄连,有苦说不出)

      • CTO 内心 OS:整个后台都是我在管的,出现这种事情,后面可能会吃不了兜着走,现在幸好拉到一个做垫背的来分担一下事故的责任。

      • Ps:以上故事纯属虚构,大家看看就好,切勿太过当真。不过有一点是真的,若不想日后扯皮,最好还是要留一手。

使用了这个框架后,我如何知道出现了 Json 错误,从而保证问题不被掩盖?

  • 对于这个问题,解决方案也很简单,使用 GsonFactory.setParseExceptionCallback API,如果后台返回了错误的数据结构,在调试模式下,直接抛出异常即可,开发者可以第一时间得知;而到了线上模式,对这个问题进行上报即可,保证不漏掉任何一个问题(可上传到后台或者 Bugly 错误列表中),示例代码如下:
// 设置 Json 解析容错监听
GsonFactory.setParseExceptionCallback(new ParseExceptionCallback() {

    @Override
    public void onParseObjectException(TypeToken<?> typeToken, String fieldName, JsonToken jsonToken) {
        handlerGsonParseException("解析对象析异常:" + typeToken + "#" + fieldName + ",后台返回的类型为:" + jsonToken);
    }

    @Override
    public void onParseListException(TypeToken<?> typeToken, String fieldName, JsonToken listItemJsonToken) {
        handlerGsonParseException("解析 List 异常:" + typeToken + "#" + fieldName + ",后台返回的条目类型为:" + listItemJsonToken);
    }

    @Override
    public void onParseMapException(TypeToken<?> typeToken, String fieldName, String mapItemKey, JsonToken mapItemJsonToken) {
        handlerGsonParseException("解析 Map 异常:" + typeToken + "#" + fieldName + ",mapItemKey = " + mapItemKey + ",后台返回的条目类型为:" + mapItemJsonToken);
    }
    
    private void handlerGsonParseException(String message) {
        Log.e(TAG, message);
        if (BuildConfig.DEBUG) {
            throw new IllegalArgumentException(message);
        }  else {
            CrashReport.postCatchedException(new IllegalArgumentException(message));
        }
    }
});

作者的其他开源项目

微信公众号:Android轮子哥

Android 技术 Q 群:10047167

如果您觉得我的开源库帮你节省了大量的开发时间,请扫描下方的二维码随意打赏,要是能打赏个 10.24 :monkey_face:就太:thumbsup:了。您的支持将鼓励我继续创作:octocat:(点击查看捐赠列表

广告区

  • 我现在任腾讯云服务器推广大使,大家如果有购买服务器的需求,可以通过下面的链接购买

【腾讯云】云服务器、云数据库、COS、CDN、短信等云产品特惠热卖中

【腾讯云】中小企业福利专场,多款刚需产品,满足企业通用场景需求

License

Copyright 2020 Huang JinQun

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

   http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Apache License Version 2.0, December 2020 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright 2020 Huang JinQun Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.

简介

暂无描述 展开 收起
Kotlin 等 2 种语言
Apache-2.0
取消

发行版

暂无发行版

贡献者

全部

近期动态

加载更多
不能加载更多了
Kotlin
1
https://gitee.com/yu_xh/GsonFactory.git
git@gitee.com:yu_xh/GsonFactory.git
yu_xh
GsonFactory
GsonFactory
master

搜索帮助