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
而不是阻塞主线程来获取查询结果。
我正在从我的 activity 开始一个新线程,这个线程执行 10 秒操作,然后使用 runOnUiThread()
在 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
而不是阻塞主线程来获取查询结果。