【问题标题】:Android - modify adapter from the UI thread and not from background threadAndroid - 从 UI 线程而不是从后台线程修改适配器
【发布时间】:2019-11-10 11:59:29
【问题描述】:

我有一个从 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()。

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

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

【问题讨论】:

  • 不确定这是否能解决问题,但您在调用super.onPostExecute(result); 之前调用了activity.adapter.notifyDataSetChanged(); 此外,您不需要在doInBackground 内调用UIThread 调用activity.runOnUiThread,您可以在@ 内完成所有这些操作987654326@
  • 是的,我在 onPostExecute 中切换了顺序来测试它,但它也像现在一样工作。我无法重现该问题,在我的设备上它始终有效。该异常仅在一台设备上出现过一次。那么在 onPostExecute 中我还应该做什么呢?

标签: android ui-thread


【解决方案1】:

更改您的AsyncTask,以便计算doInBackground() 中的列表,然后将其返回给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();
    }
}

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

【讨论】:

  • 谢谢,之前忘记在Getcontacts中把最后一个参数改成List了。在您的代码中,我认为我们也应该将 protected String doInBackground 更改为 protected List doInBackground ,这样它就可以工作了。我会在未来的版本中检查此更改后是否会出现此问题,但希望不会。
  • 哎呀,是的,你完全正确,doInBackground() 必须返回List&lt;Actors&gt;
【解决方案2】:

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

activityReference.get().actorsList.add(actor); doInBackground 方法中的这一行正在更新您传递给适配器的实际列表。 doInBackground 中的所有这些计算必须非常快地发生,并且来自 onPostExecutenotifyDataSetChanged 正在被调用。我不知道怎么做,但是这一次您的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

【讨论】:

  • 我不确定如何在那里创建自定义列表,但如果我在 for 循环中这样做会怎样:activity.runOnUiThread(new Runnable() { @Override public void run() { activityReference.get().actorsList.add(actor); activity.adapter.notifyDataSetChanged(); } });
  • 不要这样做,这将是一个未优化的代码。通过本地列表,我的意思是在您的 AsyncTask 中创建一个新的 ArrayList。我将添加一些示例代码。
  • 当我添加到 for 循环中时:actors.add(actor);然后它说无法解析方法添加。在执行后我添加了 activityReference.get().actorsList.add(actors);它说无法解析演员。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-04-16
  • 1970-01-01
  • 2023-04-05
  • 1970-01-01
  • 2014-05-28
  • 2012-04-24
  • 1970-01-01
相关资源
最近更新 更多