つくるの大好き。

つくるのが大好きな人の記録。

Android C++開発 [1] : CMakeプロジェクトの作成

お久しぶりです!
この夏はものすごく忙しくてブログを書く時間がとれませんでした。
何をやっていたかというとAndroidで動作するライブラリをC++で作り、Unityラッパーも作成してUnity上でも利用できるようにする、という事をしていました。
C++でのAndroid開発はかなりレアな体験で、色々と学べたのでシェアします。
レアすぎて需要が無さそうだなあ(笑)

プロジェクトの作成

Android StudioC++を扱うプロジェクトを作成するにはプロジェクト作成のウィザードの中で "Include C++ support" のチェックを付けます。 f:id:peugeot-106-s16:20181107110904p:plain

生成されるプロジェクトは下記のものを含みます。

名前 説明
java 画面のActivity等のJavaソースコード
cpp C++ソースコード
build.gradle(Module: app) ビルド設定

f:id:peugeot-106-s16:20181107111814p:plain

build.gradle(Module: app)について

Android のビルドシステムにはGradleが使われます。
C++のビルドで使われるCMakeとの関連付けはこのファイルの中で行われます。

"externalNativeBuild"の設定でC++ビルドのオプションとしてC++11を利用すること、CMakeのビルド設定が書かれているCMakeLists.txtへのパスが指定されています。
独自のカスタマイズを行う場合はこれらを編集します。

f:id:peugeot-106-s16:20181107112526p:plain

とりあえず実行

このまま特に変更をせずにアプリを起動するとC++側から"Hello from C++"という文字列を取得して画面に表示するものが起動します。 f:id:peugeot-106-s16:20181107113007j:plain

コードで何が行われているかを解説します。
native-lib.cpp

extern "C" JNIEXPORT jstring JNICALL
Java_com_example_satoshi_1maemoto_myapplication_MainActivity_stringFromJNI(
        JNIEnv *env,
        jobject /* this */) {
    std::string hello = "Hello from C++";
    return env->NewStringUTF(hello.c_str());
}

C++側ではマネージドのJavaVM環境と連携をとるためJNIの規約に沿った名前のメソッドが用意されています。
規約としてはJavaのパッケージ名の "." を "_" に置き換え、引数でJavaVMへのポインタを受け取るというのがシンプルな説明です。
実際には様々なかたちのデータを引数でやり取りするため様々なテクニックがあります。
例えば配列の配列を受け取るとか、Java側のコールバックメソッドのアドレスを受け取りC++からJavaをコールバックするなど、、この辺りは次回以降扱いたいと思います。

JNIの規約で作ったC++関数は下記のようにJavaから呼び出すことができます。
MainActivity.java

public class MainActivity extends AppCompatActivity {
    // C++ライブラリ(native-lib.so)をロード
    static {
        System.loadLibrary("native-lib");
    }

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

        // C++側の stringFromJNI()関数を呼び出し文字列を取得
        TextView tv = (TextView) findViewById(R.id.sample_text);
        tv.setText(stringFromJNI());
    }

    // C++関数は native 指定で宣言が必要
    public native String stringFromJNI();
}

実機でのUnitTest

プロジェクトの何も手を加えない状態でUnitTestのモックも作成されています。 UnitTestには2種類あり、開発環境内で動作させる通常のUnitTestと、デバイス内で動作をさせるInstrumentedTestがあります。
やっぱり事件は現場で起こるのでできるだけ後者を行いたいものです。ビルド時間はかかってしまうのですが。

あらかじめ用意されているモックは特にC++側との連携はされていないものですが、もちろんC++関数のテストも可能です。 次回以降このInstrumentedTest内でUIを表示するにはどうすれば良いかなども扱いたいです。

ExampleInstrumentedTest.java

@RunWith(AndroidJUnit4.class)
public class ExampleInstrumentedTest {
    @Test
    public void useAppContext() {
        // Context of the app under test.
        Context appContext = InstrumentationRegistry.getTargetContext();

        assertEquals("com.example.satoshi_maemoto.myapplication", appContext.getPackageName());
    }
}

InstrumentedTestの実行は、クラスを右クリックしてRunまたはDebugを選択します。
f:id:peugeot-106-s16:20181107115148p:plain

まとめ

AndroidC++開発のファーストステップをまとめました。