Handler、Loop、MessageQueue的工作原理<温故知新>

为了更好地理解Handler的工作原理,说下与Handler一起工作的几个组件。

 1. MessageQueue:Handler接收和处理的消息对象。

 2.Looper:每个线程只能拥有一个Looper。它的loop方法负责读取MessageQueue中的消息,读到信息
    之后就把消息交给发送该消息的Handler进行处理。

 3.MessageQueue消息队列,它采用先进先出的方式来管理Message。程序创建Looper对象时,会在它
    的构造器中创建MesssQueue对象。
//贴份Looper的构造器源代码:  
    private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }

该构造方法使用了private修饰,不是public表明是不可以通过构造器去创建Looper对象。
从构造方法中不难看出,程序在初始化Looper时会创建一个与之关联的MessageQueue,而这个MessageQueue就负责管理消息。

Handler有两个要点,前篇有说过——发送消息和处理消息,程序使用Handler发送消息,由Handler发送的消息必须被送到指定的MessageQueue。也就是说,如果希望Handler正常工作,必须在当前线程有一个MessageQueue;否则消息就没有MessageQueue进行保存了。不过MessageQueue是由Looper负责管理的,也就是说,如果希望Handler正常工作,必须在当前线程中有一个Looper对象,为了保证当前线程中有Looper对象,可分为以下两种情况处理:

    1.在主UI线程中,系统已经初始化了一个Looper对象,因此程序直接创建Handler即可,然后就
      可通过Handler来发送消息、处理消息。

    2.你自己启动的子线程,必须自己创建一个Looper对象,并启动它,创建Looper对象并调用它的

prepare()方法保证每个线程最多只有一个Looper对象。

//贴份Prepare()方法的源代码
private static void prepare(boolean quitAllowed) {
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper(quitAllowed));
    }

接下来调用Looper的静态loop()方法来启动它。loop()方法使用一个死循环不断取出MessageQueue中的消息,并将取出的消息分给该消息对应的Handler进行处理。

//贴份Looper类中的loop()方法,部分关键的源代码
 for (;;) {
     // might block
     //获取消息队列中的下一个消息,如果没有消息,就会阻塞
            Message msg = queue.next(); 
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                //如果消息为null,表明消息队列正在退出.
                return;
            }

            // This must be in a local variable, in case a UI event sets the logger
            Printer logging = me.mLogging;
            if (logging != null) {
                logging.println(">>>>> Dispatching to " + msg.target + " " +
                        msg.callback + ": " + msg.what);
            }

            msg.target.dispatchMessage(msg);

            if (logging != null) {
                logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
            }

            // Make sure that during the course of dispatching the
            // identity of the thread wasn't corrupted.
            //使用final修饰该标识符,保证在分发消息的过程中线程标识符不会被修改
            final long newIdent = Binder.clearCallingIdentity();
            if (ident != newIdent) {
                Log.wtf(TAG, "Thread identity changed from 0x"
                        + Long.toHexString(ident) + " to 0x"
                        + Long.toHexString(newIdent) + " while dispatching to "
                        + msg.target.getClass().getName() + " "
                        + msg.callback + " what=" + msg.what);
            }

            msg.recycleUnchecked();
        }

组织一下,Looper、MessageQueue、Handler各自的作用

Looper:每个线程只有一个Looper,它负责管理MessageQueue,会不断地从MessageQueue中取出消息,
        并将消息分给对应的Handler处理。

MessageQueue:由Looper负责管理。它采用先进先出的方式来管理Message。

Handler:它能把消息发送给Looper管理的MessageQueue,并负责处理Looper分给它的消息。

在线程中使用Handler的步骤

    1.调用Looper的prepare()方法为当前线程创建Looper对象,创建Looper对象时,它的构造器
      会创建与之配套的MessageQueue。

    2.有了Looper之后,创建Handler子类的实例,重写handlerMessage()方法,该方法负责处理来自于其他线程的消息。

    3.调用Looper的loop()方法启动Looper。

简述了它们之间的作用,不上个实例感觉缺少点啥

该实例允许用户输入一个数值上限,当用户点击按钮时,该应用会将该上限数值发送到新启动的线程中,让该线程来计算该范围内的所有质数。

为了将用户在UI界面输入的数值上限动态地传给新启动的线程,此实例将会在线程中创建一个Handler对象,然后UI线程的事件处理方法就可以通过该Handler向新线程发送消息。

实例代码:

/**
 * Created by Administrator on 2017/8/5 0005.
 * 之所以不直接在UI線程中計算該範圍內的所有質數,是因爲UI線程需要響應用戶動作
 * 如果在UI線程中執行一個“耗時”操作,將會導致UI線程被阻塞,從而讓應用程序失去響應,在Demo中,如果用戶輸
 * 入的數據太大,系統可能需要較長時間才能
 * 計算出所有質數,這就可以能導致UI線程失去響應。
 */

public class mainsActivity extends Activity {

    private EditText etnum;
    private CalThread calThread;
    public static final int MESSAGE =1;
    public static final String UPPER_NUM ="upper";

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //布局文件比较简单,就一个文本框和一个按钮
        setContentView(R.layout.activity_main);
        etnum = (EditText) findViewById(R.id.et_account);
        calThread = new CalThread();
        //启动新线程
        calThread.start();
    }
    //为按钮的点击事件提供事件处理方法
    public void onClickCal(View view){
        //创建消息
        Message msg =new Message();
        msg.what =MESSAGE;
        Bundle bundle =new Bundle();
        bundle.putInt(UPPER_NUM,Integer.parseInt(etnum.getText().toString()));
        msg.setData(bundle);
        //向新线程中的Handler发送消息
        calThread.mHandler.sendMessage(msg);
    }
     class CalThread  extends Thread{
         Handler mHandler;
        @SuppressLint("HandlerLeak")
        @Override
        public void run() {
/_________________________________________________________________________/
    Looper.prepare();
            mHandler = new Handler(){
                @Override
                public void handleMessage(Message msg) {
                    if (msg.what ==1){
                        int anInt = msg.getData().getInt(UPPER_NUM);
                        List<Integer> nums =new ArrayList<>();
                        //计算从2开始、到anInt的所有质数
                        outer:
                        for (int i =2 ;i<=anInt;i++){
                            //用i除以从2开始、到i的平方根的所有数
                            for ( int j =2;j<=Math.sqrt(i);j++){

                                //如果可以整除,则表明这个数不是质数
                                if (i != 2 && j==0){
                                    continue outer;
                                }
                            }
                            nums.add(i);
                        }
            //使用Toast显示统计出所有质数
                        Toast.makeText(mainsActivity.this, nums.toString(), Toast.LENGTH_SHORT).show();
                    }
                }
            };
            Looper.loop();
 /_________________________________________________________________________/
        }
    }
}

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

运行此Demo,无论用户输入多大的数值,计算该范围内的质数都将会交给新线程来完成,而前台UI线程不会受到影响。

尽量避免在UI线程中执行耗时操作,这样可能就会导致出一个 著名的ANR( Application Not Responding).

只要在UI线程中执行需要消耗大量时间的操作,都会引发ANR,引发的问题就会导致Anroid应用程序无法响应输入事件和Broadcast。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值