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() в фрагменте).
Я немного подозрительно, что когда-нибудь он может проиграть.
Но теперь я готов к этому! Спасибо, ребята.