プロジェクトへの C / C++ コードの追加 | Android Developers
にあるとおり、JNIのチュートリアルを実行した際のメモです
ドキュメント
このドキュメントにある通りに実行しました
プロジェクトへの C / C++ コードの追加 | Android Developers
関連ドキュメント、サンプル
- JNIのヒント| Android NDK | Androidデベロッパー
- KotlinからJavaコードを呼び出す
- NDK の概要 | Android NDK | Android Developers
- Closer Look At Android Runtime: DVM vs ART
- Google Developers Japan: Android Studio 2.2 が CMake と ndk-build をサポート
- googlesamples/android-ndk | GitHub
ビルド
Android Studioのデフォルトプロジェクト
プロジェクトへの C / C++ コードの追加 | Android Developers
このドキュメントにある通りに、Include C++ support
にチェックを入れて、新規プロジェクトを作成しました。ビルドに成功すると以下のような画面になりました。
C++で実行された"Hello from C++"という文字列がKotlinに渡されます。
以下、ソースコードについての解説をおこなってみます
###MainActivity.kt
package org.ymmtyuhei.jnitrial
import android.support.v7.app.AppCompatActivity
import android.os.Bundle
import kotlinx.android.synthetic.main.activity_main.*
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// Example of a call to a native method
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")
}
}
}
MainActivity が System.loadLibrary() を使用してネイティブ ライブラリnative-lib
を読み込みます。
これで、ライブラリのネイティブ関数である stringFromJNI() をアプリで使用できるようになります。
public native String stringFromJNI();
このメソッドの宣言内の native キーワードは、この関数が共有ライブラリ内にある(つまり、ネイティブサイドにある)ことを仮想マシンに伝えます。
native-lib.cpp
#include <jni.h>
#include <string>
extern "C" JNIEXPORT jstring
JNICALL
Java_org_ymmtyuhei_jnitrial_MainActivity_stringFromJNI(
JNIEnv *env,
jobject /* this */) {
std::string hello = "Hello from C++";
return env->NewStringUTF(hello.c_str());
}
戻り値の型である jstring は、Java Native Interface の仕様で定義されているデータ型です。 実際は文字列ではなく、Java の文字列へのポインタです。
-
jstring の後には関数名を記述します。関数名は、Java の関数名と、その関数を含むファイルへのパスに基づいて作成します。 次のルールに沿って作成します。
- 先頭に Java_ を追加します。
- 最上位のソース ディレクトリからの相対ファイルパスを記述します。
- スラッシュの代わりにアンダースコアを使用します。
- ファイル拡張子(.java)は省略します。
- 最後のアンダースコアの後に関数名を追加します。
-
JNIEnv* は VM へのポインタで、jobject は Java サイドから渡される暗黙的な this オブジェクトへのポインタです。
-
次の行は、VM API (*env) を呼び出し、これに戻り値(Java サイドでこの関数がリクエストした文字列)を渡します。
参考 : サンプル: hello-jni | Android NDK | Android Developers
build.gradle
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
android {
compileSdkVersion 27
defaultConfig {
applicationId "org.ymmtyuhei.jnitrial"
minSdkVersion 27
targetSdkVersion 27
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
externalNativeBuild {
cmake {
cppFlags ""
}
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
externalNativeBuild {
cmake {
path "CMakeLists.txt"
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation"org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version"
implementation 'com.android.support:appcompat-v7:27.1.1'
implementation 'com.android.support.constraint:constraint-layout:1.1.0'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
}
後述するCMakeLists.txtをexternalNativeBuildと指定します。
これにより、build.gradleがCMakeを実行し、C++コードを.soファイルとしてコンパイルし、APKに含めます。
externalNativeBuild {
cmake {
path "CMakeLists.txt"
}
}
CMakeLists
# 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.4.1)
# 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).
src/main/cpp/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} )
CMake用の設定ファイル。
CMakeがこのファイルを参照しC++ソースコードのビルドを行います。ndk-buildの場合はAndroid.mkやApplication.mkが相当します。
今回、CMakeはnative-lib.cpp をコンパイルして共有オブジェクトライブラリとしてlibnative-lib.so
を生成します。
生成されたら、GradleがAPKパッケージに組み込みます。
以下の手順で、APKにlibnative-lib.so
が組み込まれている様子を確認できます。
- Build > Build APK
- Build > Analyze APK
参考 :
#既存のプロジェクトから生成した.soを利用する
先のプロジェクトで生成した、libnative-lib.soを、別のプロジェクトで使ってみます
参考ドキュメント
ネイティブライブラリへのGradleのリンク | 事前構築されたネイティブライブラリを含める
.soファイルをコピー
.soは以下のcmakeのディレクトリに生成されています。
それを新しく作成したプロジェクトの以下のディレクトリにコピペしました。そのさいx86...などからコピーします
build.gradleに.soを追加
android {
...
sourceSets {
main {
jniLibs.srcDirs 'imported-lib/src/', 'more-imported-libs/src/'
}
}
}
つまづいたところ
先のMainActivityと同じようにSystem.loadLibrary...などから読み出してみるもランタイムエラー。
AnalyzeAPKでAPKの中身を確認してみると、ライブラリは正常にコピーされている。
Logcatを見てみると以下のようなエラー。
呼び出し元パッケージ名と、.soのビルド時のパッケージト名が違うことで、失敗しているようだ。
java.lang.UnsatisfiedLinkError: No implementation found for java.lang.String org.ymmtyuhei.importjni.MainActivity.stringFromJNI() (tried Java_org_ymmtyuhei_importjni_MainActivity_stringFromJNI and Java_org_ymmtyuhei_importjni_MainActivity_stringFromJNI__)
以下、大変雑な検証ですが...
- 元のプロジェクトに戻り、関数名を変更しビルド、再度パッケージを転送したら期待通りに実行できた。
#include <jni.h>
#include <string>
extern "C" JNIEXPORT jstring
JNICALL
Java_org_ymmtyuhei_importjni_MainActivity_stringFromJNI(
JNIEnv *env,
jobject /* this */) {
std::string hello = "Hello from C++";
return env->NewStringUTF(hello.c_str());
}
- あるいは元の.soを出力したときのパッケージ名とクラス名に、.soの関数を実行するだけのクラスを作成し(MainActivityという名のただの呼び出し飲みを行うクラス)、
MainActivityから読み出し、実行することができました。
package org.ymmtyuhei.importjni
...
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val m = MainActivity()
my_text.text = m.sample_text()
}
}
package org.ymmtyuhei.jnitrial
class MainActivity {
fun sample_text() : String{
return stringFromJNI()
}
external fun stringFromJNI(): String
companion object {
init {
System.loadLibrary("native-lib")
}
}
}
用語
Java関連用語
JDK
Java Development Kit とは、Java開発者向けのバイナリ形式でありオラクルによりリリースされる、Java SE、Java EEやJava MEの各プラットフォーム全ての実装である。Javaプラットフォームの導入以来、JDKは最も広く使われているソフトウェア開発キット (SDK) です。
JNI
Java Native Interfaceとは、Javaで記述されたプログラムと、他の言語で書かれた実際のCPUの上で動作するコード(ネイティブコード)とを連携するためのインタフェース仕様である。Java言語からネイティブコードを利用するためのABIと、逆にネイティブコードからJavaバイトコードを動作させるためのバーチャルマシンを利用するためのAPIの2つから成る。
JVM
Java virtual machineとは、Javaバイトコードとして定義された命令セットを実行するスタック型の仮想マシンです。
LLDB
LLDB(Low Level Debugger)とは、ソフトウェアデバッガである
Android関連用語
NDK
Native Development Kitとは、Android アプリで C および C++ コードを使用できるようにするツールのセットです。
ADT
Android Development Toolsとは、Eclipse IDE用のカスタムプラグイン。新しいAndroidプロジェクトをすばやく設定し、アプリUIを構築し、アプリをデバッグし、署名付き(または署名なし)アプリパッケージ(APK)を配布用に書き出すことができます。
CMake
Gradle と一緒に使用してネイティブ ライブラリをビルドする外部ビルドツール。ndk-build を使用する予定の場合は、このコンポーネントは不要。
DVM
Dalvic Virtual Machineは、Androidプラットフォームで採用されていたレジスタベースの仮想マシン
ART
Android Run Timeは、ランタイム環境で動作するAndroidオペレーティングシステムを指します。Android 5.0以降で、従来のDalvikからARTに置き換えられ、正式サポートされている。
.dex
Dalvik Executable、Android OSがDVMで実行する、実行ファイルです。
アプリに Java ソースコードがまったく含まれていなくても、ビルドプロセスによって、ネイティブ コンポーネントを実行するための .dex 実行ファイルは生成されます。
コンパイラ関連用語
より一般的なはなし
ソースコード(.cpp) → オブジェクトコード(.so) → 実行ファイル(.dex,.jar)
オブジェクトコード、オブジェクトファイル
オブジェクトコードあるいはオブジェクトファイルとは、コンパイラがソースコードを処理した際に作り出される中間的存在のコードのことです。オブジェクトコードはバイナリで、コンパクトで構文が解析済みのコードです。適切にリンクすることで最終的な実行ファイルやライブラリを作成できます。
実行ファイル
実行ファイル(Executable、Executable file)とは、コンピュータがプログラムとして解釈実行できるファイルである。実行可能ファイル、実行形式ファイル、実行形式、バイナリファイル。拡張子 ".bin" ".exe"など。
ABI
Application Binary Interfaceとは、アプリケーション(ユーザ)プログラムとシステム(OSやライブラリ)との間の、バイナリレベルのインタフェースです。また、アプリケーション相互間や、それらの部品(プラグイン等)とのバイナリインタフェースでもあります。
NDK はこれらの定義に対して .so ファイルをビルドします。
API
APIはソースコードとライブラリ間のインタフェースを定義したもので、同じAPIをサポートしたシステム間では同じソースコードをコンパイルして利用できます。一方、ABIはオブジェクトコードレベルのインタフェースであり、互換ABIをサポートするシステム間では同じ実行ファイルを変更無しで動作させることができます。
NDK関連
本記事には登場しないがndk-buildを使う場合必要な知識
- 次の 2 つの項目は、ndk-build スクリプトを使用してビルドする、または ndk-gdb スクリプトを使用してデバッグをする場合のみ必要です。
###Android.mk
Android.mk 設定ファイルを jni フォルダ内に作成する必要があります。 このファイルにはモジュール、モジュールの名前、コンパイル対象のソースファイル、ビルドフラグ、リンクするライブラリが定義されており、これを ndk-build スクリプトが参照します。
###Application.mk
アプリに必要なモジュールのリストとモジュールの説明が含まれています。 たとえば次のような情報があります。
- 特定のプラットフォームのコンパイルに使用する ABI。
- ツールチェーン。
- 組み込む標準ライブラリ(静的 STLport および動的 STLport、または既定のシステム)。