• 为什么我的子线程更新了 UI 没报错?借此,纠正一些Android 程序员的一个知识误区


    开门见山:

    这个误区是:子线程不能更新 UI ,其应该分类讨论,而不是绝对的。

    半小时前,我的 XRecyclerView 群里面,一位群友私聊我,问题是:

    为什么我的子线程更新了 UI 没报错?

    我叫他发下代码我看,如下,十分简单的代码。

    @Override
    protected 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 初始化之前。

    修改验证 --- 抛出错误

    @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() {
                        try {
                            // 等待 onResume 执行完,让 viewRootImpl 初始化完成
                            Thread.sleep(3000); // ---------- 这里,看这里
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        title.setText("我执行不了");
                    }
                }
        ).start();
    }
    

  • 相关阅读:
    ontentEditable和designMode的区别
    execCommand、queryCommandState
    ios微信h5音频audio无法自动播放
    微信二次分享图片不显示
    Airbnb javascript编码规范
    array reduce
    适配iPhoneX全屏
    web前端性能优化
    requestAnimationFrame实现动画
    js 中的forEach,for in ,for of 的使用
  • 原文地址:https://www.cnblogs.com/linguanh/p/7898996.html
Copyright © 2020-2023  润新知