java - 当活动被销毁然后重新创建时,未调用观察者并且后值无法从后台线程工作
问题描述
下面的简单示例代码有一个异步任务,它反复休眠 2 秒,然后触发一个回调函数(我在这里onResultAvailable
使用"some text .."
它来模拟一个接收消息的 websocket 线程......)。
类中的onResultAvailable
方法应该使用(因为它是从后台线程调用的)MyViewModel
更新变量text
(这是 a )。MutableLiveData<String>
postValue
所以这段代码应该text
每两秒更新一次变量,并通过观察者显示文本MyTestActivity
;所以它应该显示以下内容,它第一次按预期工作:
Some text 0
Some text 1
Some text 2
... etc
但是,当我按下后退按钮然后MyTestActivity
再次打开时,text
变量不再被修改,postvalue
观察者不再被调用。请注意,使用修改的其他变量setvalue
仍按预期运行。
任何想法为什么postvalue
第一次从后台线程工作正常,但是当活动被销毁然后重新创建时不再工作?在这种情况下,如何更新text
变量的值?
MainActivity.java
public class MainActivity extends AppCompatActivity {
Button button;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
button = findViewById(R.id.myButton);
button.setOnClickListener(v -> {
startActivity(new Intent(MainActivity.this, MyTestActivity.class));
});
}
}
MyTestActivity.java
public class MyTestActivity extends AppCompatActivity {
TextView myTextView, myTextViewTime;
Button myButton;
MyViewModel vm;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_my_test);
myTextView = findViewById(R.id.myTextView);
myTextViewTime = findViewById(R.id.myTextViewTime);
myButton = findViewById(R.id.myButton);
vm = ViewModelProviders.of(this).get(MyViewModel.class);
vm.getText().observe(this, text -> {
myTextView.setText(text);
Log.d("DEBUG", "observed: " + text);
});
vm.getTimestamp().observe(this, strTs -> {
myTextViewTime.setText(strTs);
Log.d("DEBUG", "observed: " + strTs);
});
myButton.setOnClickListener(v -> {
vm.setTimestamp("" + System.currentTimeMillis());
});
}
}
MyViewModel.java
public class MyViewModel extends ViewModel implements MyAsyncTaskDelegate {
private MutableLiveData<String> text;
private MutableLiveData<String> timestamp;
private MyAsyncTask task;
public MyViewModel() {
text = new MutableLiveData<>();
text.setValue("Hello World!");
timestamp = new MutableLiveData<>();
timestamp.setValue(""+System.currentTimeMillis());
task = new MyAsyncTask(this);
task.execute();
}
public LiveData<String> getText() {
return text;
}
public LiveData<String> getTimestamp() {
return timestamp;
}
public void setTimestamp(String ts) {
timestamp.setValue(ts);
}
@Override
public void onResultAvailable(String result) {
text.postValue(result);
}
}
MyAsyncTask.java
public class MyAsyncTask extends AsyncTask<Void, Void, Void> {
private MyAsyncTaskDelegate delegate;
MyAsyncTask(MyAsyncTaskDelegate delegate) {
this.delegate = delegate;
}
@Override
protected Void doInBackground(Void... voids) {
for(int i = 0; i < 100; i++) {
try {
Thread.sleep(2000);
delegate.onResultAvailable("Some text " + i);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return null;
}
}
MyAsyncTaskDelegate.java
public interface MyAsyncTaskDelegate {
void onResultAvailable(String result);
}
解决方案
任何想法为什么后值第一次从后台线程正常工作,但是当活动被销毁然后重新创建时不再工作?
简而言之:发生这种情况是因为MyAsyncTask.doInBackground()
会无限循环,并且对于大多数 Android 版本,s 没有并行执行AsyncTask
。
在MyTestActivity.onCreate()
中,您生成一个新实例,MyViewModel
该实例开始它自己的MyAsyncTask
。因为第一个实例MyAsyncTask
永远不会结束,所以第二个实例永远不会真正开始。的第一个实例MyAsyncTask
仍然保留MyViewModel
(aka MyAsyncTaskDelegate
) 的第一个实例。但由于第一个MyViewModel
实例与活动 UI 元素没有任何连接,因此更新不会显示在屏幕上。
您可以使用调试器通过在各种方法中设置断点并比较“实例编号”来验证这一点(每个类名都有一个“@”和一个唯一编号)
请注意,仅当您MyTestActivity
按 BACK 离开时才会发生有问题的行为。如果您旋转设备,更新将继续。
这是“按预期工作”。通过按 BACK,您表明您不再需要 的当前实例MyTestActivity
:它不会保留在后堆栈中,它可以并且将被完全销毁。
这AsyncTask
是另一种情况:它是 的成员,MyViewModel
另一方面持有对 的引用MyViewModel
。可以说存在引用死锁:只有在整个周围进程停止时才会停止。
在这种情况下如何更新文本变量的值?
覆盖onBackPressed()
并MyTestActivity
停止执行MyAsyncTask
. 有关如何执行此操作,请参阅AsyncTask.cancel()上的文档。
推荐阅读
- javascript - 获取数据失败:this.functionname 不是函数(ReactJS)
- json - 当我点击列表时,如何在详细信息屏幕中传递数据?
- javascript - 在数组中获取 indexOf 单击的按钮父级
- python - 我如何循环代码直到用户输入更改python中的变量
- android - 如何使用 CLI 命令检查 Android Gradle 依赖项的更新?
- scrollview - 在 SwiftUI 中,NavigationBarItem 在由嵌入在 List 或 Form 内的 ScrollView 中的 NavigationLink 推送的 View 中不起作用?
- javascript - 在 Webpack4 中设置 ./src 的路径?
- laravel - 为什么我在 Laravel 中得到一个未定义的常量错误?
- javascript - ExpressJs 在 Vanilla jS 上路由和显示页面
- gnuplot - 在 Gnuplot 输出中压缩的图例、标题和轴标签