はじめに
この記事はだいぶ前(2012年)にココログの「キリーの日本語プログラミング」に掲載したコラムですが、再編集してこちらにも投稿します。今となっては古い話ではありますが、OSによってプログラム言語(AndroidではJava)が決め打ちされている環境で規定と異なるもの(Mind)を走らせたことの記録としてお読みください。
画面遷移(他のアクティビティの起動)
メイン画面から別の画面に遷移するためにはAndroidで言うアクティビティの起動を使うことになる。
Mindで言うところの「プログラム実行」/「子プロセス実行」、Cなら fork()+exec() の感覚だろうと思っていたのだが、実際にやってみると想像とはかなり異なる部分があり、原理を理解したり実装するのに時間がかかった。
アクティビティの起動
まずアクティビティの起動はMindで以下のように記述する。
<パッケージ名>と <クラス名>で インテントを実行
上が実行されると Mind→C→Java へと制御が移行し最終的に以下のJavaコードが動作する。
intent = new Intent();
intent.setClassName( pkgname, classname );
startActivity( intent );
Javaでは上記のほかに、Intentクラスを生成するときのコンストラクタにてクラスを直接指定する方法もあるが、MindでJava側のクラスを把握するのは大変なので文字列として扱える上記方法を採用した。
またMindでは、クラス名を長記述ではなく短形式でも指定できるようにした。本記事冒頭のサンプルでは以下のように起動している。(自身のパッケージ内のサブアクティビティを起動するにはこのような感じとなる)
※ ↓クラス名は短形式で可
私のパッケージ名と "Subactivity"で インテントを実行し ・・
このように、アクティビティの起動自体は大したことは無かったのだが、これに付随したことで大きな問題が出た。ポイントはC記述の共有ライライブラリ(DLL)のAndroid環境下での扱われ方である。
C記述のDLLのAndroid環境下での扱われ方
当初実験したものは以下のような構成だった。
(パッケージ化する前の元ファイル状態のうち主要なものだけを抜粋)
assets
|-- bin
| |-- euc-uni-encode.bin ←Mindが使うUnicode変換テーブル
| `-- uni-euc-decode.bin ←Mindが使うUnicode変換テーブル
`-- mco
|-- Subactivity.mco ←サブアクティビティのMindオブジェクトコード
`-- TestSubactMind2.mco ←メインアクティビティのMindオブジェクトコード
bin
|-- classes
| `-- jp
`-- co
`-- scriptslab
`-- TestSubactMind2 ←パッケージ名
|-- Subactivity.class ←サブアクティビティのJavaクラス
`-- TestSubactMind2.class ←メインアクティビティのJavaクラス
libs
|-- armeabi
| `-- libdllker.so ←C記述対応のDLL (この中からMind記述部を実行)
C記述のDLLはMindにとってはランタイムライブラリの位置づけであり、アクティビティが違っても中身は同じだからという理由で、1つのDLL(libdllker.so)を2つのアクティビティで共用した。
元々が共有ライブラリなのだからこれで良いだろうと思ったのだが、実際に走らせてみて分かったことは、たとえアクティビティ毎にそれぞれロード(System.loadLibrary())したとしても、DLLが割り当てられる空間は1組だけでありアクティビティ毎での割り当てではなかった。(恐らく二度目のロードは無視されたのではないか)
以前に「複数のアクティビティは同一プロセスで走行する」というサイトの情報を見ていたずなのだが、つい アクティビティ=プロセス であるかのような錯覚をしていた。
しかし中身が同じDLLを2つ生成することに抵抗があったため以下のような細工をしてみた。
libs
|-- armeabi ↓2つのDLLの中身は同じ
|-- libdllker_Subactivity.so ←libdllker.soをリネーム・コピー
`-- libdllker_TestSubactMind2.so ←libdllker.soをリネーム・コピー
Java→Cに入る部分の関数名は独特の名称になるのだが2つのアクティビティ向けに次のようなエントリを作成した。
jstring
Java_jp_co_scriptslab_TestSubactMind2_Subactivity_dllStartup( JNIEnv* env, jobject thiz, jstring pkgnamej, jstring classnamej, jbyteArray mcobufferj )
{
dllStartup_common( env, thiz, pkgnamej, classnamej, mcobufferj );
}
jstring
Java_jp_co_scriptslab_TestSubactMind2_TestSubactMind2_dllStartup( JNIEnv* env, jobject thiz, jstring pkgnamej, jstring classnamej, jbyteArray mcobufferj )
{
dllStartup_common( env, thiz, pkgnamej, classnamej, mcobufferj );
}
本来であれば前者の関数は前者のDLLにだけ、後者の関数は後者のDLLにだけ存在すべきなのだが、それではやはりDLLを2つ作成することになってしまうため、あえて両方の関数を入れることで同一DLLで済むようにした。
しかしこの方法はうまくなかった。たとえば Java_jp_~~_Subactivity_dllStartup() は libdllker_Subactivity.so の中にあるそれが呼び出されることを期待したのだが、そうはならず、他方のDLL内関数が呼ばれてしまうことが分かった。
先にも書いたが、1つのパッケージに複数のアクティビティが存在する場合であってもプロセスは1つであり、その結果、DLLのロードも1系統になっている。
たとえば2つのDLLに同じ関数エントリが存在する今回のようなケースでは「どちらのDLLに属するか」の判断はされず、とりあえずシンボル的に見つかった関数が呼ばれているのではないかと想像している。
最終案の方法
前置きが長くなったが結局以下のような方法をとった。
libs
|-- armeabi ↓2つのDLLの中身は異なる
|-- libdllker_Subactivity.so
`-- libdllker_TestSubactMind2.so
ほとんど同じ内容のDLLをアクティビティごとに複数用意することになるが、この煩雑さは自動処理で生成できるからあまり問題にはならないだろう。(そのようなツールを作るという面倒さはあったが)
繰り返しになるがこれらのDLL群はJavaから呼ばれるエントリ関数の名前だけが異なり、そのほかは同一である。
アクティビティの終了とDLLとの関係
次の問題はアクティビティの終了とDLLとの関係である。
教科書的にはJava内で onDestroy() が呼び出されたことをもってアクティビティの終了とみなすことができるはずなのだが、実はそのような状況でもDLLは開放されないことが分かった。(その先の同一アクティビティの再起動に備えているのかも知れない)
当初は onDestroy()→C→Mind へと移行してMindのライブラリ終了処理をおこなっていたのだが、それでは次回のアクティビティの起動時にDLLロードのタイミングが入らないためそのあとのイベント発生などでMindの動きがおかしくなることが分かった。
対策として、DLLロード済みの状態でアクティビティが起動した(JavaのonCreate()が呼ばれた)とき、DLLはロードせず、Mindの初期化単語も呼び出さず、直接「メイン」を実行する(初期画面の描画などをおこなう)仕組みを作ることで解決した。
結果的に、Mindの終了処理は呼ばれないことになるが、特に問題は無い。(Mind+Cで獲得したメモリは本当のプロセス終了時に開放されるはず)
つづく
最近、Mindユーザーの@mylifewithviolinさんがMindコンパイラの内部に興味を持たれているようなので参考情報として投稿してみました。本記事はしばらくつづきます。次は(ステップ6/9)。