AndroidでQtを共有システムライブラリとして使用する方法

Android 7.0では、ネイティブライブラリのnamespacesが導入されました。これはアプリは Android NDK で提供されるパブリックライブラリと、アプリ自身のネイティブライブラリディレクトリにあるライブラリしかアクセスできないことを意味します。このためデフォルトでは、すべての Qt Android アプリは、アプリのネイティブライブラリディレクトリに展開される独自の Qt ライブラリセットと共にデプロイされます。この制限によりこれらのライブラリは、他のアプリで使用することはできません。Qt ライブラリが見つかるかどうか、正しいバージョンであるかどうかは保証されませんが、多くのデバイスにアプリをインストールすることができます。

しかし、Android Automotive の上に IVI システムを実装するために Qt の使用を計画している OEM にとって、これは理想的ではありません。各アプリに同じライブラリをデプロイしなければならない事を考えると魅力的とは思えないかもしれません。なぜならQt アプリの数が増えれば増えるほど、それらの複数のライブラリのセットがスペースを占有するという大きな問題が発生するからです。

システムアプリは、システムライブラリディレクトリの下にあるすべてのネイティブライブラリを使用することができます。このブログでは、Qt ライブラリを共有システムライブラリとしてインストールした Android イメージを構築する方法について説明します。またQt 6.4.0 で導入された新しい Unbundled deployment を使用して、すべてのライブラリを APK にバンドルする代わりに、ターゲットデバイスにある Qt ライブラリにリンクするアプリを簡単に構築して配布する方法について説明します。ただしAndroidの制限により、これらのライブラリはシステムパーティションにインストールされたシステムアプリのみが利用可能であることに注意してください。

ライブラリーの追加

アプリには独自のライブラリセットが付属していないため、Qt ライブラリがデバイス上で検索できるか確認する必要があります。Qt ライブラリを追加するには、AOSP ビルド ツリーを変更する必要があります。Android Automotive エミュレータのビルド方法について復習したい場合はこちらを参照してください。この記事では、仮想デバイスとして Android エミュレータで実行できる Android Automotive 12 x86_64 イメージを構築し、Qt ライブラリとサンプルアプリを同梱します。

上記のリンクで説明されているように AOSP ビルド環境を設定した後、<aosp-root>/external の下に「qt」という名前のディレクトリを作成します。ここで、アプリが使用する Qt ライブラリを追加します。これらは、Qt for Android のインストール先からコピーすることができます。また、Android.mk または Android.bp ファイルを作成して、AOSP ビルドに対して何を含めたいかを規定する必要があります。ここでは前者を使用します。同じフォルダの下にAndroid.mkファイルを追加しましょう。これで、ディレクトリ構造は次のようになります。

libs-dir-tree

また、例えばターゲットアーキテクチャ(この場合はx86_64)用のサブディレクトリを追加して、そこにライブラリを配置することもできます。LOCAL_SRC_FILESAndroid.mkファイルの場所と関連するソースファイルを指していることを確認してください。また実際に追加する必要のあるライブラリのリストは、アプリによって異なることに注意してください。ここではこのサンプル アプリが使用するライブラリを追加しています。

Android.mkファイルでは、まずLOCAL_PATHを現在の作業ディレクトリに設定します。これはファイルの冒頭で一度だけ行う必要があります。次にインストールするライブラリごとにエントリーを追加します。


LOCAL_PATH := $(call my-dir)

# One entry for each library
include $(CLEAR_VARS)
LOCAL_MODULE_TAGS := optional
# The name should match the library name without the .so suffix
LOCAL_MODULE := libQt6Quick_x86_64
LOCAL_MODULE_SUFFIX := .so
LOCAL_MODULE_CLASS := SHARED_LIBRARIES # Path to the library, relative to LOCAL_PATH LOCAL_SRC_FILES := $(LOCAL_MODULE)$(LOCAL_MODULE_SUFFIX) # Relative path to install location -
# this will result in the libraries being installed under system/lib(64)/qt/ LOCAL_MODULE_RELATIVE_PATH := qt LOCAL_CHECK_ELF_FILES := false include $(BUILD_PREBUILT) # And the rest of the entries ...

上の例ではlibQt6Quick_x86_64というモジュールを追加場合ですが、これはビルド済みのC++共有ライブラリで、モジュールと同じ名前のビルド済みの.soは、Android.mkファイルと同じディレクトリにあります。

LOCAL_MODULE_RELATIVE_PATH:= qt

上記のようにシステムライブラリディレクトリの下の "qt "というサブディレクトリにライブラリをインストールするよう指示しています。デフォルトでは、AOSPビルドはELFファイルをチェックして、ライブラリのすべての依存関係がLOCAL_SHARED_LIBRARIESエントリに適切にリストされていることを確認します。これはモジュール宣言の中にチェックをスキップする行を追加することで無効にすることができます。

LOCAL_CHECK_ELF_FILES := false

この場合AOSPビルドはライブラリの依存関係を知らないので、一緒にインストールされないことに注意してください。この場合、各ライブラリを追加するように明示する必要があります。これは製品のmakeファイルの下に、インストール可能なモジュールとしてそれらをすべてリストアップすることで実現できます。例えば、ファイル <aosp-root>/packages/services/Car/car_product/build/car.mk を変更し、インストールしたいライブラリをすべて PRODUCT_PACKAGES の下にリストアップします。


# Append to existing packages, using the name you gave the library module
PRODUCT_PACKAGES += \
    ... \    
    libQt6Core_x86_64 \
    libQt6Quick_x86_64 \
    # The rest of the libraries...
    ...

すべてのライブラリの追加が完了したら、アプリの組み込みに移りましょう。

Building the app


Qt Creatorで「新規プロジェクト - Qt Quick Application」で作成されるテンプレートが、この目的にはちょうどよいでしょう。ここでは、単にQtAppと呼ぶことにします。
Qt for Android キットとデフォルトの配置を使用してビルドし、APK の内容を見てみると、Qt のすべての依存関係が "lib" ディレクトリの下に含まれていることがわかります。

qt-app-bundled-apk-and-contents

Qtはライブラリをシステムライブラリとして提供するので、これは必要ありません。そこで、Unbundled deployment を利用するために、アプリの CMakeLists.txt にいくつかの Qt CMake のプロパティを設定します。


set_target_properties(${target_name} PROPERTIES
    QT_ANDROID_NO_DEPLOY_QT_LIBS True
    QT_ANDROID_SYSTEM_LIBS_PREFIX /system/lib64/qt/
)

QT_ANDROID_NO_DEPLOY_QT_LIBS を true に設定すると、デプロイツールは C++ ライブラリを APK にパッケージしないようになります。代わりに QT_ANDROID_SYSTEM_LIBS_PREFIX を使用して、デバイス上のライブラリを検索する場所をアプリに指示します。ここでは、構築するイメージが64ビットであるため、/system/lib64/qt/を指すようにパスを設定しています。arm64やx86_64のような64ビットアーキテクチャをターゲットにしている場合、これはライブラリのデフォルトディレクトリになります。x86やarmv7などの32ビットイメージをビルドする場合は、代わりに/system/lib/qt/にスワップします。

プロパティを設定した後に新たにビルドすると、APK のサイズが通常の Qt Android アプリよりもずっと小さくなっていることにすぐに気がつくでしょう。(この単純なアプリの場合、サイズは 12 MB 以上から 100 kB 以上に減少しています)

APK の中を覗いてみると、lib フォルダがないことがお分かりいただけるでしょう。 

qt-app-unbundled-apk-and-contents

次に、いよいよアプリを画像に取り込みます。冒頭で述べたように、インストールするライブラリを使用できるのはシステム パーティション下にインストールされたアプリだけなので、先ほどビルドしたアプリをシステム アプリとして追加する必要があります。そこで再び、AOSP ツリー下にフォルダーを作成します。ここでは QtApp と呼び、<aosp-root>/packages/apps/Car/ の下に配置します。
作成したフォルダの下に、先ほどビルドした APK とそのライブラリをコピーします。ライブラリは、アプリのビルド ディレクトリの下にあり、デフォルトでは libapp<アプリ名>_<ターゲット-arch>.so という名前になっています。QtApp というアプリの場合、libappQtApp_x86_64.so というライブラリがコピーされます。ここでもAndroid.mk ファイルを作成しますが、今回は少し中身が違うようです。


LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)

LOCAL_MODULE_TAGS := optional
# Module name should match apk name to be installed (without the .apk extension)
LOCAL_MODULE := QtApp
LOCAL_SRC_FILES := $(LOCAL_MODULE).apk
LOCAL_MODULE_CLASS := APPS
LOCAL_MODULE_SUFFIX := $(COMMON_ANDROID_PACKAGE_SUFFIX)
# Adds listed files under app's native library dir
LOCAL_PREBUILT_JNI_LIBS := \
    # Replace with the name of your app's library
    libQtApp_x86_64.so \

LOCAL_CERTIFICATE := platform
include $(BUILD_PREBUILT)

今回はビルド時にビルド済みアプリを追加することを知らせます。
またアプリと一緒に提供するライブラリーをリストアップし、アプリのネイティブ ライブラリー ディレクトリにインストールするようにします。


LOCAL_PREBUILT_JNI_LIBS := \
    # Replace with the name of your app's library
    libappQtApp_x86_64.so \

アプリ用の組み込み済みJNIライブラリは、インクルードする前にモジュールとして宣言する必要はありません。代わりにAndroid.mkファイルとの関連で、ライブラリへのパス、サフィックスをインクルードして提供します。

ライブラリとアプリの両方がAOSPツリーに追加されたので、イメージをビルドすることができます。ここでは、以下の手順でイメージを作成します。
ビルドしたイメージを元に Android Virtual Device を作成したら、エミュレータ上で起動して見てみましょう。system/app/QtAppというフォルダがあり、その中身は以下のようになっています。qt-app-on-device

そのため、アプリ自身のライブラリは、アプリ自身のディレクトリの下から見つかる唯一のものとなりました。一方、他のすべてのQtライブラリは、/system/lib64/qt/の下にインストールされるようになりました。

これにより、QtAppやデバイスにインストールされた他のすべてのQtシステムアプリは、共有ライブラリの単一セットを使用できるようになりました - 各アプリがそれぞれを持参する必要はありません。


Blog Topics:

Comments