たくさんの自由帳
Androidのお話
たくさんの自由帳
投稿日 : | 0 日前
文字数(だいたい) : 2157
うそつくな
Android
の@NonNull
アノテーションはたまによくnull
を渡す。これがJava
なら問題なかったけど、Kotlin
だと例外で落ちてしまうって話。
本記事で言う@NonNull
はandroid.annotation
、androidx.annotation
のことです。
多分2パターン存在します。
.java
を作る必要があるので、なんか負けた気分になります(が、Android
が@NonNull
守らないのが悪いので仕方ない、、、)
例えば、こんな感じにJavaで書かれたインターフェースに@NonNull
がついている場合
(以下はAndroid
のOnGestureListener
です)
public interface OnGestureListener {
boolean onDown(@NonNull MotionEvent e);
void onShowPress(@NonNull MotionEvent e);
boolean onSingleTapUp(@NonNull MotionEvent e);
boolean onScroll(@NonNull MotionEvent e1, @NonNull MotionEvent e2, float distanceX, float distanceY);
void onLongPress(@NonNull MotionEvent e);
boolean onFling(@NonNull MotionEvent e1, @NonNull MotionEvent e2, float velocityX, float velocityY);
}
自前でこのインターフェースを継承するインターフェースを作成し、@NonNull
を@Nullable
に置き換えます。
このときJavaで作成する必要があります。
import android.view.GestureDetector;
import android.view.MotionEvent;
import androidx.annotation.Nullable;
/** OnGestureListener を Nullable にしたもの */
public interface NullableOnGestureListener extends GestureDetector.OnGestureListener {
@Override
boolean onDown(@Nullable MotionEvent e);
@Override
void onShowPress(@Nullable MotionEvent e);
@Override
boolean onSingleTapUp(@Nullable MotionEvent e);
@Override
boolean onScroll(@Nullable MotionEvent e1, @Nullable MotionEvent e2, float distanceX, float distanceY);
@Override
void onLongPress(@Nullable MotionEvent e);
@Override
boolean onFling(@Nullable MotionEvent e1, @Nullable MotionEvent e2, float velocityX, float velocityY);
}
これで、Kotlin
でもNullable
として扱ってくれるので、null
が入ってきた場合でも落ちなくなります。
MotionEvent
が全部Nullable
になりました。やったね
class MainActivity : ComponentActivity(), NullableOnGestureListener {
override fun onDown(e: MotionEvent?): Boolean {
// TODO
}
override fun onShowPress(e: MotionEvent?) {
// TODO
}
override fun onSingleTapUp(e: MotionEvent?): Boolean {
// TODO
}
override fun onScroll(e1: MotionEvent?, e2: MotionEvent?, distanceX: Float, distanceY: Float): Boolean {
// TODO
}
override fun onLongPress(e: MotionEvent?) {
// TODO
}
override fun onFling(e1: MotionEvent?, e2: MotionEvent?, velocityX: Float, velocityY: Float): Boolean {
// TODO
}
// 省略...
}
これはやっていいのかわからないのですが、どうしてもKotlin
で完結させたい場合は使えます。
Suppress
で黙らせるやつですね。引数がNonNull
からNullable
になるだけ(引数が増えているわけではない)なので、おそらく実行時に落ちることはないと思いますが、、、
@Suppress("NOTHING_TO_OVERRIDE", "ACCIDENTAL_OVERRIDE", "ABSTRACT_MEMBER_NOT_IMPLEMENTED")
class MainActivity : ComponentActivity(), GestureDetector.OnGestureListener {
override fun onDown(e: MotionEvent?): Boolean {
// TODO
}
override fun onShowPress(e: MotionEvent?) {
// TODO
}
override fun onSingleTapUp(e: MotionEvent?): Boolean {
// TODO
}
override fun onScroll(e1: MotionEvent?, e2: MotionEvent?, distanceX: Float, distanceY: Float): Boolean {
// TODO
}
override fun onLongPress(e: MotionEvent?) {
// TODO
}
override fun onFling(e1: MotionEvent?, e2: MotionEvent?, velocityX: Float, velocityY: Float): Boolean {
// TODO
}
// 省略....
}
java.lang.NullPointerException: Parameter specified as non-null is null: method kotlin.jvm.internal.Intrinsics.checkNotNullParameter, parameter cellInfo
at io.github.takusan23.newradiosupporter.tool.NetworkCallbackTool$listenNetworkStatus$1$callback$1.onCellInfoChanged(Unknown Source:2)
at android.telephony.TelephonyCallback$IPhoneStateListenerStub.lambda$onCellInfoChanged$18(TelephonyCallback.java:1504)
at android.telephony.TelephonyCallback$IPhoneStateListenerStub$$ExternalSyntheticLambda41.run(Unknown Source:4)
at android.os.Handler.handleCallback(Handler.java:938)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loopOnce(Looper.java:346)
at android.os.Looper.loop(Looper.java:475)
at android.app.ActivityThread.main(ActivityThread.java:7889)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:548)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1009)
onCellInfoChanged
でNullPointerException
になってしまう。
原因はonCellInfoChanged
をAndroid
が呼び出す際にcellInfo
をnull
で渡しているため、、、
/**
* Interface for cell info listener.
*/
public interface CellInfoListener {
/**
* Callback invoked when a observed cell info has changed or new cells have been added
* or removed on the registered subscription.
* Note, the registration subscription ID s from {@link TelephonyManager} object
* which registers TelephonyCallback by
* {@link TelephonyManager#registerTelephonyCallback(Executor, TelephonyCallback)}.
* If this TelephonyManager object was created with
* {@link TelephonyManager#createForSubscriptionId(int)}, then the callback applies to the
* subscription ID. Otherwise, this callback applies to
* {@link SubscriptionManager#getDefaultSubscriptionId()}.
*
* @param cellInfo is the list of currently visible cells.
*/
@RequiresPermission(allOf = {
Manifest.permission.READ_PHONE_STATE,
Manifest.permission.ACCESS_FINE_LOCATION
})
void onCellInfoChanged(@NonNull List<CellInfo> cellInfo);
}
これ単純にビルド時にnull
の可能性があるときに警告を出すものなので、ライブラリ開発者とかは意識するといいかもしれないですが、それ以外ならぶっちゃけ無くてもまあ、、、
public class NonNullCheck {
NonNullCheck() {
methodA("はい");
// 警告が出るだけでビルドは通ってしまう
methodA(null);
}
private void methodA(@NonNull String argA) {
// NonNull でも null 入るときは入るので
if (argA == null) {
return;
}
System.out.println(argA);
}
}
Kotlin
はNull
を厳格にチェックするため、?
がついていない引数はビルドも通らないし、実行時も落ちるようになっています。
class NonNullCheckKt {
init {
methodA("はい")
// ビルドは通らない
methodA(null)
}
fun methodA(argA: String) {
// argA は null にはならない
}
}
また実行時にもNonNull
は機能し、関数内に引数がnull
ではないことを確認する処理が自動で挿入されています。
(全然Androidとは関係ないKotlin
プロジェクトだけど変わらないはず)
/**
* ドロップ数を返す。失敗したら1
*
* @param itemStack アイテム
* */
private fun getDropSize(itemStack: ItemStack): Int {
return itemStack.name.string.toIntOrNull() ?: 1
}
逆コンパイルするとnullチェック
する関数が挿入されている
private final int getDropSize(class_1799 itemStack) {
Intrinsics.checkNotNullExpressionValue(itemStack.method_7964().getString(), "itemStack.name.string");
StringsKt.toIntOrNull(itemStack.method_7964().getString());
return (StringsKt.toIntOrNull(itemStack.method_7964().getString()) != null) ? StringsKt.toIntOrNull(itemStack.method_7964().getString()).intValue() : 1;
}
さて、@NonNull
でも実行時はnull
を渡す可能性がある話をしましたが、、、いかが。
まぁ予想通りKotlin
でもアノテーションを尊重してNonNull
として扱われます。
https://kotlinlang.org/docs/java-interop.html#nullability-annotations
今回、@NonNull
がJava
の場合に落ちないけど、Kotlin
の場合にいきなり落ちるようになったのはこの影響です。
public interface NonNullInterface {
void onCallback(@NonNull String string);
}
class NonNullCheckKt : NonNullInterface {
override fun onCallback(string: String) {
}
}
以上です。
targetSdk = 33
から、MotionEvent
がNonNull
になった影響でわりとIssue
がちらほら(全然良くない;;)
ちなみに私が引っかかったonCellInfoChanged
に関してもnull
を返さないよう修正されたそうですが、古いバージョンには残り続けるでしょうね、、、
https://issuetracker.google.com/issues/237308373