2010-11-03 17 views
24

W istniejącym projekcie Android Napotkałem następujący fragment kodu (gdzie włożeniu śmieci debugowania)Runnable jest wysłana pomyślnie, ale nie uruchamiać

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

nowego do rozwoju Android i zebrana wokół google, ja zrozum, że jest to sposób na robienie rzeczy bez blokowania głównego wątku (UI), przy jednoczesnym ustawianiu obrazu w wątku UI po dekodowaniu. (at least according to android-developers) (który został zweryfikowany przez zalogowaniu Thread.currentThread().getName() w różnych miejscach)

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

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

z nie śladu wiadomości z Runnable. Wygląda więc na to, że Runnable nie ma wartości run(), chociaż img.post() zwraca . Wywołanie ImageView w onCreate() i zadeklarowanie go jako final nie pomoże.

Nie mam pojęcia. Samo ustawienie bitmapy bezpośrednio, podczas blokowania wątku UI, naprawia pewne rzeczy, ale chcę wszystko naprawić. Czy ktoś rozumie, co tu się dzieje?

(ps. To wszystko było zaobserwować na Android 1.6 telefonu i Android-3 SDK)

+0

podobne pytania z przydatnych odpowiedzi: [Jaka jest różnica między Activity.runOnUiThread (runnable działania) i Handler.post()?] (Http: // stackoverflow .pl/questions/1839625/whats-the-difference-between-activity-runonuithreadrunnable-action-and-handler), [Różnica między Handler.post (Runnable r) i Activity.runOnUiThread (Runnable r)] (http://stackoverflow.com/questions/7452884/difference-between-handler-postrunnable-r-and-activity-runonuithreadrunnable) –

Odpowiedz

52

Jeśli spojrzeć na docs dla View.post istnieją pewne istotne informacje:

metoda ta może być wywołana z zewnątrz UI wątku tylko wtedy, gdy ten widok jest dołączony do okna.

Ponieważ robisz to na onCreate, to jest prawdopodobne, że Czasami View nie zostaną dołączone do okna jeszcze. Możesz to sprawdzić, przesyłając onAttachedToWindow i umieszczając coś w dziennikach, a także rejestrując się po opublikowaniu. Zobaczysz, że gdy post nie powiedzie się, połączenie będzie miało miejsce przed onAttachedToWindow.

Jak wspomnieli inni, można użyć Activity.runOnUiThread lub podać własny przewodnik. Jednakże, jeśli chcesz zrobić to bezpośrednio z samego View, można po prostu dostać obsługi View „s

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

Jest to szczególnie przydatne, jeśli masz własny widok, który zawiera jakieś tło załadunku. Dodatkową korzyścią jest to, że nie trzeba tworzyć nowego oddzielnego programu obsługi.

+9

Sprawdziłem to tylko krótko, ale wygląda na to, że jeśli widok nie jest dołączony do okna, to również nie ma obsługi. – ThomasW

+0

@ThomasW Moje rozwiązanie działało w moim scenariuszu, ale na pewno YMMV. O ile mi wiadomo, dokumenty nie określają, kiedy 'getHandler()' będzie ważny, ale po zbadaniu [kodu] (http://grepcode.com/file/repository.grepcode.com/java /ext/com.google.android/android/2.3_r1/android/view/View.java#View.dispatchAttachedToWindow%28android.view.View.AttachInfo%2Cint%29) wygląda na to, że moje rozwiązanie działa tylko wtedy, gdy widok jest najmniej * o * do załączenia. Jeśli nawet nie spróbujesz przywiązać go do okna, to masz rację, nie będzie dostępny handler. – kabuko

+1

@kabuko należy to zmienić teraz, gdy funkcjonalność się zmieniła. Dokumenty nie zawierają już klauzuli, do której się odwołujesz. Zakładam, że dzieje się tak, ponieważ, zgodnie ze źródłem, funkcjonalność wydaje się być zmieniona: '// Wykonaj zakolbione akcje na każdym przejściu, w przypadku gdy oderwany widok zakrył akcję \ n getRunQueue(). ExecuteActions (attachInfo.mHandler); 'Oznacza to, że wysyłanie do widoku powinno działać, pod warunkiem, że widok został dołączony jeden raz (gdzie otrzymuje początkowe odniesienie do Handler ViewRootImpl). Ale nie możesz nadal publikować w widoku, zanim zostanie on początkowo dołączony. – dcow

7

Myślę, że problem jest aktualizowania UI (ImageView) z osobnym wątku, który nie jest UI wątku . Interfejs użytkownika może być aktualizowany tylko przez wątek interfejsu użytkownika.

Problem ten można rozwiązać za pomocą Handler:

Handler uiHandler; 

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

Następnie wymienić:

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

z

uiHandler.post(new Runnable() { 

aby upewnić się, że ImageView jest aktualizowana w UI wątek.

Handler jest dość mylące pojęcie, ja również wziął godzin badań, aby naprawdę zrozumieć ten temat;)

+0

Nie rozumiem dlaczego, ale to też działa! Więc wszystko jest bardziej wiarygodne niż 'View.post()'. Dzięki! – mvds

+0

Zaskakujące jeszcze raz! To działa. Dzięki! Ale zastanawiam się, czy 'View.post' i' postDelayed' są tak nieprzewidywalne! Działają bezbłędnie na niektórych urządzeniach z Androidem iw niektórych przypadkach nieudolnie. Duh! – sud007

6

nie widzę niczego złego w co oczywiście tam masz; wywołanie metody View.post() powinno spowodować jej uruchomienie w wątku interfejsu użytkownika. Jeśli Twoja Aktywność zniknie (być może poprzez obrót ekranu), Twój ImageView nie będzie aktualizowany, ale nadal oczekiwałbym, że wpis w logu powie "ustawianie mapy bitowej ...", nawet jeśli nie możesz tego zobaczyć.

Proponuję próbuje następujących i zobaczyć, czy to robi różnicę:

1) Wykorzystanie Log.d (średnia rejestratora Android) raczej System.out

2) zdać Runnable do działania. runOnUiThread() zamiast View.post()

+0

Nie rozumiem, dlaczego, ale to wydaje się działać! W jednej z dziesięciu prób uzyskałem błąd segmentacji (podobno w bibliotece libc), ale nie mogę uwierzyć, że to jest powiązane. – mvds

+0

Dobre połączenie @Shawn. Nigdy nie zauważyłem funkcji runOnUiThread(). Co ciekawe, sprawdziłem kod źródłowy i runOnUiThread używa Handler'a do implementacji. Tak więc @mvds, obie prace nie są przypadkowe, używają tej samej metody pod maską. – xandy

+0

Co jeszcze nie jest dla mnie jasne, dlaczego rozwiązanie @ mdvs nie działa. Artykuł Google na http://developer.android.com/resources/articles/painless-threading.html odwołuje się do View.post (Runnable), którego używał, a Javadoc mówi "Powoduje, że Runnable może zostać dodany do kolejka komunikatów: Runnable zostanie uruchomiony w wątku interfejsu użytkownika." –

1

Miałem ten sam problem i użycie metody view.getHandler() również nie powiodło się, ponieważ program obsługi nie był obecny. runOnUiThread() rozwiązał problem. Prawdopodobnie robi to kolejkę do czasu, aż interfejs będzie gotowy.

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

Jestem trochę podejrzany, że może czasem zawodzić. Ale teraz jestem na to gotowy! Dzięki chłopaki.

10

Rozszerzyłem klasę ImageView, aby rozwiązać ten problem. Zbieram runnables przekazane do opublikowania, natomiast widok nie jest dołączony do okna, a w onAttachedToWindow po zebraniu działającym.

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

To naprawdę mi pomogło! Thanx!) – ruslik

+0

Awesome. To jest odpowiedź! – AndyB

3

Użyj następującego kodu, można pisać kod do MainThread zawsze i wszędzie, ale nie zależy od żadnego Context lub Activity. Które mogą zapobiec view.getHandler() awarii lub żmudnych onAttachedToWindow() spożywczych itp

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