ygenda です。
Android Studio 2.2.2の導入。今回は後編です。
後編では、カスタマイズ(C言語、C++)および実機での動作確認を記します。
前編の記事はこちら。
0. カスタマイズの準備
カスタマイズにあたり、まずはプログラム名(ライブラリ名)を決めましょう。自由に命名してかまいません。
本記事ではC言語編では「sample」、C++編では「cpp_sample」にしました。
1. カスタマイズ(C言語編)
まずは、C言語編のカスタマイズ手順をまとめました。ベースは、前編の最終状態のものです。
最初にMainActivity.javaを編集します。
まずは最後のSystem.loadLibrary()の引数部分を修正します。今回のプログラム名を sample と決めたので、native-lib から sample に修正しました。
あと、C言語の関数を呼び出す関数名を変更し、GetString()にしました。
外部参照宣言部分の関数名が赤字になっています(外部参照先が存在しないため)。これは、後で解決します。
次に、Cソースファイルを格納するためのフォルダを新規作成してみます。
「app」→「src」→「main」の部分で右クリックし、「新規」→「Folder」→「JNI Folder」とたどり選択します。
新規作成するフォルダを、新たなソースファイル格納場所と認識させます。
上記の画像において、「Change Folder Location」にチェックを入れて、New Folder Locationの欄に「src/main/jni」と入れます。
終わったら「終了」をクリックします。
「app」→「src」→「main」→「jni」(今作成したフォルダ)で右クリックし、今度は「新規」→「C/C++ Source File」を選択します。
ソースファイル名と、拡張子の入力画面です。ここでは、ソースファイル名をプログラム名と同じにすると都合がよいです。拡張子はC言語のものを作るので「.c」を選びます。
自作のヘッダファイルを作る場合は、「Create an associated header」欄にチェックを入れましょう。今回は作らないので、チェックを入れていません。
終わったら「OK」をクリックします。
新規作成されたsample.cの画面が表示されます。
このファイルをプロジェクトに登録するため、「Sync Now」をクリックします。
CMakeLists.txtの画面です。プログラム名とCソースファイル名の部分を、赤く囲った内容に修正します。
終わったら、build.gradle(app:~)のほうを変更(空白を入れて消す、という操作でOK)して、「Sync Now」をクリックしてプロジェクト内の同期を取ります。
いよいよ、未解決だったGetString()の実体をCソースファイル内に作成します。ここで、MainActivity.javaの赤文字になっている関数名の部分をクリックしてから、「Alt」+「Enter」キーを押した後、表示された「Create function ~」をクリックします。
sample.c 内に、関数の実体が生成されました。ちなみに、この操作をした場合の関数の作成先は常に「(命名したプログラム名).c」となるようです。プログラム名とソースファイル名を合わせたほうが良い、と書いたのはこのためです。
ここで、関数名に注目してください。呼び出し側に対して、やたらと長い関数名になっていますね。
実は、JNIを介してJavaからC言語の関数を呼び出す場合、C言語側の関数名の前に以下のプレフィックスが付きます。
"Java_[呼び出し元のJavaのパッケージ名]_[呼び出し元のJavaのクラス名]_"
呼び出し元のJavaのパッケージ名は、MainActivity.javaの1行目のpackage の後ろに書かれているものです。ただしピリオドは識別子として使えないので、全てアンダースコア(_)に置き換える必要があります。
次に、JNIEXPORTやJNICALLなど、JNIで必要な情報が宣言されたヘッダファイル<jni.h>のインクルードを追加します。
最後に、関数の本体処理を記します。ここでは、strというchar型の配列に画像のような文字列を格納し、それをそのままNewStringUTFのパラメータとして渡すようにしました。
MainActivity.java側で赤文字表示されていたGetString()の外部参照部分が、黒文字になっていますね。
あとは、呼び出し元のJava側、呼び出し先のC言語側の両方にブレークポイントを貼って、デバッグモードで実行します。
両方のブレークポイントで意図通り止まることが確認でき、
エミュレータ上に、sample.c で作成した文字列が表示されることも確認できました。
次に、これをベースとしてC++用のカスタマイズを実施します。
2. カスタマイズ(C++編)
今度は、C++のカスタマイズです。
事前に、src/main 配下にあったcppフォルダは削除してから、作業を実施しました。
「app」→「src」→「main」の部分で右クリックし、「新規」→「Folder」→「JNI Folder」とたどり選択します。
新規作成するフォルダを、新たなソースファイル格納場所と認識させます。
上記の画像において、「Change Folder Location」にチェックを入れて、New Folder Locationの欄に「src/main/cpp」と入れます。
終わったら「終了」をクリックします。これで、問題ないかと思ったのですが...
MainActivity.java内のSystem.loadLibrary()の引数を、sampleからcpp_sample に変更します。
CMakeLists.txt を修正します。プログラム名とC++ソースファイル名の部分を、赤丸の中のように変更します。
「app」→「src」→「main」→「cpp」(先ほど作成したフォルダ)で右クリックし、「新規」→「C/C++ Source File」を選択します。
ソースファイル名と、拡張子の入力画面です。ソースファイル名はプログラム名と同じ cpp_sample、拡張子はC++のものを作るので「.cpp」を選びます。
入力が終わったら「OK」をクリックします。
build.gradle((Project:~)でも(app:~)でもよい)を選択し、右上の「Sync Now」をクリックしてプロジェクト内の同期を取ります。
MainActivity.javaのGetString()の外部参照部分が赤文字になっています。1.と同様赤文字の部分をクリックしてから「Alt」+「Enter」キーを押し、表示された「Create function ~」をクリックします。
こうすれば、src/main/cpp/cpp_sample.cpp の中に関数の実体が自動作成されると思っていたのですが...実際には何故かsrc/main/jni/ の下に cpp_sample.c という名前のCソースファイルが勝手に新規作成され、そちらに関数の実体が自動作成されてしまいました。
先ほどsrc/main/cpp にフォルダ場所を変更したはずなのですが、何故か効いていないようです(問題ないかと思ったのですが...の文章が、ここにつながります。何故効かないかは、現在調査中)。
仕方がないので、今回はcpp_sample.c内に自動作成されてしまった部分を丸々コピーして、src/main/cpp/cpp_sample.cpp にペーストする措置を取りました。
ただ、これだけではC言語用の内容をコピーしたにすぎません。そこで、C++用に変更しました。
赤丸で示した部分が、変更箇所になります。
まず、C++ということで折角だからstringクラスを使いたいところですね。そこで、<string>というC++の標準ヘッダファイルをインクルードします。
そして、名前空間を解決するために「using namespace std;」という一文を追加しました。
その次に、関数の本体部分の前に「extern "C"」というのを追加しました。これは「関数名に対するC++のマングルは余計なので、やらなくていいよ」という指示です。
○ C++のマングルについて
C++の場合、ソースコードに記した関数名に対して、コンパイルの段階で付加情報が関数名の後ろに付きます。これは、C++にオーバーロード(同じ名前の関数を、引数の内容を変えれば複数作れる機能)が存在するためです。
付加情報というのは、関数の引数型の情報を特定するための何らかの文字列です。付加情報を関数名の後ろに付けることを「マングル」と呼びます。
ただし、JNIのインタフェースで使用する関数名については、上記のマングルは考慮されていません(マングルすると関数を呼び出せなくなる)。したがって、余計なマングルをしないようにする工夫が必要なのです。
最後に、関数の本体処理部分を追加しました。
stringクラスのstrに文字列を格納して、それをNewStringUTF()の引数にしました。
後は、戻り値の部分です。C言語では、
(*env)->NewStringUTF(env, [文字列])
を返していましたが、C++の場合だとインタフェースが少し異なっていて
env->NewStringUTF([文字列].c_str())
を返しています。NewStringUTFの引数が1つになっている点にも、注意です。
c_str()というのはstringクラスの関数メンバで、文字列をC言語形式の型(char *)で返すためのものです。
ここまで終わったら、ブレークポイントを張ってC言語の場合同様にデバッグモードで実行します。
問題なくできていますね。
3. 実機での動作確認
導入の締めは、実機での動作確認です。
まずは、Android StudioでUSBドライバをインストールします。
「ツール」→「Android」→「SDK Manager」を選択し、出てきた上記画面の「SDK Tools」タブの「Google USB Driver」にチェックを入れて「OK」をクリックします。
これで、USBドライバがインストールされました。
次に、実機であるAndroid端末側のセットアップです。
「設定」→「システム」の中にある「開発者向けオプション」を選択...したいところですが、それが一覧にありません。
現在のAndroid端末では、「使用者が開発者であると認識できないと、このオプションが使えない」という仕様になっているようです。
開発者と認識してもらうには、「設定」→「システム」→「端末情報」を選択し、その中にある「ビルド番号」を7~8回連続でタップします。
開発者と認識されたら「開発者向けオプション」というのが出てきます。この中の「USBデバッグ」をONにします。
これで、実機側の設定は完了です。
そして、いよいよUSBケーブルでPCと実機とを接続します。
実行する際の端末選択画面において、Connected Devicesの欄に実機の情報が表示されればOKです。
あとは、これを選択して実行します。
なお、実行の途中で
というメッセージが出る場合があります。
このメッセージが出たら素直に「OK」をクリックして指示に従いましょう。
実行が完了したら、実機画面の表示内容を確認してみましょう。
少々見づらくて申し訳ないですが、私の実機画面でもエミュレータで表示された画面が期待通りに表示されています。
以上、前編・後編と2回に分けたAndroid Studio 2.2.2の導入に関する記事でした。