飴屋

Androidアプリメモ/メモ13

proguardで起きた問題に対処した

proguardのことをよく知らずにこれまでやってきました。proguardはコードを難読化したり、不要なコードを削除してくれたりとプロがラムの安全と品質を担保してくれるツールだそうで、製品をリリースする場合はとりあえず使っておけと、どの参考サイトも利用を推奨していたので、昔から使ってきました。Logクラスのロギング処理も消し込んでくれるのですね。こういう便利なツールが中身をよく知らなくても、よしなに動いてくれていたのは嬉しいですね。

ですが、ちょっと問題が発生しました。

まず、デバッグ時は問題なく動いていたのに、リリース版ではクラッシュする現象に気が付きました。(それもアプリのメイン機能で発生して、実質アプリが使えないという現象)スタックトレースをみると見覚えのないメソッドを呼び出そうとしたけど、メソッドが見つからなかったようです。

自分の書いたソースの中にはそんなメソッドがないので、キツネに化かされたような気になりましたが、調べているとgeneratedなフォルダの中に問題のソースを発見しました。これはprotocol buffers(protobuf)で自動生成されたjavaのコードではないですか。これがリリース版でだけ問題になるとは一体?

そこでやっとproguardの難読化と結びつきました。generatedなソースも難読化されるはずですが、スタックトレースではメソッド名が難読化されないで呼び出されていました。難読化前のメソッド名が文字列にでも残っちゃっていたんですかね。protobufの性質上、メソッド名がデータ名とリンクしなくてはならないなんてことがあるのかもしれません。

というわけでproguardの動作設定ファイル(proguard-rules.pro)に以下のような制御文を追加しました。

-keep class net.candychip.appname.ClassName.** { *; }

keepすると難読化されずに済むとのこと。最初の.**で内部クラスまで掘って対応してくれ、{*;}が中のメソッドやフィールド全部を指定してくれるようです。generatedなソースは中身を追いかけたくないので、関連しそうなところを全部指定しました。.**の部分が文法チェックでUnresolved class nameと表示されるのが気になりますが、とりあえずこれで試してみます。

(追記:2020/8/4)

間違っていました。keepの書き方が違いました。

-keep class net.candychip.appname.ClassName$** { *; }

これが正しいようです。内部クラスの区切り文字は「.」ではなくて「$」が正しいのでした。通りでスタックトレースでよく「$」文字をみかけたわけです。クラスの指定が間違っていたので、当然keepの指示が届いておらず、修正したつもりで何も修正されていませんでした。

こういう時はちゃんと難読化されているかどうかバンドルファイルの中身を覗いてみるべきなのでした。Android Studioでは「Build - Analyse APK」メニューでリリース用のファイルを指定して中身を確認できました。base/dex/classes.dex ファイルにClassのパッケージ構成が書いてあるので、中を確認すると、やはり全部まるっと難読化されていました。上のkeep指定の修正後、もう一度試すと、指定のクラスの内部クラスがクラス名、メソッド名、フィールド名も併せて難読化されなくなりました。ここまで確認しないで油断していてはいけなかったのでした。

余談ですが、proguardは難読化とともに命名が短い単語に置き換わるので、難読化を一部止めたことでちょっとファイルサイズに影響がありました。難読化だけではなくて不要なメソッド等をカットするところまでやってくれるそうですが、generatedなソースは本当に不要かどうか自分でも判断できないので、無駄をカットする機能もオフにしました。こっちの方がファイルサイズに影響を与えたかな。

メモ一覧