概要
AndroidアプリでJNI(Java Native Interface)を利用し、C言語で記述した演算ロジックを動的登録する方法を解説します。本記事では四則演算(加算・減算・乗算・除算)を行う簡易電卓を例に、JNI_OnLoadによる動的登録の実装手順を示します。
レイアウト設定
レイアウトファイル(activity_main.xml)で2つのEditText(数値入力用)と4つのButton(演算ボタン)を配置します。デバッグ時にレイアウトが崩れる場合は、適宜調整してください。
Java側コード
MainActivity.javaにて、以下の流れで実装します。
- 変数宣言(EditText、Button、float値)
- onCreate内で初期化処理(initメソッド)
- 演算処理(yunsuanメソッド)でボタンにクリックリスナーを設定
- nativeメソッド(add, sub, mul, div)を定義
- 動的ライブラリをロード(System.loadLibrary)
package com.example.jsuanq;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;
public class MainActivity extends Activity {
private EditText inputA, inputB;
private Button btnAdd, btnSub, btnMul, btnDiv;
private float valA, valB;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initViews();
setupListeners();
}
private void initViews() {
inputA = findViewById(R.id.editText1);
inputB = findViewById(R.id.editText2);
btnAdd = findViewById(R.id.add);
btnSub = findViewById(R.id.sub);
btnMul = findViewById(R.id.mul);
btnDiv = findViewById(R.id.div);
}
private void setupListeners() {
OnClickListener listener = new OnClickListener() {
@Override
public void onClick(View v) {
valA = Float.parseFloat(inputA.getText().toString());
valB = Float.parseFloat(inputB.getText().toString());
float result = 0;
switch (v.getId()) {
case R.id.add: result = add(valA, valB); break;
case R.id.sub: result = sub(valA, valB); break;
case R.id.mul: result = mul(valA, valB); break;
case R.id.div: result = div(valA, valB); break;
}
Toast.makeText(MainActivity.this, String.valueOf(result), Toast.LENGTH_SHORT).show();
}
};
btnAdd.setOnClickListener(listener);
btnSub.setOnClickListener(listener);
btnMul.setOnClickListener(listener);
btnDiv.setOnClickListener(listener);
}
// native メソッド宣言
private native float add(float a, float b);
private native float sub(float a, float b);
private native float mul(float a, float b);
private native float div(float a, float b);
// 動的ライブラリをロード
static {
System.loadLibrary("ssfiction");
}
}
JNI側(C言語)の実装
jniディレクトリ内にCソースファイル(例:ssfiction.c)を作成し、JNI動的登録を行います。ポイントは以下の通りです。
- 各演算関数(addc, subc, mulc, divc)を定義
- JNINativeMethod構造体配列に関数とシグネチャをマッピング
- RegisterNatives関数でJavaクラスと関連付け
- JNI_OnLoad内で登録処理を実行
#include <jni.h>
jfloat addc(JNIEnv* env, jobject obj, jfloat a, jfloat b) {
return a + b;
}
jfloat subc(JNIEnv* env, jobject obj, jfloat a, jfloat b) {
return a - b;
}
jfloat mulc(JNIEnv* env, jobject obj, jfloat a, jfloat b) {
return a * b;
}
jfloat divc(JNIEnv* env, jobject obj, jfloat a, jfloat b) {
// ゼロ割を避けるため簡易実装
if (b == 0) return 0;
return a / b;
}
// メソッド登録テーブル
static JNINativeMethod methods[] = {
{"add", "(FF)F", (void*)addc},
{"sub", "(FF)F", (void*)subc},
{"mul", "(FF)F", (void*)mulc},
{"div", "(FF)F", (void*)divc}
};
jint registerNativeMethods(JNIEnv* env) {
jclass clazz = (*env)->FindClass(env, "com/example/jsuanq/MainActivity");
if (clazz == NULL) return JNI_ERR;
jint result = (*env)->RegisterNatives(env, clazz, methods,
sizeof(methods) / sizeof(methods[0]));
(*env)->DeleteLocalRef(env, clazz);
return result;
}
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) {
JNIEnv* env = NULL;
if ((*vm)->GetEnv(vm, (void**)&env, JNI_VERSION_1_4) != JNI_OK) {
return JNI_ERR;
}
if (registerNativeMethods(env) != JNI_OK) {
return JNI_ERR;
}
return JNI_VERSION_1_4;
}
Android.mkの設定
jniディレクトリにAndroid.mkを作成し、以下のように記述します。
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := ssfiction
LOCAL_SRC_FILES := ssfiction.c
LOCAL_ARM_MODE := arm
LOCAL_LDLIBS += -llog
include $(BUILD_SHARED_LIBRARY)
上記設定後、ndk-buildを実行して.soファイルを生成し、アプリを実行すると四則演算が動作します。
補足
動的登録(JNI_OnLoad内でRegisterNatives)を用いることで、Java側のメソッド名とC関数名を自由に対応付けることができ、メンテナンス性が向上します。