Задать вопрос

Как решить ошибку с ListView и AsyncTask в Android?

Доброго всем времени суток! Возникла (хотя она никуда и не исчезала) такая проблема при обновлении данных адаптера. У пользователей она возникает довольно часто - за 2 дня в общем около 200 крэшей у ~15000 пользователей. Хотелось бы узнать, как можно её исправить.
LogCat
FATAL EXCEPTION
java.lang.IllegalStateException: The content of the adapter has changed but ListView did not receive a notification. Make sure the content of your adapter is not modified from a background thread, but only from the UI thread. [in ListView(2131099702, class android.widget.ListView) with Adapter(class devz.tim.vk.helpers.WallAdapter)]
       at android.widget.ListView.layoutChildren(ListView.java:1544)
       at android.widget.AbsListView.onLayout(AbsListView.java:2300)
       at android.view.View.layout(View.java:14072)
       at android.view.ViewGroup.layout(ViewGroup.java:4607)
       at android.widget.RelativeLayout.onLayout(RelativeLayout.java:948)
       at android.view.View.layout(View.java:14072)
       at android.view.ViewGroup.layout(ViewGroup.java:4607)
       at android.widget.FrameLayout.onLayout(FrameLayout.java:448)
       at android.view.View.layout(View.java:14072)
       at android.view.ViewGroup.layout(ViewGroup.java:4607)
       at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1655)
       at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1513)
       at android.widget.LinearLayout.onLayout(LinearLayout.java:1426)
       at android.view.View.layout(View.java:14072)
       at android.view.ViewGroup.layout(ViewGroup.java:4607)
       at android.widget.FrameLayout.onLayout(FrameLayout.java:448)
       at android.view.View.layout(View.java:14072)
       at android.view.ViewGroup.layout(ViewGroup.java:4607)
       at android.widget.FrameLayout.onLayout(FrameLayout.java:448)
       at android.view.View.layout(View.java:14072)
       at android.view.ViewGroup.layout(ViewGroup.java:4607)
       at android.view.ViewRootImpl.performLayout(ViewRootImpl.java:1997)
       at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:1818)
       at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1115)
       at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:4526)
       at android.view.Choreographer$CallbackRecord.run(Choreographer.java:725)
       at android.view.Choreographer.doCallbacks(Choreographer.java:555)
       at android.view.Choreographer.doFrame(Choreographer.java:525)
       at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:711)
       at android.os.Handler.handleCallback(Handler.java:615)
       at android.os.Handler.dispatchMessage(Handler.java:92)
       at android.os.Looper.loop(Looper.java:137)
       at android.app.ActivityThread.main(ActivityThread.java:4921)
       at java.lang.reflect.Method.invokeNative(Method.java)
       at java.lang.reflect.Method.invoke(Method.java:511)
       at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1027)
       at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:794)
       at dalvik.system.NativeStart.main(NativeStart.java)


Данные обновляются через AsyncTask. В doInBackground(); проводится обновление данных в LinkedHashMap, после чего в onPostExecute(); обновляется сам адаптер.
AsyncTask
public class LoadWall extends AsyncTask<Void, Void, Void> {
    public int[] OWNER_ID;
    public int OFFSET;
    public int COUNT;
    public boolean REFRESH;
    public Context context;
    public Activity activity;
    public ArrayList<String> LINKS = new ArrayList<String>();
    public ArrayList<LinkedHashMap<String, String>> LIST;
    public WallAdapter ADAPTER;
    public ListView LISTVIEW;
    JSONObject obj;
    public ArrayList<LinkedHashMap<String, String>> tempList;

    public LoadWall(int[] OWNER_ID, int OFFSET, int COUNT, boolean REFRESH,
            Context context, ArrayList<LinkedHashMap<String, String>> LIST,
            WallAdapter ADAPTER, ListView LISTVIEW) {
        this.OWNER_ID = OWNER_ID;
        this.OFFSET = OFFSET;
        this.COUNT = COUNT;
        this.REFRESH = REFRESH;
        this.context = context;
        this.LIST = LIST;
        this.ADAPTER = ADAPTER;
        this.LISTVIEW = LISTVIEW;
        activity = (Activity) context;
    }

    @Override
    protected void onPreExecute() {
        activity.setProgressBarIndeterminateVisibility(true);
        activity.setProgressBarVisibility(true);
        if (REFRESH == true){
            LIST.clear();
            if(tempList != null){
                tempList.clear();
            }
            LISTVIEW.requestLayout();
            ADAPTER.notifyDataSetChanged();
        }
        AppHelper.refreshAttacher.setRefreshing(true);
        tempList = new ArrayList<LinkedHashMap<String, String>>();
        tempList = LIST;
    };

    @Override
    protected Void doInBackground(Void... params) {
            LINK = "here is link for json";
            obj = Helpers.getJson(LINK);
            if (obj != null) {
                Helpers.refreshListView(obj, tempList);
            }
        return null;
    }

    @Override
    protected void onPostExecute(Void result) {
        if(obj==null){
            Toast.makeText(context, context.getString(R.string.loadfailed), Toast.LENGTH_LONG).show();
        }
        LIST = tempList;
        ADAPTER.notifyDataSetChanged();
        activity.setProgressBarIndeterminateVisibility(false);
        activity.setProgressBarVisibility(false);
        AppHelper.refreshAttacher.setRefreshComplete();
        super.onPostExecute(result);
    };
}

Решение на StackOverflow не помогло.
  • Вопрос задан
  • 5011 просмотров
Подписаться 3 Оценить 2 комментария
Решения вопроса 1
TonyCode
@TonyCode
Цепляемся за следующую строку Logcat-а:

"Make sure the content of your adapter is not modified from a background thread, but only from the UI thread."


Далее видим в AsyncTask следующее:

@Override
    protected Void doInBackground(Void... params) {
            LINK = "here is link for json";
            obj = Helpers.getJson(LINK);
            if (obj != null) {
                Helpers.refreshListView(obj, tempList);
            }
        return null;
    }


Ошибка:

Helpers.refreshListView(obj, tempList); вызывается в методе doInBackground() которая выполняется в background thread!

Решение:

Вынести вызов refreshListView() в метод onProgressUpdate() AsyncTask-а (который уже выполняется на UI Thread!):

protected Void doInBackground(Void... params) {
    // ...
    publishProgress(new MyProgress(obj, tempList));
    // ...
}

protected void onProgressUpdate(MyProgress... progress) {
    if (progress.size() > 0) {
        Helpers.refreshListView(progress[0].getObj(), progress[0].getTempList());
    }
}


---
Upd: Подробнее - т.к. в UIThread надо передавать и obj и tempList - то для них нужно сделать класс-wrapper, например так - (каждый класс в своём файле, естественно)

public class LoadWall extends AsyncTask<Void, MyProgress, Void> {
    // ...
    @Override
    protected Void doInBackground(Void... params) {
        // ...
        publishProgress(new MyProgress(obj, tempList));
        // ...
    }

    @Override
    protected void onProgressUpdate(MyProgress... progress) {
        if (progress.size() > 0) {
            Helpers.refreshListView(progress[0].getObj(), progress[0].getTempList());
        }
    }
    // ...
}

/**
 * Используется для передачи прогресса в UI-Thread
 */
public class MyProgress {

    private final Object obj;
    private final ArrayList<LinkedHashMap<String, String>> tempList;

    public MyProgress(Object obj, ArrayList<LinkedHashMap<String, String>> tempList) {
        this.obj      = obj;
        this.tempList = tempList;
    }

    public Object getObj() {
        return obj;
    }

    public ArrayList<LinkedHashMap<String, String>> getTempList() {
        return tempList;
    }
}
Ответ написан
Пригласить эксперта
Ответы на вопрос 2
@bimeg
Удалите в onPreExecute последнюю строчку.
Вот эту
tempList = LIST;
Ответ написан
Комментировать
WNeZRoS
@WNeZRoS
По коду не очень понятно как всё устроено, но последние две строчки в onPreExecute выглядят странно.
Ответ написан
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы