首页 > 解决方案 > 当活动被销毁然后重新创建时,未调用观察者并且后值无法从后台线程工作

问题描述

下面的简单示例代码有一个异步任务,它反复休眠 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);
}

标签: javaandroidmvvm

解决方案


任何想法为什么后值第一次从后台线程正常工作,但是当活动被销毁然后重新创建时不再工作?

简而言之:发生这种情况是因为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()上的文档。


推荐阅读