読者です 読者をやめる 読者になる 読者になる

vaguely

和歌山に戻りました。ふらふらと色々なものに手を出す毎日。

AndroidでNDK

AndroidでNDKを使ってみたメモです。

環境構築

  1. Eclipseで、Android SDKの設定をしておきます。
  2. DeveloperサイトからMac OS X 64bit用をダウンロードして、任意の場所に展開しておきます。
  3. Eclipseのメニューの環境設定>Android>NDKで、Step2で展開した場所のパスを設定します。
  4. 同じくメニューのHelp>Install New Software>Addで、以下のリポジトリを追加します
    (最後の"luna"はバージョンに合わせて変更します)。
http://download.eclipse.org/releases/luna
  1. Step4で表示された項目のうち、[Programming Language]から以下をインストールします
    (Development ToolsまたはDevelopment Tools SDKが、項目としては表示されなかったような気がしますが、表示されたものをチェックしてインストールすればOKです)。
C/C++ Development Tools
C/C++ Development Tools SDK
C/C++ Library API Documentation Hover Help

プロジェクト作成

通常のAndroidプロジェクトと同じように、New>Other>AndroidAndroid Application Projectから作成します
(Blank Activityにして、Target APIは21にしてみました)。

ネイティヴコードの使用準備

  1. Package Explorerで、プロジェクト直下に[jni]というフォルダを作成します。
  2. 同じくPackage Explorerの、プロジェクト名の上で右クリック>Android Tools>Add Native Supportをクリックします
    (自動でStep1のjniフォルダに[プロジェクト名.cpp]と[Android.mk]というファイルが作成されます)。
  3. Step1のjniフォルダに[Application.mk]を作成して、以下を記述します ([APP_PLATFORM]のバージョンはプロジェクトに合わせて変更してください)。
APP_PLATFORM := android-21
APP_ABI := armeabi armeabi-v7a x86
  1. プロジェクトをビルドします。ネイティヴコードをJavaから呼び出すためのライブラリ名の入力を求められるので、任意で設定します。

C++

Javaから呼ばれたら文字列を返すメソッドを作ります。

AndroidNdkTest.cpp

#include < jni.h >

extern "C"
{
  jstring Java_jp_co_masanori_androidndktest_MainActivity_getHello(JNIEnv *env, jobject obj)
  {
       jstring jstHello = env->NewStringUTF("世界さん チーッス");
     return jstHello;
  }
}
  • C++では[extern "C"]が必要となります。今回のようにメソッドが一つだけなら、{}で括らずメソッドの頭につけても良いかと。
  • NDK(というかjni)では、そのままではstd::stringは使用できず、jstringで文字列を扱えるようです。
  • メソッド名は[Java]、[呼び出すクラスのパッケージ名]、[呼び出すクラス名]、[メソッド名]という形式となっています。
    またそれぞれの項目、パッケージ名に含まれる[.]は[]に置き換える必要があるため、メソッド名やパッケージ名などに[]が含まれていると上手く呼び出せないという罠が...。
  • 引数の[JNIEnv][jobject]は固定で、任意で引数を渡したい場合は3つ目以降の引数として渡します。

Java

作成したC++メソッドを呼び出すプログラムを作ります。

MainActivity.java

package jp.co.masanori.androidndktest;

import android.app.Activity;
import android.os.Bundle;
import android.widget.TextView;

public class MainActivity extends Activity {

    TextView _txtHello;
    public native String getHello();

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

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

        _txtHello = (TextView)findViewById(R.id.txtHello);
        _txtHello.setText(getHello());
    }
}
  • プロジェクト作成時にデフォルトで作成されるMenuについては特に何もしていないので省いています。
  • C++メソッドから受け取った文字列を表示するためのTextViewを追加しています。
  • jni側のメソッドの宣言は、[native 型 メソッド名()]の形で行います。
  • jniのファイルにアクセスするには、以下のようにライブラリのロードが必要です
    (ライブラリ名はネイティヴコードの使用準備のStep4で設定したもので、jni>Android.mk>LOCAL_MODULEに記述されています)。
static {
  System.loadLibrary("ライブラリ名");
}
  • なお、ネイティヴコードの使用準備のStep3でAPP_ABIの項目を記述していないと、メソッド名やライブラリ名などが正しくてもUnsatisfiedLinkErrorが発生します (Application.mkの作成は任意と書かれたりしていたので結構ハマりました)。

C++のファイルを追加したい

自動で作成される[プロジェクト名.cpp]の主な役割はJavaとの連携にあると思います。
ではさらにクラスやファイルを追加するにはどうするか?というと、割と普段のC++のままで大丈夫だったりします。

Add_Text.h

単純に呼び出したら文字列を返すメソッドを作ります。

#include < jni.h >

class Add_Text
{
public:
    jstring GetStrText(JNIEnv *env);
};

Add_Text.cpp

#include "Add_Text.h"

jstring Add_Text::GetStrText(JNIEnv *env)
{
    return env->NewStringUTF("!!");;
}

AndroidNdkTest.cpp

先ほどのコードに、Add_Textクラスを使用する内容を追加します。

#include < jni.h >
#include < string.h >
#include "Ctrl_Object.h"

extern "C"
{
  jstring Java_jp_co_masanori_androidndktest_MainActivity_getHello(JNIEnv *env, jobject obj)
  {
    Add_Text *addText = new Add_Text;
    char chrCombined[256];
    jstring jstHello = env->NewStringUTF("世界さん チーッス");
    // 文字列結合のため、Char型に変換.
    const char *chrHello = env->GetStringUTFChars(jstHello, 0);

      jstring jstText = addText->GetStrText(env);
    // 文字列結合のため、Char型に変換.
    const char *chrText = env->GetStringUTFChars(jstText, 0);

    // 文字列を追加して一つにまとめる.
    strcpy(chrCombined, chrHello);
    strcat(chrCombined, chrText);

    // 解放
    env->ReleaseStringUTFChars(jstHello, chrHello);
    env->ReleaseStringUTFChars(jstText, chrText);

    delete Add_Text;
    return env->NewStringUTF(chrCombined);
  }
}

思ったこと

とりあえずバージョンによってやり方が違っていたり、jniがAndroid固有のものではなくJavaからきているために環境も違ったりと、調べるのがちょっと面倒かな、というのが正直なところ。

また、stdがそのままでは使えないなどC++Androidプログラミング、というには少し壁がある気はします。

ただ、openFrameworksのようにネイティヴコードを使うフレームワークを取り入れたり、コードがある程度他のプラットフォームと共通化させられるなどメリットもあると思いますので、状況に応じて使い分けていきたいところです。

【参考】