为何指定observeOn(Schedulers.io())后onNext仍能更新TextView?
嘿,这个问题挺有意思的——看起来你发现了Android UI线程检查里一个容易让人困惑的细节!首先得明确一个核心规则:Android官方要求所有UI操作必须在主线程执行,但你的代码没崩溃,并不是因为这个规则失效了,而是因为当前场景下没有触发Android的线程检查机制。
咱们一步步拆解:
1. Android的UI线程检查到底什么时候生效?
Android的UI线程检查是由ViewRootImpl负责的,它会在执行UI绘制(比如performTraversals)或者请求UI更新(比如requestLayout、invalidate)的时候,判断当前线程是不是创建它的主线程。但ViewRootImpl并不是在View创建时就存在的——它是在View被**附加到窗口(attachToWindow)**之后才会被初始化的。
比如你在Activity的onCreate方法里执行这段RxJava代码,此时Activity还没走完onStart/onResume流程,TextView还没被附加到窗口,ViewRootImpl还没创建,这时候调用setText只是修改了TextView内部的文本缓存,并不会触发UI绘制请求,自然也就不会触发线程检查。
2. 为什么你觉得它“成功更新”了?
如果TextView还没attach到窗口,你调用setText修改的只是它的内部状态,当后续Activity完成启动流程,View被附加到窗口后,主线程会自动执行一次UI绘制,把你之前设置的文本显示出来——看起来像是非主线程更新了UI,但实际上最终的绘制还是在主线程完成的。
但如果你的代码是在Activity的onResume之后执行(比如点击按钮触发),这时候TextView已经attach到窗口,调用setText会触发invalidate(),进而触发ViewRootImpl的线程检查,这时候一定会抛出CalledFromWrongThreadException异常。
3. 正确的做法是什么?
不管当前场景会不会崩溃,遵守Android的UI线程规则才是安全的。你应该把observeOn的调度器换成AndroidSchedulers.mainThread(),确保onNext在主线程执行:
Observable.range(11,10) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) // 切换到主线程 .subscribe(new Observer<Integer>() { @Override public void onSubscribe(@NonNull Disposable d) { } @Override public void onNext(@NonNull Integer integer) { textView.setText(String.valueOf(integer)); Log.d(TAG, "onNext: "+Thread.currentThread().getName()); } @Override public void onError(@NonNull Throwable e) { } @Override public void onComplete() { } });
这样不管在什么生命周期阶段执行,都能安全地更新UI,不会出现崩溃风险。
内容的提问来源于stack exchange,提问作者ChandrimG

