英文:
Android How Do I Call RecyclerVIew notifyDataSetChanged() after ThreadPoolExecutor finishes?
问题
我正在通过跟随这个教程来学习ThreadPoolExecutor。为了演示它的用法,我创建了一个简单的Android项目,其中包含一个RecyclerView,用于显示一些字符串。最初,字符串数组(String[] myDataset = new String[10]
)有10个空值。我的ThreadPoolExecutor会生成一些随机字符串并填充数组。因此,每当生成新的字符串并放入数组中时,我都应该调用notifyDataSetChanged()
方法,以便RecyclerView会更新并显示这些随机字符串。
问题
我不明白如何调用notifyDataSetChanged()
,所以我陷入了困境。我遇到了这个异常:
Caused by: android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
因为我了解AsyncTask,我知道这个错误意味着我不能在后台线程中调用此方法,而必须在主线程/ UI线程中调用它(在AsyncTask中,会像这样写):
@Override
protected void onPostExecute(String result) {
weakReference.get().notifyDataSetChanged(); // 类似这样的操作
}
我需要它在ThreadPoolExecutor中的对应方法。我在Google上查找并找到了这个答案,但我不确定如何操作。
以下是必要的代码片段:
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.MyViewHolder> {
private String[] myDataset;
private final ThreadPoolExecutor threadPoolExecutor;
private Future future;
private Runnable getRunnable(final int i) {
Runnable runnable = new Runnable() {
@Override
public void run() {
String randomString = MyAdapter.getRandomString(i) + " " + i; // <--生成随机字符串
Log.e(TAG, randomString);
myDataset[i] = randomString;
try { Thread.sleep(3000); }
catch (InterruptedException e) { e.printStackTrace(); }
}
};
return runnable;
}
public void doSomeBackgroundWork() {
Runnable[] commands = new Runnable[myDataset.length];
for (int i1 = 0; i1 < commands.length; i1++) {
final int j1 = i1;
commands[j1] = () -> {
String randomString = MyAdapter.getRandomString(j1) + " " + j1;
Log.e(TAG, randomString);
myDataset[j1] = randomString;
try { Thread.sleep(3000); }
catch (InterruptedException e) { e.printStackTrace(); }
// notifyDataSetChanged(); // <-- 错误。在哪里/如何调用它?
};
threadPoolExecutor.execute(commands[j1]);
}
}
public MyAdapter(String[] myDataset) {
this.myDataset = myDataset; // {null, null, ... ...}
this.threadPoolExecutor = DefaultExecutorSupplier.getInstance().forBackgroundTasks(); // 返回新的ThreadPoolExecutor(...参数...);
// future[i] = threadPoolExecutor.submit(command); future[i].cancel(true); 使用方式如此
doSomeBackgroundWork();
}
// ... 其余的与RecyclerView相关的代码
}
有人能帮助我吗?谢谢您的阅读。
英文:
I am learning ThreadPoolExecutor by following this tutorial. To demonstrate its usage, I made a simple android project, it has a recyclerview that will show some Strings. Initially, the array of Strings(String[] myDataset = new String[10]
) has 10 nulls. My threadPoolExecutor generates some random strings and fills up the array. So whenever a new String is generated and placed inside the array, I should call notifyDataSetChanged()
so that the recyclerView will update and show those random Strings.
the problem
I don't understand how to call notifyDataSetChanged()
and so I am pinned down. I got this exception:
Caused by: android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
Since I know AsyncTask, I understand that this error means I cannot call this method in background thread but I have to call it in main thread/ui thread ( so in AsyncTask, it would look like this:
@Override
protected void onPostExecute(String result) {
weakReference.get().notifyDataSetChanged(); // something like that
}
). I need it's ThreadPoolExecutor counterpart. I did google and found this but I am not sure how to do this.
The necessary code segment is given below:
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.MyViewHolder> {
private String[] myDataset;
private final ThreadPoolExecutor threadPoolExecutor;
private Future future;
private Runnable getRunnable(final int i) {
Runnable runnable = new Runnable() {
@Override
public void run() {
String randomString = MyAdapter.getRandomString(i)+" "+i; // <--create random string
Log.e(TAG, randomString);
myDataset[i] = randomString;
try { Thread.sleep(3000); }
catch (InterruptedException e) { e.printStackTrace(); }
}
};
return runnable;
}
public void doSomeBackgroundWork(){
Runnable[] commands = new Runnable[myDataset.length];
for(int i1=0; i1<commands.length; i1++) {
final int j1 = i1;
commands[j1] = () -> {
String randomString = MyAdapter.getRandomString(j1)+" "+j1;
Log.e(TAG, randomString);
myDataset[j1] = randomString;
try { Thread.sleep(3000); }
catch (InterruptedException e) { e.printStackTrace(); }
// notifyDataSetChanged(); // <-------- Error. Where/How should I call it?
};
threadPoolExecutor.execute(commands[j1]);
}
}
public MyAdapter(String[] myDataset) {
this.myDataset = myDataset; // {null, null, ... ...}
this.threadPoolExecutor = DefaultExecutorSupplier.getInstance().forBackgroundTasks(); // returns new ThreadPoolExecutor( ... parameters... );
// future[i] = threadPoolExecutor.submit(command); future[i].cancel(true); use it like this
doSomeBackgroundWork();
}
// ... the rest of the recyclerview related code
}
Could anyone help me? Thank you for reading.
答案1
得分: 2
有一个Handler类在所有需要从另一个线程与UI线程进行通信的情况下(AsyncTask也在使用它)。
一些可能的选择:
-
使用与主Looper相连的Handler:
Handler handler = new Handler(getMainLooper()); handler.post(new Runnable() { @Override public void run() { notifyDataSetChanged(); } });
-
使用你提到的"runOnUiThread":
runOnUiThread(new Runnable() { @Override public void run() { notifyDataSetChanged(); } });
-
使用你的UI视图的"post"方法(例如RecyclerView):
yourRecyclerView.post(new Runnable() { @Override public void run() { notifyDataSetChanged(); } });
英文:
There is a Handler class under the hood in all cases where you need to communicate to UIThread from the another Thread (AsyncTask use it as well).
Some of possible choices:
-
Use Handler, connected to main looper:
Handler handler = new Handler(getMainLooper());
handler.post(new Runnable() {
@Override
public void run() {
notifyDataSetChanged();
}
}); -
Use "runOnUiThread" that you've mentioned:
runOnUiThread(new Runnable() {
@Override
public void run() {
notifyDataSetChanged();
}
}); -
Use the "post" method of your UI-View (RecyclerView, for example):
yourRecyclerView.post(new Runnable() {
@Override
public void run() {
notifyDataSetChanged();
}
});
答案2
得分: 0
如果有人需要的话,这是我根据[sergiy-tikhonov](https://stackoverflow.com/users/12718414/sergiy-tikhonov)的答案所做的。
public void 进行一些后台工作(){
Runnable[] 命令集 = new Runnable[myDataset.length];
for(int i1=0; i1<commands.length; i1++) {
final int j1 = i1;
commands[j1] = () -> {
String 随机字符串 = MyAdapter.getRandomString(j1)+" "+j1;
Log.e(TAG, 随机字符串);
myDataset[j1] = 随机字符串;
try { Thread.sleep(3000); }
catch (InterruptedException e) { e.printStackTrace(); }
// notifyDataSetChanged(); // <-------- 错误
recyclerViewWeakReference.get().post(new Runnable() { // <---- 这是更改
@Override
public void run() {
notifyDataSetChanged();
}
});
};
threadPoolExecutor.execute(commands[j1]);
}
}
所以如您所见,我尝试了第三个选项。首先我在父片段(或者如果您在使用活动则是在活动中)中创建了一个`WeakReference<RecyclerView> recyclerViewWeakReference = new WeakReference<RecyclerView>(myRecyclerView)`。然后我将弱引用传递给了`MyAdapter`。我使用了`weakReference`,因为在AsyncTask中这是您所做的,所以我的直觉告诉我要这样做。希望对您有所帮助。
英文:
In case anyone needs, here is what I did based on sergiy-tikhonov's answer.
public void doSomeBackgroundWork(){
Runnable[] commands = new Runnable[myDataset.length];
for(int i1=0; i1<commands.length; i1++) {
final int j1 = i1;
commands[j1] = () -> {
String randomString = MyAdapter.getRandomString(j1)+" "+j1;
Log.e(TAG, randomString);
myDataset[j1] = randomString;
try { Thread.sleep(3000); }
catch (InterruptedException e) { e.printStackTrace(); }
// notifyDataSetChanged(); // <-------- Error
recyclerViewWeakReference.get().post(new Runnable() { // <---- this is the change
@Override
public void run() {
notifyDataSetChanged();
}
});
};
threadPoolExecutor.execute(commands[j1]);
}
}
So as you can see, I tried the third option. First I created a WeakReference<RecyclerView> recyclerViewWeakReference = new WeakReference<RecyclerView>(myRecyclerView)
in the parent fragment (or activity if you are using that). Then I passed the weak reference into MyAdapter
. I used weakReference
because that is what you do with AsyncTask,so my instincts alerted me to do so. I hope this is helpful.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论