• 主线程与子线程之间相互通信


          在平时写程序时,我们一般都是在子线程中向主线程发送消息,从而完成请求的处理,这个很常见,不用多说了。那么有时候,我们也可能碰到这样子的一种需求:需要主线程来向子线程发送消息,希望子线程来完成什么任务。如果这样子应该怎么做呢?这就是这篇文章将要讨论的内容。

    一、HandlerThread类

          主线程发送消息给子线程,通常思维逻辑就是:其实很简单,在主线程中实例化一个Handler,然后让他与子线程相关联(只要它与子线程的Looper相关联即可),这样子它处理的消息就是该子线程中的消息队列,而处理的逻辑都是在该子线程中执行的,不会占用主线程的时间。那么我们就来实现一下,看看这样子到底行得通还是行不通。新建项目,修改它的MainActivity的代码,如下即可:

     1 package com.example.handldertest;
     2 
     3 import android.app.Activity;
     4 import android.os.Bundle;
     5 import android.os.Handler;
     6 import android.os.Looper;
     7 import android.util.Log;
     8 import android.widget.TextView;
     9 
    10 public class ThreadHandlerActivity extends Activity{
    11     
    12 
    13     //创建子线程
    14     class MyThread extends Thread{
    15         private Looper looper;//取出该子线程的Looper
    16         public void run() {
    17          
    18             Looper.prepare();//创建该子线程的Looper
    19             looper = Looper.myLooper();//取出该子线程的Looper
    20             Looper.loop();//只要调用了该方法才能不断循环取出消息
    21         }
    22     }
    23     
    24     private TextView tv;
    25     private MyThread thread;
    26     
    27     
    28     private Handler mHandler;//将mHandler指定轮询的Looper
    29     
    30     protected void onCreate(Bundle savedInstanceState) {
    31             super.onCreate(savedInstanceState);
    32             tv = new TextView(this);
    33             tv.setText("Handler实验");
    34             setContentView(tv);
    35             thread = new MyThread();
    36             thread.start();//千万别忘记开启这个线程
    37             //下面是主线程发送消息
    38             mHandler = new Handler(thread.looper){
    39                 public void handleMessage(android.os.Message msg) {
    40                     Log.d("当前子线程是----->", Thread.currentThread()+"");
    41                 };
    42             };
    43             mHandler.sendEmptyMessage(1);
    44     }
    45 
    46 }

          好了,现在运行该程序。有没有得到预期的结果呢?显然没有,因为报错误了,如下:

          这是一个空指针错误。这是为什么呢?仔细思考,也不难发现原因。因为当主线程走到第38行时,此时子线程的Looper对象还没有被创建出来,那么此时thread.looper肯定为空了。其实这个时间是很不好控制的,当然了,你可以让主线程休眠2秒后再执行第38行以后的代码。但是如果有很多个子线程都需要主线程类给其分配任务怎么办??那简直要乱套了。所以我们就更好的解决方式。就是android显然也考虑到了这个问题,于是它我们提供了一个HandlerThread类。这个类是专门处理这个问题的。

          当主线程中有耗时的操作时,需要在子线程中完成,通常我们就把这个逻辑放在HandlerThread的对象中执行(该对象就是一个子线程),然后在需要开始执行逻辑的地方发送一个Message来通知一下就可以了。下面我们就修改上面的代码,看一看如何使用HandlerThread这个类。修改MainActivity中的代码如下:

     1 package com.example.handldertest;
     2 
     3 import android.app.Activity;
     4 import android.os.Bundle;
     5 import android.os.Handler;
     6 import android.os.HandlerThread;
     7 import android.util.Log;
     8 import android.widget.TextView;
     9 
    10 public class ThreadHandlerActivity extends Activity{
    11     
    12 
    13     
    14     private TextView tv;
    15 
    16     private Handler mHandler;//将mHandler指定轮询的Looper
    17     
    18     protected void onCreate(Bundle savedInstanceState) {
    19             super.onCreate(savedInstanceState);
    20             tv = new TextView(this);
    21             tv.setText("Handler实验");
    22             setContentView(tv);
    23         
    24             //实例化一个特殊的线程HandlerThread,必须给其指定一个名字
    25             HandlerThread thread = new HandlerThread("handler thread");
    26             thread.start();//千万不要忘记开启这个线程
    27             //将mHandler与thread相关联
    28             mHandler = new Handler(thread.getLooper()){
    29                 public void handleMessage(android.os.Message msg) {
    30                     Log.d("当前子线程是----->", Thread.currentThread()+"");
    31                 };
    32             };
    33             mHandler.sendEmptyMessage(1);//发送消息
    34     }
    35 
    36 }

          运行程序,打印的结果如下:

         从打印结果来看,当前子线程的名字正是我们所起的那个名字“handler thread"。

         你会有疑问,表面上看HandlerThread并没有创建自己的Looper啊?而且既然是一个线程,那么我们肯定也能重写它的run方法吧。在解答你的疑问之前,我们不妨重写它的run方法来看一看会有什么结果。将代码修改如下:

     1 package com.example.handldertest;
     2 
     3 import android.app.Activity;
     4 import android.os.Bundle;
     5 import android.os.Handler;
     6 import android.os.HandlerThread;
     7 import android.util.Log;
     8 import android.widget.TextView;
     9 
    10 public class ThreadHandlerActivity extends Activity{
    11     
    12 
    13     
    14     private TextView tv;
    15 
    16     private Handler mHandler;//将mHandler指定轮询的Looper
    17     
    18     protected void onCreate(Bundle savedInstanceState) {
    19             super.onCreate(savedInstanceState);
    20             tv = new TextView(this);
    21             tv.setText("Handler实验");
    22             setContentView(tv);
    23         
    24             //实例化一个特殊的线程HandlerThread,必须给其指定一个名字
    25             HandlerThread thread = new HandlerThread("handler thread"){
    26                 @Override
    27                 public void run() {
    28                     for(int i=0;i<3;i++){
    29                         Log.d("handler thread run ",i+"");
    30                     }
    31                 }
    32             };
    33 //            HandlerThread thread = new HandlerThread("handler thread");
    34             thread.start();//千万不要忘记开启这个线程
    35             //将mHandler与thread相关联
    36             mHandler = new Handler(thread.getLooper()){
    37                 public void handleMessage(android.os.Message msg) {
    38                     Log.d("当前子线程是----->", Thread.currentThread()+"");
    39                 };
    40             };
    41             mHandler.sendEmptyMessage(1);//发送消息
    42     }
    43 
    44 }

          红色部分就是我们重写了它的run方法。再云运行程序,打印的结果如下:

         for循环的打印结果正常,但是为什么没有打印出”当前子线程“呢。其实这正是我们要解释的地方。还记得上一篇文章中实现与子线程相关联的的Handler,我们是怎么做的吗?没读过的朋友看以点击链接(http://www.cnblogs.com/fuly550871915/p/4889838.html)。其实我们实现Handlei与线程的关联正是写在run方法中的。而对于HandlerThread这样的线程,也是如此。我们翻看这个类的源代码,找到它的run方法,如下:

     1  @Override
     2     public void run() {
     3         mTid = Process.myTid();
     4         Looper.prepare();
     5         synchronized (this) {
     6             mLooper = Looper.myLooper();
     7             notifyAll();
     8         }
     9         Process.setThreadPriority(mPriority);
    10         onLooperPrepared();
    11         Looper.loop();
    12         mTid = -1;
    13     }

          在源代码的第4行,进行了实例化自己的Looper,如果继续追踪源代码翻看其getLooper方法你会发现,如果一个Handler在与HandlerThread进行绑定时,发现Looper为空,Handler则会一直等待直到Looper被创建出来为止,然后才继续执行后续的代码。所以我们重写了HandlerThread的run方法,肯定就不会去创建Looper对象,那么绑定的Handler就会永远处于等待状态,自然而然就不会打印出”当前子线程“信息了。这也是为什么我们要使用HandlerThread这个特殊的线程,因为使用这个,我们不必关心多线程会混乱,Looper会为空等一系列问题,只要去关心我们要实现的逻辑就行了。

    好了,现在做一下简单的总结吧。

    小结:
    1. Handler与哪个线程的Looper相关联,那么它的消息处理逻辑就在与之相关的线程中执行,相应的消息的走向也就在相关联的MessageQueue中。(最常见的就是Handler与主线程关联,那么接收Looper回传的消息后的逻辑就会在主线程中执行)
    2. 当主线程中需要与子线程进行通信时(比如将耗时操作放在子线程中),建议使用HandlerThread。同时要注意,千万不要去重写它的run方法。

    二、一个主线程与子线程互相通信的例子

         知识点都说完了。下面我们来写一个具体的例子实践一下吧。新建一个项目,修改它的MainActivity代码,如下:

     1 package com.example.handldertest;
     2 
     3 import android.app.Activity;
     4 import android.os.Bundle;
     5 import android.os.Handler;
     6 import android.os.HandlerThread;
     7 import android.util.Log;
     8 import android.widget.TextView;
     9 
    10 public class ThreadHandlerActivity extends Activity{
    11     
    12 
    13     
    14     private TextView tv;
    15 
    16     private Handler mHandler;//与子线程关联的Handler
    17     private Handler handler;//与主线程关联的Handler
    18     
    19     protected void onCreate(Bundle savedInstanceState) {
    20             super.onCreate(savedInstanceState);
    21             tv = new TextView(this);
    22             tv.setText("Handler实验");
    23             setContentView(tv);
    24         
    25             //实例化一个特殊的线程HandlerThread,必须给其指定一个名字
    26             HandlerThread thread = new HandlerThread("handler thread");
    27             thread.start();//千万不要忘记开启这个线程
    28             //将mHandler与thread相关联
    29             mHandler = new Handler(thread.getLooper()){
    30                 public void handleMessage(android.os.Message msg) {
    31                     Log.d("我是子线程----->", Thread.currentThread()+"");
    32                     handler.sendEmptyMessage(1);//发送消息给主线程
    33                 };
    34             };
    35             
    36             handler = new Handler(){
    37                 public void handleMessage(android.os.Message msg) {
    38                     Log.d("我是主线程----->", Thread.currentThread()+"");
    39                     mHandler.sendEmptyMessage(1);//发送消息给子线程
    40                 };
    41             };
    42             mHandler.sendEmptyMessage(1);//发送消息
    43             handler.sendEmptyMessage(1);//发送消息
    44     }
    45 
    46 }

              注释很详细,不解释 了。运行程序,结果如下:

             这样子,就会一直循环下去,轮流打印出主线程和子线程。

     

  • 相关阅读:
    make -j 8参数的作用
    使用请求头认证来测试需要授权的 API 接口
    查看Linux系统的平均负载
    服务器负载均衡的基本功能和实现原理
    Oracle RAC学习笔记:基本概念及入门
    详解物化视图(汇总比较有用的资料)
    程序优化注意的一些点
    PR 审批界面增加显示项方法
    Most Common Solutions to FRM-41839 and .tmp Files Not Being Deleted
    APPCORE Routine APIs
  • 原文地址:https://www.cnblogs.com/fuly550871915/p/4890066.html
Copyright © 2020-2023  润新知