NDKを使って、nativeに処理をさせる


Android NDKを使う(アプリの高速化) の項目において、NDKのセットアップとサンプルの実行方法を紹介しました。

今回は、実際にNDKを使用する手法を紹介します。

Sampleとして作成するのは、用意した画像データからRedの色データを消去するものです。

1ピクセル毎に処理が必要になるため、Nativeで処理を行うことで高速化を狙っています。

#画像データは初期データと処理後データを見比べた物

詳細な実装方法は続きをご覧ください。

NDKを使用する時に必要なこと

  • JNIフォルダの追加
  • Application.mk   Android.mk  (ヘッダファイル)の追加
  • NativeSourceの追加
  • JavaSourceでのStaticLibraryの呼び出し

以上の4点が必要です。

追加するファイルの場所等は以下、今回のプロジェクト構成を参考にしてください。

Android.mkとApplication.mk

Android.mkとApplication.mkを作成します。

これらは一から作成するよりは、既存のサンプルプロジェクトからコピーしてくるのがよいと思います。

#今回は ndk/samples/native_activity/jni/ からコピーし作成しました。

コピーしてきたものを修正します。

Android.mk

1
2
3
4
5
6
7
8
LOCAL_PATH := $(call my-dir)
 
include $(CLEAR_VARS)
 
LOCAL_MODULE    := native_sample
LOCAL_SRC_FILES := native_sample.c
 
include $(BUILD_SHARED_LIBRARY)

LOCAL_SRC_FILESにはソースコードを指定、LOCAL_MODULEにはモジュール名(任意)を指定します。

Application.mk

1
APP_PLATFORM := android-8

同じ様に、赤字が修正箇所です。

Androidのプラットフォームバージョンを指定します。

#今回はAndoid-8(ver 2.2) 向けに作成

Java側のソースコードの記述

先にJavaソースコードにおいて、native-Methodを記述することで

ヘッダファイルをコマンドから作成することができます。
まず、以下の様にLibraryの呼び出し部分を記述します。
ここで引数に渡す値は、LOCAL_MODULEに指定した値と同じにしておきます。

1
2
3
4
5
public class Main extends Activity {
   static {
        System.loadLibrary("native_sample");
   }
    ......

次に、native-Methodを記載します。
今回は、bitmapのピクセルデータをNativeへ受け渡すため、int-arrayを引数に持っています。

1
public native void nativeBinarization(int[] pixels, int x, int y);

最後に、Methodの呼び出し部分を記載します。
本サンプルでの流れは、 リソースから取得したBitmapオブジェクトを複製し、(AndroidはデフォルトではRGB_565を使用しています)
ピクセル毎のintデータを作成、Native-Methodへ受け渡します。
その後、初期データと改変後データを並べて表示しています。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
@Override
public void onCreate(Bundle savedInstanceState) {
......
    Bitmap bmp = BitmapFactory.decodeResource(getResources(), R.drawable.sample);
    Bitmap subbmp = bmp.copy(Bitmap.Config.RGB_565, true);
 
    int width = subbmp.getWidth();
    int height = subbmp.getHeight();
 
    int pixels[] = new int[width * height];
    int cnt = 0;
    subbmp.getPixels(pixels, 0, width, 0, 0, width, height);
 
    nativeBinarization(pixels,subbmp.getWidth(),subbmp.getHeight());
    subbmp.setPixels(pixels, 0, width, 0, 0, width, height);
    LinearLayout llayout = (LinearLayout)findViewById(R.id.llayout);
 
    ImageView binarizeBmp = new ImageView(this);
    binarizeBmp.setImageBitmap(subbmp);
    ImageView normalBmp = new ImageView(this);
    normalBmp.setImageBitmap(bmp);
 
    llayout.addView(normalBmp);
    llayout.addView(binarizeBmp);
 
}

ヘッダファイルの作成

ヘッダファイルの作成を行います。

#win環境では、cygwinを用いて下さい。

#また、javah に対してPATHを通す必要があるかもしれません。

プロジェクトのトップディレクトリ(Eclipse-workspace/<Project名>/)

において、以下コマンドを実行することで、ヘッダを作成することができます。

1
$ javah -classpath bin -o jni/<header名.h> org.jpn.techbooster.sample.nativesample2.Main

変更が必要な部分はheader名と、最後の引数です。

org.jpn.techbooster.sample.nativesample2.Main は、

org.jpn.techbooster.sample.nativesample パッケージの Main クラスにあるNative-Methodを探しに行ってね!

という意味合いです。

これで、jniフォルダにヘッダが作成されています。

NativeCodeを記述する

最後に、Native部分を実装します。

まず、Android.mkにおいて LOCAL_SRC_FILES に記載したファイルを作成します。

内部のソースコードは以下の通りです。

まずは、関数の書き出しまで、

1
2
3
4
5
6
7
#include <jni.h>
#include <android/log.h>
#include "native_sample.h"
 
JNIEXPORT void JNICALL Java_org_jpn_techbooster_sample_nativesample2_Main_nativeBinarization
(JNIEnv *env, jobject thiz, jintArray colors, jint w, jint h)
{

jniヘッダは必ずincludeする必要があります。
関数の名称ですが、作成したヘッダファイルからコピーし、作成します。
#また、自作した引数の他に2つ引数が増えていますが、JNIの仕様だと思ってください。
#詳しくはJNIについて調べると良いです。

続いて処理の部分について、

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
int r, g, b;
 int cnt;
 jint* bmpColors = (*env)->GetIntArrayElements(env,colors,0);
 
 for(cnt=0; cnt < (w * h); cnt++){
 /* rgb 抽出 */
 r = (bmpColors[cnt] & 0x0000F800) >> 11;
 g = (bmpColors[cnt] & 0x000007E0) >> 5;
 b = (bmpColors[cnt] & 0x0000001F);
 
 /* 色データ組み換え */
 bmpColors[cnt] = (r<<11) | (g << 5) | b;
 
 }
 (*env)->ReleaseIntArrayElements(env, colors, bmpColors, 0);
 
}

以下二行に関しては、JNIでの特殊な書き方になります。 対になっており、cでのmalloc-freeと同様に、使用後は必ずReleaseするようにします。

1
2
<pre>jint* bmpColors = (*env)->GetIntArrayElements(env,colors,0);
<pre>(*env)->ReleaseIntArrayElements(env, colors, bmpColors, 0);

残りの部分はRGB_565に則り、各色のデータを取得し、
Redデータのみを返さない様な形で実装しています。

NativeCodeをコンパイルする

Nativeのソースコードの記述が終了したら、コンパイルをします。
#Eclipseでの環境の様に自動ではありませんので、注意
プロジェクトのトップディレクトリにおいて、
以下コマンドを実行することで、コンパイルが可能です。
1
2
3
</div>
<div>$ndk-build</div>
<div>
無事にコンパイルエラーが出ないことを確認し、
Eclipseにおいて、Refreshを実施した後に、実行してください。
#Eclipseは .c .h .so ファイル等を管理していません。
#コマンドラインから追加された各ファイルを読み込ませる為に必要です。

長々とお疲れ様でした。