Handler与多线程

Handler与多线程

简述

  • 子线程不能更新UI

当我们在子线程中更新UI会导致Android报异常

AndroidRuntimeException :“Only the original thread that created a view hierarchy can touch its views”

因为如果支持多线程修改View的话,由此产生的线程同步和线程安全问题将是非常繁琐的,所以Android直接就定死了,View的操作必须在UI线程,从而简化了系统设计。但是,我们会发现现在Activity.onCreate()直接使用子线程去更新UI并不会报异常,查源码可知,该异常来自ViewRootImpl.checkThread()方法中,而ViewRootImpl在Activity.onResume()中被创建,即在Activity.onCreate()实际上还是可以子线程更新UI的,详情可查看

  • 主线程不能网络请求

由于网络请求过长时会阻塞主线程,导致ANR(应用程序无响应),所以在Android4.0 以后不允许在主线程进行网络连接,否则会出现 android.os.NetworkOnMainThreadException。因此,必须另起一个线程进行网络连接方面的操作。

子线程–>主线程

  • 使用Handler发消息
    protected Handler mHandler = new ActivityHandler(this);

    static class ActivityHandler extends Handler {
        WeakReference<MainActivity> mActivityReference;

        ActivityHandler(MainActivity activity) {
            mActivityReference = new WeakReference<>(activity);
        }

        @Override
        public void handleMessage(Message msg) {
            final MainActivity activity = mActivityReference.get();
            if (activity != null) {
                activity.handleMessage(msg);
            }
        }
    }

    protected void handleMessage(Message msg) {
        switch (msg.what) {
            case 0x001:
                text.setText("Task Done!!");
                break;
        }
    }

    protected void onCreate(Bundle savedInstanceState) {  
        super.onCreate(savedInstanceState);  
        setContentView(R.layout.activity_main);  
        button = (Button) findViewById(R.id.button);  //启动线程
        text = (TextView) findViewById(R.id.text);//耗时任务完成时在该TextView上显示文本  

        mRunnable = new Runnable() {  
            @Override  
            public void run() {  
                try {  
                    Thread.sleep(5000);//模拟耗时任务  
                } catch (InterruptedException e) {  
                    e.printStackTrace();  
                }
                mHandler.sendEmptyMessage(0x001);//耗时任务完成后发送消息,通知去更新UI
            }  
        };  

        button.setOnClickListener(new View.OnClickListener() {  
            @Override  
            public void onClick(View v) {  
                Thread thread = new Thread(mRunnable);  
                thread.start();  
            }  
        });  

    }

Handler机制可以看张鸿洋大神的博客– Android 异步消息处理机制 让你深入理解 Looper、Handler、Message三者关系

  • 使用Handler.post
    把mHandler.sendEmptyMessage(0x001);发消息方法更换成
       mHandler.post(new Runnable() {
           @Override
           public void run() {
               text.setText("Task Done!!");
           }
       });

查看Handler.post源码,发现其实还是调用的发送消息机制

//Handler.java
   public final boolean post(Runnable r){
        return  sendMessageDelayed(getPostMessage(r), 0);
    }
   private static Message getPostMessage(Runnable r) {
        Message m = Message.obtain();
        m.callback = r;
        return m;
    }

最后在Handler.handleCallback()中执行Runnable的run方法

    public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }
    private static void handleCallback(Message message) {
        message.callback.run();
    }
  • runOnUiThread
    runOnUiThread这是一个在Activity中的方法,在Fragment中没有,使用也很简单,在子线程中任务完成后调用该方法,把更新UI的任务放入Runnable.run中即可
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                text.setText("Task Done!!");
            }
        });

最后,再来看看源代码,发现这里调用的还是Handler的方法,调用了post方法,而post又是使用了sendMessageDelayed,所以说白了,还是使用的Handler消息机制

//Activity.java
    public final void runOnUiThread(Runnable action) {
        if (Thread.currentThread() != mUiThread) {
            mHandler.post(action);
        } else {
            action.run();
        }
    }
  • View.post
    使用方法跟前面差不多,只是替换成了
    text.post(new Runnable() {
        @Override
        public void run() {
            text.setText("Task Done!!");
        }
    });

同样的,来看看源码,

public boolean post(Runnable action) {
        final AttachInfo attachInfo = mAttachInfo;
        if (attachInfo != null) {
            return attachInfo.mHandler.post(action);
        }

        // Postpone the runnable until we know on which thread it needs to run.
        // Assume that the runnable will be successfully placed after attach.
        getRunQueue().post(action);
        return true;
    }

AttachInfo是在View第一次attach到Window时,ViewRoot传给自己的子View的。这个AttachInfo之后,会顺着布局体系一直传递到最底层的View。而mHandler在ViewRootImpl中被创建,也就是主线程的Handler.所以这个同样的也是使用的Handler消息机制.

    private class MyAsyncTask extends AsyncTask<Void, Integer, Void> {

        @Override
        protected void onPreExecute() {
            //执行前的准备,这里是主线程
        }

        @Override
        protected Void doInBackground(Void... params) {
            try {
                Thread.sleep(5000);//模拟耗时任务,这里是子线程
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return null;
        }

        @Override
        protected void onPostExecute(Void result) {
            // 进行数据加载完成后的UI操作,主线程
            text.setText("Task Done!!");
        }
    }

源码解析参考张鸿洋大神的Android AsyncTask 源码解析,最后的结论就是,还是使用了Handler消息机制

  • 总结
    以上五种方法使用的都是Handler的消息机制

主线程–>子线程

  • Thread、Runnable

这个是最传统的方法了,相信每个学过Java基础的人都知道。无非就是继承Thread类覆写run()然后通过thread.start()或实现Runnable接口复写run()然后new Thread(Runnable).start(),在上面的例子中就是通过这种最普通的方法去开新线程的,不过在实际开发中,这种开新线程的方法是很不被推荐的,理由如下:1)当你有多个耗时任务时就会开多个新线程,开启新线程的以及调度多个线程的开销是非常大的,这往往会带来严重的性能问题,例如,你有100个耗时任务,那就开100个线程。2)如果在线程中执行循环任务,只能通过一个Flag来控制它的停止,如while(!iscancel){//耗时任务}。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值