Runnable jest wysłany pomyślnie, ale nie działa

W istniejącym projekcie Androida natknąłem się na następujący fragment kodu (w którym wstawiłem miot debugowania)

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();

Nowy w rozwoju Androida i po wygooglowaniu, rozumiem, że jest to sposób na robienie rzeczy bez blokowania głównego wątku (UI), jednocześnie ustawiając obraz na wątku UI po dekodowaniu. W 2007 roku, po raz pierwszy w historii, pojawiła się nowa wersja Androida, która została wydana w 2008 roku.]}

Teraz czasami obraz po prostu się nie pojawia, a stdout mówi tylko

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

Bez śladu wiadomości z Runnable. Więc wygląda na to, że Runnable nie run(), chociaż img.post() zwraca true. Przeciągnięcie ImageView w onCreate() i deklarowanie go final nie pomaga.

Nie mam pojęcia. Po prostu ustawienie bitmapy bezpośrednio, podczas blokowania wątku UI, naprawia rzeczy, ale chcę, aby wszystko było dobrze. Czy ktoś rozumie, co tu się dzieje?

(ps. wszystko to zaobserwowano na Android 1.6 phone i android-3 sdk)

Author: mvds, 2010-11-03

6 answers

Jeśli spojrzysz na dokumenty dla View.post jest kilka istotnych informacji:

Ta metoda może być wywołana spoza wątku interfejsu użytkownika tylko wtedy, gdy ten widok jest dołączony do okna.

Ponieważ robisz to w onCreate, jest prawdopodobne, że czasami Twój View nie będzie jeszcze dołączony do okna. Możesz to sprawdzić, nadpisując onAttachedToWindow i umieszczenie czegoś w logach, a także logowanie podczas postu. Zobaczysz, że gdy post zawiedzie, wywołanie post dzieje się przed onAttachedToWindow.

Jak już wspomnieli inni, możesz użyć Activity.runOnUiThread lub zapewnić własnego opiekuna. Jeśli jednak chcesz to zrobić bezpośrednio z View, możesz po prostu uzyskać obsługęView:

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

Jest to szczególnie przydatne, jeśli masz niestandardowy widok, który zawiera jakiś rodzaj ładowania w tle. Dodatkową zaletą jest brak konieczności tworzenia nowego osobnego Handlera.

 55
Author: kabuko,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-05-04 22:45:47

Rozszerzyłem ImageView klasę, aby rozwiązać ten problem. Zbieram runnables przekazane do postu podczas widoku nie dołączone do okna i w onAttachedToWindow post zebrane 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;
    }
}
 10
Author: Nik,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-12-01 08:15:18

Myślę, że problem polega na tym, że aktualizujesz interfejs (ImageView)za pomocą osobnego wątku, który nie jest wątkiem interfejsu. Interfejs użytkownika może być aktualizowany tylko przez wątek interfejsu.

Możesz to rozwiązać używając Handler :

Handler uiHandler;

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

Następnie zastąp swój:

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

Z

uiHandler.post(new Runnable() {

Aby upewnić się, że imageview jest aktualizowany w wątku interfejsu użytkownika.

Handler to dość mylące pojęcie, potrzebowałem też godzin badań, aby naprawdę to zrozumieć;)

 7
Author: xandy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-11-03 02:36:19

Nie widzę nic ewidentnie złego w tym, co tam masz; wywołanie widoku.post() powinien spowodować jej uruchomienie w wątku UI. Jeśli Twoja aktywność zniknie (być może poprzez obrót ekranu), Twój ImageView nie zostanie zaktualizowany, ale nadal oczekuję wpisu w dzienniku, który powie "ustawianie bitmapy ...", nawet jeśli tego nie widziałeś.

Proponuję wypróbować następujące i sprawdzić, czy to robi różnicę:

1) Użyj Logu.d (standardowy Android logger) raczej ten System.out

2) Podaj swój bieg do aktywności.runOnUiThread() zamiast View.post ()

 6
Author: Shawn Lauzon,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-11-03 04:54:20

Użyj poniższego kodu, możesz opublikować swój kod do MainThread w dowolnym miejscu i czasie, ale nie zależy od Context lub Activity. To może zapobiec view.getHandler() awarii lub żmudnych onAttachedToWindow() rzeczy itp.

    new Handler(Looper.getMainLooper()).post(new Runnable() {
        @Override
        public void run() {
            //TODO
        }
    });
 3
Author: fantouch,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-07-17 09:19:38

Miałem ten sam problem i używałem widoku.getHandler() również nie powiodło się, ponieważ handler nie był obecny. runOnUiThread () rozwiązał problem. Prawdopodobnie to faktycznie robi pewne kolejkowanie, dopóki interfejs jest gotowy.

Powodem dla mnie było wywołanie zadania icon load w klasie bazowej, a wynik został zwrócony tak szybko, że główna klasa nie utworzyła widoku (getView () we fragmencie).

Trochę podejrzewam, że kiedyś może się nie udać. Ale teraz jestem gotowy na to! Dzięki chłopaki.

 1
Author: Ratatat Richie,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-06-12 14:42:36