無料ブログはココログ

« 2013年5月 | トップページ | 2013年7月 »

2013年6月

2013年6月19日 (水)

画面遷移(他のアクティビティの起動)

20130619_420130619x2_2

 メイン画面から別の画面に遷移するためには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環境下での扱われ方である。
 当初実験したものは以下のような構成だった。

        (パッケージ化する前の元ファイル状態のうち主要なものだけを抜粋)

  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との関係である。
 教科書的にはJava内で onDestroy() が呼び出されたことをもってアクティビティの終了とみなすことができるはずなのだが、実はそのような状況でもDLLは開放されないことが分かった。(その先の同一アクティビティの再起動に備えているのかも知れない)
 当初は onDestroy()→C→Mind へと移行してMindのライブラリ終了処理をおこなっていたのだが、それでは次回のアクティビティの起動時にDLLロードのタイミングが入らないためそのあとのイベント発生などでMindの動きがおかしくなることが分かった。
 対策として、DLLロード済みの状態でアクティビティが起動した(JavaのonCreate()が呼ばれた)とき、DLLはロードせず、Mindの初期化単語も呼び出さず、直接「メイン」を実行する(初期画面の描画などをおこなう)仕組みを作ることで解決した。結果的に、Mindの終了処理は呼ばれないことになるが、特に問題は無い。(Mind+Cで獲得したメモリは本当のプロセス終了時に開放されるはず)




参考URL: 「日本語プログラミング言語Mind」 (スクリプツ・ラボ)

2013年6月 3日 (月)

イベントリスナー

20130603eventlistnersample_3

レイアウトの場合と似たことだが、Java側のオブジェクトをMind側に使えても意味が無いので簡単な仕組みで両者間で連絡が取れるようにする。

まずクリックリスナーの登録は以下のように行う。

[Mindのソースコード]
-----------------------------------------------------------
  メインとは
      ~略~
    ボタン1を クリックリスナーを登録し
-----------------------------------------------------------

レイアウトの時もそうだったが、オブジェクトのクラス(ボタンなど)別にそれぞれ登録処理を使い分けるのはMindのソースが煩雑になるため、Mindの処理単語は「クリックリスナーを登録」1つとし、Mind→C→Javaと遷移した後、Java内部で処理を分岐するようにした。以下のようになる。

[Javaでの登録処理] (Mind側から呼び出される)
-----------------------------------------------------------
  public int j_setOnClickListener( int id, int type, int seqno )
  {
        int  retcode;

    retcode = 1;
    switch ( type )
    {
        case typeTextView:
            break; (未実装)
        case typeEditText:
            break; (未実装)
        case typeButton:
            buttonsMind[seqno] = id;
            buttons[seqno].setOnClickListener( new onClickListenerForButton() );
            break;
        default:    /*該当無し*/
            retcode = 0;
            break;
    }
    return retcode;
  }
-----------------------------------------------------------

上記のうち buttonsMind[seqno] = id という処理だが、これはMindで管理しているボタン情報をJava側でも記憶しておきイベント発生時、Mind側に処理が移った際に押されたボタンの識別がMindでもできるようにするためである。(Mindの側にビューオブジェクトを渡されても困るため)

次にイベント発生時の処理だが、まずJavaに入って来るので以下のようにイベント受け取る。

[Javaのイベントリスナー] (Android OS から呼び出される)
-----------------------------------------------------------
          ↓注:これはボタン用。クラス毎に用意する
    private class onClickListenerForButton implements OnClickListener {
    @Override
    public void onClick( View v )
    {
            int    found;
            int    viewno;
        found = -1;
        for ( viewno=0; viewno<buttonsCount; viewno++ )
        {
            if ( v == buttons[viewno] )
            {
                found = viewno;
                break;
                
        }
        if ( found == -1 )
        {
            return;
        }
        /* ----------------------------------------- */
        /* -------- Mindカーネルを呼ぶここから ----- */
        String retcode = calltellButtonClick( ButtonsMind[found] );
        /* -------- Mindカーネルを呼ぶここまで ----- */
        /* ----------------------------------------- */
    }
  }
-----------------------------------------------------------

上記ではMindに通知する情報として buttonsMind[] という配列を参照しているが、前記したようにMind側でボタンを識別できるようにするためである。
一方、Mindでは次のようなソースコードでイベント処理を記述する。

[Mindのイベント処理のソースコード]
-----------------------------------------------------------
色の状態は 2個の 変数。

ボタンクリック処理は    本定義  (ボタン情報 → ・)
        _ボタンは ビュー
    _番号は  変数
    _ボタンに 入れ
    _ボタンの IDからビュー番号を得て _番号に 入れ
    色の状態(_番号)を 一つ増加し
    色の状態(_番号)と 1を ANDし 真?
        ならば 赤色を
        さもなければ
            黒色を
        つぎに
    _ボタンに 文字の色を設定する
    。
-----------------------------------------------------------

上記のようにMindでは「本定義」を使ってイベント処理を実現する。(ランタイム内部で既に仮定義してある)。ほかにも、恐らく TextView については「テキストクリック処理」というような名称にするのだと思う。




参考URL: 「日本語プログラミング言語Mind」 (スクリプツ・ラボ)

« 2013年5月 | トップページ | 2013年7月 »