2011-01-08 15 views
6

Napisałem ListActivity z niestandardową listą adapterów. Lista jest aktualizowana z ContentProvider po uruchomieniu onCreate. Mam też usługę, która uruchamia się po uruchomieniu aplikacji i najpierw aktualizuje ContentProvider, a następnie wysyła audycję, w której zawartość została zaktualizowana.
Moja ListActivity odbiera transmisję i próbuje zaktualizować mój ListView. Mój problem polega na tym, że dostaję sporadyczne błędy dotyczące zmiany danych adaptera ListView bez powiadomienia ListView. Wywołuję metodę notifyDataSetChanged() na moim adapterze list zaraz po aktualizacji. Wygląda na to, że lista jest wciąż aktualizowana po pierwszym wywołaniu onCreate, gdy odbiera transmisję z usługi do aktualizacji, więc próbuje zaktualizować mój ListView, zanim zakończy aktualizację od pierwszego uruchomienia. Czy to ma sens? Oto niektóre z mojego kodu.Zmiana danych adaptera ListView bez powiadomienia o liście ListView

UWAGA: Usługa działa poprawnie, pobiera nowe dane i aktualizuje mojego dostawcę treści, a transmisję otrzymuję w mojej aktywności po jej zaktualizowaniu.

@Override 
public void onCreate(Bundle savedInstanceState) { 
    super.onCreate(savedInstanceState); 
    ctx = this; 
    getPrefs(); 
    setContentView(R.layout.main); 

    // Setup preference listener 
    preferences = PreferenceManager.getDefaultSharedPreferences(this); 
    preferences.registerOnSharedPreferenceChangeListener(listener); 


    // Setup report list adapter 
    ListView nzbLv = (ListView) findViewById(R.id.report_list); 
    nzbla = new NZBReportListAdaptor(ctx); 
    getReports(); 
    nzbla.setListItems(report_list);    
    nzbLv.setAdapter(nzbla);   
    // Broadcast receiver to get notification from NZBService to update ReportList 
    registerReceiver(receiver, 
      new IntentFilter(NZBService.BROADCAST_ACTION)); 

    startService(new Intent(ctx, NZBService.class)); 
} 

@Override 
public void onResume() { 
    super.onResume(); 
    timerHandler.resume();  
new updateSabQueue().execute(); 
    //updateList(); 
} 

@Override 
public void onPause() { 
    super.onPause(); 
    timerHandler.pause(); 
    unregisterReceiver(receiver); 
} 


private BroadcastReceiver receiver = new BroadcastReceiver() { 
    public void onReceive(Context context, Intent intent) { 
     Toast.makeText(ctx, "NZBService broadcast recieved", Toast.LENGTH_SHORT).show(); 
     updateReportList(); 
    } 
}; 


private void updateReportList() { 
    new updateReportList().execute(); 
} 



private class updateReportList extends AsyncTask<Void, Void, Boolean> { 

    /* (non-Javadoc) 
    * @see android.os.AsyncTask#onPreExecute() 
    * Show progress dialog 
    */ 
    protected void onPreExecute() { 
    } 

    /* (non-Javadoc) 
    * @see android.os.AsyncTask#doInBackground(Params[]) 
    * Get new articles from the internet 
    */ 
    protected Boolean doInBackground(Void...unused) { 
     getReports(); 
     return true; 
    } 

    /** 
    * On post execute. 
    * Close the progress dialog 
    */ 
    @Override 
    protected void onPostExecute(Boolean updated) { 
     if (updated) { 
      Log.d(TAG, "NZB report list adapter updated"); 
      synchronized(this) { 
       nzbla.setListItems(report_list);    
      } 
      Log.d(TAG, "NZB report list notified of change"); 
      nzbla.notifyDataSetChanged();       
     } 
    } 
} 

Teraz, to odpowiedź na pytanie, będę pisać mój zaktualizowany kod, starając się pomóc innym, którzy mogą wejść w poprzek.

Odpowiedz

11

Należy wykonać wszystkie aktualizacje danych adaptera w wątku interfejsu użytkownika, aby zsynchronizowany blok nie był potrzebny. Jest to również bezużyteczne, ponieważ twoja synchronizacja na AsyncTask jest tworzona za każdym razem, gdy jest wykonywana.

Innym problemem jest to, że dzwonisz pod numer notifyDataSetChanged na zewnątrz do Adapter. Powinieneś zadzwonić pod koniec swojej metody setListItems. Nie powinno to jednak powodować błędów, ponieważ jest wykonywane w wątku UI, ale nie powinno być wywoływane w ten sposób.

Należy upewnić się, że Twoja metoda getReports nie modyfikuje magazynu kopii zapasowej w żaden sposób. Ponieważ działa on w osobnym wątku, nie może modyfikować niczego, co ma także dostęp do Adapter. Nawet jeśli jest chroniony przez zamki. W metodzie doInBackground należy wygenerować listę aktualizacji lub nowej listy itd. I przekazać ją do onPostExecute, która następnie zatwierdza nowe dane do Adapter w wątku interfejsu użytkownika. Tak więc, jeśli twoja funkcja getReports zmienia się report_list, a Twoja ma odniesienie do report_list, robisz to źle. getReports musi utworzyć nowy report_list, a następnie przekazać go do swojego Adapter po zakończeniu tworzenia go w wątku interfejsu użytkownika.

Aby potwierdzić, można modyfikować tylko dane Adapter, a następnie ListView ma również dostęp do wątku interfejsu użytkownika. Korzystanie z synchronizacji/blokad nie zmienia tego wymagania.

+1

To jest dokładnie problem. Mój adapter list jest wspierany przez ArrayList obiektów "report_list" i modyfikuję go bezpośrednio w mojej metodzie getReports, która jest wykonywana w osobnym wątku.Zmodyfikowałem metodę getReports, aby działała na lokalnej tablicy ArrayList i zwróciłem ją (i przekazałem dalej do mojej metody onPostExecute). Chociaż zdawałem sobie sprawę z faktu, że nie mogę operować na moich danych adaptera listy poza wątkiem interfejsu użytkownika, po prostu nie chwytałem go w 100%, dopóki nie zawiódł mnie i zmusił mnie do znalezienia go poprawnie (lub dokładniej zapytałem o pomoc tutaj!) Dziękuję Qberticus! – brockoli

-1

Jeśli chcesz zaktualizować ListView UI z usługi, a następnie należy zadzwonić notifyDataSetChanged() na onDestroy usługi

zrobić zasilacz statyczną z podstawowej działalności nazwać adaptername.notifyDataSetChanged()

podoba się to

@Override 
     public void onDestroy() { 

       if (MainActivity.isInFront == true) { 
         if (MainActivity.adapter != null) 
           MainActivity.adapter.notifyDataSetChanged(); 
         MainActivity.listView.setAdapter(MainActivity.adapter); 
       } 
}    
+0

Huh? Nie można uczynić adaptera statycznym i wywoływać z niego niestatycznych metod, takich jak notifyDataSetChanged. Heck, nie ma nawet metody o nazwie isInFront, nawet jeśli nie było, nie można po prostu zadzwonić do klasy, ponieważ nie byłoby statyczne. – AfzalivE

+0

jest z przodu, to metoda, którą musisz wykonać .. nie ma tu żywienia łyżeczkami. Odczytaj kod najpierw, a następnie spróbuj ... –

+0

Nie możesz zdefiniować statycznej metody sprawdzającej stan instancji. Ponadto nie można uzyskać dostępu do instancji żadnej aktywności bez przynajmniej odniesienia do niej, lub ustawienie interfejsu nasłuchującego w celu zaktualizowania działania za jego pośrednictwem. Kolejnym powodem, dla którego odrzucam, jest to, że musisz uzyskać wszystkie działające zadania, aby wiedzieć, która aktywność jest z przodu, nie wspominając już o pobieraniu wystąpienia tego działania, co jest zupełnie niepotrzebne. Nawet jeśli ręcznie utrzymujesz wartość boolean isInFront, nie będzie można tak łatwo pobrać instancji. – AfzalivE

Powiązane problemy