多线程开发(中)

更新时间 修改意见
2016-08-02 陈敏

第3节 Handler

多个线程之间除了有“执行的同步”关系,还有“数据的共享”关系,以及“功能的委托”关系。

例如之前提到的视频信息显示到列表上,

  1. 委托数据查询功能:主线程启动一个工作线程thread-查询视频信息;
  2. 委托数据的界面更新功能:工作线程查询完成后,因为不能够修改界面元素,所以必须将结果通知到主线程,委托主线程将得到的结果显示到界面上。

为此,Android SDK提供了Handler帮助各个不同的线程之间传递数据或委托功能处理。

3.1 Thread、Looper与Handler的关系

一个线程创建并start以后,就会开始执行它Runnable中的run()方法。如果要这个线程一直运行而不退出的话,就要在里面设置一个循环,

Runnable runnable = new Runnable() {

    @Override
    public void run() {
        //开始循环   
        while(xxx)
        {

        }
    }
};

Looper可以为Thread创建这样一个循环的环境,

@Override
public void run() {
    ......
    Looper.prepare();
    ......
    //相当于while()循环
    Looper.loop();
    ......
}

Looper具有一个消息队列,可以存放任何线程(包括它自己)给自己布置的任务。这些任务被一条一条的放在队列当中,在loop()函数中被取出来-执行,然后又取出来-执行,周而复始,永不停止。

public static void loop() {
    ......
    for (;;) {
        //从消息队列queue中取出任务
        Message msg = queue.next();
        //用消息提供的方法来处理每个消息蕴含的任务
        msg.target.dispatchMessage(msg);

        msg.recycleUnchecked();
    }
}

Handler是和Looper相关联的,通过Handler,任何线程可以把需要完成的任务放到Looper的消息队列里面。

Thread就好比一条产线,Looper中的消息队列就是这个流水线上的传送带,带子上分成了很多格,每一格放要处理的原料和处理这些原料的工人(原料和工人打包成了一个Message)。等轮到格子上的工人时,工人才能开始处理格子里放的原料。Handler就像是一个转运工具,提供给别的模块使用。这个工具可以把原料和工人放到产线的传送带上。

注意,一条产线只有一条传送带(Looper);但可以有多个为同一条产线提供转运服务的转运工具(Handler)。

所以使用这种产线的流程是,

  1. 创建一条产线A;
  2. 在这条产线A上创建一条传送带;
  3. 当别的模块B要向这条产线布置任务的时候,就要创建在产线A上工作的工人;
  4. B要告诉工人携带哪些原料,怎么处理这些原料;
  5. 等轮到产线A上对应的格子被处理的时候,上面的工人就开始操作了;

3.2 Handler的使用

Handler最常见的使用场景是:

  1. 为了进行耗时操作,主线程创建一个工作线程完成耗时操作;
  2. 工作线程开始耗时工作,时不时向主线程发送消息,告知当前完成的状态;
  3. 主线程根据工作线程的报告,更新界面元素,展示工作进度;

为了实现这样的功能,我们首先需要知道,

  1. 每个Activity都是运行在程序的主线程当中的;
  2. 只有主线程能修改界面元素,其他线程修改的话,应用会崩溃;
  3. 主线程在创建之后,系统已经为它设置了Looper,主线程已经有了消息处理的队列(生产流水线和流水线上的格子);

Handler处理这种场景时,我们有2种方式。

3.2.1 使用sendMessage()

  1. 创建一个能向主线程消息队列中布置任务的Handler;如果创建时直接new Handler(),说明这个Handler是放在主线程的消息队列中的“工人”;
  2. 重写HandlerhandleMessage()函数;
  3. handleMessage()函数中,根据msg.what的不同,进行对应的处理;
//创建的Handler是挂在主线程上的
private Handler mHandler = new Handler()
{
    @Override
    public void handleMessage(Message msg) {
        switch(msg.what)
        {
            case MSG_XXX:
            {
                //这里是在主线程中执行的,可以修改界面元素了
            }
            break;

            case ......
            break;

            default:
                super.handleMessage(msg);
        }
    }
};

任何想要布置任务的线程只需要利用HandlersendMessage()函数,就能把任务放到主线程的消息队列中,

Runnable runnable = new Runnable() {

    @Override
    public void run() {
        //耗时的操作   
        while(!stop)
        {
            Message msg = mHandler.obtainMessage(MSG_XXX);
            //用主线程的Handler向主线程布置任务
            mHandler.sendMessage(msg);
        }
    }
};

Thread work = new Thread(runnable);
work.start();

这样主线程的消息队列中就有了这个新任务。等到这个消息被处理的时候,主线就可以根据参数修改界面元素了。

除了使用

Message msg = mHandler.obtainMessage(MSG_XXX);
mHandler.sendMessage(msg);

也可以使用

mHandler.obtainMessage(MSG_XXX).sendToTarget();

这里使用的Message就携带了“工人”和“原料”,Message通过Handler对象获取,

//只设置Message的what数值
Message msg = mHandler.obtainMessage(what);

//设置Message的what数值和Object值
Message msg = mHandler.obtainMessage(what, object);

//设置Message的what数值,arg1、arg2和Object值
Message msg = mHandler.obtainMessage(what, arg1, arg2, object);

发送Message可以直接发送,也可以延时发送,

//直接发送消息,将任务布置到消息队列中
mHandler.sendMessage(msg);

//延迟1000毫秒发送消息
mHandler.sendDelayedMessage(msg,1000)

3.2.2 使用post()

  1. 创建一个能向主线程消息队列中布置任务的Handler;如果创建时直接new Handler(),说明这个Handler是放在主线程的消息队列中的工人

    Handler mHandler = new Handler();
  2. 在工作线程中使用Handlerpost()方法,里面的Runnable就是在Handler所服务线程中运行;

    Runnable mHandlerRunnable;
    Runnable runnable = new Runnable() {
    
        @Override
        public void run() {
            //耗时的操作   
            while(!stop)
            {
                mHandlerRunnable = new Runnable() {
                    @Override
                    public void run() {
                        //这里是在主线程中执行的,可以修改界面元素了
                    }
                };
                //用主线程的Handler向主线程发送任务
                mHandler.post(mHandlerRunnable);
            }
        }
    };
    
    Thread work = new Thread(runnable);
    work.start();

还可以使用

//延迟1000毫秒执行runnable
mHandler.postDelayed(mHandlerRunnable, 1000);

3.3 Handler任务的移除

大多数情况下,当Activity退出以后,需要将它布置给主线程的任务给移除掉。如果不移除,可能会遇到大麻烦。可以想象一下,

  1. 工作线程通过Handler给主线程布置了一个任务-根据一个参数修改界面显示,此时这个任务已经放到了主线程的任务队列里面;
  2. 用户突然退出了这个Activity,Activity被销毁了,但是主线程是不会退出的(Activity只是主线程上长的一个果子,果子被摘了,但是不会影响树的存在);
  3. 主线程的任务队列依次执行到了工作线程布置的任务,任务要求更新Activity上的界面元素,但是这个Activity已经被销毁了;

这时,程序的行为表现有可能和你希望的不同,出现一些与期望不符的现象,严重时,可能会造成程序崩溃。所以当一个Activity退出销毁的时候,一定要把相关的任务移除。这样做还有助于避免内存泄露。

3.3.1 使用removeMessages()

通过它移除任务队列中所有特定Message,

mHandler.removeMessages(MSG_XXX);

3.3.2 使用removeCallbacks()

通过它移除任务队列中所有特定Runnable,

mHandler.removeCallbacks(mHandlerRunnable);

这里的mHandlerRunnable就是之前post()方式使用的mHandlerRunnable,所以在使用post()方式的时候,我们要把Runnable的引用保存起来,以便以后的移除操作。


/*******************************************************************/
* 版权声明
* 本教程只在CSDN和安豆网发布,其他网站出现本教程均属侵权。

*另外,我们还推出了Arduino智能硬件相关的教程,您可以在我们的网店跟我学Arduino编程中购买相关硬件。同时也感谢大家对我们这些码农的支持。

*最后再次感谢各位读者对安豆的支持,谢谢:)
/*******************************************************************/

你可能感兴趣的:(多线程开发,大话安卓应用开发,Handler,Thread,Looper,handler与线程)