Android如何在ThreadPoolExecutor完成后调用RecyclerView的notifyDataSetChanged()?

huangapple go评论81阅读模式
英文:

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&lt;MyAdapter.MyViewHolder&gt; {
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)+&quot; &quot;+i; // &lt;--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&lt;commands.length; i1++) {
final int j1 = i1;
commands[j1] = () -&gt; {
String randomString = MyAdapter.getRandomString(j1)+&quot; &quot;+j1;
Log.e(TAG, randomString);
myDataset[j1] = randomString;
try { Thread.sleep(3000); }
catch (InterruptedException e) { e.printStackTrace(); }
// notifyDataSetChanged();           // &lt;-------- 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也在使用它)。

一些可能的选择:

  1. 使用与主Looper相连的Handler:

    Handler handler = new Handler(getMainLooper());
    handler.post(new Runnable() {
        @Override
        public void run() {
            notifyDataSetChanged();
        }
    });
    
  2. 使用你提到的"runOnUiThread":

    runOnUiThread(new Runnable() {
        @Override
        public void run() {
            notifyDataSetChanged();
        }
    });
    
  3. 使用你的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:

  1. Use Handler, connected to main looper:

    Handler handler = new Handler(getMainLooper());
    handler.post(new Runnable() {
    @Override
    public void run() {
    notifyDataSetChanged();
    }
    });

  2. Use "runOnUiThread" that you've mentioned:

    runOnUiThread(new Runnable() {
    @Override
    public void run() {
    notifyDataSetChanged();
    }
    });

  3. 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&lt;commands.length; i1++) {
final int j1 = i1;
commands[j1] = () -&gt; {
String randomString = MyAdapter.getRandomString(j1)+&quot; &quot;+j1;
Log.e(TAG, randomString);
myDataset[j1] = randomString;
try { Thread.sleep(3000); }
catch (InterruptedException e) { e.printStackTrace(); }
// notifyDataSetChanged();           // &lt;-------- Error
recyclerViewWeakReference.get().post(new Runnable() {  // &lt;---- 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&lt;RecyclerView&gt; recyclerViewWeakReference = new WeakReference&lt;RecyclerView&gt;(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.

huangapple
  • 本文由 发表于 2020年5月4日 19:43:36
  • 转载请务必保留本文链接:https://go.coder-hub.com/61591457.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定