PreferenceFragmentの使い方

公式開発ガイドのとおりにするとエラーになるんだけどな・・・

public class SettingsActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // Display the fragment as the main content.
        getFragmentManager().beginTransaction()
                .replace(android.R.id.content, new SettingsFragment())
                .commit();
    }
}

画面を回転させた時にonCreateが呼ばれると、super.onCreate()が終わった時にすでにPreferenceFragmentが復元されている。
この時は // Display the fragment as the main content. はいらないんじゃないか?
ただPreferenceFragmentを表示する分には問題ないみたいだけど、DialogFragmentを使うとエラーが起きる。


・PreferenceFragmentを表示
・DialogFragmentを表示
・画面を回転
・DialogFragmentが呼出すPreferenceFragmentのonActivityResult()で再度DialogFragmentを表示


DialogFragment#show(FragmentManager, int)のためにPreferenceFragment#getFragmentManager()を使用するのだが、これがnullを返してくる。
getActivity()もnullを返してくるし、PreferenceFragmentの状態がおかしくなっているらしい。

if (getFragmentManager().findFragmentById(android.R.id.content) != null) {
    return;
}
// Display the fragment as the main content.
getFragmentManager().beginTransaction()
        .replace(android.R.id.content, new SettingsFragment())
        .commit();

とすればいいらしい。
それともDialogFragmentの使い方がおかしいのかな?
ちゃんとgetTargetFragment(), setTargetFragment()を使ってるんだけど・・・

ObservableCollectionの罠

ObservableCollection.InsertでArgumentExceptionが起きる時の回避策。


一覧系のコントロールにObservableCollectionをバインドする時コントロールがObservableCollectionのCollectionChangedイベントにデリゲートを設定しているが、この解除が適切にされないことがあるらしく、変なデリゲートが残っている状態でInsertメソッドを呼ぶとArgumentExceptionが起きる。


そこで
・自前で同じようなクラスを実装する。
・必要に応じてCollectionChangedデリゲートをnullクリアする。
ようにすれば回避可能。

asyncとawaitとtaskとthread

超絶勘違いをしていた。


asyncとawaitキーワードを指定して非同期処理をしてもマルチスレッドになるわけではない。


てっきりマルチスレッドになるもんだと思ってた。
その割にスレッドIDが変わらないのは何だかな〜と不思議に思ってた。


asyncキーワードをつけたメソッドを呼び出すと、そのメソッドは1つのTaskとしてThreadPoolのキューに入れられ、すぐに実行される。
さらにawaitキーワードをつけて呼び出すと、そのawait以降の処理は別のTaskとしてThreadPoolのキューに入れられ、呼んだメソッドが終了するまでブロックされる。
awaitキーワードをつけずに呼び出すと、それ以降の処理が別のTaskとしてThreadPoolのキューに入れられるところまではawaitをつけた場合と同じだが、いつ実行されるかはフレームワークのスケジューリング任せ。
でいいのかな?


Taskとスレッドは1:1になるわけではない。
Taskはコードによって明示的/暗黙的に作られるが、スレッドは必要に応じてosが作る。
ThreadPoolクラスである程度コントロールはできるらしい。
Task.Run()やTask.Factory.StartNew()を使ってTaskを作ると新しいスレッドが作られるらしいが、これは保証されてることなのかな?
たまたまなのかな?


TaskはThreadPoolのキューに入れられ、空いているスレッドによって実行される。
1つのスレッドが同時に実行できるTaskは1つだけ。
コードの中にawaitがあると、コンパイラによってawait以降は別のTaskに分かれているので、スレッドは今のTaskが完了したとして、別の(おそらくはawait対象の)Taskを実行する。
そのTaskが完了するとawait以降のTaskが実行されるが、その時のスレッドはawaitまでを実行していたスレッドとは限らない。
ただ感覚としてはTask.Run()やTask.Factory.StartNew()を使わない限り、新しくスレッドが作られることはほとんどない。
1つないし2つのスレッドがすべてのTaskを処理しているらしい。


await/asyncの使用によってシングルスレッドでも非同期処理やその待機ができるわけだが、UIに関係ない処理は別スレッドに分けたほうが多少速い気がする。

インテントでの外部アプリの起動

やりたいことは音楽再生アプリの起動なのだが、検索して出てくるのは特定のファイルを再生させる方法ばかり。
ファイルを再生するのではなく、ホームから起動するのと同じことをしたいのだが・・・
再生アプリが複数インストールされていればデフォルトのアプリ選択ダイアログでユーザーに選択してもらいたい。


暗黙的インテントにmp3ファイルのパスやurlを指定して投げればアプリの選択ダイアログが表示されるのだが、指定しないとActivityNotFoundになってしまう。


PackageManagerを使えば、暗黙的インテントに対応するアプリの一覧は取れる。
アプリが特定できれば特にファイルを指定したりせずに起動することもできる。
複数のアプリからユーザーに選択してもらうためには自前でダイアログを作らなきゃいかんのかなぁ・・・

イベントハンドラのasync

さらに先日書いた記事の補足。


実行されないコードの問題はawait付きの呼び出しではなく、async付きのイベントハンドラのようだ。

private async void OnClick(object sender, EventArgs args)
{
  Debug.WriteLine("hoge1");
  await hoge2();
  Debug.WriteLine("hoge3");
}

みたいなイベントハンドラがOnSuspendingを起点とするコードパスで呼び出されると、await以降のコードの実行は保証されない。


イベントハンドラを同期的に実行できないかといろいろ試してみた結果、組み込みのイベントはどうにもできないらしい。
もちろんイベントハンドラの中と外で同期オブジェクトでも使えば何とでもなるとは思うが、そういうことではなく。
独自にイベントを定義すればasync/awaitで待機することはできる。

public delegate Task AwaitableEventHandler(object sender, EventArgs args);
public event AwaitableEventHandler Event;

protected async Task OnEvent(object sender, EventArgs args)
{
  await RaiseEventAsync(Event, sender, args);
  // await Event(sender, args); これだと複数のイベントハンドラが設定されている時に1つが終わったらそこで待機から戻ってしまう
}

protected async Task RaiseEventAsync(Delegate handler, params object[] parameters)
{
  await Task.WhenAll(handler.GetInvocationList().Select(d => (Task)d.DynamicInvoke(parameters)));
}

みたいな感じか。


awaitを使わないハンドラでも何らかのTaskオブジェクトを戻すようにしなきゃならないとか、いまいち使い勝手が悪い気もするが。

アプリの中断

先日書いた記事の補足。


microsoftのサイトによると、中断というのはosによるプロセス/スレッドのスケジューリングの対象外になることだそうだ。
プログラムからすれば時が止まるのと同義。
先日の記事に書いた、スレッドがフォアグラウンドとかバックグラウンドとか関係ない。
OnSuspendingを抜けるとすべてのスレッドがフリーズすることになるね。


microsoftのサイトによると、ほとんどのアプリではResumeの時にすることはないそうだが、ホントか?
中断した後、再開されずにそのまま終了されることもあることを考えると、ファイルやネットワークなどのI/O処理はOnSuspendingで後始末しなければいけないのでは?
そうすると再開した時にはそれらの準備が必要になる気がするんだが・・・
いつ強制終了されても良くて、再開の時はまた最初からやり直せば済むような処理なら何も気にしなくてもいいんだろうけど。
それともファイルハンドルとか開きっぱなしで終了しても問題ないんだろか?


OnSuspendingには制限があって、できれば2秒、長くても5秒以内に終わらなければハングアップとみなされて強制終了されてしまう。
バックグラウンドで時間のかかる処理をしているからといってそうそう待てはしない。
そうなるとバックグラウンドタスクに求められるのは、任意のタイミングで(アプリの終了も考慮した)中断、再開ができる仕組みということになるが、きっちり作りこむのは大変そうだ・・・


言わずもがなではあるが、UIスレッドで時間のかかる処理をしてはいけない。
確かButton.Clickなどのイベントハンドラは5秒以内に終わらなければそれはそれでハングアップとみなされる・・・はず。