今回はAndroidライブラリを作る手順と利用時に困らないためのチェックポイントを紹介していきます。
AndroidライブラリはAAR(Android Archive)ファイルにコンパイルできるモジュールです。JARファイルはJavaのクラスやメソッドをもつライブラリリソースですが、Androidライブラリでは加えてAndroid特有のAndroidリソース(ActivityやAndroidManifestファイル)、レイアウトやドローアブル、Strings.xmlといった共有リソースも内包できます。
リファレンス、サンプルコードはこちらに用意しています。
- https://developer.android.com/studio/projects/android-library.html
- https://github.com/TechBooster/AndroidSamples/tree/master/AarDemo
サンプルプロジェクトではAndroidライブラリのlibrary-core.aarにActivityとドローアブルリソース(library.png)が入っています。このプロジェクトをベースにAndroid Studioを使ったAndroidライブラリの作り方、共有リソース利用時の注意点をまとめて解説していきます。
ライブラリモジュールを作成する
適当なアプリケーションプロジェクトを開き、プロジェクトから参照するAndroidライブラリを作ってみましょう。アプリケーションプロジェクトはHello Worldのようなシンプルなものを使ってかまいません(新規作成ウィザードに従うとappモジュールを持つプロジェクトができます)。
はじめにAndroid StudioのFileメニューのNew > New Module…を選択します。Create New Moduleダイアログが開くのでAndroid Libraryを選択してください。サンプルプロジェクトではライブラリ名をlibrary-coreとしています。
作成後のプロジェクト構成は次のとおりです。appモジュールに加えてlibrary-coreモジュールが追加されていることがわかります。
画像のとおり、library-coreモジュールもManifestファイル、Javaファイル、Resourcesを持っており、Androidライブラリでも通常のアプリ構成とほとんど変わらないことが読み取れます。Androidライブラリを作成したときに自動で生成される要素のいくつかはアプリのプロジェクトより大幅に簡略化されています。
■library-core/src/main/AndroidManifest.xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="org.techbooster.sample.liblary_core" />
■library-core/build.gradle
apply plugin: 'com.android.library' android { compileSdkVersion 27 defaultConfig { ...省略... } buildTypes { ...省略... } } dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) implementation 'com.android.support:appcompat-v7:27.1.0' implementation 'com.android.support.constraint:constraint-layout:1.0.2' implementation 'com.android.support:design:27.1.0' implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" testImplementation 'junit:junit:4.12' androidTestImplementation 'com.android.support.test:runner:1.0.1' androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1' }
library-coreモジュールのbuild.gradleは通常とは異なり、apply plugin: 'com.android.library'
が指定されています。アプリケーションの場合は'com.android.application'
を指定するので、見慣れない点かもしれません。Gradleは、このライブラリ指定に従ってビルドします。
利用する側のAppモジュールを設定する
ライブラリを利用する側のappモジュールでは、build.gradleにライブラリモジュールへの依存関係を追加します。
■app/build.gradle
...省略... dependencies { implementation project(':library-core') implementation fileTree(dir: 'libs', include: ['*.jar']) implementation 'com.android.support:appcompat-v7:27.1.0' implementation 'com.android.support.constraint:constraint-layout:1.0.2' implementation 'com.android.support:design:27.1.0' implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" testImplementation 'junit:junit:4.12' androidTestImplementation 'com.android.support.test:runner:1.0.1' androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1' }
app/build.gradle以外にもプロジェクトのsetteing.gradleにライブラリモジュールが追加されていることを確認しておきましょう(自動的に追加されているはずです)。
■project_root/settings.gradle
include ':app', ':library-core'
これでappモジュールへの依存関係追加とプロジェクト設定が確認できました。
次はライブラリモジュールに戻ってActivityを追加してみます。
ライブラリモジュールにActivityを追加する
AndroidライブラリモジュールにActivityを追加する場合も、手順は普通のアプリ開発と変わりません。
このとき、ライブラリ側のAndroidManifestファイルは、いつもよりパラメータが少なくなった、すこし見慣れない指定を行います。
■library-core/src/main/AndroidManifest.xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="org.techbooster.sample.library_core"> <application> <activity android:name=".LibraryCoreActivity" android:label="@string/library_name" android:theme="@style/LibAppTheme.NoActionBar"> </activity> </application> </manifest>
application要素の属性を指定していません。これはプロジェクトのビルド時にライブラリモジュールとappモジュールでマニフェストがmergeされるためです。
マージするタイミングでappモジュールと異なる属性をもっていると、ビルドに失敗します。たとえばapplication要素にandroid:label属性を追加して、アプリとモジュールでラベルが異なる状況を作ってみましょう。
■ビルドエラーが発生するlibrary-core/src/main/AndroidManifest.xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="org.techbooster.sample.library_core"> <application android:label="@string/library_name"> <activity ...省略... </activity> </application> </manifest>
この状態でビルドしてみるとAndroid Studioのコンソールは次のようにビルドエラーの詳細(マージ時の失敗)を表示します。
Manifest merger failed : Attribute application@label value=(@string/app_name) from AndroidManifest.xml:8:9-41 is also present at [:library-core] AndroidManifest.xml:11:18-54 value=(@string/library_name). Suggestion: add 'tools:replace="android:label"' to element at AndroidManifest.xml:5:5-22:19 to override.
モジュールのリソースは今回あげたAndroidManifestに限らず、レイアウトやvalues、drawableすべてのリソースがプロジェクトで共有される点に注意してください。もしappモジュールとライブラリモジュールで同じリソース名があった場合、appモジュールのリソースが優先されます(ビルド時にマージ作業をしていますが上書きされます)。リソース名称は重複がないように管理すべきです。たとえばlayout/content_main.xmlのようなレイアウト名は重複した場合、appモジュールのレイアウトが表示されてしまいます(一瞬ハマって驚きました)。
重複による値の上書きを避ける手法はいくつかありますが、たとえば色定義の場合、サンプルコードでは次のようにしています。
■library-core/res/colors.xml
<?xml version="1.0" encoding="utf-8"?> <resources> <color name="libColorPrimary">#388E3C</color> <color name="libColorPrimaryDark">#1B5E20</color> <color name="libColorAccent">#FF8F00</color> </resources>
接頭子にlibを追加してアプリモジュール側の名称(colorPrimary
)と区別します。実際にライブラリを作って運用する場合は、もう少し手が混んだ接頭子にしておくほうがいいはずです。
ライブラリモジュールにレイアウト、リソース(library.png)を追加して画面を作成します。
実際に作る場合、リポジトリのこのあたりのコードや構成を参考にしてください。appモジュール内のリソースと名前がかぶらないように変更してあります。
またアプリモジュールからライブラリに含まれるActivityを呼び出すコードはMainActivity.ktを参照してください。モジュール化しない場合とかわりありません。
AARファイルを読み込んで使う
AARファイル単体で受け取った場合、読み込みはAndroid StudioのFileメニューから行います。New > New Module…を選択します。Create New Moduleダイアログが開くので一番下にスクロールしてImport .JAR/.AAR Packageを選択してください。
Import Module from LibraryダイアログでAARファイルを指定してウィザードを完了します。
最後にライブラリを利用する側のappモジュールでbuild.gradleに依存ライブラリとしてモジュールを追加してください。
■app/build.gradle
...省略... dependencies { implementation project(':library-core') ...省略... }
このあたりは前述の「利用する側のAppモジュールを設定する」と同様ですね(おなじくプロジェクトのsettings.gradleもライブラリモジュールが追加されています)。
まとめ
ここまでAndroidリソースを含んだライブラリ(AAR)を作る方法を紹介してきました。AARの基礎を習得できたといえます。最後に利用側の観点に触れておきたいと思います。ライブラリモジュールを使う上では次の点に留意しておきましょう。
- ビルドシステムはライブラリのリソースとアプリのリソースをマージする。この際、同名であればアプリ側が優先される
- アプリモジュールのminSdkVersionは、ライブラリで定義されているバージョン以上にしなければならない
- ライブラリモジュールが依存する外部ライブラリとアプリモジュールの外部ライブラリのバージョン不一致は避ける
詳細はhttps://developer.android.com/studio/projects/android-library.html
もみてください。
アプリを構成するモジュールのため、いずれも強い制約ですが最後の点は注目に値します。ライブラリモジュールはAndroidリソースを持てますが、最近のActivity(=UI)は多くのライブラリを使用して構成します。Support Libraryのような一般的な外部ライブラリも使いたくなりますが、この場合は複数のモジュールで要求する外部ライブラリのバージョン不一致を引き起こす可能性があります。
依存関係の競合はライブラリモジュールのbuild.gradleを変更して解決できる場合があります。Gradleのdependencies
ブロックのimplementation
をcompileOnly
に変更できるか確認してみてください。この場合、コンパイル時のみ外部ライブラリが使われるため(ソースコードのみの利用であれば)コンパイル後の影響はありません(依存関係が切れています)。
ただし、ライブラリの提供を受ける側の立場ではこの方法は使えないため、dependencies
ブロックや、依存する外部ライブラリのバージョンを変更できません。
利用時にビルドシステム側で依存ライブラリのバージョン解決に介入する必要があります。
このようにライブラリを作るとき、使うときには、考慮すべき点がいくつかあります。回避するためにはライブラリの性質(利用対象者、スキルレベル、用途)を見極めて手法を取捨選択し、影響範囲を最小化しなければなりません。今回はここまでですが、このあたりを技術書典4で出す新刊でフォローできればと考えています。ライブラリ作成時に考慮すべき点に踏み込んで詳しく補足したいですね。
以上、おつかれさまでした。