はじめに
Qiitaマイルストーン
2019年01月20日に投稿した記事「OpenCV for Androidのlibopencv_java4.soのバイナリサイズを減らし、APKのサイズを減らす」ですが、おかげさまで3年かけて10LGTMを達成して、Qiitaマイルストーンで紹介されました。
現在ではできない方法
そこで3年前のやり方は2022年1月現在で可能なのかやってみたところ、できなかったです。そこで新たにやり方を調べたので紹介しようと思います。
Dockerを使ってビルドする
結論からいうと、MacではDockerでビルドできました。Macネイティブではできなかった理由は後で解説します。
全体ソースコード
今回のソースコードはGitHubで公開しています。
https://github.com/tfandkusu/opencv_android_reduced_docker
ビルド環境の構築
ベースとなるDockerイメージはCircleCIからお借りしました。Android SDKとAndroid NDKが初めから入っています。
FROM circleci/android:api-30-ndk
cmake、ninja、ccacheをインストールします。
RUN sudo apt-get update
RUN sudo apt-get install cmake ninja-build ccache
カレントディレクトリをホームディレクトリにして、OpenCVのソースコードをダウンロードして解凍します。
WORKDIR /home/circleci
RUN wget https://github.com/opencv/opencv/archive/refs/tags/4.5.5.tar.gz
RUN tar xvzf 4.5.5.tar.gz
ビルドのためのディレクトリに移動します。
ENV BUILD_DIR /home/circleci/opencv-4.5.5/platforms/
WORKDIR ${BUILD_DIR}
build_sdk.pyを2カ所書き換えます。
C++言語の標準ライブラリは共有ライブラリ使用からスタティックリンクにします。これをやらないと、別途、5MB前後ある libc++_shared.so
ファイルをAndroid NDKから持ってきてアプリに組み込み必要があり、スタティックリンクよりも容量が大きくなります。
self.cmake_vars['ANDROID_STL'] = 'c++_static'
haveIPP関数の戻り値をTrue固定にします。それによってx86およびx86_64アーキテクチャ向けの libopencv_java4.so
ファイルの容量が削減されます。
def haveIPP(self):
return False
これら2点の書き換えのための、patchファイルを作成しました。
--- build_sdk.py 2021-12-25 12:53:27.000000000 +0900
+++ build_sdk_fixed.py 2022-01-30 06:52:41.000000000 +0900
@@ -131,14 +131,14 @@
self.cmake_vars['ANDROID_TOOLCHAIN_NAME'] = toolchain
else:
self.cmake_vars['ANDROID_TOOLCHAIN'] = 'clang'
- self.cmake_vars['ANDROID_STL'] = 'c++_shared'
+ self.cmake_vars['ANDROID_STL'] = 'c++_static'
if ndk_api_level:
self.cmake_vars['ANDROID_NATIVE_API_LEVEL'] = ndk_api_level
self.cmake_vars.update(cmake_vars)
def __str__(self):
return "%s (%s)" % (self.name, self.toolchain)
def haveIPP(self):
- return self.name == "x86" or self.name == "x86_64"
+ return False
#===================================================================================================
patchファイルをDockerイメージに含めて、patchを適用します。
COPY --chown=circleci:circleci build_sdk.patch ${BUILD_DIR}
RUN patch -u android/build_sdk.py < ${BUILD_DIR}build_sdk.patch
環境変数 ANDROID_SDK
ANDROID_NDK
を設定します。
ENV ANDROID_SDK /opt/android/sdk/
ENV ANDROID_NDK /opt/android/android-ndk-r21e/
docker-compose.yml
を作成します。Docker側の生成物をホスト側から拾えるように volumes
オプションを設定します。
build_sdk.py
の --modules_list
引数で含めるOpenCVのモジュールを定義します。--no_samples_build
を付けないとビルドできませんでした。
version: '3'
services:
main:
build: .
volumes:
- ./output:/home/circleci/opencv-4.5.5/platforms/output
command: android/build_sdk.py output --no_samples_build --modules_list core,imgcodecs,imgproc,java
ビルドの実行
これで環境が整ったので、こちらの2コマンドだけでビルドできます。
docker compose build
docker compose run main
生成物
これで libopencv_java4.so
とjavaファイル群が生成されました。
find output/OpenCV-android-sdk/sdk/native/libs -type f
output/OpenCV-android-sdk/sdk/native/libs/armeabi-v7a/libopencv_java4.so
output/OpenCV-android-sdk/sdk/native/libs/x86/libopencv_java4.so
output/OpenCV-android-sdk/sdk/native/libs/arm64-v8a/libopencv_java4.so
output/OpenCV-android-sdk/sdk/native/libs/x86_64/libopencv_java4.so
find output/OpenCV-android-sdk/sdk/java/src -type f
output/OpenCV-android-sdk/sdk/java/src/org/opencv/core/Scalar.java
output/OpenCV-android-sdk/sdk/java/src/org/opencv/core/MatOfFloat6.java
output/OpenCV-android-sdk/sdk/java/src/org/opencv/core/Size.java
output/OpenCV-android-sdk/sdk/java/src/org/opencv/core/MatOfRotatedRect.java
以下略
削減された容量
libopencv_java4.so
はこのように削減されました。公式配布の libopencv_java4.so
は別途 libc++_shared.so
が必要ですが、今回のビルドでは不要です。
アーキテクチャ | 公式配布 | libc++_shared.so | 今回のビルド(libc++_shared.soは不要) |
---|---|---|---|
arm64-v8 | 17M | 5.9M | 8.9M |
armeabi-v7a | 11M | 4M | 5.7M |
x86_64 | 51M | 6.1M | 13M |
x86 | 36M | 5.3M | 11M |
Macネイティブでビルドできなかった理由
Android NDKはAndroid Studioでインストールできます。
その上で ANDROID_SDK
ANDROID_NDK
環境変数を設定した上で、build_sdk.py
を実行すると、 NullPointerException
が出てしまいました。
* What went wrong:
A problem occurred configuring project ':opencv'.
> java.lang.NullPointerException (no error message)
OpenCVのIssueを見ると、Android NDKのバージョンが新しくなると発生する問題だと分かりました。
そこで古いバージョンのAndroid NDKをこちらからインストールしました。
すると、このようなダイアログが出てしまい、ビルドが中断されてしまいました。Macのセキュリティを担保した上で、ダイアログを防ぐ方法は分かりませんでした。
補足
共有ライブラリが依存する共有ライブラリを調べる方法
readelf
コマンドで調べることができます。
readelf -d libopencv_java4.so
Dynamic section at offset 0x8daa80 contains 31 entries:
Tag Type Name/Value
0x0000000000000001 (NEEDED) Shared library: [libdl.so]
0x0000000000000001 (NEEDED) Shared library: [libm.so]
0x0000000000000001 (NEEDED) Shared library: [liblog.so]
0x0000000000000001 (NEEDED) Shared library: [libjnigraphics.so]
0x0000000000000001 (NEEDED) Shared library: [libz.so]
0x0000000000000001 (NEEDED) Shared library: [libc.so]
0x000000000000000e (SONAME) Library soname: [libopencv_java4.so]
以下略
Macでは brew install binutils
でインストールできます。