「 2011年11月 」一覧

NDK r7が出てた

ついさっきみたら、Android NDKのrevision 7が出てました(android-ndk-r7)。
Android 4.0 が発表された上旬ではまだでしたが、遅れて登場したようです。

いくつか更新を見ていて気になったポイントを列挙してみます。

  • マルチメディア関連の拡充。使えるのは、API Level 14以降のものっぽいですが。 
    • ネイティブでOpenMAX ALのサポートが追加。
    • OpenSL ESでも圧縮オーディオ(mp3,aac)をwaveにデコードできる機能とか。
  • CCacheのサポート。
  • これで巨大なプロジェクトのリビルドが高速に.

実験的なサポートでも魅力的なものがありました。

  • Windows環境でCygwinなしでビルドできるように ndk-build.cmd の追加。
    ただし、ndk-gdbの使用には cygwinが必要な模様です。 

なかなかおもしろくなってきました。Windows環境での開発という点も考慮されるようになってきたのはうれしいですね。このあとndk-gdbの完全なるWindows版が提供されるようになってきたら、cygwinとオサラバできそうです。


GmailのOAuth認証

ここ最近はGmailに搭載された OAuth認証のうちの anonymous 方式のほうを調べて試行錯誤していました。
AndroidでGmailを使っているので、そのアプリに生かせたらいいなというのが最終目標です。日本語で、そしてJavaでの資料が思いの外少なくて、苦労しました。

いろいろ実験してうまくいかなくて、苦労していましたが、最終的には単純にOAuthの認証パラメータが不足していたことが原因でした。抜けていたのは、 oauth_signature という項目。これがリクエスト後のパラメータ集合になく、ハッシュ値を計算した後で自分で追加してあげなければいけなかったようです。これを適用してあげることでGmailのImapの認証が通ることを確認できました。

さて、いろいろとやってたわけですが、ここまでの道のりを書いてみます。

OAuth認証とは

最近はやってきている認証方式です。ある認証システムがユーザー認証を行い、その他のサービスらはその認証結果を使用させてもらう、という感じのものです。認証システムが1カ所になる点と、その際にパスワードの転送をしなくてよいというのが、割とセキュアになっているのでよさそうなシステムです。
ここで認証システムは”ユーザーを認証”という点から少しずれて、 何らかのデータへのアクセスへの承認、という部分を制御します。実際には外部サービスの利用時にユーザーにその許可の有無を問い合わせを行います。承認されれば、次回からそのサービスは認可されている範囲でデータにアクセスすることが可能になります。

ここで、パスワードのやりとりを行わずに何らかのデータ列(キーデータ)のやりとりでこれを実現していることがポイントです。

仕組みを理解するために

いくつかのサイトで方式を勉強しました。

特に、OAuth Playgroundはある程度の仕組みをざっとわかった後で、試してみると動作を実感できるのでお勧めです。ほかのサイトの説明を見ながら、このサイトで動作をさせてみながら、理解しようとするとわかりやすくなってよいかと思います。

うまくいかないときに

いくらやってみてもうまくいかなかった点として、GmailがIMAP/SMTPで取り入れているOAuthの認証では、anonymous方式をとっています。これは、従来のOAuthでConsumerキーとして使っている部分に”anonymous”を設定する方式です。この点がまず違います。

あと、強引に何度も試行錯誤しているとGmailのシステムからアカウントのロックを適用されてしまう可能性があります。この場合には、そのアカウントロックを解除しなくてはなりません。

さらに、うまくいくケースを見つけて、うまくいかない場合とどう違うのかを比較してみることが大切かと思います。
今回ずっとAndroid端末で実験していましたがうまくいかなくて、あきらめてPCベースのプログラムを作成し、googleのサンプルコードを動かしてみました。この両者を比較して、oauth_signatureのパラメータ抜けに気づきました。

それ以外の部分では、GmailのIMAP/SMTPを説明しているページをじっくり読むことが大切だったなと思います。


GMailにアクセスするアプリ

Androidのプログラミングとして、Gmailに保存されているデータにアクセスするアプリを書いてみようと思っていたら、なんと7月末のGmailの更新でできなくなった模様。エラーメッセージがわかりにくくて、まさか別の部分に問題があるとは思っていなかったよ・・・。

エラーメッセージはこんな感じです。

だから、ずっとパーミッション部分を調べてました。
7月末のアップデートでは、Gmailアプリケーションだけがアクセスできるように修正がはいった模様です。

 要するにこれまでは、アプリはREAD_GMAILパーミッションを使うことをManifestの中で宣言すればそれでOKだったが、今回Googleが android:protectionLevel=”signature”を加えたために、Gmailアプリケーション自身しかそのDBにアクセスできな くなった(正しいsignatureを持っているのはGmailアプリだけなので)。

やろうと思っていたことができなくなっている。
これは非常にやっかいだ。。。


Androidでネイティブデバッグ

Androidでネイティブコードのデバッグが問題なく行える機種情報です。

前回には方法を説明したので、 今回はデバッグに使える機種情報をさらしてみたいと思います。
また、できない機種情報も一緒に書いておきます。デバッグを目的とする場合には、この情報が役立ってくれると幸いです。

ネイティブのデバッグに使用できない

  • GalaxyS
  • Lenovo Ideapad tablet A1

ネイティブのデバッグで使用できる(gdb動く) 

  • GalaxyS2
  • Nexus S
  • xperia ray
  • IconiaTab A500
  • Xperia acro
  • GalaxyNexus
  • GalaxyS3
試してみてほかの機種でうまくいった方の情報をお待ちしています。
2012/10/13 – 機種情報を追加しました。

Androidでネイティブデバッグ(実機でデバッグも) -その2-

前回の設定により JNIの開発環境としてもEclipseが使えるようになりました。
ビルド自体もcygwinからndk-buildコマンドを実行しなくとも、EclipseからC/C++(NDK)の含めてビルドや実行を することが可能になりました。

今回は、目標でもあるネイティブコードのソースコードレベルデバッグについて、設定方法&実行方法を説明してきます。

使用するもの

  • 前回構築した各プラグインの導入されたEclipse (indigo)
  • 前回サンプルとして使ったMyApplicationのサンプルコード
  • Android実機
    • 実機でデバッグできるようになると感動です

サンプルは手を加えて使うので、似たようなものが用意できれば特に前回のものと同じである必要はありません。
今回単にデバッグ対象というだけで使用します。

Androidの実機についてですが、GalaxyS だけはうまくいきませんでした。
ICONIA Tab A500、Nexus S、GalaxyS2 では、このデバッグ方法がうまくいくことを確認できています。

ネイティブデバッグのための設定変更

まず、AndroidManifest.xml で Debuggable を true にしておきます。これはデバッグする対象に必須です。

デバッグの構成を変更(あるいは新規作成します)。
この新規作成するデバッグの構成は、”C/C++ Application”のデバッグ構成です。
Debug Configurationのウィンドウを開き、”C/C++ Application”カテゴリを選択し、左上の新規作成のボタンを押して構成を作成します。

作成した構成を選択し、画面中央部分の、”Using GDB(DSF) Create Process Launcher “の部分のリンクできる場所をクリックします。新しいウィンドウが開き、以下の図に示すように “Standard Create Process Launder”を選択します。

このウィンドウを閉じて、設定を再開します。
Mainタブの部分では、デバッグ対象の .so 名が表示されています。これを修正します。
プロジェクトのパス内の obj/local/armeabi の中にも同様の .so が作成されています。
この場所で、後ほど作成される app_process というファイルを使用するのでこれに変更します。
→ “obj/local/armeabi/app_process”

続いて、Debuggerタブを開きます。
Debuggerが gdb/mi になっているので、 gdbserver に変更します。
GDB debugger の部分には、android-ndkに入っていたgdbを選択させます。
→ (NDKの配置先 ex:android-ndk-r6b)toolchainsarm-linux-androideabi-4.4.3prebuiltwindowsbinarm-linux-androideabi-gdb.exe

GDB command file の部分には後ほど作成する gdb2.setup を設定します。
これも obj/local/armeabi の中にできあがる予定となっています。
このDebugger Optionsの中のConnection タブを開きます。
この中の Type を  TCP に変更します。そしてPort number を 5039 に変更します。忘れやすいので注意

ここまでの設定の画像を一応張っておきます。こんな感じになっているでしょうか。

次に、Android NDKに含まれている ndk-gdb のファイルを修正します。
とりあえず、ndk-gdbはコマンドではなく単にスクリプトなので、コピーしてから編集します。
このファイルを ndk-gdb-eclipse を名前を変えてコピーします。コピー後、適当なテキストエディタで開いて、末尾付近のコードを修正します。

となっている部分を、以下のようにしてコメントアウトにしておきます。(先頭に # を入れるだけ)

さきほど後で作成するといった各ファイルをここで準備します。作成するためにまずJavaのコードで、.soがロードされたあたりでブレークポイントを張ります。前回のサンプルならば、onCreateにはいった直後あたりでとめます。この設定をしてから、MyApplicationをデバッグ実行します。

この止めてある状態で、cygwinを起動し、このプロジェクトのある場所までカレントディレクトリを移動します。
そこで、ndk-gdb-eclipseコマンドを実行します。 エラーがなければしばらくしてコマンドが終了します。

このときに、obj/local/armeabi ディレクトリをのぞいてみると、app_process, gdb.setup というファイルができあがっています。これをデバッグに使用していきます。
app_processはそのまま使用するので放置、ここでは gdb.setup を gdb2.setup としてコピーします。
コピー後、適当なテキストエディタで開き、 最終行付近にある “target remote :5039” という部分を削除して保存し直します。

ここまで準備ができたら先ほどデバッグ実行したコードは終了し、cygwinのウィンドウも終了させておきます。

デバッグの確認

さて、準備が整ったので、ソースコードレベルでバッグの動作確認をしてみます。
前回のコードよりもう少しC/C++のコードを追加して、変数の値が見れることやブレークポイントの設定が可能であることをみていきましょう。

ソースコードの変更

デバッグの効果をみてみたいので、ソースコードを変更します。

デバッグの方法

このように変更してアプリケーションをビルドします。
Javaの部分の onCreateにはいった直後あたりでブレークポイントを設定し、デバッグ実行を行います。
ブレークポイントで停止したら、cygwinを開き、プロジェクトのディレクトリまでカレントディレクトリを移動させて、ndk-gdb-eclipse コマンド(スクリプト)を実行します。
問題なく実行完了したら、Eclipseで 先ほど作成した C/C++用のデバッグ構成 で実行します。
(Debug As などで ウィンドウを開いて、C/C++ Application内の構成を選択して、Debugボタンを押します)

gdbデバッガを起動させている途中で、エラーが出ると思いますが、これは無視して先に進むことが可能です。エラーの内容としては main関数でstopする予定だったのに、その関数がないよ、という意味合いのものです。
“Target request failed: Parsing Error” というメッセージで出てくるのでどきっとしますね・・・。

うまくいっていると、Consoleウィンドウの部分に gdb が起動していることが見て取れます。

ここまでくると、C/C++のソースコードにブレークポイントを張ることが可能になります。さきほどのソースコードで, GlobalCounter++ としている部分にブレークポイントを設定し、onCreateで止めていた実行を再開させてみます。
すると、 GlobalCounter++ のブレークポイントまで実行され、停止します!

GlobalCounterの値が0であったり、env, thiz の値がなにやらとれていたり、コールスタックを確認することができたり。ソースコードレベルでのデバッグが可能となっていることが確認できると思います。
この先、ステップ実行(Step Over)をして、bufの値を確認できるところまでやってみましょう。

ステップ実行ではgdbが裏でガリガリと動いている様子がconsoleのウィンドウを見ても確認できると思います。それゆえ、結構重い処理となっています。return文のところまできたら、bufに値が格納されているのが無事確認できると思います。

まとめ

2回ほどの記事で、AndroidのNDK、C/C++の開発コードにおけるネイティブソースコードレベルデバッグの方法を説明してきました。これにより、今まではprintf文か、コンソールでgdbを実行しながら動作を確認する方法しかなかったのが、GUI上でソースコードを見ながらデバッグすることが可能となりました。

VisualStudioではWinGDBを入れれば今回のことと同様のことができますが、今説明した方法はすべてフリーの環境で構築されています。動作が遅い点はありますが、これはこれで非常に使えるものだと思っています。

既知の問題点とか

ステップインの実行がなにやら怪しいです。eclipseのデバッグ仕様なのか、引数評価、関数ジャンプのための準備、という単位でステップ実行をしているようです。そのため、関数の中に入りたいタイミングでステップインのボタンを押しても、なかなか目的の関数に入れないということが起こります。
対処法は、目的の関数に到達するまでステップインを実行し続ける、ということになります。あるいは、目的の関数先頭にブレークポイントを設定してしまい、実行を再開させる方法でもよいと思います。

フリーな環境構築ですし、ある程度不便なのは仕方ないのかもしれません。

次回以降のデバッグの方法

同じプロジェクトの2度目以降のデバッグ方法は以下の方法でできます。ちょっとは手間ですが、初回ほど大変ではありません。

  1. ビルドし直した後、onCreateで停止させる
  2. ndk-gdb-eclipse を起動させる
  3. Eclipseで C/C++用のデバッグ構成でデバッグを開始させる
  4. onCreateで止めていた実行を再開させる

別のプロジェクトの場合には、C/C++用のデバッグ構成を作成するあたりから、設定が必要になります。

参考文献

ここまで到達できるのに、いくつか参考にしたページがあります。この情報がなければここまで便利に使えるようにならなかったと思います。


Androidでネイティブデバッグ(実機でデバッグも) -その1-

AndroidのC/C++を使ったネイティブコード(JNI)を、ソースコードデバッグできるように環境を整えることができました。環境にも若干左右されるようですが、概ねうまく動いてくれるようです。VisualStudio上ではなくEclipse上でではありますが、C/C++のソースコードレベルでブレークポイントを張ったり、変数を覗き見できるようになることは、開発のしやすさ向上につながると思います。

必要なもの

ここでは、Windowsでの開発環境を想定しています。以下のものがすでに整っているという前提で以降説明していきます。また、NDKのセットアップに関してはコマンドで ndk-build を実行して .so ファイルは作れる環境まで仕上がっているということを事前条件としています。

  • Eclipse
    • JavaによるAndroid開発環境が整っていること.
    • ここに、CDT, Sequoyah プラグインを入れていきます。
  • Android-SDK, NDK
  • cygwin

Eclipseの環境を準備する

EclipseでC/C++を扱うためにプラグインを導入します。
必要なプラグインは C/C++ Development Tools (CDT)です。
これは、Eclipseの [Help] => [Install New Software] と開き、”Work with”の部分に、”Indigo – http://download.eclipse.org/releases/indigo” となるように選択するか入力するかします。
すると一覧が出てくるので、ここで”C/C++ Development Tools”を選びます。
(“Group items by category”を外しておいた方が見つけやすくなるかもしれません)
チェックを入れて、このプラグインをインストールします。
インストール後には、Eclipseの再起動が必要になります。

続いて、Sequoyah プラグインを入れます。
先ほどと同様に、”Install New Software”から、”Work with:”の部分で以下のURLを入力します。
”http://download.eclipse.org/sequoyah/updates/2.0/”
そして、”Group items by category”のチェックを外してしばらく待つとプラグインの一覧が出てきます。
すべてにチェックを入れて、プラグインのインストールを行います。これもまたインストール後はeclipseの再起動が必要です。
[Window] => [Preferences] を開き、”Android”カテゴリ内の “Native Development” を開きます。
すると、NDK Location を入力する場所があるので、ここにNDKを展開したフォルダを指定しておきます。

動作チェック

ここまでの段階でAndroid Javaプロジェクトに対して、マウス操作でC/C++コード(JNI)の追加ができるようになっています。それを確認してみます。

まず適当なAndroidプロジェクトを作成します。

プロジェクトを作成した後、パッケージエクスプローラーで、作成したプロジェクトを選択した上で、右クリックします。
その中のメニューで、 “Android Tools”という項目の中に、”Add Native Support” という項目があります。
以下にスクリーンショット で示します。

これを選択すると、cppによるJNIのサポートが追加されます。
デフォルトで作成し、その後パースペクティブが”Native Android Perspective”に切り替わります。

こんな感じになっているので、cppの部分にJavaと接合するJNIのコードを書いていくことができます。

ここでは、hello-jniのコードと同じようなものを作成してみることにします。

コード loadLibraryしている部分の、”MyApplication”は自分の.soのファイルの先頭lib, 末尾.so を除いた部分を指定します。今回適当に作成したのでこのようになっています。各自の環境に合わせて変更する必要が出てくると思います。

プロジェクトのビルド

Project Explorerで、今回のプロジェクトを選び、右クリックすると”Build Project”という項目があります。
これを選択すると、JNIの部分のC/C++コードをビルドすることができます。

ビルドした結果は、Consoleのウィンドウに出てきます。ここで問題なく .so が生成されたかを確認することができます。ちなみに、通常のJavaアプリケーションのように実行ボタンを押したときでも、必要に応じてJNI部分のビルドコードは実行されるようです。

いつものように実行ボタンを押して、問題なくJNIのコードが呼び出されて動くことを確認してみてください。
記事として長くなってきたので、デバッグ用設定部分は次回の記事に回します。

いくつかのQ&A

– ビルドに失敗する。ndk-buildが見つからないという感じ

この場合、Cannot run program “ndk-build”メッセージがConsoleに表示されているはず。NDKのディレクトリ位置にパスを通しているのにも見つからない場合、設定をちょっと変更することで回避できる。

これを解決するには、Project Explorerから対象プロジェクトを選択し右クリックして、プロパティを表示させます。その中に、C/C++ Buildという項目があり、Builder Settings のタブで、Builder のコマンドを変更すれば一応解決することが可能です。

Use default build command のチェックを外し、cygwinのパス形式でndk-buildの場所を特定し、bashコマンドの引数として渡してあげればよい。

– “Build Project”が見当たらない or 前述のC/C++ BUildが見つからない

おそらくパースペクティブが Native Android Perspective になっていないからです。
一度確認してみましょう。


Androidアプリのライフサイクル&再開処理

ネイティブコードでメインの処理を行うとして、Javaの部分は単にActivityという入れ物にしか使わない、という感じでアプリケーションを作成することを考えている。今のところOpenGLESをつかっていて、Android特有の問題に悩まされている。

  • 複雑なライフサイクルで、OpenGLのコンテキストが再生成されている
  • その再生成のタイミングで今まで使用していたリソースが無効状態となる
  • そもそもOpenGLなのに、DirectXのようなデバイスロストに似た症状が発生する

Androidの通常アプリのActivityは以下の図に示す遷移をたどる。この図はGoogleのAndroidのページにあるものと同じである。

調べてみたところ、ユーザーの操作によって、似たように見えていても違う遷移となる点も確認できた。

  • 起動後、ホームボタンを押す。その後もう1度起動させるケース
    • onCreate – onStart – onResume – (実行中) – onPause – onStop –  onRestart – onStart – onResume – (実行中)
  • 起動後、Backボタンを押す。
    • onCreate – onStart – onResume – (実行中) – onPause – onStop – onDestroy
  • 起動後、放置してスクリーンのタイムアウトなどでActivityが見えなくなる
    • onCreate – onStart – onResume – (実行中) – onPause – (スクリーン復帰) – onRestart – onStart – onResume – (実行中)
サンプルのアプリケーションでは surfaceCreatedあたりでシェーダーやテクスチャなどの初期化を行っている。これが上記のどの部分になるかを調べてみたところ、onResume の直後あたりになるようだ。そしてsurfaceChangedも同部分になるようだ。

一方で、surfaceDestroyedなる部分は、onPauseの後になる模様。onStopに遷移するタイミングで実行されるみたい。

このつくりだとアプリケーションが再び実行状態になる前には surfaceCreatedを通過するため毎回リソース生成が行われるので、問題なく画面描画ができることになる。リソースの破棄処理がどうなっているのか疑問は残るが。
たぶん、確実にリソース処理しようとすれば onPauseで一時解放しておくのがよく、surfaceCreatedのタイミングで再生成するのがいいんだろう。

さて、上記の遷移はあくまでJavaの部分でのActivityの各イベントハンドラで検知した感じの情報。
これを C/C++の部分でどうやろうか、というのが問題となってくる。従来までの作りをしているアプリケーションだと、最初に初期化して、アプリ内グローバルなインスタンスはアプリ最後まで生き残ることを想定している。このグローバルなインスタンスにはグラフィックスやサウンドの管理部分がいたりする。
書籍「Androidネイティブプログラミング」 にもある例だと、surfaceCreatedのタイミングで初期化を行い、onDrawFrameの部分でC/C++のメインループへ実行を飛ばしている。これの作りをそのままにしておくと、アプリの切り替えから復帰しようとした時には常に初期化処理から実行されることになる。2重初期化を検知してエラーとか出すような作りにしていれば、そこでエラーとなる。あるいは初期化から再スタートなので、先ほどまでのデータが残っていたりはしない。

さて、もう1度よく考えてみよう。

再初期化が必要なのはいったい何か。アプリケーションのデータは初期化してしまう必要はあるか?

そう。必要なのはデバイスにアクセスしているようなグラフィックスやサウンドの部分についてだけ。その他のアプリケーションがメモリに持っている各データ状態はそのままでいいはずである。画面遷移するまでに使っていたリソースを再度確保してあげるコードを用意して、ほかの状態から戻ってきたときにその部分を処理するようにすれば、理論上はそのアプリケーションは切り替え前の状態に戻るわけで問題なく再開が可能なはずだ。

では、リソースの解放や再確保の部分はどこで行うべきか。
個人的には次のように考えている。onPauseの部分でリソースの解放処理を行ってしまい、ほかのアプリケーションの邪魔をしないようにする。これにはリソースを減らしてあげることでAndroidシステムから真っ先に消される可能性を幾分か減らしてくれるはずだ。 そしてこのonPauseを通過したことを示すフラグを立てておく。
リソースの再確保の部分はどこに書くのか、実行するのか。これはonDrawFrameの中でやるか、surfaceCreatedあたりでやるかしか方法はなさそうである。理由はコンテキストの再生成が行われた後、ウィンドウが表示されていることなどその他の要因があるからである。どちらの部分にせよ、C/C++へのジャンプコードが入っているので、先ほど覚えておいたフラグを用いてonPauseを通過済みであるならリソースの再確保処理を行ってあげるコードを実行すればよいだろう。最後に忘れてはならない点は、このonPauseの通過フラグは onCreateのタイミングで確実にfalseを設定してあげること。

理由を説明すると、このフラグはおそらくグローバルに持つ感じになるだろう。Androidのシステムにおいてメモリイメージは再利用される可能性があるので、あるタイミングではこのフラグがtrueのまま最初から実行されレしまう可能性もある。一応onPause, onStopからonCreateへの遷移ではメモリ解放となっているが、onCreateでfalseにしてしまったほうが安全に思える。

まとめると、onPauseの部分で onLostDevice相当の関数を実装&実行させ、onResumeの後のsurfaceCreated/onDrawFrame内でonResetDevice相当の関数を実装&実行させれば、リソースの復帰が可能だろうと思われる。

AndroidのGLSurfaceViewが面倒を見てくれるのは、EGL,GLのコンテキストの再生成まで。それ以外のテクスチャやシェーダー、FBO,VBOなどは再生成を手動で行う必要がある。それはオールJavaで書いてあっても同様だ。