zoukankan      html  css  js  c++  java
  • Android线程间通信机制(Handler Looper )

    Android线程间通信机制

    当android应用程序运行时,一个主线程被创建(也称作UI线程),此线程主要负责处理UI相关的事件,由于Android采用UI单线程模型,所以只能在主线程中对UI元素进行操作,如果在非UI线程直接对UI进行了操作,则会报错,另外,对于运算量较大的操作和IO操作,我们需要新开线程来处理这些工作,以免阻塞UI线程,子线程与主线程之间是怎样进行通信的呢?此时就要采用消息循环机制(Looper)与Handler进行处理。

    一、基本概念

    Looper:每一个线程都可以产生一个Looper,用来管理线程的Message,Looper对象会建立一个MessgaeQueue数据结构来存放message。

    Handler:与Looper沟通的对象,可以push消息或者runnable对象到MessgaeQueue,也可以从MessageQueue得到消息。

    查看其构造函数:

    Handler()

    Default constructor associates this handler with the queue for the current thread.//如不指定Looper参数则默认利用当前线程的Looper创建

    Handler(Looper looper)

    Use the provided queue instead of the default one.//使用指定的Looper对象创建Handler

    线程A的Handler对象引用可以传递给别的线程,让别的线程B或C等能送消息来给线程A。

    线程A的Message Queue里的消息,只有线程A所属的对象可以处理。

    注意:Android里没有global的MessageQueue,不同进程(或APK之间)不能通过MessageQueue交换消息。

    二、Handler通过Message通信的基本方式

    使用Looper.myLooper可以取得当前线程的Looper对象。

    使用mHandler = new Handler(Looper.myLooper()); 可产生用来处理当前线程的Handler对象。

    使用mHandler = new Handler(Looper.getMainLooper()); 可诞生用来处理main线程的Handler对象。

    使用Handler传递消息对象时将消息封装到一个Message对象中,Message对象中主要字段如下:

    public int

    arg1

    当需要传递的消息是整形时arg1 和 arg2 是一种低成本的可选方案,他使用 setData()/getData()访问或修改字段。 

    public int

    arg2

    同上

    public Object

    obj

    可传送的任意object类型.

    public int

    what

    Int类型用户自定义的消息类型码

    Message对象可以通过Message类的构造函数获得,但Google推荐使用Message.obtain()方法获得,该方法会从全局的对象池里返回一个可复用的Messgae实例,API中解释如下:

    Message()

    Constructor (but the preferred way to get a Message is to call Message.obtain()).

    Handler发出消息时,既可以指定消息被接受后马上处理,也可以指定经过一定时间间隔之后被处理,如sendMessageDelayed(Message msg, long delayMillis),具体请参考API。

    Handler消息被发送出去之后,将由handleMessage(Message msg)方法处理。

     

    注意:在Android里,新诞生一个线程,并不会自动建立其Message Loop

    可以通过调用Looper.prepare()为该线程建立一个MessageQueue,再调用Looper.loop()进行消息循环

    下面举例说明:

    在main.xml中定义两个button及一个textview

    <Button

    android:id="@+id/a"

    android:layout_width
    ="80dp"

    android:layout_height
    ="60dp"

    android:padding
    ="6dp"

    android:layout_marginTop
    ="10dp"

    android:text
    ="Test looper"

    android:hint
    ="Test looper" />

    <Button

    android:id="@+id/b"

    android:layout_width
    ="80dp"

    android:layout_height
    ="60dp"

    android:padding
    ="6dp"

    android:layout_marginTop
    ="10dp"

    android:text
    ="Exit" />

    <TextView

    android:id="@+id/tv"

    android:layout_width
    ="fill_parent"

    android:layout_height
    ="wrap_content"

    android:layout_marginTop
    ="10dp"/>



     

    Activity源代码如下:

      1 public class HandlerTestActivity extends Activity implements Button.OnClickListener{
    2
    3 public TextView tv;
    4
    5 private myThread myT;
    6
    7 Button bt1, bt2;
    8
    9 /** Called when the activity is first created. */
    10
    11 @Override
    12
    13 public void onCreate(Bundle savedInstanceState) {
    14
    15 super.onCreate(savedInstanceState);
    16
    17 setContentView(R.layout.main);
    18
    19 bt1 = (Button)findViewById(R.id.a);
    20
    21 bt2 = (Button)findViewById(R.id.b);
    22
    23 tv = (TextView)findViewById(R.id.tv);
    24
    25 bt1.setId(1);//为两个button设置ID,此ID用于后面判断是哪个button被按下
    26
    27 bt2.setId(2);
    28
    29 bt1.setOnClickListener(this);//增加监听器
    30
    31 bt2.setOnClickListener(this);
    32
    33 }
    34
    35
    36
    37 @Override
    38
    39 public void onClick(View v) {
    40
    41 // TODO Auto-generated method stub
    42
    43 switch(v.getId()){//按键事件响应,如果是第一个按键将启动一个新线程
    44
    45 case 1:
    46
    47 myT = new myThread();
    48
    49 myT.start();
    50
    51 break;
    52
    53 case 2:
    54
    55 finish();
    56
    57 break;
    58
    59 default:
    60
    61 break;
    62
    63 }
    64
    65 }
    66
    67
    68
    69 class myThread extends Thread
    70
    71 {
    72
    73 private EHandler mHandler;
    74
    75 public void run()
    76
    77 {
    78
    79 Looper myLooper, mainLooper;
    80
    81 myLooper = Looper.myLooper();//得到当前线程的Looper
    82
    83 mainLooper = Looper.getMainLooper();//得到UI线程的Looper
    84
    85 String obj;
    86
    87 if(myLooper == null)//判断当前线程是否有消息循环Looper
    88
    89 {
    90
    91 mHandler = new EHandler(mainLooper);
    92
    93 obj = "current thread has no looper!";//当前Looper为空,EHandler用mainLooper对象构造
    94
    95 }
    96
    97 else
    98
    99 {
    100
    101 mHandler = new EHandler(myLooper);//当前Looper不为空,EHandler用当前线程的Looper对象构造
    102
    103 obj = "This is from current thread.";
    104
    105 }
    106
    107 mHandler.removeMessages(0);//清空消息队列里的内容
    108
    109 Message m = mHandler.obtainMessage(1, 1, 1, obj);
    110
    111 mHandler.sendMessage(m);//发送消息
    112
    113 }
    114
    115 }
    116
    117
    118
    119 class EHandler extends Handler
    120
    121 {
    122
    123 public EHandler(Looper looper)
    124
    125 {
    126
    127 super(looper);
    128
    129 }
    130
    131 @Override
    132
    133 public void handleMessage(Message msg) //消息处理函数
    134
    135 {
    136
    137 tv.setText((String)msg.obj);//设置TextView内容
    138
    139 }
    140
    141 }
    142
    143
    144
    145 }



    程序运行后点击TestLooper按键,TextView输出如下,说明新创建的线程里Looper为空,也就说明了新创建的线程并不会自己建立Message Looper。

     

     

     

    修改myThread类:

      

     1 class myThread extends Thread
    2
    3 {
    4
    5 private EHandler mHandler;
    6
    7 public void run()
    8
    9 {
    10
    11 Looper myLooper, mainLooper;
    12
    13 Looper.prepare();
    14
    15 myLooper = Looper.myLooper();
    16
    17 mainLooper = Looper.getMainLooper();
    18
    19 ......
    20
    21 ......
    22
    23 mHandler.sendMessage(m);
    24
    25 Looper.loop();
    26
    27 }
    28
    29 }

     

        Looper.prepare为当前线程创建一个Message Looper,Looper.loop()开启消息循环。这样修改是OK呢?

        答案是否定的!运行时Logcat将抛出CalledFromWrongThreadException异常错误,提示如下:

       

        意思就是说“只有原始创建这个视图层次的线程才能修改它的视图”,本例中的TextView是在UI线程(主线程)中创建,因此,myThread线程不能修改其显示内容!

           一般的做法是在子线程里获取主线程里的Handler对象,然后通过该对象向主线程的消息队列里发送消息,进行通信。

           如果子线程想修改主线程的UI,可以通过发送Message给主线程的消息队列,主线程经行判断处理再对UI经行操作,具体可以参考之前的代码。

     

    三、Handler通过runnable通信的基本方式

    我们可以通过Handler的post方法实现线程间的通信,API中关于post方法说明如下

    public final boolean post (Runnable r)

    Causes the Runnable r to be added to the message queue. The runnable will be run on the thread to which this handler is attached.

    Post方法将安排runnable对象在主线程的某个位置运行,但是并不会开启一个新的线程,验证代码如下:

     1 public class HandlerTestActivity extends Activity {
    2
    3
    4
    5 private Handler handlerTest;
    6
    7 Runnable runnableTest = new Runnable()
    8
    9 {
    10
    11 public void run()
    12
    13 {
    14
    15 String runID = String.valueOf(Thread.currentThread().getId());//输出Runnable 线程的ID号
    16
    17 Log.v("Debug",runID);
    18
    19 }
    20
    21 };
    22
    23 /** Called when the activity is first created. */
    24
    25 @Override
    26
    27 public void onCreate(Bundle savedInstanceState) {
    28
    29 super.onCreate(savedInstanceState);
    30
    31 setContentView(R.layout.main);
    32
    33 handlerTest = new Handler();
    34
    35 String mainID = String.valueOf(Thread.currentThread().getId());
    36
    37 Log.v("Debug",mainID);//输出主线程的ID号
    38
    39 handlerTest.post(runnableTest);
    40
    41 }
    42
    43 }



    Logcat里输出如下:

    说明只是把runnable里的run方法放到UI线程里运行,并不会创建新线程

    因此我们可以在子线程中将runnable加入到主线程的MessageQueue,然后主线程将调用runnable的方法,可以在此方法中更新主线程UI。

    将之前的代码修改如下:

     1 public class HandlerTestActivity extends Activity {
    2
    3 private Handler handlerTest;
    4
    5 private TextView tv;
    6
    7 /** Called when the activity is first created. */
    8
    9 @Override
    10
    11 public void onCreate(Bundle savedInstanceState) {
    12
    13 super.onCreate(savedInstanceState);
    14
    15 setContentView(R.layout.main);
    16
    17 tv = (TextView)findViewById(R.id.tv);//TextView初始化为空
    18
    19 handlerTest = new Handler();
    20
    21 myThread myT = new myThread();
    22
    23 myT.start();//开启子线程
    24
    25 }
    26
    27
    28
    29 class myThread extends Thread{
    30
    31 public void run(){
    32
    33 handlerTest.post(runnableTest);//子线程将runnable加入消息队列
    34
    35 }
    36
    37 }
    38
    39
    40
    41 Runnable runnableTest = new Runnable()
    42
    43 {
    44
    45 public void run()
    46
    47 {
    48
    49 tv.setText("此信息由子线程输出!");
    50
    51 }
    52
    53 };
    54
    55 }



    相当于在主线程中调用了runnalberun方法,更改了TextViewUI!

  • 相关阅读:
    设计模式之原型模式
    【转载】 吵翻了!这张图里到底是人还是狗?心理学家这样说
    【转载】 DeepMind 提出元梯度强化学习算法,显著提高大规模深度强化学习应用的性能
    ubuntu18.04 安装wine64出现错误: X 64-bit development files not found.
    ubuntu18.04 源码方式安装wine , 警告,libxrender 64-bit development files not found, XRender won't be supported.
    【转载】 信息如何像零食、金钱一样掌控你的大脑
    图像处理算法 之 滤波 模糊(基于OpenCV)
    图像处理之FPN校正
    ISP-黑电平校正(BLC)
    ISP基础(0y):图像传感器
  • 原文地址:https://www.cnblogs.com/leon19870907/p/2367549.html
Copyright © 2011-2022 走看看