2012/06/15

ADT18のproguard-project.txtで困ったところ

JPEG画像のExif位置情報の削除と編集が可能なツールExifPMを作成したのですが、 その署名アプリケーションを作成する時にproguard設定で、いくつかトラブルに遭遇しました。

ここで、その内容をまとめて今後の参考のためのメモを残しておく事にします。

システムの構成

この記事は次のシステム上での挙動について書かれています。 Windowsなど、他のシステム上では当てはまらない可能性もあるのでご注意ください。

  • OS: Ubuntu 12.04 LTS 64bit版
  • CPU: AMD Phenom(tm) II X4 905e Processor
  • Memory: 16GB
  • Android開発環境:NVidia Tegra Android Developer Pack 1.0r8 (最新版"Android SDK r19, ADT 18"更新済み)

症状

Eclipse上で正常に署名済みAPKファイルを作成したと思ったものの、デバイスにインストールしてみたら起動時にClassNotFoundExceptionが発生し、異常終了する問題が発生しました。 症状は次の通りです。

  • EclipseのLogCat上ではActivityクラスやContentProviderクラスが見つからないと表示される
  • 過去に正常に動いていた時の署名済みAPKファイルと比較して、100KB以上サイズが小さくなっている

署名アプリケーションはdebug時と比較すると、Proguardを使用する事によって、1MB以上あったAPKファイルのサイズが350KB程度に圧縮されています。 トラブルが発生したAPKファイルでは200〜300KB程度になっている事も症状の一つでした。

Clean...直後には正常にAPKファイルが生成されたのに、繰り返すとサイズの小さなAPKファイルが生成されてしまいます。

-dontwarnによってアプリケーションパッケージ以下から出る警告を全て無視しているため、 気がつかないうちに一部クラスが欠落したAPKファイルを作り出してしまっているのでしょう。

ADT 18でのproguard-project.txtファイルの取り扱い

これまでは各アプリケーション共通の設定とアプリケーション固有の設定を混ぜたproguard.cfgファイルを準備していましたが、ADT17からはシステム用設定とアプリケーション用設定を分けて管理する事になります。

システム全体の設定の中ではデフォルトで-optimizationsが無効化されているので、 必要な場合には有効にする必要があります。

proguardはClass.forNameでインスタンス化したり、Reflection APIを通じてクラスにアクセスする必要がある場合には、難読化によって指定するべきメソッド名や変数名が変化してしまいますから、少なくともpublicな部分は名称が変更されないようにする必要があります。

例えばAndroidManifest.xmlにはActivityやServiceのクラス名を書きますから、少なくとも ここに記載されているクラス名は難読化の対象から外す必要があります。

システム全体のproguard-project.txtフィルには、Activityとそのサブクラス名を難読化の対象から外すような設定が含まれています。

Tips

問題が発生した時に見直す点をまとめていきます。

署名APKファイルを作成する前の儀式

EclipseのProjectメニューの中で、Clean...を選択します。 Build Automaticallyにチェックを入れていない場合には、Build Projectを選択しておきます。

少なくともADT 18.0.0.v201203301601-306762を使っている現状では、同じ設定でも、Clean...を選択せずにAPKファイルを出力した場合、サイズが小さなり、正しく動かないAPKファイルが生成されています。

これについてはprojectの設定がどこか正しくない可能性が高いと思っていますが、 原因が追求できていないため、Google Playに揚げる署名APKファイルの生成時にはClean...を毎回選択してから作業を行なっています。

staticフィルドからnon-static enumを使用している場合

クラス内部でenumを定義した時に、次のようなコードを作成したところ、proguardがバグっぽい動きをしました。

修正前:問題のあったenum定義

...
public enum Orientation {
	PORTRAIT, LANDSCAPE
}
public static CommonConfig.Orientation orientation;
...

このコードはproguard以前では問題なく動いていましたが、次のように修正してからは問題なく動くようになりました。

修正後

...
public static enum Orientation {
	PORTRAIT, LANDSCAPE
}

このstaticなフィールドとして定義したい内部enum定義であれば、staticという定義は適切だと思います。 むしろ以前のコードで問題なく動いているところに少し違和感を持っています。

課金サービス(com.android.vending.BILLING)を使用している場合

システム全体のproguard-project.txtではライセンスサービス用のインタフェースは含まれていますが、 課金サービス用の設定は含まれていませんでした。

ひょっとしたら必要ないのかもしれませんが、基本的にgenディレクトリ以下に出力されるクラスは全てproguard-project.txtの中で対象から外すように設定するべきだと思います。

次のような設定を追加しました。

proguard-project.txtに追加したBilling用設定


-keep public class com.android.vending.billing.*
Google Maps APIを使用している場合のproguard-project.txt設定

mapsについてはproguardの対象にする必要がないと思っていたのですが、 手元では次のように設定しないと動きませんでした。

google maps関連のproguard-project.txt該当個所

-keep public class com.google.android.maps.**
AdMob用設定の追加

AdMob用に次のような設定を追加しています。

AdMob用proguard-project.txtの該当個所


-keep public class com.google.ads.** {
    public protected *;
}
-keep public class com.google.gson.** {
    public protected *;
}

現状のproguard-project.txtファイル全体

参考までに、現在使っているproguard-project.txtの内容を全てそのまま載せておきます。

現行proguard-project.txtの全体

# Add any project specific keep options here:

-keep public class com.google.android.maps.**
-keep class net.yadiary.android.exifpc.beans.*
-keep class net.yadiary.android.exifpc.provider.*

-keep public class com.google.ads.** {
    public protected *;
}
-keep public class com.google.gson.** {
    public protected *;
}

-keep public class com.android.vending.billing.*

# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
-keepclassmembers class net.yadiary.android.exifpc.InfoFragment.DemoJavaScriptInterface {
   public *;
}

-dontwarn net.yadiary.android.exifpc.**

さいごに

いくつかシステム全体でContentProviderのサブクラスを指定しているのに、なぜか手元でも明示的にContentProviderのサブクラスを指定しないとうまく動かなかったりしています。

おそらくenumのstatic修飾子のように、適切な記述ができていない部分があるのではないかなと思います。

そのため、ここに書かれている内容はベストとは限りませんが、現状で正しく動く設定であるのも確かなので、そこら辺をふまえて参考にしてください。

0 件のコメント: