java - Android:另一个线程导致 UI 无响应?

标签 java android android-threading

我正在从我的 Activity 中启动一个新线程,该线程执行 10 秒的操作,然后使用 runOnUiThread() 向 UI 报告

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

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

Profiler 显示 UI 线程在此工作期间正在 hibernate ,因此根据我的理解,它应该具有响应能力?我尝试使用 AsyncTask 代替,但这也不起作用。无论如何,这里有一些代码:

当窗口成为焦点时,新的线程启动:

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 秒的繁重操作房间数据库操作。

这是 Profiler 的快照,显示 sleep UI 线程和辅助线程(AsyncTask #6):

enter image description here

编辑:

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

编辑2:

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

 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 完成。您可以实现接口(interface)回调来解决这个问题。

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 秒。在此过程中主线程是可访问的。

我希望这一次也适合您

关于java - Android:另一个线程导致 UI 无响应?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/58844492/

相关文章:

android - 如何解决致命异常 : DefaultDispatcher-worker-4

java - Arraylist追加不覆盖文本文件

java - 如何正确对齐 JFrame 内的两个 JPanel

MainLooper 未执行 Android Runnable

android - 改造@Url 不会覆盖 BaseUrl

android - CardView 中包含的 ListView 未显示全长

android - AsyncTask 和 Thread/Runnable 的区别

java - 单击取消按钮 showInputDialog

java - 获取最后提交者的用户名?

java - 我如何知道我的 Observable 类发生了什么变化?