2013年1月27日日曜日

KyoroText で使用されている技術 その11




[課題] Terminalっぽいことを、してみたい。

私はxyzzyというEditorを愛用しています。xyzzyには、Editorからshellを操作する機能があります。
「KyoroTextにも、Shellモードが欲しいなぁ~」と考えていました。

ためしに実装してみたら、そこそこ動いたので簡単に紹介します。


[問題]
 Googleを多様して、以下のような問題を解決できました。
 -A Androidで、CLIアプリを動作させめ方法は?
 -B cd した後の、現在参照しているフォルダを利用したい?


[A] Androidで、CLIアプリを動作させる方法は?
 自分以外のAndroidアプリを起動する場合、通常は、Intentを使用します。
「cd」「ls」dateといった。/system/bin配下にあるネイティブアプリを動作させるには、
 どうすれば良いのでしょうか? Intentで実現できるのでしょうか?

 Runtimeクラス、ProcessBuilderクラスで実現できます。
 細かな使い方は、Google先生に聞くと良いでしょう。

 KyoroTextでは、以下のようなコードを書きました。
 https://github.com/kyorohiro/KyoroHelloAndroid/tree/master/KyoroCommon/src/info/kyorohiro/helloworld/util/shell
 CLIAppKicker

>
>   CLIAppKicker#start(String command)
>
startを呼ぶとCLIアプリを起動します。


 [B] cd した後の、現在参照しているフォルダを利用したい?

「Aの問題」が解決しました。なので、「ls」使いたい時は、「CLIAppKicker#start("ls")」
 とすれば、「ls」が呼べます。

 しかし、うまくいかない場合があります。
「cd」です。

 以下のようなシナリオを考えてみましょう。
  1. "/" にいる。
  2. cd mnt とする。
  3. cd sdcard
 として、SDカード配下に移動したいとする。

  1. CLIAppKicker#start("cd mnt")
  2. CLIAppKicker#start("cd sdcard")
 のようなコードで実現できそうです。しかし、
 子プロセスの情報は引き継がれません。

  1. CLIAppKicker#start("cd mnt")
  ---> /mntい移動する。
  2. CLIAppKicker#start("cd sdcard")
  --> /sdcardに移動する。
 となり、思い通りの場所(/mnt/sdcard)に移動できません。




 この問題は、CLIAppKicker#start("sh") とすることで解決できます。
 KyoroTextでは、以下のようなコードを書きました。
 https://github.com/kyorohiro/KyoroHelloAndroid/tree/master/KyoroCommon/src/info/kyorohiro/helloworld/util/shell
 CmdSession.java




[気をつけた点]
 topコマンドなど、プロセスが動作し続けるアプリでは、「InputStream#available() == 0」の状態で、InputStream#read()すると、次へ進まなくります。データが入力されるまで待機するためです。
なので、必ず、「InputStream#available() > 0」ことを確認してから、InputStream#read()する
ようにしました。



[次回]
テスト自動化はできた!!どのくらいテストできたか定量的に客観的にしりたい!!
どうすればよいでしょうか?
次回はEMMAというテストカバレッジツールを使用する方法について解説します。


2013年1月19日土曜日

KyoroText で使用されている技術 その10


[課題] File#listFiles(FileFilter filter) を中断できるようにしたい

KyoroTextはテキストビューアなので、ファイルシステムからテキストファイルを選択して、開く機能が必要です。

解決すべきシナリオは、
アプリ  : 1. ファイルシステム上のファイルの一覧を表示する。
ユーザー: 2. ファイルまたはフォルダーを選択する。
アプリ  : 3. 選択されたものがファイルならば、テキストとして表示する。フォルダならば、1に戻る。
といった感じでとても単純です。

しかし、ちょっとした罠があります。それは、「一覧表示すべきファイルの数: 0~無限」ということです。
大量のファイルを表示する場合、以下の問題が発生します。
[A] 一覧を出すのに時間がかかる。 
[B] 一覧表示すべきファイルの数は、ヒープに収まらないこともありえる。

たとえば、[A]については、端末起動直後などに10000件程度のファイル一覧を取得する場合、10秒以上待たされる場合もありました。
つまり、Thread#interrupt()が呼ばれても、最悪10秒以上は処理を返さないことがあるということです。


 [Androidの作り] 
// 
// 実際のコードです。
//
    public File[] listFiles(FileFilter filter) {
        File[] files = listFiles();
        if (filter == null || files == null) {
            return files;
        }
        List result = new ArrayList(files.length);
        for (File file : files) {
            if (filter.accept(file)) {
                result.add(file);
            }
        }
        return result.toArray(new File[result.size()]);
    }

[A]、[B] の各々はJava側では解決する方法はなさそうですね!!


 [KyoroTextの解決方法] 

[A]に時間がかかることはあきらめて、中断できる機能を追加することにしました。

1. File#listFiesの呼び出す時は、専用のスレットを呼び出す。
2. 1の処理が終わるまで待つ。
3. もしも、Thread#interruptが呼ばれたら、File#listFilesの処理が終わるのを待つのをやめる。

具体的には、以下のような機能を用意しました。

#AsyncronousTaskクラス

 AsyncronousTask atask = new AsyncronousTask#AsyncronousTask(Runnable 作業完了を監視したいタスク)
  Thread runner = new Thread(atask);
  runner.start();
  if(atask.syncTask()) {
     // タスクが完了した。
  } else {
     // interruptされた
  }

https://github.com/kyorohiro/KyoroHelloAndroid/blob/master/KyoroCommon/src/info/kyorohiro/helloworld/util/AsyncronousTask.java

https://github.com/kyorohiro/KyoroHelloAndroid/blob/master/KyoroCommon/src/info/kyorohiro/helloworld/util/FileListGetter.java

 [PS] 

次回、今回のAsyncronousTaskを使用する方法だと、使用する側は、AsyncronousTaskと監視対象の両方のクラスを制御しなくてはならない。これを一つにまとめると、使いやすさがUPします。
KyoroTextでおこなった方法について説明します。

キーワードは、Future Task 



2013年1月14日月曜日

KyoroText で使用されている技術 その9


InputConnectionの使い方の説明は、家に帰って動作確認しながらでないと、
記事が書けないようです。
家に帰ったら、別の事がしたなって、全然ブログま更新ができていませんでした。

InputConnectionの使い方の説明が終わるまでは、次には進まないぞ!!と考えていましたが、
やめました。

※ 通勤中に電車の中で書けるような事でないとダメみたい。


[課題] 走らせるスレッドを制限したい その1

同時に複数のスレッドごりごり走ると、低性能な端末だとダメダメな感じです。
なので、スレッド数をある程度管理化におく必要があります。
なおかつ、あまり、作業を作る側は、管理している側を意識したくない。


[KyoroTextでの解決]
  同時にひとつだけ、Threadが動く事を保障したクラスを用意しておいて。
  作業(Runnable)をそのクラスに渡す。

[パターン]
 いくつかパターンがあります。例えば、
- 1  指定された作業をキューにつんで置いて、順番にアクセスする。
- 2  作業が指定されたら、他の作業をすべて殺して、その作業を優先する。
- 3  等々

今回は、-2をとりました。

[作り]
 使う側は、とりあえず、以下のメソッドに依頼したい作業を指定するだけ。
  SingleTaskRunner#start(Runnable task)

 管理側
  1. 動作中のThreadを中断する。
  2. joinして、Threadが終了するのを待つ
  3. 新しい作業を開始する。
 
 ちょっと工夫した点は、1.、2、3 の作業を、別スレッドで行うようにしたところ。
 このstartメソッドを呼び出した側がロックを開放しないと、今動作中のThreadが終了しない
 といった事がおきると困のます。なので、別スレッドにしておくほうが安全です。
※ 工夫したというよりは、上記が理由で、KyoroTextにて、デットロックを起こしてしまったので、直したという表現がただしい。

[中身] 
 https://github.com/kyorohiro/KyoroHelloAndroid/blob/master/KyoroCommon/src/info/kyorohiro/helloworld/util/SingleTaskRunner.java


[次回のネタ]
- データ保存操作だとかを、バックグラウンドで実施したい。しかし、バックグラウンドへ移動すると、PFから、プロセスキルされる可能性が高くなります。(※例えServiceと動作していても)
  KyoroTextでの、プロセスキルへの対策を説明します。
  キーワードは、 low memory killer,  taskmanager, process kill

2013年1月1日火曜日

KyoroText で使用されている技術 その8



その7の続き...


[小課題] SurfaceViewでEditorを作る。その1 
- 未確定文字と確定文字について


 確定文字と未確定文字の扱いについて説明します。

 日本語を入力する場合、一旦、「ひらがな」を入力した後で、「漢字」に変換します。この変換される前の状態を
「未確定文字」、変換後の状態を「確定文字」と呼ぶことにします。

 例えば、「お腹が空いた」と入力したい場合、
 1. 「おなかがすいた」と未確定文字を入力
 2. 「お腹が空いた」と確定文字に変換
 となります。


 APIの流れを見てみましょう。
   1. IMEアプリ : フォーカスがあるViewから、InputConnectionを取得する。
   2. IMEアプリ : InputConnection#setComposingText("おなかがすいた", 1);
   3. Editor    : 未確定文字をユーザーに表示
   4. IMEアプリ : InputConnection#inputCommitText("お腹が空いた", 1);
   5. Editor    : 確定文字として、テキストを更新


[サンプル]
 - 上記シナリオをInstrumentationで記載しました。ログを仕込めば、APIの流れがわかります。
  https://github.com/kyorohiro/KyoroSamples/blob/master/KyoroSampleSurfaceViewEditor/src/info/kyorohiro/samples/android/test/CheckForComposingText.java



[PS]

 -  setComposingText()、inputCommitText() で渡されるテキストについて 
  setComposingText()、inputCommitText() で渡されるテキストはCharSequenceです。
  Spannable装飾ありのテキストが渡さることがあります。

  例えば、「<|>」をカーソルとします。「おなかがすいた」と入力した後で、
 「おなか」だけを変換したい場合、以下のようなシナリオになります。
 1.「おなかがすいた<|>」と入力
  2.「おなか<|>がすいた」と移動
  3.「お腹<|>がすいた」と変換
  4.「お腹がすいた<|>」と確定する。

  ほとんどの場合、この時のカーソル位置は、Editor側のカーソルではありません。
  IMEのものです。装飾ありのテキストを使用して表現されます。
  例えば、  「おなか<|>がすいた」は、「おなか」の部分の背景色をグレーにするとかしています。

  ※ このような変換処理は、IMEアプリ側がやってくれるわけですが、
     Editor側は、装飾情報を解析して、ユーザーに表示してあげる必要があります。


 -  setComposingText()、inputCommitText() で渡されるnewCursorPositionについて
Editor側のカーソル位置を表しています。
 * 0 の時、「<|>お腹がすいた」
 * 1 の時、「お腹がすいた<|>」

となります。