プロジェクトにc++を持つモジュールを加える
Android Studio3.6.2(Windows)

2020.4.19 Coskx Lab

1 はじめに

Android Studioで,オリジナルモジュールをプロジェクトに追加します。追加するモジュールにはc++で記述された関数も含むことにします。
単純化した作業を行うアプリケーションを作り,上記のシチュエーションを3つの段階を経て目的の形にします。
作成するアプリケーションは,画面上EditTextに整数を入力し,計算ボタンを押すとTextViewに2倍された値を表示することとします。

(1)基本モジュールappのみで作成します。
モジュールappのMainActivityは,与えられた値を2倍にして表示します。

(2)オリジナルモジュールをプロジェクトに追加します。
オリジナルモジュール中に与えた値を2倍にして返すクラスをjavaのみで作ります。 モジュールappのMainActivityは自分で2倍の計算を行わず,このクラスを利用して2倍の値を得ます。

(3)オリジナルモジュール中にC++のファイルを使えるようにします。
与えた値を2倍にして返すクラスでは,自分で2倍の計算を行わず, C++の関数に2倍の値を計算させます。すなわちMainActivityはモジュールのクラスに計算をさせようとしますが, モジュールのクラスでは,C++の関数に計算させて,値をMainActivityに返します。

2 使用環境

3 MainActivityのみで作成

Android Studioで新規プロジェクトをEmptyActivityで作ります。プロジェクト名は「MdlCpptest」とします。
res/layout/activity_main.xmlでボタンとEditText,TextViewを作ります。主要部分は次の通りです。

activity_main.xml

<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical"
    <Button
        android:id="@+id/button_show"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:layout_marginTop="12dp"
        android:text="calculate the double value" />
    <EditText
        android:id="@+id/edit_text"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="center" />
    <TextView
        android:id="@+id/text_view"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:text="Hello World!" />
</LinearLayout>

MainActivity.javaは,画面上EditTextに整数を入力し,計算ボタンを押すとTextViewに2倍された値を表示するだけなので,次の通りです。

MainActivity.java (パッケージ名はjp.gr.java_conf.coskx.mdlcpptestになっています。)

package jp.gr.java_conf.coskx.mdlcpptest;

import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button button1 = (Button) findViewById(R.id.button_show);
        button1.setOnClickListener(MainActivity.this);
    }

    @Override
    public void onClick(View v) {
        TextView textView = (TextView) findViewById(R.id.text_view);
        EditText editText = (EditText) findViewById(R.id.edit_text);

        // 入力された文字を取得
        String str = editText.getText().toString();
        int num = Integer.parseInt(str);
        int num2=num*2;
        str=""+num2;

        // 2倍値をTextViewにセット!
        textView.setText(str);
    }
}

ここで第1段階の動作確認ができます。

4 オリジナルモジュールの追加(ここでモジュールも作ります)

オリジナルモジュールを追加して,受け取った整数値を2倍にして返すクラスを作成します。
(1)Android Studioの「File」「New」「New Module...」「Android Library」でオリジナルモジュールを「testlibrary」の名前で作ります。
Projectの表示で,appと同じレベルにtestlibraryができます。

(2)Android Studioの「File」「Project Structure」で「Dependency」「Module」でappを選び,「Declared Dependency」の「+」を選び,「Module Dependency」を選ぶと,testlibraryが表示されるのでチェックしてOK。
これでモジュール「testlibrary」(まだ空ですが)がプロジェクト「MdlCpptest」に取り込まれたことになります。

(3)Projectの「testlibrary」を開いて「java」「jp.gr.java_conf.coskx.testlibrary」のところで右クリックして「New」「Java class」で新規クラス「Calcvalue」を作ります。 この中身は,受け取った整数値を2倍にして返すように次のようにします。

    Calcvalue.java

package jp.gr.java_conf.coskx.testlibrary;

public class Calcvalue {
    private int value=0;

    public void setValue(int v) {
        value=v;
    }

    public int getValueDbl() {
        int ret;
        ret=value*2;
        return ret;
    }
}

(4)このクラスを使って,2倍の計算をさせるためにappのMainActivity.javaを次のように変更します。
(MainActivityとは別のモジュールtestlibraryを使用するので,importが増えています。)

    MainActivity.java

package jp.gr.java_conf.coskx.mdlcpptest;

import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import jp.gr.java_conf.coskx.testlibrary.Calcvalue;

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    private Calcvalue mcalcvalue = new Calcvalue();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button button1 = (Button) findViewById(R.id.button_show);
        button1.setOnClickListener(MainActivity.this);
    }

    @Override
    public void onClick(View v) {
        TextView textView = (TextView) findViewById(R.id.text_view);
        EditText editText = (EditText) findViewById(R.id.edit_text);

        // 入力された文字を取得
        String str = editText.getText().toString();
        int num = Integer.parseInt(str);
        mcalcvalue.setValue(num);
        int num2=mcalcvalue.getValueDbl();

        str=""+num2;

        // 2倍値をTextViewにセット!
        textView.setText(str);
    }
}

ここで第2段階の動作確認ができます。

5 オリジナルモジュールにC++の関数を加える

C++の関数はnative関数として,CPUに依存したライブラリになります。どのCPUをサポートするかを指示できるのですが,ここではデフォルト通りにします。

(1)NDKのインストール Android Studioの「Tools」「SDK Manager」を選び,開いたダイアログで「SDK Tools」タブを選びます。[NDK (Side by side)] と [CMake] のチェックボックスをオンにします。OKOKFinishで戻っていきます。

(2)NDKフォルダの指示 Android Studioの「File」「Project Structure」で「SDK LOcation」を選び,「Android NDK location」のところに場所を指示します。
Android SDK Locationのフォルダの中にあります。通常は
C:\Users\tommy\AppData\Local\Android\Sdk\ndk
と思いますが複数のバージョンがインストールされている場合は更にその中の最新の方のフォルダを選びます。例えば
C:\Users\tommy\AppData\Local\Android\sdk\ndk\21.0.6113669
です。

(3)フォルダ「cpp」を「testlibrary」内に作成 Projectの表示方法を「Android」から「Project Files」に変更します。「testlibrary/src/main」の階層構造が見えるので,「main」のところで右クリックして「new」 「Directory」で名前を「cpp」にします。

(4)フォルダ「cpp」に新規cppファイル「doubler.cpp」を作成 作成した「cpp」のところで右クリックして「new」「File」で名前を「doubler.cpp」とします。
関数名をfuncDoubleとすると通常のcppファイルなら

     doubler.cpp

int funcDouble(int a)
{
    return a * 2;
}

ですが,Javaから呼び出される関数なので,記述が特別です。
関数名もパッケージ名を含むようになります。(命名規則によるようです。.が_に変更され,_は直後に1がついてエスケープされています。)パラメータの並びも独特です。jintはintのことです。

     doubler.cpp

#include <jni.h>

extern "C" JNIEXPORT jint JNICALL
Java_jp_gr_java_1conf_coskx_testlibrary_Calcvalue_funcDouble(
        JNIEnv *env,
        jobject /* this */,
        jint a
) {
    return a * 2;
}

(5)CMakeLists.txt(この名前は固定)の作成 C++プログラムをコンパイルしてライブラリ化してJavaから呼び出せるようにする設定を記述します。このファイルはcppファイルと同じ場所に置きます。
「cpp」のところで右クリックして「new」「File」で名前を「CMakeLists.txt」とします。doubler.cppから生成されるライブラリ名は何でもよいのですが,ここでは「doubler_lib」としました。この名前は後で使われます。

     CMakeLists.txt

# ビルドに必要なCMakeの最小バージョン
cmake_minimum_required(VERSION 3.4.1)

# ビルドするライブラリの定義
add_library(
        # 作成するライブラリ名
        doubler_lib
        # 作成するライブラリの属性
        SHARED
        # 作成するライブラリのソースファイル名
        doubler.cpp)    #.cppと)の間にスペースがあってはいけない(?)

# 指定されたビルド済みライブラリを検索し、パスを変数として保存します。 ここはデフォルト
find_library(
        # パスの変数
        log-lib
        # NDK library 名
        log)

# 作成されたライブラリをリンクします
target_link_libraries(
        # 作成されたライブラリ
        doubler_lib
        # リンク先ライブラリ名
        ${log-lib})

(6)CMakeLists.txtの登録 (3)..(5)で作成したフォルダ「cpp」はまだプロジェクトには登録されていません。ここで,Projectの表示方法を「Project Files」から「Android」に戻します。作成された「testlibrary」内の「cpp」がまだプロジェクトに登録されていないので,見えなくなります。
Projectの「testlibrary」で右クリックして「Link C++ Project with Gradle」で,先ほど作成した「CMakeLists.txt」を指示します。(結構深いところにあります。)Syncが終了して完了です。

(7)Calcvalue.javaの変更 Calcvalue.javaでは自分で2倍の計算をしていたのですが,こ れをC++の関数に行わせます。次のように変更します。(5)で指示したライブラリー「doubler_lib」を使えるようにします。関数を呼び出すと ころはint funcDouble(int a)として扱っています。最終行は関数int funcDouble(int a)のプロトタイプの役割です。

     Calcvalue.java

package jp.gr.java_conf.coskx.testlibrary;

public class Calcvalue {
    static {
        System.loadLibrary("doubler_lib");
    }

    private int value=0;

    public void setValue(int v) {
        value=v;
    }

    public int getValueDbl() {
        int ret;
        //ret=value*2;
        ret=funcDouble(value);
        return ret;
    }

    public native int funcDouble(int a);
}

これで目標のプロジェクトになりました。
build.gradle(testlibrary)には,CMakeLists.txtの場所についての次のような記述がありますので,確認しておきましょう。

    externalNativeBuild {
        cmake {
            path file('src/main/cpp/CMakeLists.txt')
        }
    }

5 まとめ

appのみのプロジェクトにオリジナルモジュールを追加し,さらにオリジナルモジュール注にC++の関数を導入する手順を3つの段階に分けて説明しました。