LoginSignup
3
4

More than 1 year has passed since last update.

【2022年版】OpenCV for Androidのlibopencv_java4.soのバイナリサイズを減らし、APKのサイズを減らす

Posted at

はじめに

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が初めから入っています。

Dockerfile
FROM circleci/android:api-30-ndk

cmake、ninja、ccacheをインストールします。

Dockerfile
RUN sudo apt-get update
RUN sudo apt-get install cmake ninja-build ccache

カレントディレクトリをホームディレクトリにして、OpenCVのソースコードをダウンロードして解凍します。

Dockerfile
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

ビルドのためのディレクトリに移動します。

Dockerfile
ENV BUILD_DIR /home/circleci/opencv-4.5.5/platforms/
WORKDIR ${BUILD_DIR}

build_sdk.pyを2カ所書き換えます。

C++言語の標準ライブラリは共有ライブラリ使用からスタティックリンクにします。これをやらないと、別途、5MB前後ある libc++_shared.so ファイルをAndroid NDKから持ってきてアプリに組み込み必要があり、スタティックリンクよりも容量が大きくなります。

該当箇所
公式の解説

build_sdk.py
            self.cmake_vars['ANDROID_STL'] = 'c++_static'

haveIPP関数の戻り値をTrue固定にします。それによってx86およびx86_64アーキテクチャ向けの libopencv_java4.so ファイルの容量が削減されます。

build_sdk.py
    def haveIPP(self):
        return False

これら2点の書き換えのための、patchファイルを作成しました。

build_sdk.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を適用します。

Dockerfile
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 を設定します。

Dockerfile
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 を付けないとビルドできませんでした。

docker-compose.yml
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でインストールできます。

image.png

その上で 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のセキュリティを担保した上で、ダイアログを防ぐ方法は分かりませんでした。

image.png

補足

共有ライブラリが依存する共有ライブラリを調べる方法

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 でインストールできます。

3
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
4