多线程开发(二)-Thread、 Looper与Handler关系解密

第3节 Handler

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

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

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

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

3.1 Thread、Looper与Handler的关系

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

Runnable runnable = new Runnable() {

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

        }
    }
};
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

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

@Override
public void run() {
    ......
    Looper.prepare();
    ......
    //相当于while()循环
    Looper.loop();
    ......
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

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

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

        msg.recycleUnchecked();
    }
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

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);
        }
    }
};
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

任何想要布置任务的线程只需要利用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();
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

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

除了使用

Message msg = mHandler.obtainMessage(MSG_XXX);
mHandler.sendMessage(msg);
 
 
  • 1
  • 2

也可以使用

mHandler.obtainMessage(MSG_XXX).sendToTarget();
 
 
  • 1

这里使用的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);
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

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

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

//延迟1000毫秒发送消息
mHandler.sendDelayedMessage(msg,1000)
 
 
  • 1
  • 2
  • 3
  • 4
  • 5

3.2.2 使用post()

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

    Handler mHandler = new Handler();
       
       
    • 1
  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();
       
       
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

还可以使用

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

3.3 Handler任务的移除

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

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

这时,程序就会出错。所以当一个Activity退出销毁的时候,一定要把相关的任务移除。

3.3.1 使用removeMessages()

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

mHandler.removeMessages(MSG_XXX);
 
 
  • 1

3.3.2 使用removeCallbacks()

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

mHandler.removeCallbacks(mHandlerRunnable);
 
 
  • 1

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

第4节 HandlerThread

3.1章节介绍了Thread、Looper与Handler的关系。为了让开发者能简单的使用具备LooperThread,而不需要开发者写代码组合它们,Android SDK提供了HandlerThread

HandlerThread的原理很简单,

  1. 创建一个线程A;
  2. 在A上创建一个Looper

这些这个新创建的线程就可以不停的接收和处理任务了。

4.1 HandlerThread的使用

  1. 创建并启动HandlerThread

    HandlerThread mHandlerThread = new HandlerThread("创建的线程名字");
    mHThread.start();
       
       
    • 1
    • 2
  2. 获取可以访问HandlerThread对象任务队列的Handler;把Thread的Looper指定给Handler

    Handler mHandler = new Handler(mHandlerThread.getLooper());
       
       
    • 1
  3. 使用HandlerHandlerThread对象布置任务;

    mHandler.sendMessage(MSG_XXX);
       
       
    • 1
  4. HandlerThread对象退出;

    //马上退出线程
    mHandlerThread.quit();
    
    //执行完队列中的任务后退出线程
    mHandlerThread.quitSafely();
       
       
    • 1
    • 2
    • 3
    • 4
    • 5

4.2 HandlerThread与Handler、Thread名称区分

HandlerThread与Handler、Thread名字相似,这里再做一下澄清。

  • Thread:线程,单纯的一个线程;
  • HandlerThread:具备任务队列的线程,它就是一个线程;
  • Handler:可以访问指定线程任务队列的一个工具;

第3节 Handler

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

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

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

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

3.1 Thread、Looper与Handler的关系

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

Runnable runnable = new Runnable() {

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

        }
    }
};
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

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

@Override
public void run() {
    ......
    Looper.prepare();
    ......
    //相当于while()循环
    Looper.loop();
    ......
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

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

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

        msg.recycleUnchecked();
    }
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

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);
        }
    }
};
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

任何想要布置任务的线程只需要利用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();
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

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

除了使用

Message msg = mHandler.obtainMessage(MSG_XXX);
mHandler.sendMessage(msg);
 
 
  • 1
  • 2

也可以使用

mHandler.obtainMessage(MSG_XXX).sendToTarget();
 
 
  • 1

这里使用的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);
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

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

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

//延迟1000毫秒发送消息
mHandler.sendDelayedMessage(msg,1000)
 
 
  • 1
  • 2
  • 3
  • 4
  • 5

3.2.2 使用post()

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

    Handler mHandler = new Handler();
       
       
    • 1
  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();
       
       
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

还可以使用

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

3.3 Handler任务的移除

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

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

这时,程序就会出错。所以当一个Activity退出销毁的时候,一定要把相关的任务移除。

3.3.1 使用removeMessages()

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

mHandler.removeMessages(MSG_XXX);
 
 
  • 1

3.3.2 使用removeCallbacks()

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

mHandler.removeCallbacks(mHandlerRunnable);
 
 
  • 1

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

第4节 HandlerThread

3.1章节介绍了Thread、Looper与Handler的关系。为了让开发者能简单的使用具备LooperThread,而不需要开发者写代码组合它们,Android SDK提供了HandlerThread

HandlerThread的原理很简单,

  1. 创建一个线程A;
  2. 在A上创建一个Looper

这些这个新创建的线程就可以不停的接收和处理任务了。

4.1 HandlerThread的使用

  1. 创建并启动HandlerThread

    HandlerThread mHandlerThread = new HandlerThread("创建的线程名字");
    mHThread.start();
       
       
    • 1
    • 2
  2. 获取可以访问HandlerThread对象任务队列的Handler;把Thread的Looper指定给Handler

    Handler mHandler = new Handler(mHandlerThread.getLooper());
       
       
    • 1
  3. 使用HandlerHandlerThread对象布置任务;

    mHandler.sendMessage(MSG_XXX);
       
       
    • 1
  4. HandlerThread对象退出;

    //马上退出线程
    mHandlerThread.quit();
    
    //执行完队列中的任务后退出线程
    mHandlerThread.quitSafely();
       
       
    • 1
    • 2
    • 3
    • 4
    • 5

4.2 HandlerThread与Handler、Thread名称区分

HandlerThread与Handler、Thread名字相似,这里再做一下澄清。

  • Thread:线程,单纯的一个线程;
  • HandlerThread:具备任务队列的线程,它就是一个线程;

  • Handler:可以访问指定线程任务队列的一个工具;

附:http://www.w2bc.com/article/148413

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值