【Android】minSdkVersion = 4 でアプリ開発する縛りプレイ【バッドノウハウ】


# はじめに

半年近く前のことですが，2017年の6月末に，とうとうAndroid MarketでAndroid 2.1以下の端末サポートが打ち切られました．

公式のブログ記事: [Ending support for Android Market on Android 2.1 and lower](https://android-developers.googleblog.com/2017/06/ending-support-for-android-market-on.html)

やった！

Android Marketという文字列が懐かしいですね．

「むしろ今までサポートしてたのか」とか「そんな端末もうサポートしてないので関係ない」みたいな声が聞こえてきますが，僕はハッピーな気分になりました．

# 経緯

Android 1.6が主流の時代(実際は2.1の時期でしたが，一部界隈でのIS01とかいうメガネケースの流行によって1.6ユーザが増加していた時期)に作ったアプリをずるずると2017年までそのまま公開を続けていいました．
基本的に放置気味ですが，インストール数もそこそこ多いのと自分でも使っているので，たまに機能追加とかします．

というわけで，以下の状態を保つ方針で開発していました．

- Android 1.6(Eclare) でも(とりあえず)動く(minSdkVersion = 4 !)
- Android 8.0(Oreo) でも動く
- 最近のAPIも使える場合は使いたい(compileSdkVersion = 25)

Eclareをサポートし続ける真っ当な理由は思いつかないですが縛りプレイとしては楽しいと思います．**(あたりまえですが，遊びじゃないなら適切なタイミングでサポート打ち切るべきです)**

# 基本的なところ

## 新しいAPIを呼ぶ場合

未知のメソッドを参照していると，そのクラスのロード時に例外が発生します．

- リフレクションで呼びだす
- APIバージョンごとにWrapperクラスを作る

SDKで新たに定義された定数を使いたいだけであれば，static finalかつプリミティブ型の定数はコンパイル時に展開されるので定数自体の参照は問題ありません． (OSのバージョンによって処理を分けるために，コード中に `Build.VERSION_CODES.FROYO` とか書いても大丈夫ですし)

新しい定数を既存のAPIに渡しても，古いバージョンのAndroidではほとんどの場合無視されるので，なんとなく動きます．(動作することは保証されてなさそうですが)

## 古いAPIを呼ぶ

仕様が推奨されていないAPIは deprecatedになったあと，数世代後に削除されることがあります．

- ほとんどは"Hide"に設定されているだけで実際には呼び出せます．
- リフレクションで呼ぶ
- ビルド時に参照する `framework.jar` を差し替える(例: https://sites.google.com/site/hippunosource/home/android/androidnohide-apiwo-shi-yongsuru-rifurekushonha-wei-shi-yong )

実際は，プロジェクトの構成が面倒になるのと，そもそもdeprecatedなAPIを呼ばないようにしたほうが良いので，どうしても必要な時だけリフレクションを使うのが良い(気がします)．

# 遭遇しがちな問題

## APKインストール時にdexoptがクラッシュする

今はランタイムがARTになってしまったので，dexopt自体存在を忘れられつつありますが，
Android 5未満の環境ではアプリの実行前にdexファイルから環境ごとに最適化されたodexファイルを生成するためにdexoptというプログラムが動いていました．

dexの総メソッド数65535の制約は有名ですが，注意しなければいけないのは昔のAndroidはdexoptが使えるメモリが数MBしかなく，65535未満のメソッド数でも場合によってはクラッシュします．(最近は，ARTとdalvikが併存する場合があるようですが，新しい端末ではメモリに余裕があるので遭遇しない)

参考： http://qiita.com/konifar/items/d98c78facbaae63badca

解決方法：メソッド数を減らす(現状ProGuardでどうにかなっているのであまり深追いしていません)

## drawable.xmlのshapeで角丸を作れない

`android:(top|bottom)(Left|Right)Radius` (ボタンとかを角丸にするやつ)が昔のバージョンから存在していますが，環境によって動いたり動かなかったり不思議な結果になったりします．どういうわけか bottomLeftRadius と bottomRightRadiusが逆に解釈されて変な形のボタン見たことある人も結構いるはずです．Androidのバージョンによって正しい結果になる記述が変わるので，場合分けが面倒です．

四隅全部同時に指定する `android:radius` は比較的良い子です．

解決方法：Androidのバージョンごとにリソースファイルを分ける or 諦める(諦めました)

## android:hardwareAccelerated=true? false?

Android 3.0あたりから，ハードウェアアクセラレーションの指定ができるようになりました．
基本，デフォルトで良しなにやってくれることを期待したいですが，特にWebViewが絡むと結構厄介．ハードウェアアクセラレーションが有効だと不思議な描画結果になる場合があるが，無効だとそもそも動かないものが出てきたりして悩ましい．

参考: http://qiita.com/naokazuterada/items/a473d6edfcc5ce61d5fe

さらにWebView内にFlashPlayerを埋め込んだりすると，ハードウェアアクセラレーション無しだとまともに動かなくなるので，有効にしつつ(多くの場合に)正しく描画されるパターンを探る必要がある．

解決方法：がんばる

## 特定Androidのバージョンで動作がおかしい＆クラッシュする

たまにあるのが，`null`渡してもとりあえず動いていたAPIが，特定のバージョンだけ`NullPointerException`が発生するとかです．

省略可能そうに見えるパラメータでも，ドキュメントにnullの場合の記述が無い限りは慎重になった方が良さそうです．

最近のAndroidStudioならsupport-annotationsライブラリで @NonNull @Nullableアノテーションが使えるので便利ですね．

## targetSdkVersion

Android内にはapkのtargetSdkVersionで処理が分岐している個所がわりとある．

有名どころではメニューボタン表示の有無やNotificationの挙動．

targetSdkVersionを11以上にするとナビゲーションバーからメニューボタンが消えたり，通知のスタイルが変わったりする．

## エミュレータがまともに動かない

最近のAVD Managerから古いイメージを起動すると，まともに動かなくなってるのは，気のせいじゃないはず．

## 多くのUIライブラリは使えない

おそらく，一番深刻なのはこれ．

UI周りのコードはライブラリ使うと楽ができますし，そもそも真っ当なUIを一から実装するのは賢くありません．

Support Libraryすらまともに使えないし，気付いたら Google Mobile Ads SDK とかも動かなくなっていた．

解決方法：諦める＆自分で実装する

# 最後に

12/7時点での，「アクティブなインストール端末数」です．

![image.png](https://qiita-image-store.s3.amazonaws.com/0/58515/4751e251-d674-46fd-d343-522fa3764fea.png)

なんで，1.6や2.1がいるんでしょうか．．．いくつかのアプリ見てみましたが，微妙に古い端末が残っていました．

他にも，dexのバイト数の上限決めてゴルフする開発をしていたらdexファイルも読めるようになってきたので，その話も書きたかったけど余裕がないのでここまでに．

今更，あんまり役に立たない記事ですが，根本的に何か変わったわけではないので，Android開発をしていると今後も似たようなことが繰り返されるんじゃないかと思います．

正直 Android7以下の端末はもう捨てて良いんじゃ(ry

# 言い訳

．．．ごめんなさい

2017-12-07T22:45:00+09:00 .oO(そういえば ドワンゴ Advent Calendar 2017 のネタをそろそろ考えないと)
2017-12-07T22:50:00+09:00 えっ12/7？．．．当日中に書けばセーフ？
2017-12-07T23:59:15+09:00 ギリギリ(無理だったらs/+09:00/Z/ して日本時間じゃない言い訳をしようと思っていた) 

適当にお蔵入りしてたネタを引っ張り出して，内容の薄い記事を書くのが限界でした．