Android C++開発 [3] : 多次元配列をJNIでやり取りする
少し間が空きましたがまたモチベーションが出てきたので続きを書きます(笑)
先回はAndroid実機上で実行するInstrumentedTestでUIを表示しました。
今回はJava側で生成した多次元配列をC++側に渡し、C++側でバイナリデータをセットします。そしてそのデータをJava側に受け渡し、ビットマップとしてImageViewに表示します。
実際のユースケースとしては二つのカメラから取得した映像データを画面に表示するような時に利用できます。特殊ですねw
Activityの変更
先回作成したActivityに二つのImageViewを配置します。
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つの映像が表示されます。
まとめ
Andoid実機で動作するInstrumentedTestでC++側コードと多次元配列をやり取りする方法をお伝えしました。
プロジェクト一式は下記にアップしています。