日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > Android >内容正文

Android

Android避坑指南,Gson与Kotlin碰撞出一个不安全的操作

發布時間:2024/3/12 Android 31 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Android避坑指南,Gson与Kotlin碰撞出一个不安全的操作 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

本文已經授權「鴻洋」公眾號原創首發。

最近發現微信多了個專輯功能,可以把一系列的原創文章聚合,剛好我每周都會遇到很多同學問我各種各樣的問題,部分問題還是比較有意義的,我會在周末詳細的寫demo驗證,簡單擴展一下寫成文章分享給大家。

當然不鼓勵大家隨便私聊我問問題,大家可以去星球提問,公眾號后臺回復「星球」就能看到入口了,那里有5000多人,我畢竟還是有工作要忙。

先看一個問題

來一起看一段代碼:

public class Student {private Student() {throw new IllegalArgumentException("can not create.");}public String name; }

我們如何通過Java代碼創建一個Student對象?

我們先想下通過Java創建對象大概有哪些方式:

  • new Student() // 私有
  • 反射調用構造方法 //throw ex
  • 反序列化 // 需要實現相關序列化接口
  • clone // 需要實現clone相關接口
  • 好了,已經超出我的知識點范疇了。

    不免心中嘀咕:

    這題目太偏了,毫無意義,而且文章標題是 Android 避坑指南,看起來毫無關系

    是的,確實很偏,跳過這個問題,我們往下看,看看是怎么在Android開發過程中遇到的,而且看完后,這個問題就迎刃而解了。

    問題的來源

    上周一個群有個小伙伴,遇到了一個Kotlin寫的Bean,在做Gson將字符串轉化成具體的Bean對象時,發生了一個不符合預期的問題。

    因為是他們項目的代碼,我就不貼了,我寫了個類似的小例子來替代。

    對于Java Bean,kotlin可以用data class,網上也有很多博客表示:

    在 Kotlin 中,不需要自己動手去寫一個 JavaBean,可以直接使用 DataClass,使用 DataClass 編譯器會默默地幫我們生成一些函數。

    我們先寫個Bean:

    data class Person(var name: String, var age: Int) {}

    這個Bean是用于接收服務器數據,通過Gson轉化為對象的。

    簡化一下代碼為:

    val gson = Gson() val person = gson.fromJson<Person>("{\"age\":\"12\"}", Person::class.java)

    我們傳遞了一個json字符串,但是沒有包含key為name的值,并且注意:

    在Person中name的類型是String,也就是說是不允許name=null的

    那么上面的代碼,我運行起來結果是什么呢?

  • 報錯,畢竟沒有傳name的值;
  • 不報錯,name 默認值為"";
  • 不報錯,name=null;
  • 感覺1最合理,也符合Kotlin的空安全檢查。

    驗證一下,修改一下代碼,看一下輸出:

    val gson = Gson() val person = gson.fromJson<Person>("{\"age\":\"12\"}", Person::class.java) println(person.name )

    輸出結果:

    null

    是不是有些奇怪,感覺意外繞過了Kotlin的空類型檢查。

    所以那位出問題的同學,在這里之后數據就出了問題,導致一直排查困難。

    我們再改一下代碼:

    data class Person(var name: String, var age: Int): People(){}

    我們讓Person繼承自People類:

    public class People {public People(){System.out.println("people cons");}}

    在People類的構造方法中打印日志。

    我們都清楚,正常情況下,一般構造子類對象,必然會先執行父類的構造方法。

    運行一下:

    沒有執行父類構造方法,但對象構造出來了

    這里可以猜到,Person對象的構建,并不是常規的構建對象,沒有走構造方法。

    那么它是怎么做到的呢?

    那只能去Gson的源碼中取找答案了。

    找到其怎么做的,其實就相當于解答了我們文首的問題。

    追查原因

    Gson這樣構造出一個對象,但是沒有走父類構造這種,如果真是的這樣,那么是極其危險的。

    會讓程序完全不符合運行預期,少了一些必要邏輯。

    所以我們提前說一下,大家不用太驚慌,并不是Gson很容易出現這樣的情況,而是恰好上例的寫法碰上了,我們一會會說清楚。

    首先我們把Person這個kotlin的類,轉成Java,避免背后藏了一些東西:

    # 反編譯之后的顯示 public final class Person extends People {@NotNullprivate String name;private int age;@NotNullpublic final String getName() {return this.name;}public final void setName(@NotNull String var1) {Intrinsics.checkParameterIsNotNull(var1, "<set-?>");this.name = var1;}public final int getAge() {return this.age;}public final void setAge(int var1) {this.age = var1;}public Person(@NotNull String name, int age) {Intrinsics.checkParameterIsNotNull(name, "name");super();this.name = name;this.age = age;}// 省略了一些方法。 }

    可以看到Person有一個包含兩參的構造方法,并且這個構造方法中有name的空安全檢查。

    也就是說,正常通過這個構造方法構建一個Person對象,是不會出現空安全問題的。

    那么只能去看看Gson的源碼了:

    Gson的邏輯,一般都是根據讀取到的類型,然后找對應的TypeAdapter去處理,本例為Person對象,所以會最終走到ReflectiveTypeAdapterFactory.create然后返回一個TypeAdapter。

    我們看一眼其內部代碼:

    # ReflectiveTypeAdapterFactory.create @Override public <T> TypeAdapter<T> create(Gson gson, final TypeToken<T> type) {Class<? super T> raw = type.getRawType();if (!Object.class.isAssignableFrom(raw)) {return null; // it's a primitive!}ObjectConstructor<T> constructor = constructorConstructor.get(type);return new Adapter<T>(constructor, getBoundFields(gson, type, raw)); }

    重點看constructor這個對象的賦值,它一眼就知道跟構造對象相關。

    # ConstructorConstructor.get public <T> ObjectConstructor<T> get(TypeToken<T> typeToken) {final Type type = typeToken.getType();final Class<? super T> rawType = typeToken.getRawType();// ...省略一些緩存容器相關代碼ObjectConstructor<T> defaultConstructor = newDefaultConstructor(rawType);if (defaultConstructor != null) {return defaultConstructor;}ObjectConstructor<T> defaultImplementation = newDefaultImplementationConstructor(type, rawType);if (defaultImplementation != null) {return defaultImplementation;}// finally try unsafereturn newUnsafeAllocator(type, rawType);}

    可以看到該方法的返回值有3個流程:

  • newDefaultConstructor
  • newDefaultImplementationConstructor
  • newUnsafeAllocator
  • 我們先看第一個newDefaultConstructor

    private <T> ObjectConstructor<T> newDefaultConstructor(Class<? super T> rawType) {try {final Constructor<? super T> constructor = rawType.getDeclaredConstructor();if (!constructor.isAccessible()) {constructor.setAccessible(true);}return new ObjectConstructor<T>() {@SuppressWarnings("unchecked") // T is the same raw type as is requested@Override public T construct() {Object[] args = null;return (T) constructor.newInstance(args);// 省略了一些異常處理};} catch (NoSuchMethodException e) {return null;}}

    可以看到,很簡單,嘗試獲取了無參的構造函數,如果能夠找到,則通過newInstance反射的方式構建對象。

    追隨到我們的Person的代碼,其實該類中只有一個兩參的構造函數,并沒有無參構造,從而會命中NoSuchMethodException,返回null。

    返回null會走newDefaultImplementationConstructor,這個方法里面都是一些集合類相關對象的邏輯,直接跳過。

    那么,最后只能走:**newUnsafeAllocator ** 方法了。

    從命名上面就能看出來,這是個不安全的操作。

    newUnsafeAllocator最終是怎么不安全的構建出一個對象呢?

    往下看,最終執行的是:

    public 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) { }// try dalvikvm, post-gingerbread use ObjectStreamClass // try dalvikvm, pre-gingerbread , ObjectInputStream}

    可以看到Gson在沒有找到無參的構造方法后,通過sun.misc.Unsafe構造了一個對象。

    注意:Unsafe該類并不是所有的Android 版本中都包含,不過目前新版本都包含,所以Gson這個方法中有3段邏輯都是用來生成對象的,你可以認為3重保險,針對不同平臺。 本文測試設備:Android 29模擬器

    我們這里暫時只討論sun.misc.Unsafe,其他的其實一個意思。

    sun.misc.Unsafe和許API?

    Unsafe是位于sun.misc包下的一個類,主要提供一些用于執行低級別、不安全操作的方法,如直接訪問系統內存資源、自主管理內存資源等,這些方法在提升Java運行效率、增強Java語言底層資源操作能力方面起到了很大的作用。但由于Unsafe類使Java語言擁有了類似C語言指針一樣操作內存空間的能力,這無疑也增加了程序發生相關指針問題的風險。在程序中過度、不正確使用Unsafe類會使得程序出錯的概率變大,使得Java這種安全的語言變得不再“安全”,因此對Unsafe的使用一定要慎重。
    https://tech.meituan.com/2019/02/14/talk-about-java-magic-class-unsafe.html

    具體可以參考美團的這篇文章。

    好了,到這里就真相大白了。

    原因是我們Person沒有提供默認的構造方法,Gson在沒有找到默認構造方法時,它就直接通過Unsafe的方法,繞過了構造方法,直接構建了一個對象。

    到這里,我們收獲了:

  • Gson是如何構建對象的?
  • 我們在寫需要Gson轉化為對象的類的時候,一定要記得有默認的構造方法,否則雖然不報錯,但是很不安全!
  • 我們了解到了還有這種Unsafe黑科技的方式構造對象。
  • 回到文章開始的問題

    Java中咋么構造一個下面的Student對象呢?

    public class Student {private Student() {throw new IllegalArgumentException("can not create.");}public String name; }

    我們模仿Gson的代碼,編寫如下:

    try {val unsafeClass = Class.forName("sun.misc.Unsafe")val f = unsafeClass.getDeclaredField("theUnsafe")f.isAccessible = trueval unsafe = f.get(null)val allocateInstance = unsafeClass.getMethod("allocateInstance", Class::class.java)val student = allocateInstance.invoke(unsafe, Student::class.java)(student as Student).apply {name = "zhy"}println(student.name) } catch (ignored: Exception) {ignored.printStackTrace() }

    輸出:

    zhy

    成功構建。

    Unsafe 一點用沒有?

    看到這里,大家可能最大的收獲就是了解Gson構建對象流程,以及以后寫Bean的時候會注意提供默認的無參構造方法,尤其在使用Kotlin data class的時候。

    那么剛才我們所說的Unsafe方法就沒有其他實際用處嗎?

    這個類,提供了類似C語言指針一樣操作內存空間的能力。

    大家都知道在Android P上面,Google限制了app對hidden API的訪問。

    但是,Google不能限制自己對hidden API訪問對吧,所以它自己的相關類,是允許訪問hidden API的。

    那么Google是如何區分是我們app調用,還是它自己調用呢?

    通過ClassLoader,系統認為如果ClassLoader為BootStrapClassLoader則就認為是系統類,則放行。

    那么,我們突破P訪問限制,其中一個思路就是,搞一個類,把它的ClassLoader換成BootStrapClassLoader,從而可以反射任何hidden api。

    怎么換呢?

    只要把這個類的classLoader成員變量設置為null就可以了。

    參考代碼:

    private void testJavaPojie() {try {Class reflectionHelperClz = Class.forName("com.example.support_p.ReflectionHelper");Class classClz = Class.class;Field classLoaderField = classClz.getDeclaredField("classLoader");classLoaderField.setAccessible(true);classLoaderField.set(reflectionHelperClz, null);} catch (Exception e) {e.printStackTrace();} } 來自:https://juejin.im/post/5ba0f3f7e51d450e6f2e39e0

    但是這樣有個問題,上面的代碼用到了反射修改一個類的classLoader成員,假設google有一天把反射設置classLoader也完全限制掉,就不行了。

    那么怎么辦?原理還是換ClassLoader,但是我們不走Java反射的方式了,而是用Unsafe:

    @Keep public class ReflectWrapper {//just for finding the java.lang.Class classLoader field's offset@Keepprivate Object classLoaderOffsetHelper;static {try {Class<?> VersionClass = Class.forName("android.os.Build$VERSION");Field sdkIntField = VersionClass.getDeclaredField("SDK_INT");sdkIntField.setAccessible(true);int sdkInt = sdkIntField.getInt(null);if (sdkInt >= 28) {Field classLoader = ReflectWrapper.class.getDeclaredField("classLoaderOffsetHelper");long classLoaderOffset = UnSafeWrapper.getUnSafe().objectFieldOffset(classLoader);if (UnSafeWrapper.getUnSafe().getObject(ReflectWrapper.class, classLoaderOffset) instanceof ClassLoader) {Object originalClassLoader = UnSafeWrapper.getUnSafe().getAndSetObject(ReflectWrapper.class, classLoaderOffset, null);} else {throw new RuntimeException("not support");}}} catch (Exception e) {throw new RuntimeException(e);}} } 來自作者區長:一種純 Java 層繞過 Android P 私有函數調用限制的方式,一文。

    Unsafe賦予了我們操作內存的能力,也就能完成一些平時只能依賴C++完成的代碼。

    好了,從一位朋友遇到的問題,由此引發了一整篇文章的討論,希望你能有所收獲。

    感謝郭霖,淡藍色星期三,天空等朋友。

    總結

    以上是生活随笔為你收集整理的Android避坑指南,Gson与Kotlin碰撞出一个不安全的操作的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。

    主站蜘蛛池模板: 射死你天天日 | 久久国产主播 | 国产成人精品女人久久久 | 久久三区 | 高清欧美性猛交xxxx黑人猛交 | 色中色综合 | 国产又粗又猛又爽又黄的视频小说 | 天天天天躁天天爱天天碰2018 | 高清无打码 | xx99小雪| 小小姑娘电影大全免费播放 | 亚洲不卡免费视频 | 人人爱操| 国产毛片毛片毛片毛片毛片毛片 | 大桥未久av在线播放 | a级片免费观看 | 亚洲欧美日韩色图 | 中文字幕无人区二 | 日韩精品福利在线 | 亚洲视频一区二区在线观看 | 成人免费毛片网站 | 久久亚洲精品小早川怜子 | 国产精品人八做人人女人a级刘 | 国产黄色高清视频 | 免费观看成人鲁鲁鲁鲁鲁视频 | 欧美黄色三级视频 | 国产亚洲女人久久久久毛片 | 娇妻被老王脔到高潮失禁视频 | 欧美黄色a视频 | 日韩毛片视频 | 91久久国产综合久久91精品网站 | 3d动漫精品啪啪一区二区免费 | 人人干人人澡 | 欧美色吊丝 | 欧美第一视频 | 欧美三级久久 | 亚洲精品在线一区二区 | 色中色在线视频 | 国产乱人伦精品一区二区 | 57pao国产成永久免费视频 | 日本一区二区在线免费观看 | 美女干b视频 | 免费看黄色片的网站 | 午夜福利三级理论电影 | 国内精品免费视频 | 中文字幕91在线 | 二区三区偷拍浴室洗澡视频 | 亚洲一区在线观看视频 | 久草新视频 | 日韩女同强女同hd | 日本五十路女优 | 麻豆精品久久 | 一本到免费视频 | 国产3p精品一区 | 亚洲av成人无码久久精品老人 | 亚洲一区二区三区久久久成人动漫 | 三级做爰在线观看视频 | 日日日日操 | 99免费| 97国产一区| 久草中文在线视频 | 香蕉视频官网 | 中国女人毛片 | 国产精品毛片一区二区在线看舒淇 | 亚洲成人一区在线观看 | 草逼国产 | 成人综合区 | 国产成人久久精品流白浆 | 夜夜骑夜夜骑 | 亚洲成人xxx| 国精产品乱码一区一区三区四区 | 婷婷综合一区 | 日韩一区在线免费观看 | 欧美性猛交xxxx黑人 | 国产欧美不卡 | 国产日韩欧美一区二区东京热 | 欧美精品福利视频 | 特级毛片在线 | 被绑在床强摁做开腿呻吟 | 午夜在线精品 | 中文字幕一本 | 成人h动漫在线 | 中文字幕五码 | av猫咪| 免费在线视频一区 | 日韩中文字幕精品视频 | 女人一区二区 | 亚洲AV成人无码久久精品巨臀 | 久久日本精品字幕区二区 | 91精品国产手机 | 国产ts在线| 美女精品 | 特级丰满少妇一级aaa爱毛片 | 国模私拍在线观看 | 人人澡人人澡人人澡 | 国产夜夜夜 | 女人张开腿让男人桶爽 | 性激情视频 | 欧美 亚洲 一区 |