Androidアプリケーションはプログラムの一部をNativeコードで作ることが出来ます。
Nativeコードとは端末のCPUが直接解釈できるプログラム(マシン語)のことです。
演算負荷の大きい処理を高速化したい場合に有効な手段です。
NativeコードのプログラミングはAndroid NDK(Native Development Kit)を使ってC/C++で行います。
そのためのNative開発環境を、Android Studioは組み込めるようになっています。
今回、Android StudioへNative開発環境を構築してみたので紹介します。
※本記事はAndroid Studio 4.1.1で実施しています。
目次
新規のProjectへ構築(「Native C++」テンプレート)
新しいプロジェクトを作成するとき、Android Studioは予め幾つかのテンプレートを用意しています。
その中にNative開発環境のテンプレートがあります。これを用いれば、Native開発環境の構築は自動です。
なので、これが最も簡単な方法だと思います。
以下は構築するための手順です。
File->New->Create New Project を開いて「Native C++」テンプレートを選択します。プロジェクトの情報を設定します。
準拠する規格(C++の規格)を指定します。
最後にFinishで構築を開始します。
環境が作られる前にAndroid NDKとCMakeのダウンロードが行われます。このダウンロードに時間がかかります。
気長に待ちましょう。
構築されたNative開発環境
構築された開発環境は、Native環境の有無を意識することなくアプリの実行やテスト行えるものになっています。
C/C++のプログラミングを行う以外に特別な操作は必要ありません。エミュレータならびにUSB接続されたAndroid端末でアプリを起動したければ、Runを実行するだけです。
以下は、構築された環境の詳細です。
NDKとCMake
Native開発に必要なNDKとCMakeが自動でダウンロードされています。内容はSDK Managerで確認できます。
NDK(Native Development Kit)はライブラリとツール群です。
ダウンロードされるNDKのバージョンは古いかも知れません。これは使用しているAGP(Android Gradle Plugin)のバージョン毎にデフォルトのNDKバージョンが指定されているためです。※上記例はAGP Ver4.1.2の時
最新バージョンを使いたければ手動で置き換えます。
NDKは頻繁に更新されているようです。できれば常に最新バージョンを使うのが良いと思います。
詳細は「プロジェクトで特定バージョンのNDKを構成する」を参照してください。
CMakeはビルドシステムです。
ビルドシステムはシナリオを元にソースコードからライブラリを作ることが仕事です。
C/C++言語の場合、ソースコードからライブラリを作る手順は、どのビルドシステムも大きく変わりません。だいたい、図中に書いたような処理が行われています。
CMakeはAndroid専用のツールではなく外部ツールです。詳細はCMakeを参照してください。
Native開発のフォルダ
javaと並列にcppフォルダが作成されて、ここにC/C++のソースコードとCMakeのシナリオを配置します。
cppフォルダはすでにGradleが提供するビルドシーケンスに組み込まれています(なので、フォルダアイコンが青い)。
Gradleの設定
build.gradleへNative関連の設定が追記されます。
: android { compileSdkVersion 29 buildToolsVersion "30.0.2" defaultConfig { applicationId "com.example.myapplication" : externalNativeBuild { cmake { cppFlags "" // 規格を指定した場合「cppFlags "-std=c++11"」 } } } externalNativeBuild { cmake { path "src/main/cpp/CMakeLists.txt" version "3.10.2" } } : } :
付属のNativeサンプル
構築された開発環境にはサンプルが付いてきます。はじめてNativeコードの開発を行う人にとって、とても参考になるサンプルです。
内容はとても簡単で、TextViewへ「Hello from C++」と表示するサンプルです。
この「Hello from C++」の出力をNativeコードで行っています。
以下はサンプルの詳細です。
CMakeのシナリオ
三つの処理が定義されています。
このシナリオでライブラリをビルドした結果、ファイルlibnative-lib.soが作成されることになります。
# For more information about using CMake with Android Studio, read the # documentation: https://d.android.com/studio/projects/add-native-code.html # Sets the minimum version of CMake required to build the native library. cmake_minimum_required(VERSION 3.10.2) # Declares and names the project. project("myapplication") # Creates and names a library, sets it as either STATIC # or SHARED, and provides the relative paths to its source code. # You can define multiple libraries, and CMake builds them for you. # Gradle automatically packages shared libraries with your APK. add_library( # Sets the name of the library. native-lib # Sets the library as a shared library. SHARED # Provides a relative path to your source file(s). native-lib.cpp ) # Searches for a specified prebuilt library and stores the path as a # variable. Because CMake includes system libraries in the search path by # default, you only need to specify the name of the public NDK library # you want to add. CMake verifies that the library exists before # completing its build. find_library( # Sets the name of the path variable. log-lib # Specifies the name of the NDK library that # you want CMake to locate. log ) # Specifies libraries CMake should link to your target library. You # can link multiple libraries, such as libraries you define in this # build script, prebuilt third-party libraries, or system libraries. target_link_libraries( # Specifies the target library. native-lib # Links the target library to the log library # included in the NDK. ${log-lib} )
Nativeソースコード
jstring型の文字列”Hello from C++”を返すだけの関数です。
#include <jni.h> #include <string> extern "C" JNIEXPORT jstring JNICALL Java_com_example_myapplication_MainActivity_stringFromJNI( JNIEnv* env, jobject /* this */) { std::string hello = "Hello from C++"; return env->NewStringUTF(hello.c_str()); }
Kotlin(もしくはJava)とNativeコード間のデータの受け渡しは、JNI(Java Native Interface)という仕様で行われます。
JNIでKotlinからNativeコードの関数を呼び出すとき、その関数の命名規則が定義されています。この定義に従わないとKotlinからNativeコードを参照出来ません。
この関数は二つの引数JNIEnvとjobjectを持たせる決まりで、その後に関数で必要な引数を持たせます。サンプルは必要な引数が無いのJNIEnv*とjobjectだけです。
jstringはKotlin(もしくはJava)にの型に対応するJNIの型です。jobjectはKotlinの全てのオブジェクトに対応し、jstringはString型に対応します。
また、JNIEnvは「JNIが実装する全ての関数を収めた構造体」です。この構造体のポインタを利用してNewStringUTF()が呼び出されています。NewStringUTF()はC言語の文字列(末尾が\0)をjstring型に変換しています。
Kotlinから呼出
KotlinはstringFromJNI( )を通してNativeコードを呼び出します。
Nativeコードを実行するには、アプリの起動時に共有ライブラリ(libnatice-lib.so)をロードします。それが、loadLibrary(“native-lib”)の部分です。
“native-lib”が共有ライブラリ名で、CMakeによりコンパイルされたNativeコードの関数(stringFromJNI)が収められています。
※ファイル名はlibnatice-lib.soですが読み込みでライブラリ指定するときは「lib」と「.so」を取るのがルールです。
※実態はJava_com_example_myapplication_MainActivity_stringFromJNIですがJNIを通すとKotlin側からstringFromJNIという関数名で参照できます。
また、stringFromJNIが外部定義であることを宣言(externalの部分)しています。「外部」とは読み込んだ共有ライブラリ(native-lib)のことです。これが無いと、stringFromJNIの参照先がnative-libへ向きません。
class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) // Example of a call to a native method findViewById<TextView>(R.id.sample_text).text = stringFromJNI() } /** * A native method that is implemented by the 'native-lib' native library, * which is packaged with this application. */ external fun stringFromJNI(): String companion object { // Used to load the 'native-lib' library on application startup. init { System.loadLibrary("native-lib") } } }