無料ブログはココログ

2013年9月26日 (木)

プリファレンスアクティビティ

Preferenceact3gattai_2
 先の「プリファレンス」に関連するものとして「プリファレンスアクティビティ」も実装した。
 Androidにはユーザと対話してプリファレンスのデータを編集し結果を記入してくれる高機能なアクティビティがあり、それを使うためのAPIが用意されている。プログラマとしては設定ファイルを用意せずに済み、ユーザインターフェースのコードも大幅に削減されるのでおおいに助かる。ただ高機能な分、Mindに実装するには手間がかかった。

 まず呼び出し元から。
 呼び出し先である本機能はダイアログ的に見えるがダイアログではなく独立したアクティビティなので本機能を呼び出すには一般的なアクティビティ起動と同じ手順を踏む。

   (Mindソース例)
    私のパッケージ名と "PreferenceActSub"で インテントを実行し _結果に 入れ

 次に本機能を実行するアクティビティについて。
 通常のアクティビティとは異なり、プリファレンスのGUIパーツの描画のみを記述する。
 Androidの標準的な開発手法では、設定画面のフォーマットをXMLファイルで記入するらしい。このあたりはレイアウト系の実装と同じ事情になるが、Mindでそのようなファイルを扱うことは避けたかったのであくまでJavaのコードで設定画面を作る方式をベースとし、そのJavaのコードをMindから駆動するようにした。
 書籍やWeb上の情報にはJavaコードでデザインをおこなう例が少なく困ったが、コードでの駆動方法を把握するのに以下のWebページは大変参考になった。

    「Preference(3)-Preference画面に関するTips - 愚鈍人」
    http://ichitcltk.hustle.ne.jp/gudon/modules/pico_rd/index.php?content_id=96#use_code

 上記Webページにおいて、スクリーンの設定は以下のようになっている。

    public class MyPreferenceActivity extends PreferenceActivity {

      @Override
      protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);
                        ~略~

        PreferenceScreen pScreen =
             getPreferenceManager().createPreferenceScreen(this);
        pScreen.setTitle("PreferenceScreenのタイトル");
        setPreferenceScreen(pScreen);

 これをMindで書く場合は以下のようになる。

    メインとは
                _スクリーンは 変数

        プリファレンススクリーンを生成し   _スクリーンに 入れ
        "PreferenceScreenのタイトル"を  _スクリーンに プリファレンスにタイトルを設定し
        _スクリーンで プリファレンススクリーンを有効化し

 同様に上記Webページにおいて、チェックボックス・プリファレンスを表示するJavaのコードは以下のようになっている。(依存関係の指定は省略)

        CheckBoxPreference pCheckBox = new CheckBoxPreference(this);
        pCheckBox.setKey("checkKey");
        pCheckBox.setTitle("CheckBoxPreference");
        pCheckBox.setSummary("チェックボックスの例");
        pCheckBox.setDefaultValue(true);
        pCategory1.addPreference(pCheckBox);

 Mindで書く場合は以下のようになる。(以下ではカテゴリではなくスクリーンに追加)

                _チェックボックスは 変数

        チェックボックスプリファレンスを生成し _チェックボックスに 入れ
        "checkKey"を              _チェックボックスに プリファレンスにキーを設定し
        "CheckBoxPreference"を _チェックボックスに プリファレンスにタイトルを設定し
        "チェックボックスの例"を   _チェックボックスに プリファレンスにサマリを設定し
        真を                        _チェックボックスに プリファレンスにデフォルト値を設定し
        _チェックボックスを _スクリーンに プリファレンスを追加し

 上記のようにプリファレンスアクティビティ系のMindの処理単語はおおむねJavaのメソッドと一対一になった。例外はリストプリファレンスで、項目名と項目値はJavaでは別々に与えているがMindでは一度に与える仕様とした。

 前記事に書いた通り、(単なる)「プリファレンス」の実装ではJava側インスタンスの管理はあえて行わなかったが、こちらでは行なっている。スクリーン、カテゴリをはじめ、チェックボックスなどGUIパーツすべてについてJavaで生成したオブジェクトは大域配列に格納し、その要素番号でJavaとMindを結びつけるようにした。

 現段階で実装したMindの処理単語は以下の通りである。(数が多いので入出力パラメータは記載せず単語名だけとした)

        プリファレンスファイルを指定

        プリファレンススクリーンを生成
        プリファレンスカテゴリを生成
        チェックボックスプリファレンスを生成
        エディットテキストプリファレンスを生成
        リストプリファレンスを生成
        リングトーンプリファレンスを生成

        プリファレンススクリーンを有効化

        プリファレンスにキーを設定
        プリファレンスにタイトルを設定
        プリファレンスにサマリを設定
        プリファレンスにデフォルト値を設定
        プリファレンスにダイアログタイトルを設定
        プリファレンスにダイアログメッセージを設定
        プリファレンスにリスト項目を設定

        プリファレンスを追加


 プリファレンスが変更された時のイベント処理は現時点では未実装である。




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

2013年9月25日 (水)

プリファレンス

Preference アプリ固有の設定情報を不揮発性で記憶するのに、簡単なものだったら「プリファレンス」という(正確には「共有プリファレンス」らしい)機構が使える。Windowsだったら自前で*.iniファイルあるいはレジストリに記録するところだが、この点Androidは便利だと思った。
 さらには、ダイアログを開いてユーザと対話してプリファレンスのデータを編集し結果を記入してくれるAPIも用意されていて驚いた(そちらは次の記事に)。

 プリファレンス(Javaクラス名は「SharedPreferences」)が最終的に記入される先はファイルのようだ。最初に(Javaレベルでは)インスタンスを生成する必要があり、このときファイル名とアクセスモードを指定する。このあたりはMindでの手順も同様とした。

 今までの記事でも書いたことだが、Javaのクラスインスタンス(オブジェクト)をMindで得てもあまり意味がない。レイアウトなどでは生成したオブジェクトを大域配列に記憶し、Java←→Mindはその配列要素番号で連携をとったたが、今回の場合、複数のプリファレンスを同時に処理する場面は想定しずらいと考え、オブジェクトは配列にはせずJava側で単一の大域変数に格納、それを暗黙の了解で扱うようにしてJava←→Mindのインターフェースを単純化した。

プリファレンスの生成

 Mindでの記述は以下の通りである。

    (文法)
        <ファイル名>と <パーミッション>で プリファレンスを獲得
                                                         → [ErrorMessage]、真偽


    (ソース例)
        "testdb"と MODE_PRIVATEで プリファレンスを獲得し 偽?
                ならば 重大エラー
                つぎに


 上記にある「パーミッション」は、同じアクティビティ内であるいは同一パッケージ内アクティビティ同士で共用すれば済む場合は定数「MODE_PRIVATE」を指定する。(定数を日本語名にするか考えたがとりあえずJavaと同名の英語にした)
 なお、プリファレンスの生成のあと書き込みをおこなうのか、読み出しをおこなうのかはこの段階では区別する必要はない。(このあと書き込み処理をおこなえば書き込みとなる)

エディタの割り当て(書き込み時)

 書き込みを行うには、一連の書き込みの前に一度だけ、プリファレンスに対して「エディタ」(Javaでは SharedPreferences.Editor クラス)を割り当てる必要がある。プリファレンス本体のオブジェクト維持と同様にエディタのクラスインスタンスもJava側大域変数に格納するためMindからエディタを特定する情報は無い。

    (文法)
        プリファレンスのエディタを割り当て → [ErrorMessage]、真偽

    (ソース例)
        プリファレンスのエディタを割り当て 偽?
                ならば 重大エラー
                つぎに


データの書き込み

 プリファレンスに記入するデータは必ず<キー>と<値>の対である。キーは常に文字列だが、値は論理値/整数/文字列のように種類がある。Mind側でも値のデータ型に応じて複数の処理単語を設置した。またJavaでは書き込み時にエディタを指定するのだが前述の通り暗黙のオブジェクトとしたためMindでは指定しない。

    (文法)
        <キー>と <値(1/0)>を プリファレンスに論理値を書き込み
                                                         → [ErrorMessage]、真偽
        <キー>と <値(整数)>を プリファレンスに整数を書き込み
                                                         → [ErrorMessage]、真偽
        <キー>と <値(文字列)>を プリファレンスに文字列を書き込み
                                                         → [ErrorMessage]、真偽

        <キー>と <デフォルト値(1/0)>で プリファレンスの論理値を読み出し
                                                → <値(1/0)|ErrorMessage>、真偽
        <キー>と <デフォルト値(整数)>で プリファレンスの整数を読み出し
                                                → <値(整数)|ErrorMessage>、真偽
        <キー>と <デフォルト値(文字列)>で プリファレンスの文字列を読み出し
                                                → <値(文字列)|ErrorMessage>、真偽


    (ソース例(書き込み))
        "age"と 62を プリファレンスに整数を書き込み 偽?
                ならば 重大エラー
                つぎに


    (ソース例(読み出し))
        "name"と 空列で プリファレンスの文字列を読み出し 偽?
                ならば 重大エラー
                つぎに
        氏名に 入れ


 論理値の書き込みは、Mindで論理値は「ゼロ/非ゼロ」で判定される整数として扱っているがJavaでは論理値自身がデータ型なので両者間で型変換をおこなっている。
 Mindへの実装では当初は論理値の読み書きは含めず「~に整数を書き込み」「~の整数を読み出し」で代用すれば可と思っていた。しかし後述するプリファレンス・アクティビティにおいて、論理値を扱うAPIがあるため仕方なく設置したものである。
 読み出し操作で指定するデフォルト値は、指定したファイルが存在しないか、指定したキーが存在しない場合、ここで指定した値を読み出し値とみなして返すために使われる。

コミット

 これはデータベースの用語だが手続きもデータベース流になっている。一連の書き込みのあと、コミットをおこなうことで実際にファイルに書き込まれる。(割り当てたエディタはその後明に開放する)

    (ソース例)
        プリファレンスをコミットし 偽?
                ならば 重大エラー
                つぎに


プリファレンスとエディタの開放

 Java側にそのようなAPIは無いがあえてMindに実装した。以下を実行することでJava側の大域変数にnullを記入することでメモリ開放するためである。(後者はアプリ終了時にも自動実行される)

    (ソース例)
        プリファレンスのエディタを開放し 偽?
                ならば 重大エラー
                つぎに
        プリファレンスを開放し 偽?
                ならば 重大エラー
                つぎに



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

2013年8月24日 (土)

ピッカーダイアログによる日時の入力

Datepicker5gattaitrimed

 ダイアログを経由して日付や時刻を入力するクラスとして DatePicker, TimePicker というクラスがあり、これをMindから使えるようにした。Javaの側の使い方が単純なので、Mindへの実装も他の機能に比べて容易だった。
 Mindの文法は以下のようになる。

        <日時構造体>をつかい 日付入力ダイアログを開く → ・

        <日時構造体>をつかい <0/1>で 時刻入力ダイアログを開く → ・
                                       ↑
            1=24時間制 : 定数「十二時間制」「二十四時間制」が使える

 日時構造体はダイアログが開いたとき初期表示する日付または時刻を指示するのに使われる。
 Javaでは年、月、日などを個別値で渡しているのだが、Mindでは日時構造体を1つ渡すようにした。多くの場合、セットで使うと思われる「日時を得る」との整合を考えたものである。
 これに関連するが、このダイアログを開く前におこなうであろう現在の日時取得にJavaのCalendar機能は使う必要がない。もちろんCalendar版の処理単語「システム日時をカレンダーで得る」にて取得しても良いのだが、Mind標準の「日時を得る」のほうがJava呼び出しが無い分だけ軽く動作する。
 なお時刻の入力ではJava側仕様と同様に時間制をパラメータとして与える仕様としているが、これは積まずにたとえば「十二時間制で時刻入力ダイアログを開く」という別の処理単語を設置することでパラメータを無くしてしまうことも考えた。今後の検討課題とする。

 以下は利用例である。

        ボタンクリック処理は    本定義  (ボタン情報 → ・)
                        ~略~
                        _日時情報は  構造体 日時型
                ~略~
                _日時情報をつかい 日時を得て
                _日時情報をつかい 日付入力ダイアログを開く

 ダイアログが開くと処理単語「・・ダイアログを開く」は直ちにリターンするがこの時点ではまだ値は入力されていない。一方、ダイアログ内の「設定」ボタンを押すと、イベントが発生し、以下の処理単語がコールバックされるようになっている。このあたりはJava側の仕組みと同じである。

        日付設定時処理とは 本定義 (年、月、日 → ・)

        時刻設定時処理とは 本定義 (時、分 → ・)





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

2013年8月21日 (水)

アラームマネージャとCalendar

Alarmmgrtop_2

 Android APIにはアラームマネージャ(AlarmManager)というクラスがあり、指定した時刻や指定した時間間隔で他のアクティビティやサービスを起動することができる。
 アラームマネージャでは時刻を表すオブジェクトを扱うことになるが、それは別途、Calendarクラスの機能であるため、まず先にCalendarの機能を実装した。

 以下はJavaレベルでCalendarクラスの使い方の例である。

    1. Calendarクラスのインスタンスを生成
       →例: Calendar cal = Calendar.getInstance();

    2. カレンダー要素を取り出すメソッドを使って値を得る
       →例: int year = cal.get(Calendar.YEAR);

  Mindから利用する場合、1と2は一体処理するのが良い。1で得られたJavaのインスタンスをMindに返しても使いように困るからである。さらに、2において年だけ、月だけ・・をMind→Javaの呼び出し毎に返す仕様ではやはりCalendarインスタンスの維持の問題が生じる。

  注:レイアウト/ビュー/メディアプレイヤーなどではJavaのインスタンスを
        Mindもかかわって維持管理している。これらは駆動が複雑なので一体処理
    することができないという理由からである。

 これを解決するのに、前の1と2を一体処理しかつJava側で年月日時分秒をまとめて得てからC(Mind)側に返す方法があり、Mind→Javaの呼び出し回数も1度で済む。しかし「Java側で年月日時分秒をまとめたオブジェクト」とC(およびMind)での構造体との間でフォーマット変換が必要で、これがオーバーヘッドになるのが気になった。
 今回はもう1つの方法として、年月日時分秒をパックして整数値(Javaで言うlong)を1つ返し、Mindのライブラリ内で分解して日時構造体に格納することにした。整数はJava←→Cでダイレクト引渡しのためフォーマット変換が不要になる。それに比べたらJavaでのパックとMindでのアンパックの処理時間などわずかであろう。

 余談になるが、Javaも含め関数型言語はこのようなとき不便だ。MindやForthといったスタック型言語であれば単純に年月日時分秒を表す6個の整数を積んで返せば済むのだが、関数型言語では戻り値は1つなので、6個のデータを返すのに(オーバーヘッドの少ない)整数などプリミティブ要素を使って返すことができない。結果的に「戻したい値の数」が1個なのかあるいはそれより多いのかで文法が大きく変わってしまう。

 さて日時情報を得る手段は次のものが標準Mindに元から備わっていてAndroid環境でも動作する。

                      (Mind標準仕様のもの)

        <日時構造体>をつかい 日時を得る → ・

          (上の余談と矛盾するがMind仕様で日時情報は構造体に格納し返す。
                もちろん6個のデータをスタック積みで返す方法も考えられる)

 上を実行すると指定した構造体の各要素に年月日が格納される。
 Android API(Calendarクラス)を使う場合も最終目的は同じなので、Mindでの仕様は標準仕様と似させて以下のようにした。。

                      (Android仕様のもの)

        <日時構造体>をつかい システム日時をカレンダーで得る → ・


 上記両者共通に使う日時構造体(型紙)は標準Mindに元からあるものを少し訂正し以下になった。

        日時型は   型紙
                        年は   変数
                        月は   変数
                        日は   変数
                        曜日は  変数
                日付情報は 年と 月と 日と 曜日
                        時は   変数
                        分は   変数
                        秒は   変数          ←この次にミリ秒を含めるか要検討
                時刻情報は 時と 分と 秒
          全体は 日付情報と 時刻情報。

 以上のように、Android API(Calendarクラス)を使った日時の参照と、LinuxのAPIによる日時の参照は機能として類似のものとなる。細かなことを言えば、前者はミリ秒の情報が得られるなど違いは有るのだが、おおむね似た機能と言える。
 今回の実装でこの二つの似た機能をどのように扱うか考えたが結局、両手段をともに設置することにした。当然だが Linux API版であってもAndroid API版であっても同じ値が得られる。

 日時を年月日時分秒の要素で得る方法ではなく、単一値として得る手段もある。こちらも標準Mindで(つまり Linux API)元から有り、以下のようなものでもちろんAndroid環境でも動作する。

        日時を値で得る → <秒>
        日時とミリ秒を値で得る → <秒>、<ミリ秒>      ←これは最近の拡張

 AndroidネイティブAPIを使う機能は以下の文法とした。

        システム日時を値で得る → <通算ミリ秒>

 上記両者は目的は同じようなものだが計算手段が異なるので、返されるミリ秒の値については互換性は無い。
 なお年月日時分秒を個別に取得するほうの「システム日時をカレンダーで得る」であるが、Android APIで本来得られるミリ秒値をMind側の都合で無視してしまった。秒未満の値については上記で代替できるのではないかと思っているのだが今後の開発で変更する可能性もある。

 Calendarクラスにはこの他、日時要素への加算・減算や全体として整合をとる機能などがある。たとえば「日」3だけ増加させたとき31日を超えてしまうことがあるが、次に新しい要素値を参照したとき自動的に正規化(ここでは「月」への繰上げ)してくれる。
 しかしこの機能もまた標準Mind(Linux API)にほぼ類似のものが有る。以下のものである。

        <日時構造体>を 日時を正規化 → <通算ミリ秒>

 上記により日時構造体に格納された要素が正しい値に補正されるので、手順は異なるもののAndroid APIとほぼ同じ機能と言える。これがあるため、Android APIが提供する要素の加算・減算(いずれも正規化を伴う)は今回はMind側に実装しなかった。当面様子を見ることにする。
 ちなみに、Android APIでは日時要素への加算・減算をおこなうメソッドが多数定義されているが、標準Mind(Linux API)では日時要素が構造体メンバーであり直接アクセス可能のため、加算・減算をおこなうための手段は必要無い。(正規化をおこなう処理単語のみが提供される)

        注:ここまでローカルタイムの話である。タイムゾーンは追って実装する必要がある。

 次にアラームマネージャについて。
 Mindレベルでは以下の処理単語を用意した。単語名はあちこちにあるAndroidの日本語解説で登場する呼称そのままなので、これが何なのかは理解しやすいと思う。

        <パッケージ名>、<クラス名>、
          <ユニークID>、<開始ミリ秒>
                電源ONからの経過時間でアラーム開始・スリープ解除 → 真偽

        <パッケージ名>、<クラス名>、
          <ユニークID>、<開始ミリ秒>
                時刻指定でアラーム開始 → 真偽

        <パッケージ名>、<クラス名>、
          <ユニークID>、<開始ミリ秒>
                時刻指定でアラーム開始・スリープ解除 → 真偽

        <パッケージ名>、<クラス名>、
          <ユニークID>、<開始ミリ秒>、<間隔ミリ秒>
                正確な一定間隔でアラーム開始 → 真偽

        <パッケージ名>、<クラス名>、
          <ユニークID>、<開始ミリ秒>、<間隔ミリ秒>
                一定間隔でアラーム開始 → 真偽

        <パッケージ名>、<クラス名>、
          <ユニークID>
                アラーム停止 → 真偽

  指定した時間になったらアクティビティやサービスを起動するのだが、これら外部プログラムの起動周りのことは記事「画面遷移(他のアクティビティの起動)」と同様の考えなのでそちらも参考にして欲しい。

 アラームマネージャの単語群のうちひとつの利用例は以下の通り。(一定間隔でインテントサービスを起動し、そちらで音を鳴らすというもの)

                _開始ミリ秒は 数値変数

        ~略~

        システム日時を値で得て
                 10000ミリを ミリ秒を加え _開始ミリ秒に 入れ

        私のパッケージ名と "MyIntentService"と 0をつみ (=UniqueID)
         _開始ミリ秒をつみ (=開始時刻)
          10000ミリをつみ (=間隔)
            一定間隔でアラーム開始し






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

2013年7月12日 (金)

インテントサービスで効果音を再生

 

PhotoPhoto_3
 サービスの一つである「インテントサービス」を実装した。
 インテントサービスは「サービス」という名前を冠しているため、サービスと同様に当初はWindowsのサービスのようなものだろうと想像していたのだが、実はそのようなものとはかなり異なると後になって分かった。
 要するにAndroidの「インテントサービス」は多少時間がかかる処理をバックグラウンドで実行することが目的らしい。そのため処理が終わると共にサービスは終了する(当初は「なぜサービスなのに勝手に終了するのか?」と疑問に思ったものだった)。つまり常駐することが目的なのではなく、時間のかかる処理をバックグラウンドで行うことが目的なのである。

 実は当面の開発テーマとして「薬の飲み忘れアプリ」を考えている(歳がバレるが‥)。アラームマネージャから定期的にプログラムを起動し、アラームを鳴らすべきタイミングの判定や音を鳴らす処理をインテントサービスにしようと思っている。そのため、インテントサービスの処理内容としては音を鳴らしてみることにした。

 まず元となるアクティビティを普通に作成する。アクティビティ側での起動処理は前回投稿の「画面遷移」での子のアクティビティの起動と良く似て、Mindで以下のように記述する。(サービスへのデータ渡しはまだ実装しておらず単純起動とした)

 <パッケージ名>と <クラス名>で インテントサービスを起動

 ちなみに子アクティビティの起動の時は以下であった。

 <パッケージ名>と <クラス名>で インテントを実行

後者では「・・を実行」だったものが前者では「・・を起動」となっており、呼び出し元は子の終わりを待たないことが想像できるようにした。

 処理単語「インテントサービスを起動」が実行されると Mind→C→Java へと制御が移行し最終的に以下のJavaコードが動作する。(これまた子アクティビティの起動と似たような記述となっている)

   intent = new Intent();
   intent.setClassName( pkgname, classname );
   this.startService(intent);

 1つのパッケージに主(アクティビティ)・副(インテントサービス)の二つを含めることになるのも画面遷移の時と類似であり、C記述の共有ライライブラリ(DLL)のAndroid環境下での構成は以下のようになる。

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

  assets
  |-- bin
  |   |-- euc-uni-encode.bin          ←Mindが使うUnicode変換テーブル
  |   `-- uni-euc-decode.bin          ←Mindが使うUnicode変換テーブル
  `-- mco
      |-- MyIntentService.mco         ←インテントサービスのMindオブジェクトコード
      `-- TestSoundService2.mco       ←メインアクティビティのMindオブジェクトコード

  bin
  |-- classes
  |   `-- jp
          `-- co
              `-- scriptslab
                  `-- TestSoundService2       ←パッケージ名
                      |-- MyIntentService.class   ←インテントサービスのJavaクラス
                      `-- TestSoundService2.class ←メインアクティビティのJavaクラス

  libs
  |-- armeabi
      |-- libdllker_MyIntentService.so    ←C記述DLL (Mindランタイムとディスパッチャ)
      `-- libdllker_TestSoundService2.so  ←C記述DLL (Mindランタイムとディスパッチャ)

  res
  |-- raw
  |   `-- se_maoudamashii_onepoint23.ogg    ←効果音を収めたファイル
                 ↑
                 著作:音楽素材/魔王魂 http://maoudamashii.jokersounds.com/

 JavaからC→Mindへと処理を移行する仕組み自体は前投稿の「画面遷移」と同じなのだが、作成したインテントサービスを実行してみたところ問題が発生した。2度目のサービス起動で落ちてしまうのである。
 原因は Java→C に渡される環境情報 env, thiz の2つの扱いだった。たとえば一番最初にCが呼び出される箇所は以下のようになっていた。

                 Mindカーネルの "dllstartup.c" (まずかったときの記述)

  static JNIEnv*      env_save;            ←単一の大域変数への退避はまずかった
  static jobject     thiz_save;
 
  jstring
  dllStartup_common( JNIEnv* env, jobject thiz, jstring pkgnamej,
                             jstring classnamej, jbyteArray mcobufferj )
  {
        ~略~
    env_save = env;
    thiz_save = thiz;
        ~略~

  JavaからCが呼び出されたとき、上記のように第1/第2引数で環境情報 env, thiz の2つが渡される。このあと延々とMindの処理に入るため、これらを一旦大域変数に格納し、Mind→Java の逆呼び出し時には、この大域変数を参照していたのだが、ボタン押下やサービス要求などのイベント発生時にこれを共用してはいけなかったのだ。
 JNIの本「JNI Java native Interface プログラミング(ロブ・ゴードン著、林秀幸訳、ピアゾン・エデュケーション)」によれば
     「JNIEnvポインタをstatic変数に入れてグローバルとして使用
      したくなるかもしれないが、そんな誘惑に負けてはいけない」
と書かれているのだが、まさにこれをやってしまっていた。
 対策として、この情報はJavaからC側に制御が移るごとにスタックに入れることで解決した(Cランタイムで隠蔽するのでMindからは見えない)。

 次にインテントサービス内でアラームを鳴らす方法だが、クラス SoundPool または MediaPlayer を使う方法がよく取り上げられている。効果音のような短いものは前者が使われるようだが、試しに MediaPlayer を使ってみた。
 MediaPlayerはそのコンストラクタで音源を指定する方法がよく書かれているが、Mindでの記述統一(たとえばレイアウトやビューと同じような割り当てを使いたかった)のため、コンストラクタでは音源は指定せず、MediaPlayer#setDataSource のメソッドで明に指定するようにしたが、この方法では音を出すのに都合3段階が必要となる。
 Mindでの記述は以下のようになる。

  インテントサービス処理とは 本定義 (・ → ・)
                  _音源パスは 文字列定数 「raw/se_maoudamashii_onepoint23」
                  _プレイヤー1は プレイヤー
          メディアプレイヤーを生成し 偽?
                  ならば <~略~>
                          終り
                  つぎに
          _プレイヤー1に 入れ
          _音源パスと _プレイヤー1で メディアプレイヤーにロードし 偽?
                  ならば <~略~>
                          終り
                  つぎに
          100と 0を _プレイヤー1で メディアプレイヤーを演奏し 偽?
                  ならば <~略~>
                          終り
                  つぎに。

 MediaPlayerにローカルファイル(たとえば raw/ 配下のファイル)で音源を指定する時のURIは

        android.resource://raw/se_maoudamashii_onepoint23

のようになるのだが、記述が煩雑であるためMindのライブラリ側で補完を行うことにより、アプリケーション側では短形式として

        raw/se_maoudamashii_onepoint23

で済むようにした。

      ( 注:試した音源は 著作:音楽素材/魔王魂 http://maoudamashii.jokersounds.com/ )





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

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月16日 (木)

レイアウト系

レイアウトやビューなど表示のごく基本のところをまとめておく。

Javaのオブジェクト指向コーディングをMindのコーディングにどうやって対応させるかの問題にいきなり直面した(^^;。
まずレイアウト系のオブジェクトの扱いだが、たとえばJavaでは、

    layout1 = new LinearLayout( this );

などと書いて「layout1」という名前のリニアレイアウトのインスタンスを作るが、このオブジェクト(Java内部ではたぶんポインタ)をMindへ渡されても何も使い道が無い。さらには、Javaレベルの「layout1」なる名前もまたMind側からは見えない/見たくないものである。そもそもMindでプログラムを書くのだから名前はすべてMind側で(たとえば適当な日本語名で)決めるべきものである。
結果として以下のようにした。

  ------------------------------------------------------------------------
  (Mind)
          _レイアウトは   レイアウト
    ~略~
    リニアレイアウトを生成し _レイアウトに 入れ

                                          (注:変数名の頭にアンダースコアを付ける
                                               のはMindのルールではなく単に筆者の
                                               好みである。局所変数と大域変数とで
                                               区別がつきやすいようにしている)
                          ↓
  (Java)
          private  LinearLayout[] linearLayouts = new LinearLayout[20];
    ~略~
    linearLayouts[seqno] = new LinearLayout( this );
  ------------------------------------------------------------------------

 Mindの「リニアレイアウトを生成」でリニアレイアウトのインスタンスを作ることにする。実際には単純に1から始まる整数(通し番号)を生成するだけであり、これで個々のリニアレイアウトを識別する。
 JavaではMindから渡された通し番号(上記で"seqno")を添え字としてリニアレイアウトのインスタンスを配列として管理する。先に書いたようにJava側で個別インスタンスに名前を付けても使い道が無いが配列要素は無名なのでちょうど良い。

 ビューについても同様で以下のようにした。
  ------------------------------------------------------------------------
  (Mind)
         _案内文は       ビュー
    ~略~
    テキストビューを生成し _案内文に 入れ

                          ↓
  (Java)
          private TextView[] textViews = new TextView[20];
    ~略~
    textViews[seqno] = new TextView( this );
  ------------------------------------------------------------------------

 Mind側ではレイアウトの時と同様、「テキストビューを生成」によって通し番号を生成しその番号で管理する。またテキストビューのほかにボタンやチェックボックスなど他のビュータイプも一括して操作できるよう、それらを表すタイプ情報も含めて管理している。(実は先のレイアウトもMindでは複数のレイアウトタイプを番号で識別している)

 レイアウトとテキストビューをそれぞれ1つ扱うMindのプログラムは次のようになる。
    ----------------------------------------------------------------------------
    メインとは
                    _レイアウトは  レイアウト
                    _案内文は      ビュー
      (レイアウト)
            リニアレイアウトを生成し  _レイアウトに  入れ
            垂直を                    _レイアウトに  子の追加方向を設定し
            _レイアウトを  レイアウトをアクティビティに追加し
      (ビュー)
            テキストビューを生成し  _案内文に  入れ
            18を                  _案内文に  文字のサイズを設定し
            「こんにちは」を        _案内文に  文字を設定し
            _案内文を  _レイアウトに  ビューを追加し
            。
    ----------------------------------------------------------------------------

  上のソースコード末尾の「ビューを追加」について補足する。
 引数は <元ビュー> と <先レイアウト> だが、Mind側での型管理が無いとすると、たとえば
    ----------------------------------------------------------------------------------
    _テキスト1を  _リニアレイアウト1に    テキストビューをリニアレイアウトに追加し
    _テキスト1を  _フレームレイアウト1に  テキストビューをフレームレイアウトに追加し
    ・・・
    _ボタン1を    _リニアレイアウト1に    ボタンビューをリニアレイアウトに追加し
  _ボタン1を  _フレームレイアウト1に  ボタンビューをフレームレイアウトに追加し
    ・・・
    ----------------------------------------------------------------------------------

のように、全組み合わせの処理単語(関数)を設置する必要が出て、これはいかにも苦しい。
Mind側に最低限の型管理を導入することできれいなコードになるようにした(1つの「ビューを追加」で済む)



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

2013年5月 7日 (火)

AndroidでMindをどうやって動かすか(2)

 MindではMコードがオブジェクトコードの実体であるため、当然だがMコードファイル(.mco)の扱いが重要となる。
 しかし Android OS から見た狭義のプログラムは JavaのクラスファイルとC(JNI)の共有ライブラリの2つであり、当然だがMコードは関与されない。そのためファイルの設置や読み出しは自前で行う必要がある。

 まず設置については、パッケージ(.apk)の中にMコードファイルを含めておき、アプリ起動時にそれが自動展開されるのが良いのだが、調べたところこの目的に合致したのが assets/ ディレクトリだった。
 assets/ ディレクトリは元々、画像や音楽ファイルを置くような用途を想定されているらしく、Read Only なアクセスとなるがMコードへは書き込みは無いためその点も都合が良い。また、Mindで使うUnicodeの変換テーブルなどのデータファイルもここに置くことにした。
 読み出しについては、MコードのロードはC管轄なのでCで読めると良かったのだがどうも困難なようで(Cから普通のファイルのようにはアクセスできない)、仕方なくJavaで読み出したあと、Cの初期化関数呼び出し時に渡すようにした。

 開発時のディレクトリ構成(プロジェクトフォルダ内)は以下のようになる。

MindSample/
|
|-- AndroidManifest.xml
|
|-- assets                              ←プログラムからロードするファイルはここ
|   |-- bin
|   |   |-- euc-uni-encode.bin              ←Unicodeテーブル
|   |   `-- uni-euc-decode.bin
|   `-- mco
|       `-- main.mco                        ←Mコードファイル(Mindプログラム本体)
|
|-- bin
|   |-- MindSample-debug.apk
|   ~略~
|   |-- classes
|   |   `-- jp
|           `-- co
|               `-- scriptslab
|                   `-- MindSample
|                       |-- MindSampleActivity.class
|
|-- jni                                 ←Mindのカーネルはここへ
|   |-- 0ker.c
|   |-- 1ker.c
|   |-- 2ker0.c
|   |-- 2ker1.c
|   ~略~
|   |-- sources
|   `-- unixdep.c
|
|-- libs                                ←カーネルの生成場所
|   `-- armeabi
|       `-- libdllker.so
|
|-- mind4andr                           ←Mind記述のプログラム
|   |-- andrlib                             ←ランタイムライブラリ(2)
|   |   |-- andr_basicoutput.src
|   |   |-- andr_init.src
|   |   ~略~
|   |   `-- obj
|   |-- app                                 ←ユーザ記述のMindプログラム
|   |   |-- sample.src
|   |   `-- obj
|   |-- file                                ←ランタイムライブラリ(1)
|   |   |-- asm_osdep.src
|   |   |-- asmequ.src
|   |   ~略~
|   |   `-- obj
|   `-- lib
|       |--
|       `--
|
|-- obj
|   `-- local
|       `-- armeabi
|           |-- libdllker.so
|           `-- objs
|               `-- dllker
|                   |-- dllker.o
|                   |-- dllker.o.d
|                   |-- osfunc.o
|                   `-- osfunc.o.d
|
|-- res
|   |-- layout                          ←res/layout/main.xml は置かない
|   `-- values
|       `-- strings.xml
|
`-- src
    `-- jp
        `-- co
            `-- scriptslab
                `-- MindSample
                    `-- MindSampleActivity.java


 assets/ 以下では、サブディレクトリ mco/ にMコードを格納し、bin/ にはUnicode変換用のテーブルを置いた。単にここに置いておけばビルド時に .apk に含めてもらえる。

 jni/ フォルダにはMind用のC記述プログラム(以下、単にカーネルと言うことにする)を格納している。カーネルのオブジェクトファイル群は obj/ 配下に格納され、そのうちメインのオブジェクトである libdllker.so がJava側ビルド時に libs/ に複写されこれが最終的な .apk に含められるようだ。
 libs/armeabi/ という名称から分かるように、ARM系CPU専用となっているため他のCPUでは走行できない点に注意が必要だ。他のCPUも別のカーネルを用意すれば良いのだと思うが今のところはARM向けということで進むことにする。

 mind4andr/ フォルダへはMind記述のプログラムを格納する。
 ライブラリの順位は低いほうから file → andrlib → app である。app/内にユーザ記述のMindソースを置くことにしたがこのあたりはこの先変更するかも知れない。ソケット(通信)・グラフィック・その他のためにさらに別ディレクトリが必要になると思うが追々やっていくことにする。

 res/ フォルダでは、Androidのプログラミングで必ず登場する res/layout/main.xml(レイアウトを記述するファイル)はMindでの開発では使わない。レイアウトはすべてプログラム記述とするためである。これに関係するが、書籍やWebサイトでの多くのサンプルプログラムはレイアウトを main.xml に記述しており、プログラム記述のものを探すのに苦労した。



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

2013年5月 3日 (金)

AndroidでMindをどうやって動かすか(1)

 Mindは(Windows,Linuxとも)だいぶ以前から、コンパイラが「Mコード」と呼ぶ中間コードを生成し、(Mindで言う)「カーネル」の中にある「ディスパッチャ」と呼ぶごく小さなルーチンがそれを逐次実行する方式になっている。
 たとえばMindのオブジェクトコードは拡張子が .mco というバイナリファイルになっていおり、OSから見ると単なるデータファイルのように見えるがこれがプログラムの本体である。

 検討初期からAndroidでも基本的にこの機構のまま行けるだろうと思ったが実際その通りだった。
 ちなみに現段階ではコンパイラはAndroid向けを別途作ったわけではなく、LinuxのMindコンパイラをそのまま使っている。MコードがOS非依存になっているおかげである。(将来的には内部の文字コードの関係から別開発の必要が生じるかも知れないが)

 Androidではプログラム記述がJava必須となっている。しかしJNIという機構があり部分的に C, C++ で記述できることになっている。一方、現行のMindは「カーネル」(ディスパッチャやプリミティブな機能の定義部分)が C記述であるため、C を媒介にすることで間接的にMindを動かすことができるだろうと考え、やってみたところこれもその通りだった。
 Java, C, Mind の処理分担を図で示すと以下のようになる。

001_3_cut_3

 図では三つが同じ分量のように見えるが、当然ながらユーザが記述したプログラムはMindのコードとなるため、プログラム規模が一定以上あればMindのコードが多くを占めることになる。
 これは重要な点だが、MindのソースコードがCやJavaに変換されるわけではない。CやJavaのプログラムも併用するのは確かだが、それらはMindの実行時、最下位レベルではCPUやOSを駆動する必要からそこを経由するに過ぎない。

- 文字コードについて -

 今のところ内部処理はLinux版のMindを(コンパイラ、ランタイムライブラリとも)そのままEUCコードで走らせており、Android側との境界でUTF-8に変換している。
 本来であればMindの内部処理もUnicodeにしておけば良いのだが、Mindの利点である高度な文字列操作を行うには同じUnicodeでも固定幅コード(Mindの実績ではUCS2)を使う必要があるため、Androidとの境界でコード変換が必要(たとえば UTF-8 ←→ UCS2)という点では変わりない。



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

«開発環境