Android - изменяйте адаптер из потока пользовательского интерфейса, а не из фонового потока - PullRequest
0 голосов
/ 10 ноября 2019

У меня есть ListView, заполненный из json и использующий пользовательский адаптер.

Это мой сокращенный код в ZoznamActivity:

 public class Zoznam extends AppCompatActivity{

    private ArrayList<Actors> actorsList;
    private ActorAdapter adapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

   setContentView(R.layout.search_filter);
        final ListView lv = findViewById(R.id.listView1);

        actorsList = new ArrayList<>();
        adapter = new ActorAdapter(this, "Zoznam", actorsList);

        lv.setAdapter(adapter);adapter.notifyDataSetChanged();

        new GetContacts(Zoznam.this).execute("all","all");

   private static class GetContacts extends AsyncTask<String, Void, String> {
        ProgressDialog dialog;
        private final WeakReference<Zoznam> activityReference;

        GetContacts(Zoznam context) {
            activityReference = new WeakReference<>(context);
        }

        @Override
        protected void onPreExecute() {
            Zoznam activity = activityReference.get();
            if (activity == null || activity.isFinishing()) return;
            super.onPreExecute();
            dialog = new ProgressDialog(activity);
            dialog.setMessage(activity.getResources().getString(R.string.Loading));
            dialog.setTitle(activity.getResources().getString(R.string.connecting));
            dialog.show();
            dialog.setCancelable(false);
        }

        @Override
        protected String doInBackground(String... sText1) {final Zoznam activity = activityReference.get();


            HttpHandler sh = new HttpHandler();
            String url = "URL";
            String jsonStr = sh.makeServiceCall(url);

            if (jsonStr != null) {
                try {JSONObject jsonObj = new JSONObject(jsonStr);
                    JSONArray actors = jsonObj.getJSONArray("result");

                    for (int i = 0; i < actors.length(); i++) {
                        JSONObject c = actors.getJSONObject(i);

                        Actors actor = new Actors();

                        actor.setLetter(c.getString("letter"));
                        actor.setNazov(c.getString("nazov"));
                        actor.setThumb(c.getString("thumb"));


                        activityReference.get().actorsList.add(actor);

                    }

                }  catch (final JSONException e) {

                    activity.runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            Toast.makeText(activity,
                                    R.string.Nodata,
                                    Toast.LENGTH_LONG).show();
                        }
                    }); }

return jsonStr;

            } else {
                activity.runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        Toast.makeText(activity,
                                R.string.Network,
                                Toast.LENGTH_LONG).show();
                    }
                });
                return null;
            }
        }

        protected void onPostExecute(String result) {
            Zoznam activity = activityReference.get();
            if (activity == null || activity.isFinishing()) return;
            dialog.dismiss();
            activity.adapter.notifyDataSetChanged();
            super.onPostExecute(result);

        }

    }

    protected void onResume() {
        super.onResume();
        adapter.notifyDataSetChanged();
    }

Проблема в том, что в отчетах о сбоях я обнаружил эту ошибку:

Содержимое адаптера изменилось, но ListView не получил уведомление. Убедитесь, что содержимое вашего адаптера не изменено из фонового потока, а только из потока пользовательского интерфейса. Убедитесь, что ваш адаптер вызывает notifyDataSetChanged () при изменении его содержимого.

Однако это произошло только один раз для одного устройства, но я бы хотел это исправить.

Я уже читал об этом, но у меня все еще нет решения. Как вы можете видеть в моем коде, я вызываю notifyDataSetChanged () также onResume и onPostExecute, поэтому не уверен, куда мне поместить это в другом месте.

Я думаю, что мне нужно использовать activity.runOnUiThread (new Runnable ())где-то после цикла for из json, где я добавляю элементы в arraylist, но не уверен, как это сделать, и если это решит проблему.

Мое последнее обновление кода:

 for (int i = 0; i < actors.length(); i++) {
                    JSONObject c = actors.getJSONObject(i);

                    final Actors actor = new Actors();

                    actor.setLetter(c.getString("letter"));
                    actor.setNazov(c.getString("nazov"));
                    actor.setThumb(c.getString("thumb"));


                    activity.runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            activityReference.get().actorsList.add(actor);
                            activity.adapter.notifyDataSetChanged();
                        }
                    });

                }

1 Ответ

0 голосов
/ 10 ноября 2019

Возможно, вы получили исключение, потому что вы обновили список адаптеров в фоновом потоке.

activityReference.get().actorsList.add(actor); Эта строка в методе doInBackground обновляет фактический список, который вы передали адаптеру. Все эти вычисления в doInBackground должны происходить довольно быстро, и notifyDataSetChanged из onPostExecute вызывается. Я не уверен, как, но на этот раз ваш onPostExecute не был вызван вовремя, поэтому ваш список был обновлен, но ваш ListView не получил уведомление.

Вы можете создать локальный списокв вашем AsyncTask и обновите список действий в onPostExecute. Пример кода -

private static class GetContacts extends AsyncTask<String, Void, String> {
    ProgressDialog dialog;
    ArrayList<Actors> actors = new ArrayList<>();
    ...
    @Override
    protected String doInBackground(String... sText1) {
        ...
        for (int i = 0; i < actors.length(); i++) {
            ...
            // activityReference.get().actorsList.add(actor); <-- remove this.
            actors.add(actor);
        }
        ...
    }

    protected void onPostExecute(String result) {
        super.onPostExecute(result);
        Zoznam activity = activityReference.get();
        if (activity == null || activity.isFinishing()) return;
        dialog.dismiss();
        activityReference.get().actorsList.add(actors); <-- add this
        activity.adapter.notifyDataSetChanged();
    }
}

Это должно работать, но вы можете еще больше улучшить свой код с помощью обратных вызовов. Таким образом, вам не нужно будет хранить ссылку на действие внутри asyncTask, и вы можете переместить всю логику из asyncTask. Этот ответ должен быть полезным https://stackoverflow.com/a/15693380/6168272.

...