minSdkVersionとtargetSdkVersionの関係


Androidでアプリ開発をする際に、minSdkVersionとtargetSdkVersionの関係性がいまいち理解できていなかったので、
実際にコード書いて検証してみた話。

# まとめ

* 下位互換性を保証するためにminSdkVersionを指定する場合は、minSdkVersion以降で追加されたAPIはそのまま使ってはならない
* minSdkVersionを指定しつつも、より最新の機種では新しいAPIを使いたい場合は、実行時のAPIレベルを見て処理を分ける
* (Support Libraryが使えそうであれば使う)


# 準備

## build.gradle

* コンパイルはAPIレベル22で行う
* 最低動作保証APIレベルは15とする

``` build.gradle
minSdkVersion 15
targetSdkVersion 22
```
## 検証コード

以下のようなNotificationを実装する。

``` MainActivity.java
Notification notification = new Notification.Builder(context)
    .setSmallIcon(R.mipmap.ic_launcher)
    .setContentTitle("test title")
    .setContentText("test content")
    .setContentIntent(pendingIntent)
    .setColor(Color.RED) // API level 21 から追加されたAPI
    .build(); // API level 16 から追加されたAPI
```

## Androidエミュレータ

以下のバージョンを準備。

* Android 4.0.3 (API level 15)
* Android 5.1   (API level 22)

# 1.検証

## 1) まずは何も考えずに実装してみる

最低動作保証APIレベルを15と指定しているのにもかかわらず、
APIレベル16以降で追加されたメソッドを実行しようとしているので、
APIレベル16以降から追加されたメソッド部分で警告が出る。
ただしコンパイルはAPIレベル22で行われるので、apk自体は作成できる。

![01.png](https://qiita-image-store.s3.amazonaws.com/0/17122/b537d021-b363-42b9-f7d5-24defd025e7c.png "01.png")


このコードは、Android 4.0.3のエミュレータでは動かない。
（当該APIが存在しないため）

![android4.0.3.png](https://qiita-image-store.s3.amazonaws.com/0/17122/e586b5de-5d5b-b3ab-fbdb-38da2aa1e5ca.png "android4.0.3.png")

``` stackTrace
08-08 10:29:09.807    1558-1558/org.tksmaru.sandbox.android.sdkversion E/AndroidRuntime﹕ FATAL EXCEPTION: main
    java.lang.NoSuchMethodError: android.app.Notification$Builder.setColor
            at org.tksmaru.sandbox.android.sdkversion.MainActivity$1.onClick(MainActivity.java:38)
            at android.view.View.performClick(View.java:4084)
            at android.view.View$PerformClick.run(View.java:16966)
            at android.os.Handler.handleCallback(Handler.java:615)
            at android.os.Handler.dispatchMessage(Handler.java:92)
            at android.os.Looper.loop(Looper.java:137)
            at android.app.ActivityThread.main(ActivityThread.java:4745)
            at java.lang.reflect.Method.invokeNative(Native Method)
            at java.lang.reflect.Method.invoke(Method.java:511)
            at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:786)
            at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:553)
            at dalvik.system.NativeStart.main(Native Method)
```

Android 5.1では動く。

![android5.1.png](https://qiita-image-store.s3.amazonaws.com/0/17122/f47f4c16-44b1-5846-3823-d737ad57e5fa.png "android5.1.png")

## 2) 動作保証したい全てバージョンで動かすには

### minSdkVersion以降で追加されたAPIは使わない


``` MainActivity.java
Notification notification = new Notification.Builder(context)
    .setSmallIcon(R.mipmap.ic_launcher)
    .setContentTitle("test title")
    .setContentText("test content")
    .setContentIntent(pendingIntent)
    .getNotification(); // API level 16 以降はdeprecatedだが、22時点では動く
```

### 実行時のAPIレベルごとに処理を分ける

特定の実行時APIレベル以上のときだけ新規追加されたAPIを利用できるようにする。
このように条件分岐すると、コンパイル時の警告も出なくなる。

ただし、コードが特定のAPIレベルに依存することになるので、メンテナンス性が良いとは言いづらい。

``` MainActivity.java
Notification notification;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
    // API level 21 以上
    notification = new Notification.Builder(context)
        .setSmallIcon(R.mipmap.ic_launcher)
        .setContentTitle("test title")
        .setContentText("content is this")
        .setContentIntent(pendingIntent)
        .setColor(Color.RED) // API level 21 から追加
        .build(); // API level 16 から追加
} else {
    // API level 20 以下
    notification = new Notification.Builder(context)
        .setSmallIcon(R.mipmap.ic_launcher)
        .setContentTitle("test title")
        .setContentText("content is this")
        .setContentIntent(pendingIntent)
        .getNotification();
}

```

### Support Libraryを使う

（まだ調べてない）


# まとめ

* 下位互換性を保証するためにminSdkVersionを指定する場合は、minSdkVersion以降で追加されたAPIはそのまま使ってはならない
* minSdkVersionを指定しつつも、より最新の機種では新しいAPIを使いたい場合は、実行時のAPIレベルを見て処理を分ける
* (Support Libraryが使えそうであれば使う)


# 参考

https://github.com/tksmaru/SdkVersion