Runnable已成功发布但未运行

时间:2021-02-04 17:29:34

In an existing Android project I've encountered the following piece of code (where I inserted the debugging litter)

在现有的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();

New to Android development, and having googled around, I understand that this is the way to do stuff without blocking the main (UI) thread, while still setting the image on the UI thread after decoding. (at least according to android-developers) (which I have verified by logging Thread.currentThread().getName() at various places)

Android开发的新手,并且已经google了,我明白这是在不阻塞主(UI)线程的情况下执行操作的方法,同时仍然在解码后在UI线程上设置图像。 (至少根据android-developers)(我通过在各个地方记录Thread.currentThread()。getName()来验证)

Now sometimes the image just doesn't show up, and stdout only says

现在有时图像不会出现,stdout只会说

I/System.out( 8066): bitmap: android.graphics.Bitmap@432f3ee8 img: android.widget.ImageView@4339d698
I/System.out( 8066): runnable posted

with not a trace of the messages from the Runnable. So appearantly the Runnable doesn't run(), although img.post() returns true. Pulling the ImageView in onCreate() and declaring it final doesn't help.

没有来自Runnable的消息的痕迹。所以看起来Runnable不会运行(),尽管img.post()返回true。在onCreate()中拉出ImageView并将其声明为final也无济于事。

I'm clueless. Simply setting the bitmap directly, while blocking the UI thread, does fix things, but I want to get things right. Does anybody understand what's going on here?

我很无能为力。简单地设置位图,同时阻止UI线程,确实可以解决问题,但我想把事情弄清楚。有人知道这里发生了什么吗?

(ps. this was all observed on an Android 1.6 phone and android-3 sdk)

(ps。这是在Android 1.6手机和android-3 sdk上观察到的)

6 个解决方案

#1


55  

If you look at the docs for View.post there's some relevant info:

如果您查看View.post的文档,可以获得一些相关信息:

This method can be invoked from outside of the UI thread only when this View is attached to a window.

仅当此视图附加到窗口时,才能从UI线程外部调用此方法。

Since you're doing this in onCreate, it is likely that sometimes your View will not be attached to the window yet. You can verify this by overriding onAttachedToWindow and putting something in the logs and also logging when you post. You'll see that when the post fails, the post call happens before onAttachedToWindow.

由于您在onCreate中执行此操作,因此有时您的视图可能尚未附加到窗口。您可以通过覆盖onAttachedToWindow并在日志中放置内容并在发布时进行日志记录来验证这一点。您会看到,当帖子失败时,帖子调用会在onAttachedToWindow之前发生。

As the others have mentioned, you can use Activity.runOnUiThread or provide your own handler. However, if you want to do it directly from the View itself, you can simply get the View's handler:

正如其他人所提到的,您可以使用Activity.runOnUiThread或提供自己的处理程序。但是,如果您想直接从View本身执行此操作,您只需获取View的处理程序:

view.getHandler().post(...);

This is especially useful if you have a custom view that includes some sort of background loading. There's also the added bonus of not having to create a new separate handler.

如果您有一个包含某种背景加载的自定义视图,这将特别有用。还有一个额外的好处,就是不必创建新的单独处理程序。

#2


10  

I extended ImageView class to solve this problem. I collect runnables passed to post while view not attached to window and in onAttachedToWindow post collected runnable.

我扩展了ImageView类来解决这个问题。我收集传递给post的runnables而没有附加到window的视图和onAttachedToWindow post收集的runnable。

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;
    }
}

#3


7  

I think the problem is you are updating the UI (ImageView) with a separate thread, which is not the UI Thread. The UI can only be updated by the UI Thread.

我认为问题是你是用一个单独的线程更新UI(ImageView),而不是UI线程。 UI只能由UI线程更新。

You can solve this by using Handler:

您可以使用Handler解决此问题:

Handler uiHandler;

public void onCreate(){
    ...
    uiHandler = new Handler(); // This makes the handler attached to UI Thread
    ...
}

Then replace your:

然后替换你的:

if ( !img.post(new Runnable() {

with

uiHandler.post(new Runnable() {

to make sure the imageview is updated in the UI Thread.

确保在UI线程中更新imageview。

Handler is a quite confusing concept, I also took hours of research to really understand about this ;)

处理程序是一个相当令人困惑的概念,我也花了几个小时的研究来真正理解这个;)

#4


6  

I don't see anything obviously wrong with what you have there; calling View.post() should cause it to run on the UI thread. If your Activity went away (perhaps through a screen rotation), then your ImageView wouldn't be updated, but I would still expect a log entry to say "setting bitmap ...", even if you couldn't see it.

我没有看到你在那里有什么明显的错误;调用View.post()应该使它在UI线程上运行。如果您的Activity消失了(可能是通过屏幕旋转),那么您的ImageView将不会更新,但我仍然希望日志条目说“设置位图...”,即使您看不到它。

I suggest trying the following and see if it makes a difference:

我建议尝试以下内容,看看它是否有所作为:

1) Use Log.d (the standard Android logger) rather that System.out

1)使用Log.d(标准Android记录器)而不是System.out

2) Pass your Runnable to Activity.runOnUiThread() rather than View.post()

2)将Runnable传递给Activity.runOnUiThread()而不是View.post()

#5


3  

Use the following code, can post your code to MainThread anytime anywhere, but not depends any Context or Activity. That can prevent view.getHandler() failure or tedious onAttachedToWindow() stuffs etc.

使用以下代码,可以随时随地将代码发布到MainThread,但不取决于任何Context或Activity。这可以防止view.getHandler()失败或繁琐的onAttachedToWindow()东西等。

    new Handler(Looper.getMainLooper()).post(new Runnable() {
        @Override
        public void run() {
            //TODO
        }
    });

#6


1  

I had the same problem, and using view.getHandler() also failed because the handler was not present. runOnUiThread() solved the problem. Presumably this does actually do some queueing until the UI is ready.

我遇到了同样的问题,并且使用view.getHandler()也失败了,因为处理程序不存在。 runOnUiThread()解决了这个问题。据推测,这确实会在UI准备好之前进行排队。

The cause for me was calling the icon load task in a base class and the result being returned so quickly that the main class hadnt estabished the view (getView() in fragment).

我的原因是在基类中调用图标加载任务,结果返回得如此之快,以至于主类没有建立视图(片段中的getView())。

I'm a bit suspicious that it might spuriously fail sometime. But I'm now ready for it! Thanks guys.

我有点怀疑它可能会在某个时候虚假地失败。但我现在准备好了!多谢你们。

#1


55  

If you look at the docs for View.post there's some relevant info:

如果您查看View.post的文档,可以获得一些相关信息:

This method can be invoked from outside of the UI thread only when this View is attached to a window.

仅当此视图附加到窗口时,才能从UI线程外部调用此方法。

Since you're doing this in onCreate, it is likely that sometimes your View will not be attached to the window yet. You can verify this by overriding onAttachedToWindow and putting something in the logs and also logging when you post. You'll see that when the post fails, the post call happens before onAttachedToWindow.

由于您在onCreate中执行此操作,因此有时您的视图可能尚未附加到窗口。您可以通过覆盖onAttachedToWindow并在日志中放置内容并在发布时进行日志记录来验证这一点。您会看到,当帖子失败时,帖子调用会在onAttachedToWindow之前发生。

As the others have mentioned, you can use Activity.runOnUiThread or provide your own handler. However, if you want to do it directly from the View itself, you can simply get the View's handler:

正如其他人所提到的,您可以使用Activity.runOnUiThread或提供自己的处理程序。但是,如果您想直接从View本身执行此操作,您只需获取View的处理程序:

view.getHandler().post(...);

This is especially useful if you have a custom view that includes some sort of background loading. There's also the added bonus of not having to create a new separate handler.

如果您有一个包含某种背景加载的自定义视图,这将特别有用。还有一个额外的好处,就是不必创建新的单独处理程序。

#2


10  

I extended ImageView class to solve this problem. I collect runnables passed to post while view not attached to window and in onAttachedToWindow post collected runnable.

我扩展了ImageView类来解决这个问题。我收集传递给post的runnables而没有附加到window的视图和onAttachedToWindow post收集的runnable。

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;
    }
}

#3


7  

I think the problem is you are updating the UI (ImageView) with a separate thread, which is not the UI Thread. The UI can only be updated by the UI Thread.

我认为问题是你是用一个单独的线程更新UI(ImageView),而不是UI线程。 UI只能由UI线程更新。

You can solve this by using Handler:

您可以使用Handler解决此问题:

Handler uiHandler;

public void onCreate(){
    ...
    uiHandler = new Handler(); // This makes the handler attached to UI Thread
    ...
}

Then replace your:

然后替换你的:

if ( !img.post(new Runnable() {

with

uiHandler.post(new Runnable() {

to make sure the imageview is updated in the UI Thread.

确保在UI线程中更新imageview。

Handler is a quite confusing concept, I also took hours of research to really understand about this ;)

处理程序是一个相当令人困惑的概念,我也花了几个小时的研究来真正理解这个;)

#4


6  

I don't see anything obviously wrong with what you have there; calling View.post() should cause it to run on the UI thread. If your Activity went away (perhaps through a screen rotation), then your ImageView wouldn't be updated, but I would still expect a log entry to say "setting bitmap ...", even if you couldn't see it.

我没有看到你在那里有什么明显的错误;调用View.post()应该使它在UI线程上运行。如果您的Activity消失了(可能是通过屏幕旋转),那么您的ImageView将不会更新,但我仍然希望日志条目说“设置位图...”,即使您看不到它。

I suggest trying the following and see if it makes a difference:

我建议尝试以下内容,看看它是否有所作为:

1) Use Log.d (the standard Android logger) rather that System.out

1)使用Log.d(标准Android记录器)而不是System.out

2) Pass your Runnable to Activity.runOnUiThread() rather than View.post()

2)将Runnable传递给Activity.runOnUiThread()而不是View.post()

#5


3  

Use the following code, can post your code to MainThread anytime anywhere, but not depends any Context or Activity. That can prevent view.getHandler() failure or tedious onAttachedToWindow() stuffs etc.

使用以下代码,可以随时随地将代码发布到MainThread,但不取决于任何Context或Activity。这可以防止view.getHandler()失败或繁琐的onAttachedToWindow()东西等。

    new Handler(Looper.getMainLooper()).post(new Runnable() {
        @Override
        public void run() {
            //TODO
        }
    });

#6


1  

I had the same problem, and using view.getHandler() also failed because the handler was not present. runOnUiThread() solved the problem. Presumably this does actually do some queueing until the UI is ready.

我遇到了同样的问题,并且使用view.getHandler()也失败了,因为处理程序不存在。 runOnUiThread()解决了这个问题。据推测,这确实会在UI准备好之前进行排队。

The cause for me was calling the icon load task in a base class and the result being returned so quickly that the main class hadnt estabished the view (getView() in fragment).

我的原因是在基类中调用图标加载任务,结果返回得如此之快,以至于主类没有建立视图(片段中的getView())。

I'm a bit suspicious that it might spuriously fail sometime. But I'm now ready for it! Thanks guys.

我有点怀疑它可能会在某个时候虚假地失败。但我现在准备好了!多谢你们。