2012年5月25日金曜日

[Android] 実験人形

(画像は、小池一夫原作、叶精作作画による『実験人形ダミー・オスカー 1 (グループゼロ) 』より引用)

記念すべき100エントリ目なので、口絵で出オチw

さて、『Glaeja』がver.2.7.0でLocaleTaskerのプラグインに対応したわけなんですが、この実装のときに日本語の情報がなくってちょっと往生しました。

Taskerのサイトには情報があるんですが英語ですし、「詳細はLocaleのサイト読め」となっています。

で、Localeのサイトのほうも英語なのは当然として、置いてあるサンプルコードがエラー処理とか、HoneyComb以降のUI対応とか丁寧に書かれすぎてて、逆にわかりにくいものになっちゃってます。

というわけで、Locale/TaskerのプラグインSetting/Actionに対応するための実装に関する簡単な解説を日本語で書いておきます。

これを読んで、みんなももっと Locale/Taskerに対応しちゃえばイイとおもいます。



Locale/Taskerには、2種類のプラグイン(LocaleではconditionsettingTaskerではconditionaction)がありますが、以下に記すのはTaskerの"action"プラグインの実装についてです。

はじめに.

Locale/Taskerでは、ある条件になった場合に実行するTaskアクションとして、「他アプリに情報を渡して処理させる」という機能があり、これをLocaleでは“setting”プラグイン、Taskerでは“action”プラグインと呼んでいます。

プラグインとは名乗っていますが、その実装はただのアプリで、Locale/TaskerとはIntentを介して、設定や情報の授受をおこなっています。処理の流れを下図に示します。


要するに、
  • 設定を行なうための"Activity"
  • Taskアクションを受信する"BroascastReceiver"
  • これらを識別させるための"IntentFilter"(の記載された"AndroidManifest.xml")
の3つを含むアプリを作成すればイイわけです。

設定用Activity.

設定用の"Activity"で決められているのは、起動呼び出しの際のIntentFilterだけです。これは、Locale/Taskerから

"com.twofortyfouram.locale.intent.action.EDIT_SETTING"

というアクションで呼び出されることに決められていますので、"AndroidManifest.xml"に、

    
        
    

こんな感じでIntentFilterを記述しておく必要があります。

何をどのように設定するかについては決められていません、どうやろうと(ある程度は)自由です。
決められているのは、設定されたものをLocale/Taskerへ返す方法(Intentへの詰め込み方)と、その制約です。

これは、
  • 設定終了時にsetResult()で返すIntentが、
  • Taskアクション実行時に(ほぼそのままの形で)Broadcastされる
ためです。つまり「どんな項目を詰め込もうが解釈するのは自分のReceiverなので問題ない」というわけです。

Intentへの詰め込み方について、参考のため『Glaeja』のコードの該当部分を以下に記します。
public static final String EXTRA_STRING_BLURB = "com.twofortyfouram.locale.intent.extra.BLURB";
 public static final String EXTRA_BUNDLE = "com.twofortyfouram.locale.intent.extra.BUNDLE";
 public static final String BUNDLE_EXTRA_STRING_VARNAME = "com.gmail.kanitawa.glaeja.locale.extras.VAR_NAME";
 public static final String BUNDLE_EXTRA_STRING_VARCONTENT = "com.gmail.kanitawa.glaeja.locale.extras.VAR_TEXT";
 public static final String BUNDLE_EXTRA_VARIABLE_REPLACE_KEYS = "net.dinglisch.android.tasker.extras.VARIABLE_REPLACE_KEYS";

 @Override
 public void finish() {
  if (mIsCancelled) { setResult(RESULT_CANCELED);
  } else {
   final String var_name = ((EditText) findViewById(R.id.locale_VarName)).getText().toString();
   final String var_content = ((EditText) findViewById(R.id.locale_VarContent)).getText().toString();
   final String blurb = var_name + " = " + var_content;

   if (0 == var_name.length()) { setResult(RESULT_CANCELED);
   } else {
    final Intent resultIntent = new Intent();
    final Bundle resultBundle = new Bundle();

    resultBundle.putString(BUNDLE_EXTRA_STRING_VARNAME, var_name);
    resultBundle.putString(BUNDLE_EXTRA_STRING_VARCONTENT, var_content);
    resultBundle.putString(BUNDLE_EXTRA_VARIABLE_REPLACE_KEYS, BUNDLE_EXTRA_STRING_VARCONTENT);

    resultIntent.putExtra(EXTRA_BUNDLE, resultBundle);

    if (blurb.length() > 50) { resultIntent.putExtra(EXTRA_STRING_BLURB, blurb.substring(0, 50));
    } else { resultIntent.putExtra(EXTRA_STRING_BLURB, blurb); }

    setResult(RESULT_OK, resultIntent);
   }
  }
  super.finish();
 }

要するに、
  1. 設定した項目をBundleに詰め込んで、
  2. そのBundleを"com.twofortyfouram.locale.intent.extra.BUNDLE"というkeyIntentに詰め込む
わけです。
ここでは、
  • 『Glaeja』で取り扱う変数名として"com.gmail.kanitawa.glaeja.locale.extras.VAR_NAME"
  • 上記変数名に代入する文字列として"com.gmail.kanitawa.glaeja.locale.extras.VAR_TEXT"
の2つと、22行目で、
  • "net.dinglisch.android.tasker.extras.VARIABLE_REPLACE_KEYS"
というkeyで、Bundleに入れた代入する文字列のkey名("com.gmail.kanitawa.glaeja.locale.extras.VAR_TEXT")の3つを詰め込んでいます。

設定した項目をBundleに詰め込むときのkeyは任意です。後でReceiverで受信したときに自分で取り出すわけですから。ただし、
  • ParcelableではなくSerializableなオブジェクトを使え
  • Android標準のオブジェクトを使え
  • トータルで100KB以下にしろ
という制約があります。さらにTaskerでは、
  • int, long, float, double, boolean, String, String[] しか使えない
という制約が加わります。…まぁこんだけ使えりゃ問題ありませんね。

最後の"net.dinglisch.android.tasker.extras.VARIABLE_REPLACE_KEYS"は、Taskerで拡張されたもので、
このkeyを持つ文字列に、Bundleに入れた別のkey名を加えておくと、そのkey名の文字列に"%"で始まるTaskerVariableが含まれていた場合、変数値に置換してから、Taskアクション実行時のIntentとしてBroadcastしてくれます。
複数のkeyを置換させたい場合には、それらを空白文字で区切っておきます。

この場合ですと、エディットテキスト"R.id.locale_VarContent"に"%"で始まるTaskerVariable名を記述すると、Taskアクション実行時にそれが値に書き換えられてくれる、というわけです。

2627行目で、返すIntentExtraとして"com.twofortyfouram.locale.intent.extra.BLURB"というkeyで文字列を加えています。

これは、Locale/Taskerの設定画面に表示するための説明文ですので、適切な文字列を作って入れておきましょう(必須ではないっぽいので無くても構わないと思いますが…)。

受信用BroadcastReceiver.

Taskアクションで発行されるIntentは、

"com.twofortyfouram.locale.intent.action.FIRE_SETTING"

というアクションで発行されますので、これを受信する"BroascastReceiver"は"AndroidManifest.xml"に、

    
        
    

こんな感じで登録してください。

ここで大事なのは、Locale/TaskerのアクションPlugin選択時に候補として現れるためには、Receiverが"AndroidManifest.xml"にIntentFilter付きで登録されている必要がある、ということです。つまり、Service等で無名関数を使ってダイナミックにregisterReceiver()してはいけません。

"BroascastReceiver"自体の実装は簡単です。参考のため『Glaeja』のコードの該当部分を以下に記します。
public class FireReceiver extends BroadcastReceiver {

 @Override
 public void onReceive(Context context, Intent intent) {
  String action = intent.getAction();
  if (action.equalsIgnoreCase(EditActivity.ACTION_FIRE_SETTING)) {
   final Bundle bundle = intent.getBundleExtra(EditActivity.EXTRA_BUNDLE);

   final String var_name = bundle.getString(EditActivity.BUNDLE_EXTRA_STRING_VARNAME);
   final String var_content = bundle.getString(EditActivity.BUNDLE_EXTRA_STRING_VARCONTENT);

   // 抽出したvar_nameとvar_contentをGlaejaに登録して描画更新させる処理

   }
  }
 }
}

6行目でお約束のアクションチェックをした後、7行目でIntentからBundleを取り出し、9・10行目で詰め込んである変数名と内容文字列を抽出しています。
実際の『Glaeja』では、この後に抽出した変数名とその内容文字列を保存し、全ウィジェットの描画更新をさせています。



さて、これで簡単な解説は終わりです。

Localeの開発者ページからダウンロードできるサンプルコードでは、エラーチェックやら何やら色々な処理が加えられています。
そのうちのいくつかを簡単に解説すると、
  • BundleScrubber:誤って、SerializeできないオブジェクトがBundleに含まれていた場合に、データを変な形で取り出さないようにキチンと削除するためのクラス
  • BreadCrumber設定Activityのタイトルを、いわゆる「パン屑リスト」タイプにするための文字列生成クラス
ですかね。

「あぁ、そういうものなのか」と頭においた上でサンプルコードを読めば、ちょっとは読み易いかもしれません。


いずれにせよ、プラグイン開発するんなら、やはり一度は原文を読んでおくべきだと思います。

24 件のコメント:

  1. こんにちは、Glaejaの時計表示で少し気になったことがあるのですが、12時間表示の際、0時表記と12時表記と選べるのはいいのですが午前は0時で午後は12時という表記は出来ないのでしょうか?
    私の勉強不足なだけで既に出来るのでしたら申し訳ないのですが、出来ないのであれば選択肢の一つとして追加していただく事は可能しょうか?
    よろしくお願いいたします。

    返信削除
  2. > 午前は0時で午後は12時という表記

    ???

    午前は「0~11時表記」、午後は「1~12時表記」に自動的に表記変更して欲しい、ということですか?

    もしくは逆に午前を「1~12時表記」、午後を「0~11時表記」ですか?

    実際の表示がどうなるかは、

    前者だと「夜中12時半を『0時30分』」「昼12時半を『12時30分』」、

    後者だと「夜中12時半を『12時30分』」「昼12時半を『0時30分』」

    と表示されることになります。

    …そんな不統一な表記法を実装することは決してありません。

    おやりになりたいのであれば、
    $a$か$H$で夜中か昼かを判断して$K$と$h$を切り替えて表示する@...@を組めばよろしいかと思います。

    返信削除
  3. 私がやりたかったのは前者の方ですね。

    既にやりようによっては出来たのですね・・・出来そうだとは思いつつ質問してしまいました・・・。
    最近弄りだしたばかりであまり複雑な設定はできないので、@...@の使い方がわからないのです・・・。
    一度記述を見れば要領を得ることができそうなのですが、
    「$a$か$H$で夜中か昼かを判断して$K$と$h$を切り替える」という動作をさせたい場合、どのような記述になるのかご教示いただけないでしょうか?
    よろしくお願いします。

    返信削除
  4. > 一度記述を見れば要領を得ることができそう

    それは素晴らしい。

    http://bananawani-mc.blogspot.jp/2011/10/android.html

    ここに@@の記述例がたくさんありますので、
    それを参考にしてみてください。

    他にも、当ブログの[Glaeja]タグのついたエントリや、『Glaeja』のコメント欄にもたくさん記述例があります。

    返信削除
  5. この記事と関係ないことですみませんが、glaejaを使っているんですが、アプリ一覧などを見ているとたまに画面が黒くなり、ホーム画面にもどってしまうんです。

    機種はXPERIA acroです。

    どうすれば直りますか?

    返信削除
    返信
    1. それに関してですが、原因はほぼ判明しています。
      Glaejaの定時描画更新を司るサービスが何らかの原因でKillされ、
      それが再起動するときに天気情報をネットワークから再取得する
      のですが、これが重くてまたサービスがKill→再起動→再取得→…
      とある種の暴走を起こしています。

      この暴走がCPUを大量に消費し、そのためホームアプリが
      巻き添えを食って再起動しているわけです。

      現在のバージョン(v2.8.9)で、これが起こってしまった場合に
      止める方法は、残念ながらありません。
      いや、ないわけではないのですが…

      アンインストール→再インストール

      しか方法がないんです。


      現在、これが起こらないようにするための
      修正版の検討作業に入っておりますが、
      すこし時間がかかるかと思います。

      削除
  6. Glaeja、いつも便利に使わせていただいています。
    typoの報告を一つ。
    グラデーションレイヤーの設定で「終了位置(垂直)」
    を押したとき、設定のダイアログボックス内では(垂直)でなく(水平)と出ます。

    返信削除
    返信
    1. あ、ホントだ。
      ご報告ありがとうございます。
      次バージョンで修正しておきます。

      削除
  7. こんにちは、glaejaについての質問です。
    リッチテキストで文字列郡一括ではなく文字列ごとに傾きを変えたいのですが、エスケープキャラクタ等で表現する方法はありますでしょうか。

    うまく文章に出来ないのですが、デジタル時計の本来であればコロンで表示される部分を、am又はpmを90°傾け縦表記のようにして表示させたいのです。

    現在はリッチテキストでコロンの部分に空白を挟み、テキストでその空白部分を埋めるように上記を表示させているのですが、使っているフォントが数字ごとに大きさの異なる物なので時間によってam(pm)と被ってしまい見栄えが良くありません。
    現時点でどのようにしても無理な場合、リッチテキスト内で個別に傾きも指定できるような機能の実装も検討していただければと思い、合わせてコメントさせていただきます。

    意味が分からなかったらすみません。

    返信削除
    返信
    1. 3回ほど読んで意味がわかりました。

      「リッチテキスト」は、異なるフォント・サイズ・色で描かれた文字列群を
      同一のベースラインに表示させるレイヤーです。

      この「同一のベースライン」てのがキモで、文字列群ごとに傾きを変えるってのは、
      文字列群ごとに異なるベースラインを持つことになります。

      こうした場合、ベースラインが異なるわけですから、
      次の文字列群の表示開始位置をどこにしてイイのかわからなくなるため、
      「リッチテキスト」で文字列群ごとに傾きを変えられるようにすることは
      決してありません。

      「自由度が高い」ということは「指定しないといけないものが多い」ということであり、
      そのことはひいては「指定できない」ということにもなりうるのです。


      やりたいことは、「デジタル時計の時・分を区切るコロンの代わりに縦置きにしたAM/PMを置く」ことで、
      これをプロポーショナルフォントで出来ないか、ってことですよね?

      そうですねぇ、、、、、

      ・「時」と「分」を別々のテキストレイヤーで作り、
      ・「時」は右揃え、「分」は左揃えにする
      ・こうすると「時」「分」の間の位置は固定されるので、「AM/PM」が隠されない

      もしくは、
      ・00~23時までの各時ごとに、「時」の終わる座標を調べて、
      ・縦置きの「AM/PM」の表示位置を「変形と移動」レイヤーで、
      ・各時ごとに上で調べた終わり座標に移動するよう@@を書く

      くらいでしょうかね。

      削除
  8. さっそくのお返事ありがとうございます。

    根本的にリッチテキストの解釈が間違っていたようでお恥ずかしい限りです、すみません。
    わかりやすく説明していただけたので理解できました。

    とりあえずはコロン部分を縦書きにしてリッチテキストに組み込み対処しようと思います。
    最終的にはkanitawaさんが提示してくださった、

    ・00~23時までの各時ごとに~

    という方法で完成としたいのですが、どうしてもわからなかった場合には再度ご教授くだされば嬉しいです。

    それともう一つよろしいですか?
    ホームに二つglaejaを設置しているのですが、編集しようと目的のウィジェットをタップすると、もう一方のウィジェット編集画面になってしまいます。
    戻るをタップしても「glaeja(ID:○○)のウィジェット設定」と編集画面をループし続け、最終的にはglaejaを強制終了しなければならないのですが、対処法はありますか?

    返信削除
    返信
    1. > ループし続け

      ん~~~、、、、、
      レイヤー編集画面から戻るときとかに【HOME】ボタンを押してませんか?
      Glaejaのスキン編集では、出来るだけ【戻る】ボタンでホームまで戻ってください。

      削除
    2. 確かにいつもHOMEボタンで終了しています。
      原因はそれだったのですね。
      次回からは戻るで終了して様子を見てみます。

      重ね重ねすみません。
      とても早いご回答をありがとうございました!

      削除
  9. ストアのレビューにて破線の要望を書き込んだ者です。

    レビューで質問してしまったことを謝罪させてくだい。
    Twitterを拝見させてもらっているのですが、kanitawaさんの呟きを見て顔から火が出ました…。
    ましてや前回も同様なことをしてしまったのに本当に申し訳ありません。

    その上、要望まで聞いていただいてとても頭が上がりません。ありがとうございました。


    Glaejaの一周年おめでとうございます。

    返信削除
    返信
    1. Twitterのは、黙って作業してると寂しくなってくるので
      寂しさを紛らわすためのボケですw お気になさらず頭をお上げくださいw

      直メールだろうがTwitterだろうがマケレビューだろうが、
      目に入った不具合は(それが不具合である限り)何某かの対応はします。

      ただ、マケレビューは(現状では)会話が出来ないので、
      原因究明が難しいことが多いため、対応順がかなり後回しになります。

      それを踏まえての「レビューじゃなくメールかBlogコメで」なのです。
      「レビューだと対応しないよ」ってんじゃないのでご安心ください。

      破線の件ですが、前から検討項目には入っていましたが、
      他人から明示されたのは初めてでしたので、今回実装しました。

      削除
  10. ボケでしたか…。
    呟きを見つけてからの一週間、Glaejaの編集が辛くて生きた心地がしませんでしたが、これで一安心です。

    もしも何か不具合を発見した場合、次回からこちらで報告させていただきます。

    破線の実装ありがとうございました。

    返信削除
  11. 失礼致します。

    過去のエントリーから"P4" Calendar & Weatherをダウンロードさせていただきました。
    zipファイルをSDカードに入れ、解凍するところまではよいのですが、解凍が終わりません。
    とても時間のかかるものなんでしょうか?よろしくお願いいたします。

    返信削除
    返信
    1. いや、どんなに遅い端末でも解凍なんざ数秒で終わるでしょう。
      多分、ZIPファイルが壊れてますね、それ。

      PCをお持ちなら、PCでDL後に端末へ転送してやるか、
      お持ちでなければ、DLに使うブラウザを変えてみるかして
      試してください。

      削除
  12. 返信ありがとうございます。

    ううむ。opera miniや標準ブラウザ、PCからのD&Dも試しましたが結果は同じでした。
    私の携帯の方に問題があるのでしょうかね…
    ちなみに機種はXperia rayです。

    返信削除
  13. 連投失礼いたします。

    一度ウィジェットを表示させてから解凍させた結果無事解凍が一瞬で終わりました。
    どうやら見落としていたようです。失礼しました。

    返信削除
    返信
    1. > 一度ウィジェットを表示させてから

      気になったので調べてみました。

      GlaejaをPLAYからインストールした後に、
      「一度もホームにウィジェットを設置したことがない」状態だと
      ZIPの解凍が終わりませんね(裏でエラー吐いてた)。

      SDカード上に「com.gmail.kanitawa.glaeja/skins」等の
      フォルダがまだ作られてないため、ファイル解凍エラーが
      出ていたようです。

      不具合ですので、次バージョンで修正しておきます。
      ご報告ありがとうございました。

      削除
  14. いつも大変お疲れ様です。
    前回のヴァージョンアップから何故か天気情報が更新されなくなってしまいました。
    天気情報取得の設定で現在地をかえれば再取得しに行くのですが、その後は更新されなくなってしまいました。
    機種依存かもしれませんが、何かお気づきの点あればヒントお願いします。
    ちなみに機種はsc-06dです。

    返信削除
    返信
    1. うーん、、、?
      当方の環境では、特に問題なく更新されてますねぇ。

      天気情報の更新は、v2.9.2でごっそりと変わりましたので、
      v2.9.1からの上書き更新では何か起こったのかも知れませんねぇ。

      お手数ですが、現在使用しているウィジェットのスキンを全て保存した後、
      アンインストール→新規にインストールして、
      天気取得先の設定を行った後にウィジェットのスキンを戻してみてください。

      …これで絶対直る!という保証はできませんが……


      ちなみに、次回予定されている更新(多分8月末?)でも
      この天気情報更新に関する部分がごっそり変更される予定です。

      こういう部分を更新するとき、下方互換性(上書き更新での整合性)には
      かなり気をつけてはいるんですが、完璧とはまいりません。

      なので、自動更新設定を止めた上で、
      更新情報をじっくりと見て、「…なんかヤバそう」と思ったら、
      スキン保存の後に、APKバックアップ→アンインストール→新規インストールで挑んでください。
      それが一番安全だと思います。何かあっても戻せますしねw

      削除
  15. 早速の返信ありがとうございます。
    了解しました。早速試してみます^^

    返信削除