Android2.3(APIレベル9)~の対応プログラムの開発について


はじめに

こんにちは。Daddy's Officeの市川です。

先日、使わなくなったAndroidを監視カメラにするソフト「LiveCapture3 Android」を公開したのですが、そこではまったのが、古いAndroidへの対応です。

使わなくなったスマホを利用するというコンセプトなので、なるべく古いAndroidでも動くようにする必要があり、最終的には、Android2.3(APIレベル9)以降すべてで動作する方向で開発しました。

あまり凝ったUIは不要だったので、基本的にはAndroid Support Libraryを外してしまえばOKかと思ったのですが、Android6.0(APIレベル23)以降で導入された、実行時パーミッションリクエストを実装するために、Support Library V4のActivityCompatとContextCompatだけは使う必要がありました。

当然、これらを使用せずとも同様の処理は記述可能なのだと思いますが(すいません、調べていませんが、、、)、権限処理に時間を割くのは本質的ではないので、これら権限処理に必要なSupportLibraryV4だけをリンクできる方法を調査しました。

SupportLibrary V4の最小APIレベル

単純にSupportLibraryV4だけを追加すればよいと思っていたのですが、現在、SupportLibraryV4の最小APIレベルはAndroid4.0(APIレベル14)に上がっているとのこと。
バージョン サポートとパッケージ名

Support Library バージョン 26.0.0(2017 年 7 月リリース)以降、すべてのサポート ライブラリ パッケージを対象に、サポートされる最小 API レベルが Android 4.0(API レベル 14)に変更されました。このため、サポート ライブラリの最近のリリースを使用する場合は、v# パッケージ表記が最小の API サポートレベルを示しているとは限りません。最近のリリースで行われたこの変更は、v4 と v7 のライブラリ パッケージでサポートされる API の最小レベルが本質的に同等であることも意味します。たとえば、support-v4 と support-v7 のどちらのパッケージも、26.0.0 リリース以降の Support Library に対して最小 API レベル 14 をサポートします。

これではminSdkVersionを14まで上げないと、Gradle Syncがエラーになってしまいます。。。

ということで、まずは、minSdkVersion 9のままでSupportLibraryV4を追加してもGradle Syncがエラーにならないようにします。

プロジェクト作成

まず、AndroidStudioで、「No Activity」で、空のプロジェクトを作成し、Gradleの内容を書き換えます。

  • minSdkVerisonを9に変更
  • support-v4ライブラリ以外の依存ライブラリをすべて削除
android {
    compileSdkVersion 28
    defaultConfig {
        applicationId "com.daddysoffice.sample.test"
        minSdkVersion 9
        targetSdkVersion 28
        versionCode 1
        versionName "1.0"
    }
}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'com.android.support:support-v4:28.0.0'
}

この状態でGradle Syncを実行すると、マニフェストのマージエラーになります。

ERROR: Manifest merger failed : uses-sdk:minSdkVersion 9 cannot be smaller than version 14 declared in library [com.android.support:support-v4:28.0.0] C:\Users\xxx\.gradle\caches\transforms-2\files-2.1\xxxxxxxxxxxx7f111f5\AndroidManifest.xml as the library might be using APIs not available in 9
    Suggestion: use a compatible library with a minSdk of at most 9,
        or increase this project's minSdk version to at least 14,
        or use tools:overrideLibrary="android.support.v4" to force usage (may lead to runtime failures)

エラーの内容は、前述のとおりで、com.android.support:support-v4:28.0.0で定義されているversion 14よりもminSdkVersion 9 は小さい、とのこと。

この解決策として提示されている最後の方法を使います。

use tools:overrideLibrary="android.support.v4" to force usage (may lead to runtime failures)

tools:overrideLibrary

マニフェストファイルに、uses-sdk tools:overrideLibrary="xxxx" を記載すると、xxxxライブラリ内に記載されたminSdk/targetSdkVersionを無視することになるとのこと。

今回は、Android6.0(APIレベル23)以降のときにだけ、実行時パーミッション処理を走らせます。
APIレベル22以下の場合にはライブラリを使わないので、この最後の方法で問題ありません。

早速、AndroidManifest.xmlに、この記述を追加します。

AndroidManifest.xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.daddysoffice.sample.broadcasttest"
    xmlns:tools="http://schemas.android.com/tools"> <<これと

    <uses-permission android:name="android.permission.CAMERA" />

    <uses-sdk tools:overrideLibrary="android.support.v4/>  << これを追加

これで、再度Gradle Syncを走らせると、今度は下記のエラーが発生。

Manifest merger failed : uses-sdk:minSdkVersion 9 cannot be smaller than version 14 declared in library [com.android.support:support-fragment:28.0.0] C:\Users\xxxxx\.gradle\caches\transforms-2\files-2.1\xxxxxxxxxx710eb0a2\AndroidManifest.xml as the library might be using APIs not available in 9
    Suggestion: use a compatible library with a minSdk of at most 9,
        or increase this project's minSdk version to at least 14,
        or use tools:overrideLibrary="android.support.fragment" to force usage (may lead to runtime failures)

どうやら、supportV4をリンクすることで、いろいろとくっついてくる模様。。。

とりあえず、マニフェストのtools:overrideLibraryに、android.support.fragmentを追加して、再度Gradle Syncを実行。

すると、また別のライブラリがエラーで出てくる。。

これをエラーが出てこなくなるまで繰り返した結果、tools:overrideLibraryは、以下のようになりました。

AndroidManifest.xml
    <uses-sdk tools:overrideLibrary="android.support.v4,android.support.mediacompat,
      android.support.fragment,android.support.coreui,android.support.coreutils,     
      android.support.v7.appcompat,android.support.graphics.drawable,
      android.support.loader, android.support.v7.viewpager,
      android.support.coordinatorlayout,android.support.drawerlayout,     
      android.support.slidingpanelayout,android.support.customview,
      android.support.swiperefreshlayout, android.support.asynclayoutinflater,
      android.support.compat,androidx.versionedparcelable,     
      android.arch.lifecycle,android.support.documentfile,
      android.support.localbroadcastmanager,     
      android.support.print,android.support.interpolator,     
      android.support.cursoradapter,android.arch.lifecycle.viewmodel,
      android.arch.lifecycle.livedata,     
      android.arch.lifecycle.livedata.core,android.arch.core" />

これ、方向性としてあっているのか?と少し疑問に思いながらも、とりあえずビルドができるようになりました。

ビルドを通す

とりあえず、Gradle Syncが通るようになったので、あとはビルドエラーを潰します。

Theme.AppCompatなどは使わないので、styles.xmlを削除し、AndroidManifest.xmlのapplicationに記載されたandroid:themeも削除

AndroidManifest.xml
   <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme" /> << これを削除

これでビルドが通りました!

実装

実装はOSバージョンごとに処理を分岐しながらコードを記述する必要があります。

コード中で処理を分岐する場合は、Build.VERSION.SDK_INT を使用して処理を分岐します。

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            // 
            // Android6.0(APIレベル23)以降の場合は、実行時にパーミッションのチェックを行う
            if (checkPermission()){
                startApplication();
            }
        }
        else {
            //
            // 上記以外はそのままアプリケーション開始
            startApplication();
        }

指定したOSバージョン以下では使わないことが確実なメソッドの場合は、@TargetApiアノテーションを使うことで、Lint(静的解析ツール)のエラーを制御できます。

ただし、この方法は単にLintのエラーを出さなくしているだけなので、実行時に対象OSレベル以下の場合に、このメソッドがコールされないように自分で注意して実装する必要があります。


   @TargetApi(Build.VERSION_CODES.M)
    private boolean checkPermission(){
        int permissionCheck 
             = ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA);

        if (permissionCheck != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(
                    this,
                    new String[] { android.Manifest.permission.CAMERA },
                    PERMISSIONS_REQUEST_CAMERA);

            return false;
        }

        return true;
    }

おわりに

この方法で何とか、Android2.3以降の対応アプリを開発できましたが、本来、こんなことをしないで良いようにSDKのバージョンアップをしてほしいところです。。

いまいちAndroidが好きになれなかったのですが、今回の件で、ますます嫌いになってしまった気がします。。。