开门见山:
这个误区是:子线程不能更新 UI ,其应该分类讨论,而不是绝对的。
半小时前,我的 XRecyclerView 群里面,一位群友私聊我,问题是:
为什么我的子线程更新了 UI 没报错?
我叫他发下代码我看,如下,十分简单的代码。
@Overrideprotected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); title = (TextView) findViewById(R.id.title_tips); doGet("http;//www.baidu.com", new Callback() { @Override public void onFailure(Request request, IOException e) { } @Override public void onResponse(Response response) throws IOException { title.setText(response.body().string()); // 这里在子线程更新了 text } });}private void doGet(String url,Callback callback) { OkHttpClient client = new OkHttpClient(); Request.Builder builder = new Request.Builder(); Request request = builder.url(url).get().build(); client.newCall(request).enqueue(callback);}
简单解析下。他用了 OkHttp 的异步 enqueue 的请求,并在成功后更新了 textView 的 text。
明确一点:
- okhttp 的同步异步的回调都是在子线程里面的。
那么这样来说,按照我们被一直灌输的原理: 子线程不能刷新UI,上面这段代码妥妥地爆错啊。
而我要说的是:
上面的代码不一定爆错,它还会稳稳的顺利执行。
你十分怀疑了?
你可以尝试下。嫌麻烦,你可以运行下下面这段通透
的子线程更新UI代码
public class TestActivity extends Activity { private TextView title; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); title = (TextView) findViewById(R.id.title_tips); new Thread( new Runnable() { @Override public void run() { // 子线程更新UI title.setText("我 tm 妥妥地执行完毕"); } } ).start(); }}
试了的都知道,真 tm 执行了没爆错。
颠覆了吗?
原因
在看到他发给我的代码,onCreate
里面的部分,一切已经明了,这也是我之前面试几年经验的人设过的坑。下面我直接讲原因,源码分析那些你们自己去看吧,你应该去看
。
- 子线程不能更新 UI 的限制是 viewRootImpl.java 内部限制了
void checkThread() { // 该方法是 viewRootImpl.java 内部代码 if (mThread != Thread.currentThread()) { throw new CalledFromWrongThreadException( "Only the original thread that created a view hierarchy can touch its views."); }}
- 对组件 Activity 而言,viewRootImpl 的初始化在 onCreate 之后,onResume 之后。
- 如果你的子线程更新代码在满足下面的条件下,那么它可以顺利运行:
- 修改应用层的 viewRootImpl.java 源码,解除限制
- 把你更新代码写在 onResume 之前,例如 onCreate 里面,且,更新之际要赶在 viewRootImpl 初始化之前。
修改验证 --- 抛出错误
@Overrideprotected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); title = (TextView) findViewById(R.id.title_tips); new Thread( new Runnable() { @Override public void run() { try { // 等待 onResume 执行完,让 viewRootImpl 初始化完成 Thread.sleep(3000); // ---------- 这里,看这里 } catch (InterruptedException e) { e.printStackTrace(); } title.setText("我执行不了"); } } ).start();}