Android JNI動的登録による簡易電卓アプリの実装手順

概要

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関数名を自由に対応付けることができ、メンテナンス性が向上します。

7月13日 22:40 投稿