🧹

【Android】ランタイムでのClassNotFoundExceptionやNoSuchMethodErrorの解決(Vivox利用時)

2023/12/19に公開

前提

  • Unity製のアプリを、UaaLとして書き出し、AndroidStudioでビルドしている。
  • Unityアプリでは、音声通話ライブラリとしてVivoxを利用している。
  • 環境
    • Unity 2022.3.10f1
    • Vivox16.0.0
    • Android Studio Giraffe | 2022.3.1 Patch 1

問題

  • AndroidStudioでビルドしたapkを実機で動かした時、ランタイムで以下のエラーが発生し(Logcatから検出)、Vivoxが機能していない。(AndroidStudioでのビルドはreleaseビルド)
    • AndroidJavaException: java.lang.ClassNotFoundException: com.vivox.vivoxnative.VivoxNative
    • AndroidJavaException: java.lang.NoSuchMethodError: no static method "Lcom/vivox/sdk/jni/androidsdkJNI;.SwigDirector_HttpRequestProcessorBase_process(Lcom/vivox/sdk/jni/HttpRequestProcessorBase;I[B[BI[B[B[BZJ[B[BI[B[I[I[[B[[B)Z"
  • AndroidStudioでのビルド時にエラーは発生しない(ランタイムでのみ発生)
  • UaaLを含むプロジェクトをAndroidStudioでreleaseビルドした場合のみで問題が発生する。AndroidStudioでdebugビルドをしたときや、AndroidStudio上部バーの再生ボタンからビルドした時、Unityから直接apkとしてビルドしたときには問題が発生しない。

解決

  • ビルド時のMinifyによって、クラスやメソッドが読み取れない(後述のR8による圧縮過程で削除されていると思われる)ことが原因。
  • UnityのProjectSettings > Player > Publishing Settingsで、Build > Custom Proguard Fileにチェックを入れる。
  • 自動生成されたproguard-user.txt(Assets/Plugins/Android/proguard-user.txt)に対して、必要なクラスを記述する。
-keep class com.vivox.vivoxnative.** {
   *;
}
-keep class com.vivox.sdk.** {
   *;
}
  • (なお、Unityのビルド前ではなく、AndroidStudioからprogard-user.txtを変更しても反映される。)

AndroidStudioのリリースビルド時の圧縮について

https://developer.android.com/studio/build/shrink-code?hl=ja

  • 以下のタスクをR8が行っている。

アプリのサイズをできる限り小さくするには、リリースビルドで「圧縮」を有効にして、使用されていないコードとリソースを削除する必要があります。圧縮を有効にすると、アプリのクラスとメンバーの名前を短くする「難読化」と、より積極的な戦略を適用してアプリのサイズをさらに小さくする「最適化」によるメリットも得られます。

  • 実際に自分のProjectでも、build.gradle(Module :app)で以下の記述があった(公式docにはプロジェクトレベルのbuild.gradleと書いてあるがプロジェクトレベルには記述されていなかった)
    buildTypes {
        release {
            minifyEnabled true
            shrinkResources true
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
  • R8の行う圧縮は、minifyEnabledtrueにした場合に有効になる。R8がアプリのエントリポイントを特定し、そのエントリポイントからアクセスされうるメソッドや変数等を把握する。これらに含まれなかったコードが「到達不能」とみなされ、削除される。
  • 今回のエラーがまさにそのケースだが、コードの圧縮時に、R8が正確にコードを分析できない場合があることが公式ドキュメントにも記述されている

R8 で正確に分析することが困難な場合もあり、アプリが実際に必要とするコードが削除されてしまうこともあります。以下のようなケースでは、R8 が誤ってコードを削除する可能性があります。
・アプリが Java Native Interface(JNI)からメソッドを呼び出す場合
・アプリが実行時にコードを検索する場合(リフレクションの使用時など)

参考

https://tamilabo.com/2020/07/06/android-nosuch-methods/

Discussion