既存のAndroidプロジェクトで、次のコードに遭遇しました(デバッグ用のゴミを挿入した場所)
_ImageView img = null;
public void onCreate(...) {
img = (ImageView)findViewById(R.id.image);
new Thread() {
public void run() {
final Bitmap bmp = BitmapFactory.decodeFile("/sdcard/someImage.jpg");
System.out.println("bitmap: "+bmp.toString()+" img: "+img.toString());
if ( !img.post(new Runnable() {
public void run() {
System.out.println("setting bitmap...");
img.setImageBitmap(bmp);
System.out.println("bitmap set.");
}
}) ) System.out.println("Runnable won't run!");
System.out.println("runnable posted");
}
}.start();
_
Android開発の初心者であり、グーグルで調べたところ、これがメイン(UI)スレッドをブロックせずに、デコード後にUIスレッドに画像を設定しながら作業を行う方法であることを理解しています。 (少なくともAndroid開発者によると) (さまざまな場所でThread.currentThread().getName()
をログに記録して確認しました)
今 時々 画像が表示されず、stdoutは「
_I/System.out( 8066): bitmap: Android.graphics.Bitmap@432f3ee8 img: Android.widget.ImageView@4339d698
I/System.out( 8066): runnable posted
_
runnableからのメッセージの痕跡はありません。したがって、run()
はtrue
を返しますが、Runnableはimg.post()
を返さないようです。 onCreate()
でImageViewをプルしてfinal
と宣言しても役に立ちません。
私は無知です。 UIスレッドをブロックしながら、ビットマップを直接設定するだけで問題は解決しますが、問題を解決したいと思います。誰かがここで何が起こっているのか理解していますか?
(ps。これはすべてAndroid 1.6電話とAndroid-3SDKで観察されました)
View.post
のドキュメントを見ると、いくつかの関連情報があります。
このメソッドは、このビューがウィンドウにアタッチされている場合にのみ、UIスレッドの外部から呼び出すことができます。
onCreate
でこれを行っているため、View
がまだウィンドウにアタッチされていない可能性があります。これを確認するには、 onAttachedToWindow
をオーバーライドし、ログに何かを入れて、投稿時にログに記録します。投稿が失敗すると、onAttachedToWindow
の前に投稿の呼び出しが発生することがわかります。
他の人が述べたように、 Activity.runOnUiThread
を使用するか、独自のハンドラーを提供できます。ただし、View
自体から直接実行する場合は、View
のハンドラーを取得するだけです。
view.getHandler().post(...);
これは、ある種のバックグラウンド読み込みを含むカスタムビューがある場合に特に便利です。新しい個別のハンドラーを作成する必要がないという追加のボーナスもあります。
この問題を解決するためにImageView
クラスを拡張しました。ビューがウィンドウにアタッチされていないときに投稿に渡されたランナブルを収集し、onAttachedToWindow
で収集されたランナブルを投稿します。
public class ImageView extends Android.widget.ImageView
{
List<Runnable> postQueue = new ArrayList<Runnable>();
boolean attached;
public ImageView(Context context)
{
super(context);
}
public ImageView(Context context, AttributeSet attrs)
{
super(context, attrs);
}
public ImageView(Context context, AttributeSet attrs, int defStyle)
{
super(context, attrs, defStyle);
}
@Override
protected void onAttachedToWindow()
{
super.onAttachedToWindow();
attached = true;
for (Iterator<Runnable> posts = postQueue.iterator(); posts.hasNext();)
{
super.post(posts.next());
posts.remove();
}
}
@Override
protected void onDetachedFromWindow()
{
attached = false;
super.onDetachedFromWindow();
}
@Override
public boolean post(Runnable action)
{
if (attached) return super.post(action);
else postQueue.add(action);
return true;
}
}
問題は、UIスレッドではない別のスレッドでUI(ImageView)を更新していることだと思います。 UIは、UIスレッドによってのみ更新できます。
Handler :を使用してこれを解決できます。
Handler uiHandler;
public void onCreate(){
...
uiHandler = new Handler(); // This makes the handler attached to UI Thread
...
}
次に、次のものを交換します。
if ( !img.post(new Runnable() {
と
uiHandler.post(new Runnable() {
uIスレッドでimageviewが更新されていることを確認します。
ハンドラーは非常に紛らわしい概念です。私もこれについて本当に理解するために何時間もの調査をしました;)
私はあなたがそこに持っているものに明らかに悪いことは何も見ていません。 View.post()を呼び出すと、UIスレッドで実行されます。アクティビティが(おそらく画面の回転によって)消えた場合、ImageViewは更新されませんが、ログエントリに「ビットマップを設定しています...」と表示されると思います。
次のことを試して、違いが生じるかどうかを確認することをお勧めします。
1)System.outではなくLog.d(標準のAndroidロガー))を使用します
2)RunnableをView.post()ではなくActivity.runOnUiThread()に渡します
次のコードを使用して、いつでもどこでもコードをMainThreadに投稿できますが、Context
またはActivity
に依存しません。これにより、view.getHandler()
の失敗や面倒なonAttachedToWindow()
などを防ぐことができます。
new Handler(Looper.getMainLooper()).post(new Runnable() {
@Override
public void run() {
//TODO
}
});
同じ問題が発生し、ハンドラーが存在しないため、view.getHandler()の使用も失敗しました。 runOnUiThread()は問題を解決しました。おそらく、これはUIの準備ができるまで実際にいくつかのキューイングを行います。
私の原因は、基本クラスでアイコンの読み込みタスクを呼び出した結果がすぐに返されるため、メインクラスがビューを確立していなかったためです(フラグメントのgetView())。
いつか誤って失敗するのではないかと少し疑っています。しかし、私は今それの準備ができています!みんなありがとう。