Android:另一个线程使 UI 无响应?

Android: Another thread is making the UI unresponsive?

我正在从我的 activity 开始一个新线程,这个线程执行 10 秒操作,然后使用 runOnUiThread()

向 UI 报告

在 10 秒的操作期间,UI 变得无响应并且不响应任何用户交互。在这种情况下,我试图使用工具栏中的按钮关闭 activity。抛出 ANR 错误,但在工作线程完成后处理按钮单击。

尽管线程正在工作,应用程序仍然能够显示旋转 ProgressBar 如果工作是在 UI 线程上完成的,则不会发生这种情况。

Profiler 显示 UI 线程在这项工作期间正在休眠,所以据我了解它应该是响应式的?。我试过使用 AsyncTask 代替,但这也不起作用。无论如何,这是一些代码:

当 window 成为焦点时,新的 Thread 开始:

Activity:

  @Override
    public void onWindowFocusChanged(boolean hasFocus) {
        super.onWindowFocusChanged(hasFocus);

        if(hasFocus && !recyclerSetup){
            progressBar.setIndeterminate(true);
            progressBar.setVisibility(View.VISIBLE);
            WorkThread thread = new WorkThread();
            thread.start();
        }
    }

线程:

  private class WorkThread extends Thread {

        @Override
        public void run() {
            getViewModelAndWords();
            runOnUiThread(() -> setupRecycler());
        }
    }

  private void getViewModelAndWords() {
        viewModel = ViewModelProviders.of(this).get(WordViewModel.class);
        adapter = new WordDetailedAdapter(this, viewModel, this, this, !favGroup.equals(ANY_WORD_PARAM));
        allWords = viewModel.getAllWords();
    }

我不确定 viewModel 是否与此问题有关,但 viewModel.getAllWords() 方法执行繁重的 10 秒 Room 数据库操作。

这是 Profiler 的快照,显示 正在休眠 UI 线程和工作线程 Thread(AsyncTask #6):

编辑:

好的,所以我认为问题在于房间数据库操作/viewModel。将 getAllWords() 的内容替换为 Thread.sleep(10000); 释放了 UI 线程用于用户交互,因此以下代码(出于某种原因)阻止了用户输入:

编辑 2:

按照建议,我现在使用 onPostExecute() 和一个接口来检索单词:

 public static class GetAllWordsWithCallBackTask extends AsyncTask<Void, Void, List<Word>>{

        WordViewModel.iGetWords listener;
        WordDao wordDao;

        public GetAllWordsWithCallBackTask(WordDao wordDao, WordViewModel.iGetWords listener) {
            this.listener = listener;
            this.wordDao = wordDao;
        }

        @Override
        protected List<Word> doInBackground(Void... voids) {
            return wordDao.getAllWords();
        }

        @Override
        protected void onPostExecute(List<Word> words) {
            listener.gotWords(words);
        }
    }

get() 已被删除,我只是执行任务,传入 listener 来处理回调:

  public void getAllWordsWithCallBack(WordViewModel.iGetWords listener) {
        try {
            new GetAllWordsWithCallBackTask(wordDao, listener).execute();
        } catch (Exception e) {
            Crashlytics.log("Getting all words exception: "+e.getMessage());
            e.printStackTrace();
        }
    }

这很好用,单词成功返回到我的 activity,但是 UI 在执行操作时仍然没有响应

编辑 1 您在 AsyncTask 上调用 .get()。调用线程等待 AsyncTask 完成。您可以实现接口回调来解决此问题。

Here is a solution for you're problem

编辑 2:
我仔细查看了您的代码,再次确认,您在此处发布的代码没有错误。

使用带回调的 AsyncTask 是一种可能的解决方案。您的代码在后台线程中运行,结果会在不阻塞的情况下传递给主线程。

我认为你的错误在于将数据从回调传输到 ViewModel 或 MainActivity。
解决此问题的最佳解决方案是使用 LiveData

我试图尽可能接近地重建你的代码。也许它会帮助你找到错误。

WordDb

@Database(entities = {Word.class}, version = 3)
@TypeConverters(DateConverter.class)
public abstract class WordDb extends RoomDatabase {

    private static WordDb INSTANCE;

    public abstract WordDao wordDao();

    static synchronized WordDb getInstance(Context contextPassed){
        if(INSTANCE == null){
            INSTANCE = Room.databaseBuilder(contextPassed.getApplicationContext(),WordDb.class,"word_db")
                    .fallbackToDestructiveMigration()
                    .build();
        }
        return INSTANCE;
    }
}

WordRepo

class WordRepo {
    private WordDao wordDao;

    WordRepo(Context applicationContext) {
        WordDb wordDb = WordDb.getInstance(applicationContext);
        wordDao = wordDb.wordDao();
    }

    void getAllWords(WordRepo.iGetWords listener) {
        try {
            Log.i("WordRepo", String.format("getAllWords() called from %s", Thread.currentThread().getName()));
            new GetAllWordsWithCallBackTask(wordDao, listener).execute();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static class GetAllWordsWithCallBackTask extends AsyncTask<Void, Void, List<Word>> {

        WordRepo.iGetWords listener;
        WordDao wordDao;

        GetAllWordsWithCallBackTask(WordDao wordDao, WordRepo.iGetWords listener) {
            this.listener = listener;
            this.wordDao = wordDao;
        }

        @Override
        protected List<Word> doInBackground(Void... voids) {

            Log.i("WordRepo", String.format("GetAllWordsWithCallBackTask.doInBackground() called from %s", Thread.currentThread().getName()));
            return wordDao.getAll();
        }

        @Override
        protected void onPostExecute(List<Word> words) {
            Log.i("WordRepo", String.format("GetAllWordsWithCallBackTask.onPostExecute() called from %s", Thread.currentThread().getName()));

            listener.gotWords(words);
        }
    }

    public interface iGetWords {
        void gotWords(List<Word> words);
    }
}

MainViewModel

public class MainViewModel extends AndroidViewModel {

    MutableLiveData<List<Word>> wordList = new MutableLiveData<>();
    private static final String TAG = "MainViewModel";

    public MainViewModel(@NonNull Application application) {
        super(application);
    }

    void getAllWords() {

        Log.i(TAG, String.format("getAllWords() called from %s", Thread.currentThread().getName()));
        WordRepo repo = new WordRepo(getApplication());

        repo.getAllWords(new WordRepo.iGetWords() {
            @Override
            public void gotWords(List<Word> words) {
                wordList.setValue(words);
            }
        });
    }
}

MainActivity 中的 getViewModelAndWords()

    private void getViewModelAndWords() {

        Log.i(TAG, String.format("getViewModelAndWords() called from %s", Thread.currentThread().getName()));

        viewModel = ViewModelProviders.of(this).get(MainViewModel.class);

        viewModel.wordList.observe(this, new Observer<List<Word>>() {
            @Override
            public void onChanged(List<Word> words) {
                //Do something with youre result
                Log.i(TAG, String.format("viewModel.wordList livedata returned %d results", words != null ? words.size() : -1));
            }
        });

        viewModel.getAllWords();


        Log.i(TAG, "viewModel.getAllWords() done");


    }

如果您发现您的代码出了什么问题,请发表评论

正如@mayokun 已经提到的,我建议使用 RxJava 或将您的项目迁移到 Kotlin + Coroutines 以保持您的代码干净整洁。

在这里你可以找到更多:

Medium - Coroutines on Android (part I): Getting the background

CodeLabs - Using Kotlin Coroutines in your Android App

Medium - RxAndroid Basics: Part 1

Medium - RxJava VS. Coroutines In Two Use Cases

我已经用大约 300,000 条记录成功地测试了这段代码。 运行 此操作已在我的模拟器上阻止异步任务大约 30 秒。在此过程中可以访问主线程。

希望这次也对你有用

  return new GetAllWordAsyncTask(wordDao).execute().get();

通过调用 get(),您强制当前调用线程同步等待结果返回,这会使您的后台查询在执行时阻塞主线程。

解决方案是使用回调和 onPostExecute 而不是阻塞主线程来获取查询结果。