Runnable отправляется успешно, но не выполняется

В существующем проекте 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) потока, при этом все еще устанавливая изображение в потоке пользовательского интерфейса после декодирования. (по крайней мере, согласно разработчикам android) (который я проверил путем регистрации Thread.currentThread().getName() в разных местах)

Теперь иногда изображение просто не отображается, а stdout только говорит

I/System.out( 8066): bitmap: [email protected] img: [email protected]
I/System.out( 8066): runnable posted

не содержащий следов сообщений из Runnable. Таким образом, Runnable не run(), хотя img.post() возвращает true. Вытягивание ImageView в onCreate() и объявление его final не помогает.

Я не знаю. Просто установка растрового изображения напрямую, при блокировке потока пользовательского интерфейса, действительно исправляет ситуацию, но я хочу, чтобы все было правильно. Кто-нибудь понимает, что происходит здесь?

(ps. все это наблюдалось на Android 1.6 и Android-3 sdk)

Ответ 1

Если вы посмотрите на документы для View.post, там есть соответствующая информация:

Этот метод может быть вызван извне потока пользовательского интерфейса только тогда, когда этот вид прикреплен к окну.

Поскольку вы делаете это в onCreate, скорее всего, иногда ваш View еще не будет прикреплен к окну. Вы можете проверить это, переопределив onAttachedToWindow и помещая что-то в журналы, а также регистрируясь при публикации. Вы увидите, что когда сообщение не выполняется, пост-вызов происходит до onAttachedToWindow.

Как уже упоминалось выше, вы можете использовать Activity.runOnUiThread или предоставить свой собственный обработчик. Однако, если вы хотите сделать это непосредственно из самого View, вы можете просто получить обработчик View:

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

Это особенно полезно, если у вас есть пользовательский вид, который включает некоторую фоновую загрузку. Там также добавлен бонус не создавать новый отдельный обработчик.

Ответ 2

Я расширил класс ImageView, чтобы решить эту проблему. Я собираю runnables, переданные в сообщение, пока представление не привязано к окну, а в 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

Я думаю, проблема в том, что вы обновляете пользовательский интерфейс (ImageView) отдельным потоком, который не является потоком пользовательского интерфейса. Пользовательский интерфейс может обновляться только с помощью потока пользовательского интерфейса.

Вы можете решить эту проблему, используя 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() {

чтобы убедиться, что изображение просмотрено в потоке пользовательского интерфейса.

Обработчик - довольно запутанная концепция, я также потратил несколько часов исследований, чтобы действительно понять об этом;)

Ответ 4

Я не вижу ничего явно неправильного в том, что у вас там; вызов View.post() должен заставить его работать в потоке пользовательского интерфейса. Если ваша активность исчезла (возможно, с помощью поворота экрана), то ваш ImageView не будет обновлен, но я все равно ожидаю, что запись в журнале скажет "настройка растрового изображения...", даже если вы не смогли ее увидеть.

Я предлагаю попробовать следующее и посмотреть, имеет ли это значение:

1) Используйте Log.d(стандартный регистратор Android), а не System.out

2) Передайте свой Runnable в Activity.runOnUiThread(), а не View.post()

Ответ 5

Используйте следующий код, можете отправлять свой код в MainThread в любое время в любом месте, но не зависеть от Context или Activity. Это может помешать view.getHandler() сбою или утомительной работе onAttachedToWindow() и т.д.

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

Ответ 6

У меня была та же проблема, и с использованием view.getHandler() также не удалось, потому что обработчик отсутствовал. runOnUiThread() решил проблему. Предположительно, это действительно делает некоторую очередность, пока пользовательский интерфейс не будет готов.

Причина для меня заключалась в вызове задачи загрузки значка в базовом классе, и возвращаемый результат так что основной класс не создал вид (getView() в фрагменте).

Я немного подозрительно, что когда-нибудь он может проиграть. Но теперь я готов к этому! Спасибо, ребята.