daiki0508'足跡

Android NDKを使ってみた

f:id:daiki0508:20210427235053p:plain

AndroidNDKを使って簡単なアプリを作ってみようと思ったのですが、ドキュメントが古かったり少なかったりして大変だったのでメモとして記事にしました。

目次

NDKって何?

NDK(Native Development Kit)は、Android で C や C++(以下、ネイティブ)のコードを使用できるようにするツールセットです。
NDK のプラットフォーム ライブラリを活用することで、ネイティブ アクティビティを管理し、センサーやタップ入力など、実際のデバイスコンポーネントにアクセスできるようになります。それほど経験のない Android プログラマーの場合、アプリを開発する際に必要となるのは Java コードとフレームワーク API に限られるため、通常、NDK はツールとして適していません。NDK が役に立つのは、以下のいずれかに該当する場合です。
・低レイテンシを実現したり、ゲームや物理学シミュレーションなど、演算負荷の高いアプリを実行したりするために、デバイスからできる限り高いパフォーマンスを引き出す必要がある場合。
・独自または他のデベロッパーの ネイティブライブラリを再利用する場合。

とあるように、簡単に言うと本来Javaで記述するAndroidアプリをCやC++で記述できるようにするためのツールということです。

私がこの先、演算不可の高いアプリを開発するかは分かりませんが(笑)

NDKの導入

まずAndroidStudioを起動して、画面左上の方にある「File」→「Settings」をクリック。

すると設定画面が開くと思うので「Appearance & Behavior」→「System Settings」をクリックした後、「Android SDK」を選択しましょう。

f:id:daiki0508:20210427225239p:plain

ここでAndroid SDKに関する様々なツールをインストール出来ます。
Android NDK」のインストールは赤く囲んでいる部分の「SDK Tools」で行うので、そこをクリックしてください。

f:id:daiki0508:20210427225358p:plain

後は上記画像にも既に表示されているように「NDK(Side by side)」と「CMake」のチェックボックスにチェックを付けてください。
この時、「CMake」のチェックボックスの付け忘れには注意してくださいね!

インストールはネット環境にもよりますが、10分くらいかかると思うので気長に待ちましょう。

NDKを使ってみよう

NDKの導入にてAndroidでネイティブコードを扱うためのツールのインストールは終わりました。
そこで、サンプルアプリをNDKを用いて実装してみましょう。

先にサンプルアプリを動かしてみたい方はこちらをクリックしてapkを端末にインストールして起動してみてください。

サンプルアプリの挙動

今回扱うサンプルアプリの簡単な挙動を以下に示しておきます。

1.EditBoxに自分のニックネームを入力
2.ニックネームをネイティブコードで書かれた関数に渡す
3.関数でメッセージの付加を行った文字列をMainActivityに返す。

ソースファイルを配置するディレクトリの作成

まずはネイティブコードを記述しておくためのソースファイルを配置するディレクトリの作成を行います。
プロジェクトエクスプローラの赤く囲んだ部分をクリックして、「Project」を選択してください。

f:id:daiki0508:20210427233858p:plain

その後、「src」ディレクトリの配下にある「main」ディレクトリで右クリック→「New」→「Directory」をクリックしましょう。

f:id:daiki0508:20210427234038p:plain

ディレクトリ名は「cpp(jniでもいけるようです)」としてください。

ソースファイルの作成

ソースファイルを配置するディレクトリの作成にて作成したディレクトリで右クリックをして「New」→「C/C++ source File」をクリックしましょう。
ファイル名は今回は「hello」、Typeは「.cpp」にでもしておきます。

これでソースファイルの作成が完了しました。
(※記述自体はもう少し後で行います)

CMakeLists.txtを作成する

次に、CMakeListsと呼ばれるC/C++のビルド設定ファイルを作成、記述していきます。
ファイルを生成する場所はcpp直下で大丈夫です。

f:id:daiki0508:20210427234413p:plain

cmake_minimum_required(VERSION 3.4.1)

add_library(
  # 識別用ライブラリ名を指定
  hello

  # 共有ライブラリとしてビルドさせる
  SHARED

  # C/C++ソースへの相対パス指定
        hello.cpp
)

target_link_libraries(
        hello
        android
        log
)

add_library

ここには作成したC/C++ライブラリの名前・ビルド方法・相対パス指定します。
今回はhello.cppの設定を追加しています。
注意点としては、「C/C++ソースへの相対パス指定」では相対アドレスを指定する必要があることです。

ここには独自ライブラリ名、android, log を指定しておきます。
こうすることでC/C++側でもログ出力が使えるようになります。

build.gradle(:app)の設定

ここの設定は簡単です。
android{}の中に

externalNativeBuild{
        cmake{
            path "src/main/cpp/CMakeLists.txt"
        }
}

と記述するだけです。

MainActivityに関数呼び出し処理を記述する

次に、ソースファイルの作成にて作成したソースファイルに内容を記述していく前に先に関数の呼び出し処理を記述しておくとソースファイル内に関数定義を記述するで便利なので、関数の呼び出しを記述します。

public native String getMessage(String msg);

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

System.loadLibrary(hoge)

ここのhogeの部分は、CMakeLists.txtを作成するで定義した「識別用ライブラリ名」と一致させる必要があるので注意しましょう。

ソースファイル内に関数定義を記述する

ここから今回はC++で関数の定義を記述していくのですが、先にMainActivityに関数呼び出しを記述していると関数名を記述しただけで自動である程度の雛形を作ってくれます。

extern "C" JNIEXPORT jstring JNICALL
Java_com_websarva_wings_android_ndksample_MainActivity_getMessage(JNIEnv *env, jobject thiz,jstring j_name) {
    // TODO: implement getMessage()
    const char *name = env->GetStringUTFChars(j_name, 0);
    std::string msg = "Hello ";
    msg+= name;
    msg += "\nWelcome to JNI World!!";
    return env->NewStringUTF(msg.c_str());
}

getMessage関数の処理

Javaコードからニックネームを受け取り(ex:hoge)

Hello hoge
Welcome to JNI World!!

という文字列を生成してJavaコードに返す。

extern "C"

この記述をすることで、上コードのようにextern "C"としてマングリング(C++特有の機能)をさせなくする。
C++の場合、こうしないとビルドエラーが出ます。

env->NewStringUTF(hoge.c_str());

ネイティブ側からJavaに返すときはenv->NewStringUTF(msg.c_str());のように変換する必要あります。

MainActivityの処理を記述

最後にMainActivityに残りの処理を記述していきます。
このサンプルアプリの全コードは以下にあります。

https://github.com/daiki0508/NDKSample

package com.websarva.wings.android.ndksample;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity implements TextWatcher {
    private EditText editText;
    private TextView textView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        editText = findViewById(R.id.edit);
        editText.addTextChangedListener(this);
    }

    public void Execute_NDK(View view){
        String name = editText.getText().toString();
        String result = getMessage(name);
        textView = findViewById(R.id.result_text);
        textView.setText(result);
    }

    public native String getMessage(String msg);

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

    @Override
    public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {

    }

    @Override
    public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {

    }

    @Override
    public void afterTextChanged(Editable editable) {
        String name = editable.toString();
        Button button = findViewById(R.id.button);

        // ユーザーのニックネームが未入力の場合はボタンを有効にしない
        if (name.length() > 0){
            button.setEnabled(true);
        }else {
            button.setEnabled(false);
        }
    }
}

まとめ

NDKを使ってネイティブコードで簡単なサンプルアプリを作ろうと思ったのですが、意外にも記事の情報が古くてビルド設定系でエラーが多発していました(笑)

引用・参考情報

https://pisuke-code.com/android-how-to-install-ndk/

https://github.com/android/ndk-samples

https://android-developers.googleblog.com/2020/02/native-dependencies-in-android-studio-40.html