つくるの大好き。

つくるのが大好きな人の記録。

Android C++開発 [3] : 多次元配列をJNIでやり取りする

少し間が空きましたがまたモチベーションが出てきたので続きを書きます(笑)

先回はAndroid実機上で実行するInstrumentedTestでUIを表示しました。
今回はJava側で生成した多次元配列をC++側に渡し、C++側でバイナリデータをセットします。そしてそのデータをJava側に受け渡し、ビットマップとしてImageViewに表示します。

実際のユースケースとしては二つのカメラから取得した映像データを画面に表示するような時に利用できます。特殊ですねw

Activityの変更

先回作成したActivityに二つのImageViewを配置します。
f:id:peugeot-106-s16:20181211155019p:plain ImageViewの名称はそれぞれimageView01, imageView02としておきます。

Java側InstrumentedTestの実装

InstrumentedTestでは後述するC++メソッドに二つのバイナリバッファを渡し、データを格納させます。

@Test
public void imageRefreshTest() throws InterruptedException {
    int stride = 320;
    int lines = 240;
    Bitmap[] bitmaps = {
            Bitmap.createBitmap(stride, lines, Bitmap.Config.ARGB_8888),
            Bitmap.createBitmap(stride, lines, Bitmap.Config.ARGB_8888)
    };
    int[][] buffers = {
            new int[stride * lines],
            new int[stride * lines]
    };

    assertTrue(getBufferArray(buffers));

    bitmaps[0].setPixels(buffers[0], 0, stride, 0, 0, stride, lines);
    bitmaps[1].setPixels(buffers[1], 0, stride, 0, 0, stride, lines);
    this.refreshImageOnUiThread(bitmaps);

    Thread.sleep(5000);
}

下記の処理を行っています。

  • UIに表示する2つのBitmapを生成
  • C++に受け渡す2つバイナリバッファをもつ2次元配列 buffresを生成
  • C++関数 getBufferArray()をコール
  • バイナリバッファの値をBitmapにセットする
  • UIスレッドでBitmapをImageViewに表示する

このコードからわかるようにバッファはJava側で生成されたものです。 このバッファをC++でアクセスしてみましょう。

C++ネイティブコードからJavaの多次元配列にアクセスする

C++側は下記のようなコードとなっています。

native-lib.cpp

extern "C" JNIEXPORT jboolean JNICALL Java_com_example_satoshi_1maemoto_myapplication_ExampleInstrumentedTest_getBufferArray(JNIEnv *env, jobject, jobjectArray array) {
    auto elementCount = env->GetArrayLength(array);
    if (elementCount == 0) {
        return false;
    }

    for (auto index = 0; index < elementCount; index++) {
        jintArray element = (jintArray)env->GetObjectArrayElement(array , index);
        auto buffer = env->GetIntArrayElements(element, nullptr);
        auto bufferCount = env->GetArrayLength(element);

        auto color = ((index % 2) == 0) ? 0x00000000 : 0x00FFFFFF;
        auto step = ((index % 2) == 0) ? 1 : -1;
        for (auto pixel = 0; pixel < bufferCount; pixel++) {
            buffer[pixel] = 0xFF000000 | color;
            color += step;
        }

        env->ReleaseIntArrayElements(element, buffer, 0);
        env->DeleteLocalRef(element);
    }
    return true;
}
  • env->GetArrayLengh()で配列の要素数を取得できる
  • env->GetObjectArrayElement()でJava側2次元配列の1次元目の要素を取得する。このサンプルではint配列が格納されている要素[element]が取得できる。
  • env->GetIntArrayElements()でさらに[element]に格納されているint配列[buffer]を取得する。
  • bufferにデータを格納する
  • 最後に参照を開放する

このようにC++側では配列の要素を取得して利用、使い終わったら参照を開放する、という手順でデータアクセスをします。
少し手間ですがJava側は非同期にGCが走ることがあるのでこのような使い方になるようです。

UIスレッドでBitmapを表示する

C++側からデータが取得できたのでUIに表示しましょう。 この処理も先回のテキスト表示と同様UIスレッドで処理を行わせる必要があります。
そのため下記のようなユーティリティ関数を用意しました。

private void refreshImageOnUiThread(Bitmap[] bitmaps)
{
    class ShowImagesAction implements Runnable
    {
        private Bitmap[] bitmaps;
        public ShowImagesAction(Bitmap[] bitmaps)
        {
            this.bitmaps = bitmaps;
        }

        @Override
        public void run() {
            ImageView imageView01 = rule.getActivity().findViewById(R.id.imageView01);
            imageView01.setImageBitmap(this.bitmaps[0]);

            ImageView imageView02 = rule.getActivity().findViewById(R.id.imageView02);
            imageView02.setImageBitmap(this.bitmaps[1]);
        }
    }

    this.rule.getActivity().runOnUiThread(new ShowImagesAction(bitmaps));
}

実際にInstrumentedTestを実行すると下記のようにグラデーションのかかった2つの映像が表示されます。 f:id:peugeot-106-s16:20181211162205j:plain

まとめ

Andoid実機で動作するInstrumentedTestでC++側コードと多次元配列をやり取りする方法をお伝えしました。

プロジェクト一式は下記にアップしています。

github.com