AndroidStudioにおいてデバッグ状況・感触については以前に記事にしました。混在デバッグもうまくできていて残すはどうネイティブのプロジェクトの設定ができるかというところについてのみとなりました。
今回はそれについて確認してみたいと思います。
プロジェクト作成
AndroidStudio で新規にプロジェクトを作成します。
プロジェクト作成後に追加する方向で検証したいので、 Include C++ Support にはチェックを入れないでおきます。
こうしてできたプロジェクトに C++(JNI) のサポートを追加します。
app の部分で右クリックして、 “Link C++ Project with Gradle” の項目を選択します。
すると新規にウィンドウが開いて、どのタイプのビルドシステムで C++ に関して追加するのかを問われます。
現時点において、 CMake, ndk-build が選択可能となっていました。
CMake では CMakeLists.txt, ndk-build では Android.mk が存在していることが必要です。
これらについてそれぞれを確認していきます。
ndk-build 編
ビルドシステムで ndk-build を選択した場合には、 Android.mk が必要です。
今回のプロジェクトでは MyApp/app/src/main/cpp というフォルダを作成して、ここに Android.mk を配置しました。
配置した内容は hello-jni サンプルにあった Android.mk, hello-jni.c というファイルの2つです。
先にファイルが存在していないとパス選択ができないのがポイントでしょうか。選択した後、 AndroidStudio がファイルを解析して、プロジェクトの更新を行います。うまく行けば、以下のように External Build Files が追加されたり、 hello-jni.c のソースが見えていたりするはずです。
こうやってできたプロジェクト構成では hello-jni.c ファイルを開いて、ブレークポイントを設定、デバッグがスムーズに行えました。そのままAndroid.mk が使えるので、今までのテクニックはそのまま生かせるんじゃないでしょうか。ただ Android.mk のほうは従来手法という位置づけらしいので、これ以上の深追いはしないでおきます。
CMake 編
ビルドシステムで CMake を選択した場合には、 CMakeLists.txt が必要です。
今回のプロジェクトでは MyApp/app/src/main/cpp というフォルダを作成して、ここに CMakeLists.txt を配置しました。
といっても空のテキストファイルですが。追加作業が終わってから中身を記述しようと思います。
もちろん CMakeLists.txt の中身が記載されていても全く問題はないですね。この場合には追加設定時に自動的に各ファイルが登録完了となると思います。
cpp フォルダに配置したのは CMakeLists.txt だけでなく hello-jni サンプルにあった hello-jni.c のファイルも追加しています。
“Link C++ Project with Gradle” での CMake 追加が終わったら、 空テキストファイルの CMakeLists.txt を以下のように記述しました。
cmake_minimum_required(VERSION 3.4.1) add_library( hello-jni SHARED hello-jni.c ) # Include libraries needed for hello-jni lib target_link_libraries( hello-jni log android)
記述が終わったら AndroidStudio 側に反映させるために、 Sync project with gradle files というメニューを選択します。
もしくは以下のボタンを押して、反映させます。
うまく同期・反映できると以下のように External Build Files の中に CMakeLists.txt が見えていたり、hello-jni.c が見えたりします。
デバッグについて確認してみたいと思います。パッケージ名を合わせるために、hello-jni.c のコードを変更し、Java のコードを jni を呼び出すように変更しました。これらの変更については以下の画面のようにしました。
ブレークポイントを Java, C++ 双方側にセットして実行してみました。
上記のように、うまくどちら側ともブレークポイントで停止して、変数を見ることができています
今回 AndroidStudio での JNI デバッグを試していて、お!と思ったのは、ステップイン実行で Java から C++(JNI) へのステップインができたことでした。昔 eclipse 時代にはこれはできなかった芸当だったとおもいます。
構成について
通常は複数のライブラリを構築して、それを束ねてアプリケーション部分である jni (.so) が作成されます。このような場合をどう構成できるかを考えてみたいと思います。またデバッグについても確かめてみたいと思います。
CMake の仕組みでサブディレクトリごとに CMakeLists.txt をおいて処理ができるようなので、サブディレクトリでライブラリを作成するようなもので試してみます。
ディレクトリ構成としてこのような感じになっています。
app/src/main/cpp + hello-jni.c, CMakeLists.txt(1) + module1 + include | + module1.h + module1.c + CMakeLists.txt(2)
ここで (1) の CMakeLists は以下のようにしてあります。
cmake_minimum_required(VERSION 3.4.1) add_subdirectory( module1 ) include_directories( ./module1/include) add_library( hello-jni SHARED hello-jni.c ) # Include libraries needed for hello-jni lib target_link_libraries( hello-jni module1 log android)
(2) の CMakeLists.txt は以下のようにしてあります。
cmake_minimum_required(VERSION 3.4.1) include_directories( ./include) add_library( module1 STATIC module1.c )
module1.c の中身はシンプルなものなので、後ほどのスクリーンショットでご確認ください。
これらを作成した後、 AndroidStudio に同期 (Sync) させます。
適当にこのライブラリへの呼び出しコードを追加して、その部分にブレークポイントを張って、ステップインしてみた時の状態が以下のものです。
うまくステップインでライブラリ側のコードへ潜ることができました。
少なくともアプリケーションを生成する際に作るスタティックライブラリにおいてはデバッグでソースコードを追いかけていくことが可能そうです。
サンプル Hello-libs
プロジェクト外で既にビルドされた lib についてどうしようか思案していたところ、 Hello-libs というサンプルが都合良さそうでした。
これを確認してみることにしました。
このサンプルでは、ビルド済みのライブラリ (gperf, gmath) を使用する例になっています。
どのように自分のプロジェクトにそのようなライブラリを登録させるのかについて、CMakeLists.txt や build.gradle の書き方としての参考になるのではないかと思います。
この例のさらに良いところは、shared library, static library についての例となっているところでしょうか。
プロジェクトの新規作成で今までのサンプルと同じようにして Hello-libs サンプルを取得・実行してみます。
そして JNI 側に入ったところにブレークポイントを設定して、デバッグ実行させてみます。
ここまでは今までの成果から正常にブレークポイントで停止できると思います。ここから、スタティックライブラリやシェアードライブラリの中にデバッガでステップインできるか見ていきたいと思います。GetTicks 関数が既にライブラリ内なので、即ステップインをしてみました。
しかし結果は上記の通り。ソースコードは全く変化しませんでした。コールスタック情報の部分に関数にステップインしている形跡があります。
これはその後の gpower 関数でも同様の挙動でした。
このように残念な状態になってしまった理由ですが、それは使用したライブラリが自分で生成したものではないため、デバッグ用の情報が合っていないためです。ソースコードのファイルパス情報が違うとか、そもそもデバッグ情報がついていないものとか。しかしライブラリを使用する利用者側としてはデバッグすることも少ないと思うので、これはあまり気にならないポイントでしょうか。
付属のライブラリのビルド
デバッグ情報の問題なら手元で1度ビルドしたものにしてしまえばいい、ということでそのようにこのサンプルを変更します。
Hello-libs の最上位階層に settings.gradle というファイルがあり、これを開いてコメントになっている部分を解除します。
// include ':gen-libs' ↓ include ':gen-libs'
また依存関係の設定のために app 内の build.gradle も編集します。 dependencies の部分を以下のように変更してください。
dependencies { compile fileTree(include: ['*.jar'], dir: 'libs') compile 'com.android.support:appcompat-v7:23.4.0' compile 'com.android.support.constraint:constraint-layout:1.0.0-alpha7' compile project(':gen-libs') }
編集したら保存して、 AndroidStudio 側で、同期 (sync) を行ってください。
すると、 app 以外に gen-libs というモジュール が出現するかと思います。
もしかすると、 buildToolsVersion の設定や、各インストール状況によって失敗するかもしれませんがそのときには警告に従ってツールをインストールしてください。
(自分の環境でも1回それで追加のツールインストールをしたところ成功しました)。
そして、gen-libs を選択した後、 メニューから Build / Make Module ‘gen-libs’ を選択・実行します。
その後で app を再びビルドします。
これで再びブレークポイントを設定して、ステップインで各ライブラリの中をデバッグできるか確認します。
先ほどと同じようにステップインしたところ上記のように、意図したとおりにライブラリ側のソースコードを開くことができました。
また gpower 関数にステップインしたところ、以下のようになりました。
もしうまくいかなかった場合には、端末からアプリのアンインストールや、アプリそのもののリビルドを行ってみてください。プロセスが残ったままだったり、アプリへの変更が検知されずそのままだったりといったことが考えられます。
ライブラリを何度も更新する必要がないのであれば、 先の setting.gradle, app の build.gradle の設定を元に戻してしまうとよさそうです。そうすれば Build Project の際にライブラリを含めてビルドすることから解放されます。
まとめ
ライブラリを用いた場合でのデバッグについて、またプロジェクト(AndroidStudioで言うところのモジュール)をどのように設定していったらいいのかのヒントが今回の調べでわかった気がします。やりやすい、やりにくいなどの点は実際に使用する場面にならないとわからないかもしれませんが、できるということはわかりました。
今までの調べた内容から、C++ での開発もやりやすくなったという印象があります。 AndroidStudio 2.2 は C++ の強化をしたという話はほんとだったなと感じました。ただ今度はCMake に慣れていく必要が出てきたというのがありますが、android.mk よりは楽なんじゃないかなと思っています。
久しぶりの長い記事となりましたが、参考になれば幸いです。
あと今回の内容なら、2回に分割すべきだったかもと思いました。