2015/01/27

ActionBarActivity#setSupportActionBar() で NoClassDefFoundError が発生する端末がある

私が Google Play で公開しているアプリのクラッシュ報告に、以下のような Stack Trace が送られてきました。
java.lang.NoClassDefFoundError: android.support.v7.internal.view.menu.MenuBuilder
at android.support.v7.widget.ActionMenuView.getMenu(ProGuard:620)
at android.support.v7.widget.Toolbar.ensureMenu(ProGuard:825)
at android.support.v7.widget.Toolbar.getMenu(ProGuard:817)
at android.support.v7.internal.app.ToolbarActionBar.getMenu(ProGuard:554)
at android.support.v7.internal.app.ToolbarActionBar.setListMenuPresenter(ProGuard:558)
at android.support.v7.app.ActionBarActivityDelegateBase.setSupportActionBar(ProGuard:178)
at android.support.v7.app.ActionBarActivity.setSupportActionBar(ProGuard:92)
at com.kokufu.android.apps.divelogbook.MainActivity.onCreate(ProGuard:84)
at android.app.Activity.performCreate(Activity.java:5122)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1150)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2315)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2403)
at android.app.ActivityThread.access$600(ActivityThread.java:165)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1373)
at android.os.Handler.dispatchMessage(Handler.java:107)
at android.os.Looper.loop(Looper.java:194)
at android.app.ActivityThread.main(ActivityThread.java:5391)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:525)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:833)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:600)
at dalvik.system.NativeStart.main(Native Method)

原因

調べてみると、Samsung やその他一部の端末で発生するバグらしい。

対策

これ、appcompat-v7 のバグではなく、OS のバグだと思われます。 OS に古い appcompat-v7 が組み込まれているのが問題ではないかと。
つまり、アプリ側ではどうしようもない。 …と諦めかけてたのですが、ProGuard を使って回避するナイスなアイデアが!

難読化をしない場合、以下の一行を ProGuard の設定ファイルに記述すればオッケーです。
-keep class !android.support.v7.internal.view.menu.**,** {*;}

他にも難読化したい場合には、以下の方が無難でしょう。
-keep class !android.support.v7.internal.view.menu.**,android.support.v7.** { *; }
-keep interface !android.support.v7.internal.view.menu.**,android.support.v7.** { *; }

出力された mapping ファイルを確認し、MenuBuilder の名前が変わっていることを確認しておきます。

build/outputs/mapping/release/mapping.txt
android.support.v7.internal.view.menu.MenuBuilder -> android.support.v7.internal.view.menu.i:

私の手元には、この問題が起こる端末が無いため、動作確認出来ていません。
名前変えただけで、class path の問題を回避できるんだか、ちょっと不安です。
もし、確認された方がいらっしゃいましたら、是非教えてください。
2015/01/20

Android 5.0 で LVL を使うと IllegalArgumentException が発生する

Android Developer としては大分遅いほうだと思いますが、最近やっと手持ちの Nexus 7 を Android 5.0 にしました。
そこで、LVL を適用したアプリを立ち上げてみたところ、以下のようなエラーが。

Caused by: java.lang.IllegalArgumentException: Service Intent must be explicit: Intent { act=com.android.vending.licensing.ILicensingService }
       at android.app.ContextImpl.validateServiceIntent(ContextImpl.java:1674)
       at android.app.ContextImpl.bindServiceCommon(ContextImpl.java:1773)
       at android.app.ContextImpl.bindService(ContextImpl.java:1751)
       at android.content.ContextWrapper.bindService(ContextWrapper.java:538)
       at com.google.android.a.a.i.a(ProGuard:150)
       at com.kokufu.android.apps.sqliteviewer.MainActivity.l(ProGuard:69)
       at com.kokufu.android.apps.sqliteviewer.MainActivity.onCreate(ProGuard:65)
       at android.app.Activity.performCreate(Activity.java:5933)
       at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1105)
       at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2251)
       at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2360)
       at android.app.ActivityThread.access$800(ActivityThread.java:144)
       at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1278)
       at android.os.Handler.dispatchMessage(Handler.java:102)
       at android.os.Looper.loop(Looper.java:135)
       at android.app.ActivityThread.main(ActivityThread.java:5221)
       at java.lang.reflect.Method.invoke(Native Method)
       at java.lang.reflect.Method.invoke(Method.java:372)
       at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:899)
       at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:694)

調べてみると、バグらしい

結構、重大なバグだと思うのですが、これまで放って置かれているのは、LVL がソース提供だからでしょうか少なくとも 2015/1/20 現在の Google Play Licensing Library Rev. 2 では修正されていません。
リンクにもあるとおり、以下のように LVL の中身を修正することで問題は解決します。

LicenseChecker.java (修正前)
                    boolean bindResult = mContext
                            .bindService(
                                    new Intent(
                                            new String(
                                                    Base64.decode("Y29tLmFuZHJvaWQudmVuZGluZy5saWNlbnNpbmcuSUxpY2Vuc2luZ1NlcnZpY2U="))),
                                    this, // ServiceConnection.
                                    Context.BIND_AUTO_CREATE);

                    if (bindResult) {
                        mPendingChecks.offer(validator);
                    } else {
                        Log.e(TAG, "Could not bind to service.");
                        handleServiceConnectionError(validator);
                    }

LicenseChecker.java (修正後)
                    Intent serviceIntent = new Intent(
                            new String(Base64.decode("Y29tLmFuZHJvaWQudmVuZGluZy5saWNlbnNpbmcuSUxpY2Vuc2luZ1NlcnZpY2U=")));
                    serviceIntent.setPackage("com.android.vending");

                    boolean bindResult = mContext
                            .bindService(
                                    serviceIntent,
                                    this, // ServiceConnection.
                                    Context.BIND_AUTO_CREATE);