Android - 从 UI 线程而不是后台线程修改适配器

Android - modify adapter from the UI thread and not from background thread

我有一个从 json 填充并使用自定义适配器的 ListView。

这是我在 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 未收到通知。确保您的适配器的内容不是从后台线程修改的,而只是从 UI 线程修改的。确保您的适配器在其内容更改时调用 notifyDataSetChanged()。

但是它只在一台设备上发生过一次,但我想解决这个问题。

我已经阅读了更多关于此的主题,但我仍然没有解决方案。正如您在我的代码中看到的,我也在调用 notifyDataSetChanged() onPostExecute,所以不确定问题出在哪里。

您可能遇到了异常,因为您在后台线程上更新了适配器列表。

activityReference.get().actorsList.add(actor); doInBackground 方法中的这一行正在更新您传递给适配器的实际列表。 doInBackground 中的所有这些计算必须进行得非常快,并且正在调用 onPostExecute 中的 notifyDataSetChanged。我不确定是怎么回事,但是有一次你的 onPostExecute 没有及时呼叫,因此你的列表已更新,但你的 ListView 没有收到通知。

您可以在 AsyncTask 中创建本地列表并在 onPostExecute 中更新 activity 的列表。示例代码-

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 中保留 activity 的引用,并且可以将所有逻辑移出 asyncTask。这个答案应该会有帮助 .

更改您的 AsyncTask,以便计算 doInBackground() 中的列表,然后 return 将其更改为 onPostExecute(),然后在那里进行所有更新:

// change the type parameters to return a List
private static class GetContacts extends AsyncTask<Void, Void, List<Actors>> {

    @Override
    protected List<Actors> doInBackground(Void... ignored) {
        // add this at the top
        List<Actors> actors = new ArrayList<>();

                    // inside your loop, replace this line:
                    // activityReference.get().actorsList.add(actor);
                    // with this instead
                    actors.add(actor);

        // and at the end, return the list
        return actors;
    }

    // the list you returned from doInBackground() is passed here
    protected void onPostExecute(List<Actors> result) {
        // update your activity all at once in this method
        activity.actorsList.clear();
        activity.actorsList.addAll(result);
        activity.adapter.notifyDataSetChanged();
    }
}

我删减了一堆代码以使内容更易于阅读,但总体思路就在那里。