アンドロイドでNDKの使い方

  アンドロイド開発においては、NDKというツールが使用できます。NDKは既存のアンドロイドのjav…

 

アンドロイド開発においては、NDKというツールが使用できます。NDKは既存のアンドロイドのjavaコードの代わりにCや, C++の言語の記述からアプリを開発するツールです。NDKは以下の特徴があります。

  • 既存のCや, C++のコードの再利用ができる
  • javaの仮想マシン(dalvik)を通じないので、高速に実行できる
  • 実行においてはCPUが直接に実行する仕組みなので、デバイスのCPUに依存する
  • 主に処理が思い3Dグラフィックスや, アニメーションの表現に使われる傾向がある
  • NDKツールを使う場合は、アプリの処理の一部分をJNIの規約に従って記述するのが一般的な方法です。この記事ではアンドロイドのプロジェクトにNDKツールを設定する方法とJNI規約について簡単に説明します。以下はNDKがすでにインストールされていることを前提にしています。

     

    プロジェクトでのNDKツール設定

    ①生成したプロジェクトをクリックし、「右クリック」⇒「Android tool」⇒「Add Native Support…」をクリックします。

    NDKツール追加

    プロジェクトにNDKツール追加する

     

    ②モジュール(共有ライブラリ)の名前を指定する

    モジュールの名前指定

    モジュールの名前指定

     

    ③NDKの設定を確認する。

    NDK設定確認

    NDK設定確認

    NDKパース確認

    NDKパース確認

     

    ④Android.mkファイルの作成

    Android.mkにはmodule名や、headerファイルのパースなどを記述します。

    LOCAL_PATH := $(call my-dir)
    include $(CLEAR_VARS)
    LOCAL_MODULE    := nativeDescription
    LOCAL_SRC_FILES := nativeDescription.cpp
    LOCAL_LDLIBS     += -llog -ldl
    include $(BUILD_SHARED_LIBRARY)
    

     

    ⑤Android SDKでのNDKモジュールのロード
    MainActivityでは以下のようにモジュールをロードします。

    static {    
        System.loadLibrary("nativeDescription");
    }
    

     

    ⑤コードを記述する
    コードの記述においては、以下のJNI規約をご参考ください。

     

    JNI規約

    java側とc/c++側においてお互いの関数やメソッドを呼び出したり、パラメータを伝送したりするために、JNI規約を使います。規約にはシグネチャー記述や、データ型の表記変更など色々ややこしいですので、表記に注意する必要があります。ここでは以下のインターフェースに対してのサンプルプコードを記述しますので、ご参考になると幸いです。

  • java/c++間での整数型パラメータの交換
  • java/c++間での配列パラメータの交換
  • java/c++間でのオブジェクパラメータの交換
  • java/c++間でのオブジェクトのエレメントの参照
  • java/c++間でのpublicメンバメソッドの参照
  • java/c++間でのstaticメンバメソッドの参照
  • java/c++間でのprivateメンバメソッドの参照
  •  
     

    MainActivity.java

    package com.example.nativedescription;
    
    import android.os.Bundle;
    import android.app.Activity;
    import android.widget.TextView;
    
    public class MainActivity extends Activity {
    	private static final String TAG = "Native Test";
        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            StringBuffer buf = new StringBuffer();
            TextView  tv = new TextView(this);
    
            float avg;
            float sum;
            float math = 80.f;
            float science = 73.8f;
            float english = 88.f;
            float[]	gradeArry = new float[5];
            Student student = new Student("Lee", "zaemin", 30);
    
            avg = nativeGetAverageA(math, science, english);
            buf.append("nativeGetAverage(float,float,float) return : "+String.valueOf(avg)+"\n");
    
            gradeArry[0] = math;
            gradeArry[1] = science;
            gradeArry[2] = english;
            avg = nativeGetAverageB(gradeArry);
            buf.append("nativeGetAverage float[] return : "+String.valueOf(avg)+"\n");
    
            student.setGradeData(math, science, english);
            avg = nativeGetAverageC(student);
            buf.append("nativeGetAverage Object return : "+String.valueOf(avg)+"\n");
    
            buf.append("nativechangeData object\n");
            buf.append("-----------------------\n");
            buf.append("before age :"+String.valueOf(student.Age)+"\n");
            nativeChangeAge(student);
            buf.append("after age  : "+String.valueOf(student.Age)+"\n");
            buf.append("-----------------------\n");
    
            avg = nativeFireStaticMethod(student);
            buf.append("nativeFirePublicMethod object return : "+String.valueOf(avg)+"\n");
            avg = nativeFireStaticMethod(student);
            buf.append("nativeFireStaticMethod object return : "+String.valueOf(avg)+"\n");
            sum = nativeFirePrivateMethod(student);
            buf.append("nativeFirePrivateMethod object return : "+String.valueOf(sum)+"\n");
    
            tv.setText( buf.toString() );
            setContentView(tv);
        }
    
        /**
         * @param  float 数学成績
         * @param  float 科学成績
         * @param  float 英語成績
         * @return float 平均
         */
        public native float	 nativeGetAverageA(float math, float science, float english);
    
        /**
         * @param  float[] 成績配列
         * @return float 平均
         */
        public native float  nativeGetAverageB(float[] fArry);
    
        /**
         * @param  Student オブジェクト
         * @return float 平均
         */
        public native float  nativeGetAverageC(Student obj);
    
        /**
         * メンバ変数値の変更
         */
        public native void   nativeChangeAge(Student obj);
    
        /**
         * public javaメソッド実行
         */
        public native float  nativeFirePublicMethod(Student obj);
        /**
         * static javaメソッド実行
         */
        public native float  nativeFireStaticMethod(Student obj);
        /**
         * private メソッド実行
         */
        public native float  nativeFirePrivateMethod(Student obj);
    
        static {
            System.loadLibrary("nativeDescription");
        }
    }
    

    Student.java

    package com.example.nativedescription;
    
    public class Student {
    	String 				Name;
    	String				NickName;
    	int					Age;
    	final static int	Subject_Num = 3;
    
    	float[] 			GradeData;
    	float 				avg;
    
    	public Student(String name, String nick_name, int age ){
    		Name = name;
    		Age  = age;
    		NickName = nick_name;
    		GradeData = new float[Subject_Num];
    	}
    	public void setGradeData(float math, float science, float english){
    		GradeData[0] = math;
    		GradeData[1] = science;
    		GradeData[2] = english;
    	}
    	public float getMathGrade(){
    		return GradeData[0];
    	}
    	public float getScienceGrade(){
    		return GradeData[1];
    	}
    	public float getEnglishGrade(){
    		return GradeData[2];
    	}
    	public float getAverageGrade(){
    		return (getSumGrade()/Subject_Num);
    	}
    	public String changeNickName(String nick_name){
    		NickName = nick_name;
    		return NickName;
    	}
    
    	private float getSumGrade(){
    		float sum = 0.f;
    		for(int i=0; i<Subject_Num ; i++){
    			sum += GradeData[i];
    		}
    		return sum;
    	}
    
    	static float getAverageGradeStatic(float math, float science, float english){
    		return (math+science+english)/3.f;
    	}
    	static int getSubjectNum(){
    		return Subject_Num;
    	}
    }
    

    NativeDescription.cpp

    #include <jni.h>
    #include <stddef.h>
    #include "nativeDescription.h"
    
    #include <android/log.h>
    #define LOG_TAG "nativeDescription.cpp"
    #define LOGD(...) ((void)__android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__))
    
    JNIEXPORT jfloat
    Java_com_example_nativedescription_MainActivity_nativeGetAverageA( JNIEnv* env, jobject thiz,
    		jfloat math, jfloat science, jfloat english)
    {
    	LOGD("Java_com_example_nativedescription_MainActivity_nativeGetAverageA");
    	float avg = (math+science+english)/3.0;
        return avg;
    }
    
    JNIEXPORT jfloat
    Java_com_example_nativedescription_MainActivity_nativeGetAverageB( JNIEnv* env, jobject thiz,
            		jfloatArray fArry)
    {
    	LOGD("Java_com_example_nativedescription_MainActivity_nativeGetAverageB");
    	int fLength = env->GetArrayLength(fArry);				//配列の長さをゲットする
    	//C++上でjavaの配列にアクセスできるポイント配列を取得
    	float *fData = env->GetFloatArrayElements(fArry, 0);
    
    	float sum = 0.0;
    	float avg;
    	for(int i=0 ; i<fLength ; i++ ){
    		sum += fData[i];
    	}
    	avg = sum/(float)fLength;
    	//C++上で確保したメモリ空間をreleaseする
    	env->ReleaseFloatArrayElements(fArry, fData, 0);
    
    	return avg;
    }
    
    JNIEXPORT jfloat
    Java_com_example_nativedescription_MainActivity_nativeGetAverageC( JNIEnv* env, jobject thiz,
                	jobject obj)
    {
    	LOGD("Java_com_example_nativedescription_MainActivity_nativeGetAverageC");
    	//クラスゲット
    	jclass cls = env->GetObjectClass(obj);
    	//クラスのメンバ変数idの取得([F : floatの配列を表すシグネチャ, fArrayはインスタンのメンバ変数名)
    	jfieldID fieldId = env->GetFieldID(cls, "GradeData", "[F");
    
    	//ゲットしたクラスのメンバ変数idから、そのidのインスタンスのメンバ変数の取得 (配列なのでオブジェクトとして扱う)
    	jobject objArray = env->GetObjectField(obj, fieldId);
    
    	//データをfloat配列型に変換する
    	jfloatArray* fArray = reinterpret_cast<jfloatArray*>(&objArray);
    
    	jsize fLength = env->GetArrayLength(*fArray);
    
    	//C++上でjavaの配列にアクセスできるポイント変数を取得
    	float *fdata = env->GetFloatArrayElements(*fArray, 0);
    
    	float sum = 0.0;
    	float avg;
    	for(int i=0 ; i<fLength ; i++ ){
    	    sum += fdata[i];
    	}
    	avg = sum/(float)fLength;
    	//C++上で確保したメモリ空間をreleaseする
    	env->ReleaseFloatArrayElements(*fArray, fdata, 0);
    
    	return avg;
    }
    
    JNIEXPORT void
    JNICALL Java_com_example_nativedescription_MainActivity_nativeChangeAge(JNIEnv *env, jobject thiz,
        			jobject obj)
    {
    	LOGD("JNICALL Java_com_example_nativedescription_MainActivity_nativeChangeAge");
    	//クラスゲット
    	jclass cls = env->GetObjectClass(obj);
    	if( cls==NULL ){
    		return;
    	}
    	jfieldID fieldId = env->GetFieldID(cls, "Age", "I");
    	jint age = env->GetIntField(obj, fieldId);
    	LOGD("age	   : %d", age);
    
    	env->SetIntField(obj, fieldId, 28);
    }
    
    JNIEXPORT jfloat
    JNICALL Java_com_example_nativedescription_MainActivity_nativeFirePublicMethod(JNIEnv *env, jobject thiz,
            		jobject obj)
    {
    	LOGD("JNICALL Java_com_example_nativedescription_MainActivity_nativeFirePublicMethod");
    	jclass cls = env->GetObjectClass(obj);
    	if( cls==NULL ){
    		return 0;
    	}
    	//クラスのメンバmethodのidをゲットする
    	jmethodID mid = env->GetMethodID(cls, "getAverageGrade", "()F");
    	//static関数の呼び出しなので、インスタンスの代わりにクラスを渡す
    	return env->CallFloatMethod(obj, mid);
    }
    
    JNIEXPORT jfloat
    JNICALL Java_com_example_nativedescription_MainActivity_nativeFireStaticMethod(JNIEnv *env, jobject thiz,
            		jobject obj)
    {
    	LOGD("JNICALL Java_com_example_nativedescription_MainActivity_nativeFireStaticMethod");
    	jclass cls = env->GetObjectClass(obj);
    	if( cls==NULL ){
    		return 0;
    	}
    	//クラスのメンバmethodのidをゲットする
    	jmethodID mid = env->GetStaticMethodID(cls, "getAverageGradeStatic", "(FFF)F");
    	//static関数の呼び出しなので、インスタンスの代わりにクラスを渡す
    	return env->CallStaticFloatMethod(cls, mid, 60.0, 72.0, 94.2);
    }
    
    JNIEXPORT jfloat
    JNICALL Java_com_example_nativedescription_MainActivity_nativeFirePrivateMethod(JNIEnv *env, jobject thiz,
                	jobject obj)
    {
    	LOGD("JNICALL Java_com_example_nativedescription_MainActivity_nativeFirePrivateMethod");
    	jclass cls = env->GetObjectClass(obj);
    	if( cls==NULL ){
    		return 0;
    	}
    	//クラスのメンバmethodのidをゲットする
    	jmethodID mid = env->GetMethodID(cls, "getSumGrade", "()F");
    	//static関数の呼び出しなので、インスタンスの代わりにクラスを渡す
    	return env->CallFloatMethod(obj, mid);
    }