zoukankan      html  css  js  c++  java
  • Android:使用Handler在线程之间通信

    概述

    假设一个情景,在一个应用程序中,要完成一个比较复杂、耗时较长的计算任务,如果将这个任务直接在主线程中开始,那么用户界面就会停止对用户操作的响应,而去解决这个计算任务,直到任务完成。 
    显然,这不是我们想要的结果。那么就需要用到多线程来解决这个问题。 
    但是新的问题又出现了,在C#和Android中,子线程是不能直接修改用户界面的数据的。也就是说,子线程计算出的结果,不能直接在子线程中让它显示在用户界面上。那么就需要把数据首先传递给主线程,让主线程去修改用户界面。 
    Handler就是为了解决这个问题出现的。

    什么是Handler

    • 从宏观上来说,Handler是一种消息传递机制
    • 从微观上来说,Handler是一个类

    在子线程中向主线程发送数据

    下面通过一段伪代码来实现子线程向主线程发送数据 
    假设我们新建了一个项目,这个项目有下面这个Activity:

    public class CounterActivity extends AppCompatActivity {
        public Button pressBtn;
        public TextView textView;
        @Override
        protected void onCreate(Bundle savedInstanceState) 
        {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_counter);
            {
                @Override
                public void onClick(View v) 
                {
                //启动一个线程进行一个复杂计算
                //当计算完成时,我们不能在线程中这样写:
                //textView.setText("result");
                }
            });
        }
    }

    很简单,这个Activity包含两个空间,一个TextView用来显示计算结果,一个Button用来开启一个线程进行一个复杂计算。 
    那么当计算完成时,我们不能在线程中直接修改TextView的内容,而是要把结果通过Handler传递给主线程。注意消息的传递方向:子线程→主线程。 
    现在就出现了一个问题,在哪里定义Handler,或者说Handler属于谁?主线程还是子线程?答案是主线程。要给谁发消息,就要用谁的Handler。 
    什么叫“谁的”?要在java中使用线程,一般都是继承Thread类并重写其中的run()方法,不管主线程还是子线程,都是以类的实例的方式存在的,也就是说,要用Handler向主线程发送消息,这个Handler必须是主线程的类成员,不能是子线程的;同理,要用Handler向子线程发送消息,那么这个Handler必须在子线程中声明,而不能在主线程中声明。 
    在主线程中声明了Handler之后,就要重写这个Handler的 
    void hanleMessage(Message msg)方法。 
    至于如何构建Message对象,如何对Message对象进行筛选和数据提取,这里就不做说明了,很简单。 
    进一步完善上面的伪代码如下:

    public class MainActivity extends AppCompatActivity {
        //由上面的讲解,显然我们要在这个MainActivity中定义一个Handler
        public Handler mHandler;
        public Button pressBtn;
        public TextView textView;
        @Override
        protected void onCreate(Bundle savedInstanceState) 
        {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_counter);
            //在这里重写mHandler的handleMessage方法
            mHandler=new Handler()
            {
                @Override
                public void handleMessage(Message msg)
                {
                    //对msg进行处理,获得其中的数据也就是计算结果
                    //修改用户界面
                    textView.setText("result");
                }
            };
            pressBtn.setOnClickListener(new View.OnClickListener() 
            {
                @Override
                public void onClick(View v) 
                {
    
                    new Thread()
                    {
                        @Override
                        public void run()
                        {
                        //在这里进行一段复杂计算
                        //可以用Thread.sleep(1000);代替
                            try 
                            {
                                Thread.sleep(1000);
                            } 
                            catch (InterruptedException e) 
                            {
                                e.printStackTrace();
                            }
                            //启动一个线程进行一个复杂计算
                            //当计算完成时,我们不能这样写:
                            //textView.setText("result");
                            //而是要通过主线程的Handler通知主线程
                            Message msg=new Message();
                            //应当对要传递的Message进行定制,
                            //将计算结果附在Message对象中
                            mHandler.sendMessage(msg);
                        }
                    }.start();
                }
            });
        }
    }

    这样,当计算完成时,子线程就会把结果通过Handler发送给主线程,然后退出,由主线程完成修改UI的任务。

    在主线程中向子线程发送数据

    由上面的例子,我们就可以想到,应该在子线程中声明一个Handler对象,并且实现它的void handleMessage(Message msg)方法。然后在主线程中调用子线程的Handler的void sendMessage(Message msg)方法向子线程发送数据。 
    这里就应该把Handler作为一个宏观的消息传递机制来看而不仅仅是一个类了。因为它和Looper,MessageQueue,Message是作为一个整体完成消息传递任务的。关于这部分内容,让我们先做一个实例后,在进行解释。 
    首先我们需要思考,在哪种情况下需要主线程向子线程发送消息。 
    在通常情况下,我们启动一个线程的时候,都是因为有一个需要马上进行处理的数据而开启一个线程,也就是说这个线程在开启之后马上就要开始处理数据。在线程启动的时候,可以直接将数据传递给线程,显然这里就用不到Handler了。 
    但是,有这样一种情况,我们想在界面初始化的时候,就有一个子线程随着主线程一起启动,一直等待我们传递数据给它,然后让它进行处理。显然这里就需要向子线程发送消息,就需要Handler来实现了。

    public class CounterActivity extends AppCompatActivity {
        public Button pressBtn;
        public TextView textView;
        //在这里定义一个属于主界面的线程类,这个线程类必须包含一个Handler成员
        //而且这个Handler不能是私有的
        //而且重写了handleMessage方法
        //关于类的嵌套可以查看其它资料,这里就不解释了
        public class mThread extends Thread
        {
            public Handler cHandler;
            @Override
            public void run()
            {
                //初始化Looper
                Looper.prepare();
                cHanler=new Handler()
                {
                    @Override
                    public void handleMessage(msg)
                    {
                        //对msg进行处理,获得主线程传来的数据
                        //对数据进行处理
                    }
                };
                Looper.loop();
            }
        };
    
        @Override
        protected void onCreate(Bundle savedInstanceState) 
        {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_counter);
            //实例化一个上面定义的线程类,并启动它
            final mThread mthread=new mThread();
            mthread.start();
            pressBtn.setOnClickListener(new View.OnClickListener()
            {
                @Override
                public void onClick(View v) 
                {
                    //当按钮被单击的时候,需要向已经启动的线程mthread
                    //传递数据,这里就要使用Handler
                    //将数据封装到Message对象中
                    Message msg=new Message();
                    //调用子线程的Handler,将msg发送给子线程
                    mthread.cHandler.sendMessage(msg);
                }
            });
        }
    }

    这样,主线程就可以向一个已经启动的子线程发送消息了。 
    但是上面的这段代码也许会让我们有以下疑惑: 
    1. Looper.prepare()和Looper.loop()是什么作用? 
    2. 第一个例子在主线程中使用Handler为什么没有出现Looper? 
    3. 显然如果线程被启动,也就是mthread.start();这行代码执行后,run()函数并没有陷入循环,那么子线程马上就会执行完退出,怎么还可以向子线程发送消息? 
    其实所有的原因都在Looper这里。

    Handler、Looper、MessageQueue、Message

    在Handler这个消息传递机制中,Handler、Looper、MessageQueue、Message是作为一个整体同时出现的,也就是说,缺少这四个要素中的任意一个,这个消息传递机制都无法实现。

    • MessageQueue:顾名思义,是一个消息队列,由Looper负责管理,它采用先进先出的方式来管理Message;
    • Looper:每个线程只有一个Looper,它负责管理MessageQueue,会不断从MessageQueue中取出消息,并将消息分给对应的Handler处理;
    • Handler:它能把消息发送给Looper管理的MessageQueue,并负责处理Looper分给它的消息。

    简单的说,每个线程(不论主线程还是子线程)如果要使用Handler,必须要有Looper和MessageQueue。当调用某个线程的Handler的void sendMessage(Message msg)方法时,msg就会被存入这个线程的MessageQueue,而Looper会不断地读取MessageQueue,将msg发给这个线程的Handler,这个Handler的void handleMessage(msg)就会处理这个msg。

    我们来回答上面提出的三个问题: 
    1. Looper.prepare()和Looper.loop()是什么作用? 
    答: Looper.prepare()为当前线程定义一个Looper实例 
    Looper.loop()方法是一个死循环,不断读取MessageQueue里面的消息 
    2. 第一个例子在主线程中使用Handler为什么没有出现Looper? 
    答: 在第一个例子中,主线程中并没有出现Looper,是因为父类已经定义了Looper,所以在这里就没有必要显示声明了。 
    3. 显然如果线程被启动,也就是mthread.start();这行代码执行后,run()函数并没有陷入循环,那么子线程马上就会执行完退出,怎么还可以向子线程发送消息? 
    答: 解决了第一个问题,这个问题也就显而易见了,Looper.loop()本身就是一个死循环。

    在主线程和子线程直接互相通信

    这个程序就是《疯狂安卓讲义》3.5.2这一节的实例——计算所有质数的改写版。 
    首先启动一个子线程,等待主线程将一个数(作为上限)通过Handler传递给它,然后进行计算,计算完成后再使用Handler将结果发送给主线程,主线程修改用户UI。 
    写累了,直接上代码: 
    主活动java代码:

    package com.example.marine.calprimary3;
    
    import android.os.Handler;
    import android.os.Looper;
    import android.os.Message;
    import android.support.v7.app.AppCompatActivity;
    import android.os.Bundle;
    import android.view.View;
    import android.widget.Button;
    import android.widget.EditText;
    import android.widget.TextView;
    import android.widget.Toast;
    
    import java.util.ArrayList;
    
    public class MainActivity extends AppCompatActivity {
    
        public EditText inputText;
        public TextView resultText;
        public Button startBtn;
        public Handler mHandler;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            final CalThread calThread=new CalThread();
            calThread.start();
            inputText=(EditText)findViewById(R.id.input_text);
            startBtn=(Button)findViewById(R.id.start_btn);
            resultText=(TextView)findViewById(R.id.result_text);
            mHandler=new Handler()
            {
                @Override
                public void handleMessage(Message msg)
                {
                    if(msg.what==0x124)
                    {
                        String result=msg.getData().getIntegerArrayList("result").toString();
                        resultText.setText(result);
                    }
    
                }
            };
            startBtn.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    Message msg=new Message();
                    Bundle bundle=new Bundle();
                    bundle.putInt("upper",Integer.parseInt(inputText.getText().toString()));
                    msg.setData(bundle);
                    msg.what=0x123;
                    calThread.cHandler.sendMessage(msg);
                }
            });
    
        }
        class CalThread extends Thread
        {
            public Handler cHandler;
            @Override
            public void run()
            {
                Looper.prepare();
                cHandler=new Handler()
                {
                    @Override
                    public void handleMessage(Message msg)
                    {
                        if(msg.what==0x123) {
                            int upper = msg.getData().getInt("upper");
                            ArrayList<Integer> nums = new ArrayList<Integer>();
                            outer:
                            for (int i = 2; i <= upper; i++) {
                                for (int j = 2; j <= Math.sqrt(i); j++) {
                                    if (i != 2 && i % j == 0) {
                                        continue outer;
                                    }
                                }
                                nums.add(i);
                            }
                            Message msg_send = new Message();
                            msg_send.what = 0x124;
                            Bundle bundle_send = new Bundle();
                            bundle_send.putIntegerArrayList("result", nums);
                            msg_send.setData(bundle_send);
                            mHandler.sendMessage(msg_send);
                        }
                        Toast.makeText(MainActivity.this, "计算完成", Toast.LENGTH_LONG).show();
                    }
                };
                Looper.loop();
            }
        }
    }
    

    xml布局文件代码:

    <?xml version="1.0" encoding="utf-8"?>
    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:paddingBottom="@dimen/activity_vertical_margin"
        android:paddingLeft="@dimen/activity_horizontal_margin"
        android:paddingRight="@dimen/activity_horizontal_margin"
        android:paddingTop="@dimen/activity_vertical_margin"
        tools:context="com.example.marine.calprimary3.MainActivity">
    
        <EditText
            android:id="@+id/input_text"
            android:layout_width="800px"
            android:layout_height="150px"
            android:text=""
            android:textSize="70px"/>
        <Button
            android:id="@+id/start_btn"
            android:layout_width="800px"
            android:layout_height="150px"
            android:layout_below="@id/input_text"
            android:textSize="70px"
            android:text="Start"/>
        <TextView
            android:id="@+id/result_text"
            android:layout_width="800px"
            android:layout_height="wrap_content"
            android:text="计算结果"
            android:textSize="50px"
            android:layout_below="@id/start_btn"/>
    </RelativeLayout>

    界面效果 
    这里写图片描述

    结语

    为了搞明白Handler的原理,在网上找了很多文章,但是写的都不是让我很满意和清楚,所以只能自己探索了。 
    再次说明,这系列文章的最终目的并不是开发app,只是为了快速了解安卓程序的运行和编写过程,所以如果有理解错误的地方,还请多多指正。

  • 相关阅读:
    js正则匹配以某字符串开始字符串
    vue+vue-resource+vue-cookie随笔
    [考试反思]1001csp-s模拟测试(b):逃离
    [考试反思]0929csp-s模拟测试55:消逝
    [考试反思]0928csp-s模拟测试54:转瞬
    [考试反思]0927csp-s模拟测试53:沦陷
    [考试反思]0926csp-s模拟测试52:审判
    [考试反思]0924csp-s模拟测试51:破碎
    Function:凸包,决策单调性,题意转化,单峰函数三分,离线处理
    土兵占领:二分答案,最大流
  • 原文地址:https://www.cnblogs.com/vegetate/p/9997168.html
Copyright © 2011-2022 走看看