スレッドから別オブジェクトを触ると異常終了?! 解決編

 先日の「スレッドからオブジェクトをイジると異常終了する」件。
 2つの方法で解決した。

 おそらくは邪道な方法だと思うので、参考ぐらいにとどめた方がいいと思う。(^_^;

 前提となる、アプリの構造は以下のような感じだ。

	Activity
	  └GameRun (スレッド)
	     └TextView

※.この他にも、TextView と同列のView があるのだが、ここでは省略。

【動作】
 GameRun がスレッドとして動作し、表示メッセージをTextView へ送る。

【問題】
 GameRunスレッドから、setText や class変数の変更など、TextViewの操作を行うと異常終了する。

 解決するには、Handler を使用するしかないようだ。
 ただそれには不都合があり、今回は、別の方法を探ってみた。

リスナーを使う

 Handler ときて、最初に連想したのがこの方法だ。
 リスナーも通知先をハンドラという。
 ならば、リスナー経由で操作を行えば、根本の原因を回避できるのではないか?
 というワケ。

	GameRun
	 ↓ 通知 ( _Listener.setPadding( padding[] ); )
	TextView
	  method が実行され、変数に値が格納される。
	   setPadding( padding[] ){
	      mesPadding = padding;
	   }

 ただしこの方法は、必ずmethod を介さなければならない。
 上記の例でいえば、スレッドを使わない場合、次のようにシンプルだ。

	TextView.mesPadding = padding;

 ただの代入でも、method を介さなければならないのは、ちょいとシャク。
 ただそれだけの話ではあるんだけどね。

 この方法はことのほか、すんなり問題を解決した。
 ソースもすっきりシンプル。
 値を返すようなものにも使用できる。

リスナー と Handler を使う

 前記方法で問題は解決したように見えたが、別の箇所で再び同じ問題が起きた。
 そこではsetTextやLayoutView のremove を使っており、前記方法では解決されなかったのだ。
 おそらく、描画しているものに対して、直接操作を行おうとしたからだろう。

 解決策としてまず思い浮かんだのが、タイマーである。
 タイマーはキューを受けると、時間をおいて処理を実行する仕掛け。
 View側であらかじめ、setTextやremoveするようなタイマーを作り、スレッド側はキューを送る。
 処理の実行はView側なので、根本原因は回避できるハズだ。
 ……って、待て待て。
 それって、Handler そのものなんじゃね? (^_^;

 そこでView のmethod にHandler を仕掛け、↓のように改良してみた。
 結果は成功である。

View側 method

	private Handler mHandler = new Handler();
	private String mesStr = "";

	public void setMes( String mes ){
		mesStr = mes;
		mHandler.post( new Runnable() {
			public void run() {
				setText( mesStr );
			}
		});
		
	}

スレッド側 method実行

	_MesView.setMes( "うふふ……" );

 View側で、引数 mes を、class変数 mesStr に一旦代入しているのは、そうしないとrun内で認識されないから。(シンタックス・エラーになる)
 というのも、new Runnable() で暗黙的にThread を生成しているからだ。
 つまりrun内は、別class または別method の領域となってしまうのだね。
 それはわかるが、イマイチ好きになれない書き方…。
 まぁ、是非もナシやね。

 これで問題は解決し、もうひとつの問題が発生した。
 先のsetText をappend にした場合である。

	public void appendMes( String mes ){
		mesStr = mes;
		mHandler.post( new Runnable() {
			public void run() {
				append( mesStr );
			}
		});
	}

 append は現在表示しているText に文字列を追加するmethodである。
 たとえば、こんな動作をする。

	[ソース]
	setText( "うふふ……" );
	append( "えっち♪" );

	[表示動作]
	うふふ……
	 ↓append
	うふふ……えっち♪

 ソースからすると、上記のような動作になるハズなのだが。
 実際にやってみると…こうなってしまう。

	うふふ……
	 ↓append
	えっち♪えっち♪

 これはclass変数を介しているために起きてしまう現象だ。
 普段、意識しないが、Javaでの変数は参照が基本。
 内部的にsetText されたのは、class変数のメモリー位置で、class変数の内容が変われば、当然、変わってしまう…。
 おそらくは、スレッドを介しているのも関係があるんだろうね。

 そこでappend は使わず、自前で追加するように変更した。
 これで意図どおりに実行されるようになった。

	public void appendMes( String mes ){
		mesStr = mesStr+mes;
		mHandler.post( new Runnable() {
			public void run() {
				setText( mesStr );
			}
		});
	}

 以上の方法には、リスナーのみの時とは、異なる注意点がある。

1. 即応性がない。
 実行タイミングは、Handler(Looper) 任せなので、いつ実行されるか不明だ。
 付随して、返り値を求めるようなmethod には適応できない。

2. classローカルでも、Handler を介することになる。
 たとえば、例にあげたTextView自身がsetMesを実行すると、Handler を介した実行になる。
 なんだか、自分の尻を他人に拭いてもらうようで、まどろっこしい。(笑
 また、即応性の面からすると、潜在的なバグとなる可能性もある。
 必要なら、ローカル用とHandler用、別々のmethod を作った方がいいだろうね。

3. class変数で引数を保持する。
 先の失敗例のように、Handler が実行する前に、値が変わってしまわないようにしなければならない。

by the way

 setTextは、スレッドからの実行で、ちゃんと動作している場合と、異常終了する場合が見られた。
 これはsetTextだけの話ではない。
 おそらく動作する例は、その時はまだ画面に表示されていないか、UIがひとつだけの状態だったのが原因ではないかと思う。
 一度だけの実行で、不具合ナシと、安心してはいけないね。

補足

 Handler の使い方は、まだよく把握していない。
 見よう見まねの試行錯誤で、「一応、動作している」にすぎない。
 もっとうまく、正しい方法もあると思う。
 ちゃんとした解説を見て、試して、よく理解する必要があるね。(^_^;

 しかし…。
 なんだかJavaというより、Cocoaって感じの構造になったな。(w

Posted in Listener, 雑記. Tags: , , , . スレッドから別オブジェクトを触ると異常終了?! 解決編 はコメントを受け付けていません。 »

スレッドから別オブジェクトを触ると異常終了?!

 作成中のアプリに、SelectMenuを組み込んでみたところ、異常終了するようになってしまった。
 どうも、スレッドから別オブジェクトのView(UI系)に触ってはいけない決まりらしい。

 色々と調べてみたところ、その理由はスレッド間の同期にあるようだ。
 簡単にいうと、「個々に動いているスレッドで、勝手にイジくりまわされちゃかなわん」。
 まぁ、その理屈はわかるけどね。
 でも、コッチで作った変数に触っただけで、落ちる軟弱構造はどうよ?!

 それはそれとして。
 スレッドからの操作を行うための方法はあって、それがHandler らしい。
 Handler がスレッドからの通知を受け取り、溜まった通知を順番にLooper が実行していく…。
 そんな構造のようだね。
 なるほどねぇ。
 でも、ルーピーはイヤなネーミングだなぁ。オイラ、麻生支持者だし。(w

 対処方はわかったんで、試しにやってみたんだけど…。
 やってみると、問題が色々とでてくる。
 まず、変数が使えない。
 Handler はclass の中にclass を書くような記述のため、method 内で作った変数は参照できないみたいだ。(シンタックス・エラーになる)
 しかも、うまく動かない。
 なんか、Activity ともからんでいるようで、今の構造だとちと無理なようだ。
 どうもオイラの組み方は、とことん、Android と相性がわるいらしい。(w

 しかしこの、”class の中にclass がある” 書き方ってどうにかならんかね?
 ソースが見た目、すっきりしないし。
 iアプリやってたせいか、class をボロボロ作るのはどうもねぇ…。
 容量ケチりたがる貧乏性なだけだが。(笑

 まぁ、グチクヂいっててもはじまらない。
 Handler はスレッドを扱うゲームからすると、洗礼のようなもののようだし。
 どうにか対処を考えよう。

 こんな感じで対処してみたよ。

Posted in 雑記. Tags: , . スレッドから別オブジェクトを触ると異常終了?! はコメントを受け付けていません。 »