2012年8月21日火曜日

ProGuardによるライブラリとしてのJARの難読化(2)

というわけで昨日の続き。
実際にProGuard4.8を使ってライブラリJARを難読化してみます。


 これはProGuardの起動直後の画面。
左側に各設定項目が並んでいます。
右下の「Next」を押すと、各設定項目間を進んでいきます。正直いらん。




 「Input/Output」の設定画面。
ここではInputとして難読化対象のJARなどを指定し、
どこにOutputするかを設定します。
また、前提となるライブラリとかもココで指定します。

ココで重要なのは
  • 実際に使用しているAndroidのバージョンに応じたandroid.jarをライブラリとして指定する
ことです。デフォルトではjavaの共通ライブラリが指定されているのですが、それは消しましょう。
android.jarに含まれています。
android.jarはAndroidSDKの「platforms」の中にあるよ。
他にも自作JARで使用しているライブラリがあるならココで指定しておけば取り込まれます。

一応注意だけど、「Input」として指定するのと、「ライブラリ」として指定するのは意味が違います。
「Input」したクラスは全て処理されて「Output」に吐き出されます。
「ライブラリ」として指定したクラスは解析に利用されるだけで処理対象にはなりません。


とりあえずこれだけ設定すれば、処理を行う事は出来るのですが、
デフォルトのままだと全部Shrinkされて何も無いJARが出力されてしまうので、
いくつか設定が実際必要。


 というわけでShrinkingの設定画面です。
Shrinkingとは、不要なクラスを削除してライブラリを小さくしてくれる処理なのですが、
今回のようにライブラリ「だけ」でShrinkすると、実際に使ってるクラスひとつもねーじゃん!となり、
全部のクラスが排除されます。慈悲は無い。
(本来はアプリケーションのエントリポイントからクラスの参照ツリーが形成されて、残すクラスが決定される)

というわけで、「Keep」の設定が必要になるわけです。
この画面の上側の設定項目は標準状態のままで問題ないので、
下の方にある「Keep additional classes and class members」を設定します。

ここでは二つ指定しています。
消しについては気にしないで下さい。
それぞれ
「Class example.android.library.**」「Class example.android.util.io.**」と書いてあることにします。
書式については該当のエディットボックスにカーソル持って行くとバルーンヘルプが出るのでそれを読んで下さい。

要は、ここで指定したクラスはShrink対象から除外されて、キープされる訳です。
なので、ライブラリの公開クラスを指定しておけばうまいことしてくれるようになります。

クラス指定は単純な名前指定だけでは無く、見ての通りのワイルドカード指定と、
さらにクラスの属性によるフィルタリングなどにも対応しています。
たとえば、名前自体はワイルドカードで広く指定して、その中でも「public」のクラスだけをkeepする等。
まぁ細かいことはどうでも良い場合は名前だけで良いです。



次にObfuscationの設定です。
基本的にはShrinkと同様、難読化したくないクラスをここでKeep設定しておきます。
普通はShrinkと同じ設定になるんじゃないでしょうか。
加えて、ライブラリ内で定義したクラスを公開メソッドの引数に使ってたりする場合は、それもKeepしないと外から分からなくなるので設定します。
上記画面ではちょうどそんな感じのことをしています。

注意点をまとめておくと
  • 外部で使うクラスはShrink/Obfuscation共にKeep
  • Keepしてるクラスのメソッドの引数、返値の型のクラスもKeep (ShrinkはされないのでObfuscationだけ)
  • AIDLがらみのクラスもKeepしておく。
  • 外部で使うクラスはinner classとして宣言しない。難読化まわりでハマるので。listenerとかでもtop levelクラスとして独立で定義しておく。
最後の項目で大ハマリしたのがこの記事を書くきっかけな訳だが。
実際の所、inner classは例えば「example.android.library.hoge$huga」の様に、
親クラス名に「$」付けてインナークラス名を付ければProGuardとしては参照出来るんだけど、
JARをインポートしたアプリ側からの参照がうまく出来ない。
難読化対象からは確実に外れているにもかかわらず、だ。
この辺はソース参照とclassファイル参照の違いだと思うんだけど、面倒なので独立定義にした方が良いです。
どうせアプリに公開しなきゃいけないインターフェースなので、明示的にした方がいいでしょうし。

なお、調査のためには「Print mapping」を有効にすると、どう難読化されたかを吐き出してくれます。

あとは「Optimization」とかあるけど、これは標準で良いんじゃ無いかな。
マニュアルの方にはループが過剰最適化される場合とか載ってるので、問題が有る場合とかに外した方が良いことがあるかも?

 一通りできたら「Process」ページの右下に「Process!」ってボタンがあるので、押せば設定通りに処理してくれます。
このときにエラーがあったら教えてくれるので、もしもなんか出てきたらよく読んで対処。
ちょくちょくあるのはKeep設定したクラスで記述されてるクラスがKeepされてないよ?というNoteかな。
言われたクラスもKeepしないと、ただしくKeepされません。


あとは実際にアプリの方でインポートして動作確認できれば完了。

0 件のコメント:

コメントを投稿