Android基础知识学习- Looper与Handler

学习Android开发,Looper与Handler是必须学会使用的,本人作为一个测试人员,在做Android测试时一直在探索,对Android开发更加是一窍不通,只能摸石头过河把基础学一下。

1.主线程

说到Looper与Handler就不得线程,如现在有一个需求,需要等待10S执行,在主线程中实现非常简单,代码如下

      private TextView tv;

      private Button start;

    @Override

    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_main);

        tv = (TextView)findViewById(R.id.tv);

        start = (Button)findViewById(R.id.start_button);

        start.setOnClickListener(new OnClickListener(){

 

                  @Override

                  public void onClick(View arg0) {

                       // TODO Auto-generated method stub

                       try {

                             Thread.sleep(10000);

                             tv.setText("休眠后的文字");

                       } catch (InterruptedException e) {

                             // TODO Auto-generated catch block

                             e.printStackTrace();

                       }

                  }

        });

    }

接下来使用设备运行,当多次点击按钮后,模拟器出现“Do you want to close it?”的弹窗

Android基础知识学习- Looper与Handler_第1张图片

这种情况是很常见的,当请求发送到服务器,长时间没有响应,Android操作系统就会提示用户是否继续等待。这种弹窗提示,99%的用户都会选择关闭。而造成这种不友好场景之一就是在main thread主线程中使用了等待,所以这种是不可取的。

2.子主线程

既然在主线程不可取,那就自己创建一个子线程运行,应该可以了吧?

    private TextView tv;

    private Button start;

    @Override

    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_main);

        tv = (TextView)findViewById(R.id.tv);

        start = (Button)findViewById(R.id.start_button);

        start.setOnClickListener(new OnClickListener(){

 

                  @Override

                  public void onClick(View arg0) {

                       // TODO Auto-generated method stub

                       System.out.println("点击事件的线程"+Thread.currentThread().getName());

                       new Thread(){

                             public void run(){

                                   System.out.println("线程组里执行等待的线程"+Thread.currentThread().getName());

                                   try {

                                         Thread.sleep(10000);

                                         tv.setText("休眠后的文字");

                                   } catch (InterruptedException e) {

                                         // TODO Auto-generated catch block

                                         e.printStackTrace();

                                   }

                             }

                       }.start();

                  }                

        });

    }

监听与线程都使用了匿名内部类实现,分别在点击事件触发后,以及线程执行等待时候打印一句话来区别new Thread新建线程中运行,而子线程中执行;运行代码后,报错

Android基础知识学习- Looper与Handler_第2张图片

可以很明确的看到,点击事件发生在主线程,而改变TextView内容的代码确实也是在子线程中执行的,但是报错并导致模拟器奔溃了。报错内容android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.报错行数是34行,正是改变内容的那一行代码。

这个错误的原因就是因为TextView这个控件元素的改变只能发生在创建它的线程中操作。创建它的是主线程,所以只能在主线程中改变这个控件的属性内容。总结来说就是主线程、非主线程的新开线程都有一定的问题,那该如何做呢?就是Looper跟Handler干活的时候到了。

3.Looper、Handler

(1)Handler是什么?

      Handler是用来结合线程的消息队列来发送、处理“Message对象“Runnable对象的工具。每一个Handler实例之后会关联一个线程和该线程的消息队列。当你创建一个Handler的时候,从这时开始,它就会自动关联到所在的线程/消息队列,然后它就会陆续把Message/Runnalbe分发到消息队列,并在它们出队的时候处理掉。

      从这段解释来看有点懵逼,从中发现的是线程,不知道是不是与上面留下的坑有关联呢?接下来继续分析。

(2)Message是什么?

      Message为消息对象,存放在MessageQueue消息队列中。在Android中经常使用Message.obtain()来获取Message实例。当然Message消息对象有很多成员属性,这些属性可以携带一些值通过Message消息对象存放在MessageQueue消息队列传递。而消息对象存放是通过Handler发送后存放在MessageQueue消息队列中。

(3)MessageQueue是什么?

      MessageQueue为消息队列,用来思义队列就是存放多个Message。每个线程最多只有一个MessageQueue。而MessageQueue是由Looper来管理的,主线程创建时,默认会创建一个Looper对象。非主线程创建时,需要代码创建Looper对象,创建Looper对象时,会自动创建一个MessageQueue消息队列。

(4)Looper是什么?

      Looper管理MessageQueue消息队列,并处理存放在消息队列中的Message消息对象。与MessageQueue一样,Looper也是一个线程只有一个,并且绑定线程。所以Looper处理Message时都是在与其绑定的线程中执行的。注意,Looper只是处理消息对象出去MessageQueue,并不处理消息对象存放进MessageQueue的这个过程。

在主线程创建Handler时,Handler会与主线程绑定,而Looper也是与线程绑定的,所以HandlerLooper是一一对应关系的。关系图如下

Android基础知识学习- Looper与Handler_第3张图片

4.主线程中使用Looper与Handler

private TextView tv;

private Button start;

private Handler handler;

    @Override

    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_main);          

        tv = (TextView)findViewById(R.id.tv);

        start = (Button)findViewById(R.id.start_button);

        handler = new Handler(){

              @Override

              public void handleMessage(Message msg){

                   System.out.println("我是message");

                   String text = (String)msg.obj;

                   tv.setText(text);

              }

        };

        start.setOnClickListener(new OnClickListener(){

 

                  @Override

                  public void onClick(View arg0) {

                       // TODO Auto-generated method stub

                       System.out.println("点击事件的线程"+Thread.currentThread().getName());

                       new Thread(){

                             @Override

                             public void run(){

                                   try {

                                         System.out.println("新生成线程"+Thread.currentThread().getName());

                                         Thread.sleep(10000);

                                         Message msg = Message.obtain();

                                         msg.obj = "点击后的内容";

                                         handler.sendMessage(msg);

                                   } catch (Exception e) {

                                         // TODO: handle exception

                                   }

                             }

                       }.start();                  

                  }             

        });

    }

分析代码

(1)定义全局的handler对象

private Handler handler;

(2)实例化Handler对象,匿名内部类写法重写了handleMessage方法;首先实例化Handler对象的同时这个Handler会绑定线程,目前的Handler对象是在主线程Main Thread实例化的,所以绑定的是主线程。其次handleMessage方法在MessageQueue消息队列中Message为空的情况下不会执行。

handler = new Handler(){

              @Override

              public void handleMessage(Message msg){

                   System.out.println("我是message");

                   String text = (String)msg.obj;

                   tv.setText(text);

              }

        };

(3)匿名内部类实例化一个子线程,重写run()方法。首先通过Message.obtain()得到Message消息对象,使用obj属性存放点击后的内容,再通过handlersendMessage()方法发送这个消息对象。

new Thread(){

                             @Override

                             public void run(){

                                   try {

                                         System.out.println("新生成线程"+Thread.currentThread().getName());

                                         Thread.sleep(10000);

                                         Message msg = Message.obtain();

                                         msg.obj = "点击后的内容";

                                         handler.sendMessage(msg);

                                   } catch (Exception e) {

                                         // TODO: handle exception

                                   }

                             }

                       }.start();

再结合HandlerLooperMessageQueueMessage讲解一下。当代码运行,创建主线程时,Looper被创建并绑定主线程,然后在主线程中实例化Handler对象,Handler对象也绑定主线程。然后来到点击监听事件,当点击start按钮,触发监听10s后实例化Message对象,并由HandlersendMessage方法把Message消息对象放进MessageQueue消息队列中;因为Handler对象是全局的,所以可以在Worker Thread中使用Handler对象。然后回到主线程的HandlerLooper开始干活了,当消息队列中有Message消息对象,Looper循环取出交给HandlerhandleMessage方法处理,整个流程结束。这样就解决了新开Thread中不能操作UI层控件,同时可以在新开Thread中执行数据的变化。

Handler源码分析

      上面案例Handler是在主线程运行,当主线程创建时Looper自动创建并工作。而HandlersendMessage又做了什么事情呢?下面看源码

public final boolean sendMessage(Message msg)

    {

        return sendMessageDelayed(msg, 0);

    }

      调用了sendMessageDelayed()方法,继续往下

public final boolean sendMessageDelayed(Message msg, long delayMillis)

    {

        if (delayMillis < 0) {

            delayMillis = 0;

        }

        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);

    }

      然后sendMessageDelayed()调用了sendMessageAtTime()方法,代码如下

public boolean sendMessageAtTime(Message msg, long uptimeMillis) {

        MessageQueue queue = mQueue;

        if (queue == null) {

            RuntimeException e = new RuntimeException(

                    this + " sendMessageAtTime() called with no mQueue");

            Log.w("Looper", e.getMessage(), e);

            return false;

        }

        return enqueueMessage(queue, msg, uptimeMillis);

    }

      通过源代码可以看到,sendMessageAtTime()方法实际就是把Message消息对象加入到了MessageQueue消息队列中,然后通过Looper对象从MessageQueue中取出Message对象交给Handler处理。只是说Looper对象在主线程中是自动创建并执行的,所以没有直接看到Looper的代码,把Looper对象在主线程中如何工作的彩蛋埋到后面。

5.子线程使用Looper与Handler

private TextView tv;

private Button start;

private Handler handler;

    @Override

    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_main);          

        tv = (TextView)findViewById(R.id.tv);

        start = (Button)findViewById(R.id.start_button);         

        start.setOnClickListener(new OnClickListener(){

                  @Override

                  public void onClick(View arg0) {

                       // TODO Auto-generated method stub

                       System.out.println("点击事件的线程"+Thread.currentThread().getName());

                       Message msg = handler.obtainMessage();

                       msg.obj = "点击后的内容";

                       handler.sendMessage(msg);

                  }   

        });

       

        new Thread(){

              @Override

                public void run(){

                     try {

                           System.out.println("新生成线程"+Thread.currentThread().getName());

                           //准备Looper对象

                           Looper.prepare();

                           handler = new Handler(){

                              @Override

                              public void handleMessage(Message msg){

                                   System.out.println("");

                                   String text = (String)msg.obj;

                                   //tv.setText(text);

                                   System.out.println("我是message,处理消息内容为:"+text);

                              }

                        };

                           Looper.loop();

                     } catch (Exception e) {

                           // TODO: handle exception

                     }

                }

        }.start();

    }

代码分析

(1)这次要讲解的是从Main Thread主线程中生成消息对象,通过Handler来传递消息给子线程。首先就是要在主线程中生一个消息对象,并发送消息,代码如下:

start.setOnClickListener(new OnClickListener(){

                  @Override

                  public void onClick(View arg0) {

                       // TODO Auto-generated method stub

                       System.out.println("点击事件的线程"+Thread.currentThread().getName());

                       Message msg = handler.obtainMessage();

                       msg.obj = "点击后的内容";

                       handler.sendMessage(msg);

                  }   

        });

(2)消息对象的代码是放在按钮点击事件里面的,当用户操作点击时,才会触发生成。接下来就是创建一个线程来执行消息处理。

new Thread(){

              @Override

                public void run(){

                     try {

                           System.out.println("新生成线程"+Thread.currentThread().getName());

                           //准备Looper对象

                           Looper.prepare();

                           handler = new Handler(){

                              @Override

                              public void handleMessage(Message msg){

                                   System.out.println("");

                                   String text = (String)msg.obj;

                                   //tv.setText(text);

                                   System.out.println("我是message,处理消息内容为:"+text);

                              }

                        };

                           Looper.loop();

                     } catch (Exception e) {

                           // TODO: handle exception

                     }

                }

        }.start();

(3)可以看到在handler实例化前面,执行了Looper.prepare()方法,然后再执行Handler处理消息对象,接着执行Looper.loop()方法

6.其他源码分析

在第4点源码介绍中除了handleMessage()方法外, Looper.prepare()Looper.loop()并没有出现,下面介绍这两个方法:

Looper.prepare()方法

      执行Looperprepare()方法,这个方法的主要作用就是准备Looper对象。在主线程并没有见到这个方法的执行,是因为主线程创建时,自动会创建Looper对象,无需代码再创建。

      再来看看源码

public static void prepare() {

        prepare(true);

    }

 

    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));

    }

      从源码可以看到首先调用了prepare()静态方法,再调用了prepare(boolean quitAllowed)这个静态方法,并且这里使用了ThreadLocal本地线程来管理Looper对象,当前线程中没有Looper对象则会执行new Looper()代码创建新的Looper对象。

Looper.loop()方法

      回顾之前Handler的工作方式,很容易就能猜到loop()方法的作用;它就是完成循环从MessageQueue中取出Message对象,然后调用HandlerhandleMessage()方法处理。与prepare()方法一样,在主线程中loop()方法自动调用。

      源码分析,loop方法的源码如下:

public static void loop() {

        final Looper me = myLooper();

        if (me == null) {

            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");

        }

        final MessageQueue queue = me.mQueue;

 

        // Make sure the identity of this thread is that of the local process,

        // and keep track of what that identity token actually is.

        Binder.clearCallingIdentity();

        final long ident = Binder.clearCallingIdentity();

 

        for (;;) {

            Message msg = queue.next(); // might block

            if (msg == null) {

                // No message indicates that the message queue is quitting.

                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 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.recycle();

        }

    }

      Looperloop方法代码有点多,关注核心的几点:

final Looper me = myLooper();

        if (me == null) {

            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");

        }

        final MessageQueue queue = me.mQueue;

      首先得到的是Looper对象,然后通过Looper对象得到消息队列对象;然后再往下看代码

for (;;) {

            Message msg = queue.next(); // might block

            if (msg == null) {

                // No message indicates that the message queue is quitting.

                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);

      消息队列对象queue调用next()方法取出消息对象,然后通过消息对象的target属性执行dispatchMessage()方法。从Message类可以知道target实际就是发送Message消息对象的HandlerdispatchMessage()方法实际就是处理Message消息对象啦。从这几行代码再结合Handler工作流程中说到的Loope取出消息对象,通过HandlerhandleMessage()处理,这样结合来看,就很容易理解了。 

总结:一个Handler对应一个Looper对象,一个Looper对应一个MessageQueue对象。Handler发送Message对象放到MessageQueue消息队列中,然后通过Looper取出再交给Handler处理。

Handlerpost()方法

      通过HandlersendMessage()方法可以发送消息,在Looper对象中转一圈后,又回到Handler调用handleMessage()方法处理。这其中一个弊端就是需要通过Message传递的消息只能通过whatarg1arg2obj等等属性来传递数据,并没有办法去执行复杂的功能。

      post方法就是解决这种需求的,它可以执行整个代码块内容,下面进入讲解。首先看代码

private TextView tv;

private Button start;

private Handler handler = new Handler();

    @Override

    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_main);          

        tv = (TextView)findViewById(R.id.tv);

        start = (Button)findViewById(R.id.start_button);

             

        start.setOnClickListener(new OnClickListener(){

                  @Override

                  public void onClick(View arg0) {

                       // TODO Auto-generated method stub

                       System.out.println("点击事件的线程"+Thread.currentThread().getName());

                       MyThread mt = new MyThread();

                       mt.start();

                  }   

        });

    } 

   

    class MyThread extends Thread{

          @Override

            public void run(){

                  Runnable run = new Runnable() {

                       @Override

                       public void run() {

                             // TODO Auto-generated method stub

                             System.out.println("Runnable线程"+Thread.currentThread().getName());

                             tv.setText("点击后内容");

                       }

                  };

                 

                  handler.post(run);

            }

    }

简单介绍下,写了一个子线程的内部类,在线程的run()方法中实例化Runnable对象,并修改了tv这个控件的文本内容;把Runnable对象放入Handlerpost方法中。这里有个疑惑就是讲Handler之前留下的问题,在子线程中不是不能修改UI的任何内容吗?且看下面。

然后点击按钮的时候触发事件,运行这个子线程。回到上面的疑惑,下面开始分析。

1)首先Handler是在主线程中创建的,所以handler.post()方法自然也是在主线程中执行的。

2)接下来看post方法源码

public final boolean post(Runnable r)

    {

       return  sendMessageDelayed(getPostMessage(r), 0);

    }

通过调用getPostMessage()方法把Runnable对象传入,接下来看getPostMessage()方法的源码,如下:

private static Message getPostMessage(Runnable r) {

        Message m = Message.obtain();

        m.callback = r;

        return m;

    }

      首先通过Messageobtain得到Message对象;重点来了,把Runnable对象赋值给了Message对象的callback属性,callback属性是一个Runnable对象,然后返回这个Message对象。从这里来看有点懵懵懂懂,继续往下回到Handlerpost方法,post方法调用的sendMessageDelayed()方法如下

public final boolean sendMessageDelayed(Message msg, long delayMillis)

    {

        if (delayMillis < 0) {

            delayMillis = 0;

        }

        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);

    }

      然后sendMessageDelayed()调用了sendMessageAtTime()方法,代码如下

public boolean sendMessageAtTime(Message msg, long uptimeMillis) {

        MessageQueue queue = mQueue;

        if (queue == null) {

            RuntimeException e = new RuntimeException(

                    this + " sendMessageAtTime() called with no mQueue");

            Log.w("Looper", e.getMessage(), e);

            return false;

        }

        return enqueueMessage(queue, msg, uptimeMillis);

    }

      看到这段代码已经很熟悉了,在HandlersendMessage()方法发送Message消息对象是同一个方法。而这里的post()方法不同的操作就是把Runnable对象赋值给了Messagecallback属性,并且把Message消息对象加入了MessageQueue消息队列中。然后往下看,就是消息的处理啦,回到Looperloop()方法,其中一段代码“msg.target.dispatchMessage(msg); ”应该还有印象,下面贴出dispatchMessage()方法的源代码,如下:

public void dispatchMessage(Message msg) {

        if (msg.callback != null) {

            handleCallback(msg);

        } else {

            if (mCallback != null) {

                if (mCallback.handleMessage(msg)) {

                    return;

                }

            }

            handleMessage(msg);

        }

    }

      在没有使用post()方法之前,是用HandlersendMessage()方法发送消息,这时候Messagecallback是空会执行else语句块,mCallback也是空,所以直接执行了handleMessage()方法,而在创建Handler对象时,就重写了这个方法,所以会执行重写里面的代码块。

      而到了使用post()方法,Message对象的callback就有东西了,不是空了,这时候执行的是handleCallback()方法,下面看代码

private static void handleCallback(Message message) {

        message.callback.run();

    }

      执行的是Message对象callback属性的run()方法;callback属性就是Runnable对象,所以就是执行了post()方法中传入的Runnable对象的run()方法。知道一点java线程的同学应该都知道,直接调用ThreadRunnablerun()方法是不会开辟新线程,而是在原线程中执行,所以执行的run()方法依然还是在与Handler同一个线程中执行,也就是主线程。所以就解决了我们的困惑,在Runnable中修改了UI,实际依然是在主线程中执行的,是完全OK的。

主线程的Looper工作代码

      在起初讲到Handler机制时,一直谈的话题就是主线程中Looper是自动创建并执行的,总是感觉不太爽快,还是将这层雾揭开比较爽。找到Android主线程创建时的代码如下:

Looper.prepareMainLooper();

 

        ActivityThread thread = new ActivityThread();

        thread.attach(false);

 

        if (sMainThreadHandler == null) {

            sMainThreadHandler = thread.getHandler();

        }

 

        AsyncTask.init();

 

        if (false) {

            Looper.myLooper().setMessageLogging(new

                    LogPrinter(Log.DEBUG, "ActivityThread"));

        }

 

        Looper.loop();

      从代码中可以看到,在实例化ActivityThread之前,执行了LooperprepareMainLooper()方法,代码如下:

public static void prepareMainLooper() {

        prepare(false);

        synchronized (Looper.class) {

            if (sMainLooper != null) {

                throw new IllegalStateException("The main Looper has already been prepared.");

            }

            sMainLooper = myLooper();

        }

    }

      从代码中可以看到,执行的是prepare()方法,这个方法很熟悉了;在子线程使用Handler机制时,初始化Looper对象用的就是这个prepare()方法。所以得出结论就是主线程实际也是执行了相同的操作,包括后面的loop()方法,都是一致的,只是主线程在创建之际,都已经做了这些事情啦。

      至于使用主线程调用prepare()方法传入的为什么是false,查看源代码可以知道,这个false在实例化MessageQueue消息队列继续传入,代码如下:

private Looper(boolean quitAllowed) {

        mQueue = new MessageQueue(quitAllowed);

        mThread = Thread.currentThread();

    }   

      然后在MessageQueue中,这个false给了mQuitAllowed这个变量,这个变量的作用是如果在主线程中调用MessageQueuequit()方法退出,则会抛出异常。同样在主线程中调用Looperquit()方法退出,也是执行了同一个方法,也会抛出异常。这就是为什么在主线程调用prepare()方法为什么要传入false,就是因为在主线程中不允许退出。

      接下来看Handler的代码:

if (sMainThreadHandler == null) {

            sMainThreadHandler = thread.getHandler();

        }

      这段代码很直接的告诉了我们,为什么在主线程中创建Handler对象时,也就是new Handler后,它是可以绑定到主线程的?原因就是这段代码。

你可能感兴趣的:(Android,Android基础知识学习,Android,Looper,Handler,Thread)