Android之handler消息传递机制

说明
今天学习的是Activity中UI组件中的信息传递Handler,相信很多朋友都知道,Android为了线程安全,并不允许我们在UI线程外操作UI;很多时候我们做界面刷新都需要通过Handler来通知UI组件更新!除了用Handler完成界面更新外,还可以使用runOnUiThread()来更新,甚至更高级的事务总线,当然,这里我们只讲解Handler,什么是Handler,执行流程,相关方法,子线程与主线程中中使用Handler的区别等!

Handler类简介

什么是Handler

  • Handler 是 Android 给我们提供来更新 UI 的一套机制,也是一套消息处理的机制,我们可以发送消息,也可以通过它来处理消息,Handler 在我们的 framework中是非常常见的。

  • Android 在设计的时候,就封装了一套消息创建、传递、处理机制,如果不遵循这样的机制就没有办法更新 UI 信息,就会抛出异常信息。

Handler类的主要作用

有两个:

  • 在新启动的线程中发送消息
  • 在主线程中获取、处理消息

Handler的执行流程

上面的说法看上去很简单,似乎只要分成两步即可:在新启动的线程中发送消息(Message);然后再主线程中获取并处理消息。但这个过程涉及一个问题:在新启动的线程中何时发送消息呢?在主线程中又何时去获取并处理消息呢?

为了让主线程能”适时“地处理新启动的线程所发送的消息,显然只能通过回调的方式来实现——开发者只要重写Handler类中处理消息的方法(handleMessage),当新启动的线程发送消息时,消息会发送到与之关联的消息队列(MessageQueue),而Handler会不断地从MessageQueue中获取并处理消息——这将导致Handler类中处理消息的方法被回调。

这里面的几个概念需要了解一下:

  • Message
    消息,理解为线程间通讯的数据单元。例如后台线程在处理数据完毕后需要更新UI,则可发送一条包含更新信息的Message给UI线程。

  • Message Queue
    消息队列,用来存放通过Handler发布的消息,按照先进先出执行。

  • Handler
    Handler是Message的主要处理者,负责将Message添加到消息队列以及对消息队列中的Message进行处理。

  • UI线程
    就是我们的主线程,系统在创建UI线程的时候会初始化一个Looper对象,同时也会创建一个与其关联的MessageQueue;

  • Looper:每个线程只能够有一个Looper,扮演Message Queue和Handler之间桥梁的角色,管理MessageQueue,不断地从中取出Message分发给对应的Handler处理!

简单点说

当我们的子线程想修改Activity中的UI组件时,我们可以新建一个Handler对象,通过这个对象向主线程发送信息;而我们发送的信息会先到主线程的MessageQueue进行等待,由Looper按先入先出顺序取出,再根据message对象的what属性分发给对应的Handler进行处理!

Handler的相关方法

void handleMessage(Message msg):处理消息的方法,通常是用于被重写!

sendEmptyMessage(int what):发送空消息

sendEmptyMessageDelayed(int what,long delayMillis):指定延时多少毫秒后发送空信息

sendMessage(Message msg):立即发送信息

sendMessageDelayed(Message msg):指定延时多少毫秒后发送信息

final boolean hasMessage(int what):检查消息队列中是否包含what属性为指定值的消息
如果是参数为(int what,Object object):除了判断what属性,还需要判断Object属性是否为指定对象的消息

Handler的用法

Handler写在主线程中

在主线程中,因为系统已经初始化了一个Looper对象,所以我们直接创建Handler对象,就可以进行信息的发送与处理了!

1) 发送空消息方法(sendEmptyMessage)

代码示例: 简单的一个定时切换图片的程序,通过Timer定时器,定时修改ImageView显示的内容,从而形成帧动画

MainActivity.java:

public class MainActivity extends AppCompatActivity {
    int currentImageId = 0;
    private ImageView imageView;
    int[] imgids = new int[]{
            R.mipmap.one, R.mipmap.two, R.mipmap.three, R.mipmap.four,
            R.mipmap.five, R.mipmap.six, R.mipmap.seven, R.mipmap.eight, R.mipmap.nine,
            R.mipmap.ten, R.mipmap.eleven
    };
    Handler myHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            if (msg.what == 0x123) {
                imageView.setImageResource(imgids[currentImageId++ % 11]);
            }
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        imageView = (ImageView) this.findViewById(R.id.imageView);

        //使用定时器,每隔200毫秒让handler发送一个空信息
        new Timer().schedule(new TimerTask() {
            @Override
            public void run() {
                myHandler.sendEmptyMessage(0x123);

            }
        }, 0, 200);
    }

    @Override

    public boolean onKeyDown(int keyCode, KeyEvent event) {
        if (keyCode == KeyEvent.KEYCODE_BACK) {
            moveTaskToBack(true);
        }
        return super.onKeyDown(keyCode, event);
    }

    @Override
    public void onBackPressed() {
        moveTaskToBack(true);
    }
}

上面的程序中通过Timer周期性地执行指定任务,Timer对象可调度TimerTask对象,TimerTask对象的本质就是启动一条新的线程,因为Android不允许在新线程中访问Activity中的界组件,因此程序只能在新线程里发送一条消息,通知系统更新ImageView组件。

我们重写了handler的handleMessage方法,该方法用于处理消息——当新线程发送消息时,该方法会被自动回调,handleMessage(Message msg)方法依然位于主线程,所以可以动态地修改ImageView组件的属性。

效果演示:
这里写图片描述

2) 传递Message更新UI界面(sendMessage)

sendMessage 类方法,允许你安排一个带数据的 Message 对象到队列中,等待更新。

我们实现一个定时器,每隔一秒显示数字0-9:

public class MainActivity extends AppCompatActivity {
    private static final String NUMBER_SHOW = "number";

    private Button button;
    private TextView textView;
   Handler handler = new Handler(){
       @Override
       public void handleMessage(Message msg) {
           if(msg.what == 0x123){
               Bundle bundle = msg.getData();
               int i = bundle.getInt(NUMBER_SHOW);
               textView.setText(" "+i);
           }
       }
   };
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initViews();
    }

    private void initViews() {
        textView = (TextView)this.findViewById(R.id.textView);
        button = (Button)this.findViewById(R.id.button);
        button.setOnClickListener(buttonOnClickListener);
    }

    View.OnClickListener buttonOnClickListener = new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            Thread thread = new Thread(new WorkRunnable());
            thread.start();
        }
    };

    class WorkRunnable implements Runnable{
        @Override
        public void run() {
            int i = 0;
            while(i<10){
                Message msg = Message.obtain();
                msg.what = 0x123;
                Bundle bundle = new Bundle();
                bundle.putInt(NUMBER_SHOW,i);
                msg.setData(bundle);
                handler.sendMessage(msg);
                try{
                    Thread.sleep(1000);
                }catch(InterruptedException e){
                    e.printStackTrace();
                }
                i++;
            }
        }
    }
}

我们用实现Runnable接口的方式定义一个子线程,在这个子线程里我们创建了Message对象(创建Message对象有两种方式,new Message() 以及 Message.obtain())以及一个Bundle对象,Bundle对象以键值对的方式来存放数据,然后把数据放入Message中,再通过sendMessage方法发送出去。

我们在按钮的点击事件中启动线程。

然后我们新增了一个handler类,并重写了它的handleMessage方法,在这里对具体的Message进行处理。如果发现Message的what字段的值是0x123,就提取对应Message中的数据。

效果演示:
这里写图片描述

3) postDelayed更新UI(sendMessage)

post类方法把runnable对象作为消息放到队列中等待执行,run方法中就是具体执行过程。

我们还是实现一个定时器,每隔一秒显示数字0-9:

public class MainActivity extends AppCompatActivity {
    private static final String NUMBER_SHOW = "number";

    private Button start,end;
    private TextView textView;
   Handler handler = new Handler();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initViews();
    }

    private void initViews() {
        textView = (TextView)this.findViewById(R.id.textView);
        start = (Button)this.findViewById(R.id.start);
        start.setOnClickListener(buttonOnClickListener);

        end = (Button)this.findViewById(R.id.end);
        end.setOnClickListener(endOnClickListener);
    }

    View.OnClickListener endOnClickListener = new View.OnClickListener() {
        @Override
        public void onClick(View v) {
           finish();
        }
    };

    View.OnClickListener buttonOnClickListener = new View.OnClickListener() {
        @Override
        public void onClick(View v) {
           handler.post(new PostDelayRunnable());
        }
    };

    class PostDelayRunnable implements Runnable{
        int i = 0;
        @Override
        public void run() {

            if (i >= 10) {
                handler.removeCallbacks(this);
            } else {
                textView.setText(" " + i);
                handler.postDelayed(this, 1000);
            }
                i++;
            }
        }
}

handler.post(new Runnable)方法是将这个Runnable对象加入到消息队列中之后立即得到执行,handler.postDelayed(new Runnable,Long long)方法是将这个Runnable对象每隔指定时间加入到消息队列一次。

上面代码中,我们点击按钮,post方法将会把Runnable对象加入到队列中执行,run方法得到执行;在run方法中我们更新UI组件,注意,这个时候并没有开启子线程,事实上上面操作都是在以恶搞线程即主线程中完成的。然后执行postDelayed方法,每隔1秒钟运行一次run方法,因此屏幕上将会出现0-9的数字显示。

效果演示:
这里写图片描述

Handler 与 Looper、MessageQueue 的关系

handler 负责发送消息,Looper 负责接收 Handler 发送消息,并直接把消息回传给 handler 自己,MessageQueue 就是一个存储消息的容器。

这其实就是Android中的异步消息处理机制。流程示意图如下所示:
这里写图片描述
图片来源于第一行代码。

一条Message经过这样的流程辗转调用后,也就从子线程进入了主线程,从不能更新UI变成了可以更新UI。

下面使用一个实例来介绍Looper和handler的用法,将Handler写在子线程中的情况*

如果是Handler写在了子线程中的话,我们就需要自己创建一个Looper对象了!创建的流程如下:

1 )直接调用Looper.prepare()方法即可为当前线程创建Looper对象,而它的构造器会创建配套的MessageQueue;

2 )创建Handler对象,重写handleMessage( )方法就可以处理来自于其他线程的信息了! 3
)调用Looper.loop()方法启动Looper

使用示例: 输入一个数,计算后通过Toast输出在这个范围内的所有质数

public class MainActivity extends AppCompatActivity {
    static final String UUPER_NUM = "upper";
    EditText etNum;
    Button button;
    CalThread calThread;
    class CalThread extends Thread{
        public Handler handler;
        @Override
        public void run() {
            Looper.prepare();
            handler = new Handler(){
                @Override
                public void handleMessage(Message msg) {
                    if(msg.what == 0x1233){
                        int upper = msg.getData().getInt(UUPER_NUM);
                        List<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);
                        }
                        Toast.makeText(MainActivity.this,nums.toString(),Toast.LENGTH_SHORT).show();
                    }
                }
            };
            Looper.loop();
        }
    }
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        etNum = (EditText)this.findViewById(R.id.editText);
        calThread = new CalThread();
        calThread.start();
        button = (Button)this.findViewById(R.id.button);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Message msg = Message.obtain();
                msg.what = 0x1233;
                Bundle bundle = new Bundle();
                bundle.putInt(UUPER_NUM,Integer.parseInt(etNum.getText().toString()));
                msg.setData(bundle);
                calThread.handler.sendMessage(msg);
            }
        });
    }
}

上面代码中在新线程中创建了Handler对象,由于在新线程中创建Handler时必须先创建Looper,因此程序先调用Looper.prepare()方法为当前线程创建一个Looper实例,并创建配套的MessageQueue,有了Looper对象之后,再创建Handler对象,该handler可以处理其他线程发送过来的消息。程序最后还调用了Looper.loop方法。

HandlerThread

HandlerThread 继承于 Thread,所以它本质就是个 Thread。与普通的 Thread 的差别就在于,它有个 Looper 成员变量。这个 Looper 其实就是对消息队列以及队列处理逻辑的封装,简单来说就是消息队列+消息循环。

当我们需要一个工作线程,而不是把它当作一次性消耗品,用过即废的话,就可以使用它。

接下来我们实现一个按一下按钮,TextView变换内容的实例:

这个实例有所欠缺,我学艺也不精,大家看看就好,主要是直到HandlerThread的一般用法。

public class MainActivity extends AppCompatActivity {

    private Button start,end;
    private TextView textView;
    class MyHandler extends Handler{
        public  MyHandler(){

        }

       public MyHandler(Looper looper){
           super(looper);
        }
    @Override
    public void handleMessage(Message msg) {
        if(msg.what == 99){
            String name = (String)msg.obj;
            textView.setText(name);
        }
      }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initViews();

    }

    private void initViews() {
        textView = (TextView)this.findViewById(R.id.textView);
        start = (Button)this.findViewById(R.id.start);
        start.setOnClickListener(buttonOnClickListener);

        end = (Button)this.findViewById(R.id.end);
        end.setOnClickListener(endOnClickListener);
    }

    View.OnClickListener endOnClickListener = new View.OnClickListener() {
        @Override
        public void onClick(View v) {
           finish();
        }
    };

    View.OnClickListener buttonOnClickListener = new View.OnClickListener() {
        @Override
        public void onClick(View v) {

            HandlerThread handlerThread = new HandlerThread("handlerThread");
            handlerThread.start();
            //创建handler对象必须创建Looper对象,Looper对象由HandlerThread.getLooper方法得到
            MyHandler myHandler = new MyHandler(handlerThread.getLooper());
            Message msg = myHandler.obtainMessage(99);
            msg.obj = "张三";
            //把消息传给目标对象,即生成Message对象的Handler对象
            msg.sendToTarget();
        }
    };

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值