25

在现有的 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 开发新手,并在 Google 上搜索过,我知道这是在不阻塞主 (UI) 线程的情况下做事的方法,同时在解码后仍将图像设置在 UI 线程上。(至少根据android-developers)(我已经通过Thread.currentThread().getName()在各个地方登录来验证)

现在有时图像只是不显示,标准输出只说

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

没有来自 Runnable 的消息的痕迹。所以看起来 Runnable 没有run(),虽然img.post()返回true。拉入 ImageViewonCreate()并声明它final没有帮助。

我一无所知。简单地直接设置位图,同时阻塞 UI 线程,确实可以解决问题,但我想把事情做好。有人明白这里发生了什么吗?

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

4

6 回答 6

58

如果您查看文档以获取View.post一些相关信息:

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

由于您在 中执行此onCreate操作,因此有时您View可能还不会连接到窗口。您可以通过覆盖onAttachedToWindow并将某些内容放入日志并在发布时进行记录来验证这一点。您会看到,当 post 失败时, post 调用发生在onAttachedToWindow.

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

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

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

于 2012-05-04T22:45:47.580 回答
10

我认为问题是您正在使用单独的线程更新 UI (ImageView),这不是 UI 线程。UI 只能由 UI Thread 更新。

您可以使用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 线程中更新。

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

于 2010-11-03T02:36:19.197 回答
10

我扩展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;
    }
}
于 2013-08-22T10:08:46.793 回答
6

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

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

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

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

于 2010-11-03T04:54:20.517 回答
3

使用以下代码,可以随时随地将您的代码发布到 MainThread,但不依赖于任何ContextActivity. 这可以防止view.getHandler()失败或乏味onAttachedToWindow()的东西等。

    new Handler(Looper.getMainLooper()).post(new Runnable() {
        @Override
        public void run() {
            //TODO
        }
    });
于 2014-07-17T09:19:38.583 回答
1

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

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

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

于 2013-06-12T14:42:36.840 回答