Proguard設定のメモ


Proguardの設定は例えばライブラリのインストラクションページからコピーはしてくるけど、実際何を行っているのかはわかりにくい。
なので、自分たちのプロジェクトで行っているProguardの設定が何をやっているのかを調べてみました。

# Proguardがやってくれること

基本的にProguardのやることは以下の2つです
- 難読化(クラス名、変数名、メソッド名等を短くしてコードを読みにくくします)
- 参照されていないコードを削除

これにより、Proguardをかけると最終的に生成されるAPKのサイズを減らすことができます。

このProguard、すべてのSymbolの整合性を保ったまま難読化、コードの削除を行うので、基本的には問題が無いはずなのですが、リフレクションを使ってクラス名、変数名、メソッド名の文字列を使ってにアクセスしていると、これらはProguardでは変換されないため、難読化されたシンボル名では解決できなかったり、参照されていないと判断されてコードが削除されてしまいます。

# `keep`オプション

そのため、リフレクションをつかって文字列でシンボル参照されるクラスはそのシンボル名を残す必要があります。また、削除されないようにする必要があります。そのためのオプションが`keep`オプションです。
keepオプションにはいくつかの種類があり、それぞれ少しずつ挙動が違います。

|残す項目| リネームを行わない、削除しない  |リネームを行わないが、参照されていない場合は削除する  |
|:---------------|:-------|:------------|
|クラスとそのメンバ |`-keep` |`-keepnames`|
|クラスメンバのみ   | `-keepclassmembers` |`-keepclassmembernames`
|クラスメンバが存在する場合は、クラスとそのメンバ|`-keepclasseswithmembers `|`-keepclasseswithmembernames`|


## `-keep`と`-keepnames`の違い

`-keep`は指定されたクラスのリネームも行わず、プログラム上で参照されていなくてもコードの削除を行わないが、`-keepnames`はクラスのリネームは防ぎますが、プログラム上で参照されていない場合は、そのコードを削除してしまいます。
従って、リフレクションでのみ実行、参照されるコードがある場合は`-keep`オプションを指定する必要があります。
一方、リフレクションで例えばクラスの存在するかどうかをチェックするだけで、そのコードへの参照はプログラム上で必ず行われるといったような場合は`-keepnames`を使うと参照されていないコードは削除されるので、最終的にAPKのサイズを抑えることができます。


## keepするクラス、メンバの指定方法


```
-keep [クラス指定] {
	[フィールド、メソッド指定];
}
```
Javaのクラスの定義のスタイルでワイルドカード指定ができるようになったもの、と考えるとよいと思います。

### クラスの指定

`*`は指定パッケージ直下のクラス名にマッチします。ネストされたパッケージ内のクラスにはマッチしません

```
com.example.* 
```
は`com.example.ClassA`にはマッチしますが`com.example.subpackage.ClassB`にはマッチしません

`**`は指定パッケージ以下のすべてのネストされたパッケージ内のクラスにマッチします。

```
com.example.**
```
は`com.example.ClassA`と`com.example.subpackage.ClassB`の両方にマッチします。

`?`は任意の1文字にマッチします。

```
com.example.Test?

```

は`com.example.TestA`, `com.example.TestB`にはマッチしますが、`com.example.TestAB`にはマッチしません


### フィールド、メソッドの指定

フィールド、メソッドの指定は以下のやり方で行います。


- `@Foo`で指定された場合、`@Foo`アノテーションが設定されたメンバにマッチします。
- `<init>`はすべてのコンストラクタにマッチします。

- `<fields>`はすべてのフィールドにマッチします。
- `<methods>`はすべてのメソッドにマッチします。
- `*`すべてのフィールド、メソッドにマッチします。

フィールド名、メソッド名をマッチさせる場合でも以下のワイルドカードが使えます。

- `?`任意の1文字にマッチします。
- `*`メソッド名、フィールド名のいずれかの箇所にマッチします。

アノテーション型、フィールド、メソッドの型などの型指定には以下のワイルドカードが指定できます。

- `%`は`int`や`boolean`など任意のプリミティブ型にマッチします
- `?`は型名の任意の1文字にマッチします。
- `*`はパッケージ直下のクラス名にマッチします。ネストされたパッケージ内のクラスにはマッチしません
- `**`は指定パッケージ以下のすべてのネストされたパッケージ内のクラスにマッチします。
- `***`はプリミティブ型、配列にかかわらずすべての型にマッチします。
- `...`はすべての型のすべての数の引数にマッチします。

※それ以外にも指定方法がありますので[こちら](http://proguard.sourceforge.net/manual/usage.html#classpath)を参照してください

### 指定例

実際の指定例を元に、それがどのような指定なのかを見てみます。

#### クラス名のみを`keep`する

android.app.Activityを継承するすべてのクラスのクラス名のリネームを行わない、削除もしない（フィールド、メソッドのリネームは行われる、参照されていなければ削除される可能性もある）。

```
-keep public class * extends android.app.Activity
```

#### クラス名とフィールド名、メソッド名をkeepする

`com.facebook`以下のパッケージのすべてのクラスのすべてのフィールド、メソッドのリネームを行わない、削除も行わない

```
-keep class com.facebook.** {
   *;
}
```

#### 特定のアノテーションがついたフィールド名のみをkeepする

どんなクラスでも`@com.google.api.client.util.Key`でアノテーションされたフィールドのリネームは行わない、削除も行わない（クラス名のリネームは行われる）。

```
-keepclassmembers class * {
  @com.google.api.client.util.Key <fields>;
}
```

#### 特定の型の引数を持つコンストラクタをが存在する場合はそのコンストラクタとクラス名をkeepする
`android.content.Context`,`android.util.AttributeSet`,`int`の3つの型を引数とするpublicなコンストラクタをもつクラスがある場合は、そのコンストラクタとクラス名をkeepする

```
-keepclasseswithmembers class * {
  public <init>(android.content.Context, android.util.AttributeSet, int);
}
```

#### 特定のアノテーションがついたフィールド、メソッドはリネームしない

`butterknife.`パッケージ以下のクラスのアノテーションがついたフィールド、メソッドはリネームしない（参照されていなければ削除される）。

```
-keepclasseswithmembernames class * {
    @butterknife.* <fields>;
}

-keepclasseswithmembernames class * {
    @butterknife.* <methods>;
}
```


# `dontwarn`オプション

実際のJavaのプログラムではプログラム上参照されているが、実行時には使用されないのでリンクされないライブラリがあります。そういった場合でもProguardは参照先のクラスが存在しないというエラーを出してビルドを中断してしまいます。

例えば、このようなエラー(warning)を出します。

```
 [proguard] Warning: org.apache.log4j.lf5.viewer.categoryexplorer.CategoryNodeRenderer: can't find superclass or interface javax.swing.tree.DefaultTreeCellRenderer
 [proguard] Warning: org.apache.log4j.lf5.viewer.categoryexplorer.TreeModelAdapter: can't find superclass or interface javax.swing.event.TreeModelListener
 [proguard] Warning: org.apache.log4j.lf5.viewer.LogBrokerMonitor$32: can't find superclass or interface java.awt.event.ActionListener
 [proguard] Warning: org.apache.log4j.lf5.viewer.LogBrokerMonitor$31: can't find superclass or interface java.awt.event.ActionListener

```

このような参照先のクラスが存在しなくても問題がないということが明らかな場合は`-dontwarn`オプションを指定します。


## 指定例

```
-dontwarn com.google.common.**
-dontwarn com.google.appengine.**
-dontwarn com.google.android.gms.**
-dontwarn com.google.api.client.**
```

クラスの指定方法は`-keep`のクラスの指定方法と同じです。

# `assumenosideeffects`オプション

`-assumenosideeffects`オプションで必要のないメソッド、クラス等を取り除くことができます。主にlog関連のメソッドを取り除くのに使います。

```
-assumenosideeffects class android.util.Log {
    public static *** v(...);
    public static *** d(...);
    public static *** i(...);
    public static *** w(...);
    public static *** e(...);
}
```



# Proguardについての情報

以上、自分のプロジェクトでよく使われているProguardの設定についてまとめてみました。
~~いつもハマる~~お世話になる割にProguardについてまとめられている情報は少なく、基本的に本家の情報をあたったほうが良いです。

- [Proguardのサイト](http://proguard.sourceforge.net/)[^1]

この中のManual -> [Ref Card](http://proguard.sourceforge.net/manual/refcard.html)のページなどが特に参考になります。


[^1]: このご時世フレームになっていてページのリンクが取りづらい