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();
}
}
我删减了一堆代码以使内容更易于阅读,但总体思路就在那里。
我有一个从 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();
}
}
我删减了一堆代码以使内容更易于阅读,但总体思路就在那里。