谷歌的bug:當 CompileSdk 33 遇上Kotlin

語言: CN / TW / HK

最近項目裏compose 要升級到1.3, 要求compile sdk 也要到33版本,大家都知道 一般情況下,我們修改compilesdk 都不會有什麼問題,最多就是一些api的適配,編譯不過啥的, 但是不會引發線上故障,但是這裏要注意了target sdk 的修改 就要複雜的多了, 這裏不多説,只介紹一下 我碰到的一個compilesdk 33的問題

在Compile sdk 33版本中,這個手勢監聽的接口 代碼發生了一些變化:

image.png

在這些接口方法説 參數前面加上了一個NonNull的 註解,這個註解的意思就是 告訴開發者 這個參數不可能為空

image.png

注意了 在<=32的版本中 這個註解是沒有的

image.png

對於java的開發者來説,這個影響微乎其微,但是如果你跟我一樣是kotlin的開發者就要倒黴了,

因為在<=32的時候 你繼承這個接口的時候 會提示你參數要定義成可空的

但是當你升級到33的sdk的時候,你就會發現編譯不過了

image.png

為啥?

因為33的sdk 前面説過了,方法前面有了 不可空的註解了

要讓他編譯過很簡單 我們只要把? 去掉即可

image.png

到這裏還沒結束,最坑的地方來了, 雖然你能編譯過,但是在運行時,有可能會發生crash

image.png

為啥? 熟悉kotlin的人就知道了,當你定義一個參數為不可空的類型的時候,你如果傳了一個null給這個參數,他就會報這個crash了,這種情況常見於 java代碼調用kotlin代碼的時候 這是kotlin編譯器的魔法,有興趣的可以自己反編譯看一下字節碼,實際上,當你定義一個變量為不可空的時候,如果傳值給他 他就會校驗這個值 是不是為null 為null 則直接拋異常

搞清楚問題所在以後 就得想想怎麼解決了,目前的情況就是 如果不改,就編譯不過,改了 在運行時會crash

另外: 這裏有個鏈接,可以看下該問題的討論,目前狀態是顯示 谷歌承認了該bug,看狀態顯示fixed,但是不知道為什麼 還沒有推送最新的33 sdk issueTracker

實際上解決這個問題的方法有很多,

方法1: 這個接口的實現 我們不用kotlin寫,用java寫,即可 這個方案最簡單,但是不太優雅

方法2: 魔改下android sdk 33版本的jar包,把註解去掉 這個方案也可以,但是有點麻煩

方法3: asm 字節碼修改,把那個校驗參數為null 就拋異常的代碼刪了就行了。 殺雞焉用牛刀

方法4: 寫一個delegate 即可,以後都用這個代理類去做監聽, 這個方法我認為是最簡單的,一勞永逸,而且成本極低

```

import android.content.Context; import android.os.Handler; import android.view.GestureDetector; import android.view.MotionEvent;

import androidx.annotation.Nullable;

/ * 在compile sdk 33 中 修復google的一個註解bug,該bug 會導致 要麼kotlin代碼編譯失敗 * 要麼運行時crash,這裏用代理模式 簡單的規避此問題即可 * */ public class GestureDetectorDelegate extends GestureDetector { / * @param listener * @param handler * @deprecated */ public GestureDetectorDelegate(OnGestureListener listener, Handler handler) { super(listener, handler); }

/**
 * @param listener
 * @deprecated
 */
public GestureDetectorDelegate(OnGestureListener listener) {
    super(listener);
}

public GestureDetectorDelegate(Context context, OnGestureListenerDelegate listener) {
    super(context, listener);
}

public GestureDetectorDelegate(Context context, OnGestureListener listener, Handler handler) {
    super(context, listener, handler);
}

public GestureDetectorDelegate(Context context, OnGestureListener listener, Handler handler, boolean unused) {
    super(context, listener, handler, unused);
}

/**
 * 主要修改點就是在這裏了,複寫這些方法 標記這些參數為可空的即可
 */
public interface OnGestureListenerDelegate extends OnGestureListener {
    boolean onDown(@Nullable  MotionEvent e);

    void onShowPress(@Nullable   MotionEvent e);

    boolean onSingleTapUp(@Nullable   MotionEvent e);

    boolean onScroll(@Nullable  MotionEvent e1, @Nullable  MotionEvent e2, float distanceX, float distanceY);

    void onLongPress(@Nullable  MotionEvent e);

    boolean onFling(@Nullable  MotionEvent e1, @Nullable  MotionEvent e2, float velocityX, float velocityY);
}

} ```