Android Как я могу вызвать RecyclerVIew notifyDataSetChanged () после завершения ThreadPoolExecutor? - PullRequest
0 голосов
/ 04 мая 2020

Я изучаю ThreadPoolExecutor, следуя этому учебнику . Чтобы продемонстрировать его использование, я сделал простой проект android, он имеет обзор переработчика, который покажет некоторые строки. Первоначально массив строк (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, я понимаю, что эта ошибка означает, что я не могу вызвать этот метод в фоновом потоке, но я должен вызывать его в основном потоке / потоке пользовательского интерфейса (так в AsyncTask, это будет выглядеть так:

@Override
    protected void onPostExecute(String result) {
      weakReference.get().notifyDataSetChanged(); // something like that
    }

). Мне нужен его аналог 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; // <--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

}

Может ли кто-нибудь мне помочь? Спасибо за чтение.

Ответы [ 2 ]

1 голос
/ 05 мая 2020

Существует класс класса обработчика под капотом во всех случаях, когда вам необходимо связаться с UIThread из другого потока (AsyncTask также использует его).

Некоторые из возможных вариантов:

  1. Использовать обработчик, подключенный к главному петлителю:

    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. Используйте метод "post" вашего UI-View (например, RecyclerView):

    yourRecyclerView.post(new Runnable() { @Override public void run() { notifyDataSetChanged(); } });

0 голосов
/ 05 мая 2020

В случае, если кому-то нужно, вот что я сделал, основываясь на ответе sergiy-tikhonov .

    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]);
        }
    }

Итак, как вы можете видеть, я попробовал третий вариант. Сначала я создал WeakReference<RecyclerView> recyclerViewWeakReference = new WeakReference<RecyclerView>(myRecyclerView) в родительском фрагменте (или активность, если вы используете это). Затем я передал слабую ссылку в MyAdapter. Я использовал weakReference, потому что это то, что вы делаете с AsyncTask, поэтому мои инстинкты предупредили меня об этом. Я надеюсь, что это полезно.

...