看完这篇文章,你就了解了Android Handler的一切

  前面给大家分析了Touch事件的分发响应机制,接下来分析下Handler的工作原理,争取让我们能对Handler的理解更深刻,透彻。能真正理解Handler的作用、Handler的工作原理。

  正文

  已经啰嗦了那么多,赶紧进入正题,先看一段 源码上面关于Handler的介绍

  A Handler allows you to send and process Message and Runnable objects associated with a thread's MessageQueue. Each Handler instance is associated with a single thread and that thread's message queue. When you create a new Handler, it is bound to the thread / message queue of the thread that is creating it -- from that point on, it will deliver messages and runnables to that message queue and execute them as they come out of the message queue.
    There are two main uses for a Handler: (1) to schedule messages and runnables to be executed as some point in the future; and (2) to enqueue an action to be performed on a different thread than your own.

  这段话的意思:一个Handler可以通过发送进程消息或者Runnable接口实例与一个线程中的MessageQueue关联起来。每一个Handler的实例关联一个独立的线程和线程的消息队列,当创建一个新的Handler实例,被创建的时候绑定到该线程和该线程的消息队列上。从这一点来看 它可以传递消息和Runnable对象到消息队列,并且当它们从消息队列中出来的时候去执行它们。
  对于一个Handler来说,主要用途有两点:①在将来的某一时刻定时执行 消息和Runnable。②将一个不在当前线程的活动,从另一个线程插入当前这个线程。
  总结一下就是:①线程之间进行通信。②可以把message或者Runnable 发送到 消息队列,并在将来某一时间执行它们。
  这里,我用一个在子线程中通知主线程去刷新当前时间的Demo,演示一下通常我们对于Handler的使用过程:
  这里主要介绍sendMessageXX() 这一系列方法,关于postXX()方法,由于不太常用,这里先不做介绍。
  首先我们定义我们Handler:

	//Handler 推荐做成静态的,这样,就不会持有外部类的引用,具体原理参看http://www.linuxidc.com/Linux/2013-12/94065.htm
	private MyHandler mHandler = new MyHandler(this);
	private static class MyHandler extends Handler {
		private final WeakReference<MainActivity> mActivity;

		public MyHandler(MainActivity activity) {
			mActivity = new WeakReference<MainActivity>(activity);
		}
		@Override
		public void handleMessage(Message msg) {
			MainActivity activity = mActivity.get();
			if (activity == null) {
				return;
			}
			activity.handleMsg(msg);
		}
	}

  把Handler定义成static,是谷歌推荐的做法,至于原因,大家可以看我代码的注释,也可以看下面的我摘出来的解释:

  当使用内部类(包括匿名类)来创建Handler的时候,Handler对象会隐式地持有一个外部类对象(通常是一个Activity)的引用(不然你怎么可能通过Handler来操作Activity中的View?)。而Handler通常会伴随着一个耗时的后台线程(例如从网络拉取图片)一起出现,这个后台线程在任务执行完毕(例如图片下载完毕)之后,通过消息机制通知Handler,然后Handler把图片更新到界面。然而,如果用户在网络请求过程中关闭了Activity,正常情况下,Activity不再被使用,它就有可能在GC检查时被回收掉,但由于这时线程尚未执行完,而该线程持有Handler的引用(不然它怎么发消息给Handler?),这个Handler又持有Activity的引用,就导致该Activity无法被回收(即内存泄露),直到网络请求结束(例如图片下载完毕)。另外,如果你执行了Handler的postDelayed()方法,该方法会将你的Handler装入一个Message,并把这条Message推到MessageQueue中,那么在你设定的delay到达之前,会有一条MessageQueue -> Message -> Handler -> Activity的链,导致你的Activity被持有引用而无法被回收。

  紧接着,我们需要一个子线程

	//子线程 通过Handler 发送消息 通知 Activity 去更新 时间
	private class TimerThread extends Thread {
		@Override
		public void run() {
			while (true) {
				while(loop){
					mHandler.sendEmptyMessage(MSG_WHAT);
					try {
						sleep(1000);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				
				}
			}
		}
	}

   在我们handleMsg方法中我们去更新当前的时间:

	public void handleMsg(Message msg) {
		Date d = new Date();
		SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
		dateNowStr = sdf.format(d);
		mCurrentTime.setText("" +dateNowStr);
	}
  再上一张效果图吧:


  这里就是模拟了 子线程更新UI的过程。如果我们用Handler 而是直接在子线程中去改变时间的话,会发生什么呢?大家都知道,会报错。

android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.

  意思就是只有创建视图的线程,才有权限去操作视图。

  Handler sendMessage()发送消息的工作过程分析

  既然用了,我们在分析一下Handler发送消息的工作过程,要发送消息,就需要Message对象,所以这里,有两个消息分发的必要元素HandlerMessage

  关键元素Handler和Message。  

  从上面的用法,我们是通过sendEmptyMessage 把消息发送出去了,那么发送到了哪里呢?我们去源码中看一下:
  首先:sendEmptyMessage

    public final boolean More ...sendEmptyMessageDelayed(int what, long delayMillis) {
          Message msg = Message.obtain();
          msg.what = what;
          return sendMessageDelayed(msg, delayMillis);
      }

  这里调用的sendMessageDelayed():

   public final boolean More ...sendMessageDelayed(Message msg, long delayMillis)
        {
            if (delayMillis < 0) {
                delayMillis = 0;
           }
            return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
       }
   又调用了 sendMessageAtTime(), 其实我们调用的所有的sendMessage 方法最终都是调用的 sendMessageAtTime()方法。


  这些方法都是重载,最终执行的sendMessageAtTime(),继续往下看 sendMessageAtTime() 有做了什么呢?

    public boolean More ...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);
       }
	   
	   
    private boolean More ...enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
          msg.target = this;
            if (mAsynchronous) {
                msg.setAsynchronous(true);
           }
           return queue.enqueueMessage(msg, uptimeMillis);
       }

  可以得出结论,sendMessageXXX()方法 最终 都是通过 MessagQqueue(消息队列) 的 enqueueMessage 方法 将Message 加入到 消息队列中。 
这里 equeueMessage()方法 比较长,不带大家看了,想要继续深入理解的朋友可以自己看源码,或者与我交流一起学习。

   用逆向分析的方法去探究,消息处理的过程分析。

  OK,至此,我们总结一下 Handler 通过sendMessageXX() 方法,将Message对象 发送到了消息队列(MessageQueue)中,接下来 该做什么?,既然加入到了消息队列中,那么 ,接下来应该是处理消息队列中的消息,那么消息队列是怎么处理消息的呢? 
  我们用逆向思维去推理一下, Handler  有sendMessage()方法去发送消息,也有handleMessage()方法去接收消息,如果send是头,那么handle就是尾部,我们逆着找一下,就能找到整个工作过程。 题外话,在我们看源码的时候,有时候正向遇到不理解的,可以逆向去推倒,正向不清楚工作流程,可以逆向去推理~ 最终都是一个闭环。
  先看下handleMessage()方法:
  public void More ...handleMessage(Message msg) {
     }
  是一个空的方法,返回的Message消息对象,让我们去处理,那继续看handleMessage方法是在哪里被调用的:
  public void More ...dispatchMessage(Message msg) {
         if (msg.callback != null) {
             handleCallback(msg);
         } else {
             if (mCallback != null) {
                 if (mCallback.handleMessage(msg)) {
                     return;
                }
            }
            handleMessage(msg);
        }
    }
  在dispatchMessage()方法中发现了handleMessage()方法,那继续去找dispatchMessage在哪里被调用?
    public static void More ...loop() {
        final Looper me = myLooper(); //拿到looper对象,这里的looper对象,最终是由Looper.prepareMainLooper()方法初始化的 
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
        final MessageQueue queue = me.mQueue;//拿到Looper 身上的消息队列

        // 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();
		// 这里相当于while(true)
        for (;;) {
            Message msg = queue.next(); // might block   消息队列中取出一条消息,取出消息的过程也是一个while(true)类型的,
            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);
            }
			//回收,重复使用Message对象
            msg.recycleUnchecked();
        }
    }
  我们最终在Looper里面的loop() 方法中发现了msg.target.dispatchMessage(msg)。Looper是什么呢?Loop的意思是循环,Looper可以理解为轮询器。

  关键元素消息轮询器(Looper)。

  恭喜,这里我们找到了消息处理的另一个关键元素(消息轮询器)Looper。
  这里调用了message.target.dispatchMessage(). 这个Message.target 是什么? 肯定是handler 对象了,退回前面看看 ,我们在sendMessage()的时候做了什么?
  前面在分析sendMessage 最终调用的是enqueueMessage()方法,这里给大家标记出了这样一行代msg.target = this; 这this 就是当前的handler对象,这里就相当于把Message对象设置上发送这个Message的Handler对象,谷歌的程序猿为什么这样设计呢?因为,在一个线程中我们可以创建多个Handler对象,每个Handler都可以独立的发送 sendMessage方法,把消息体加入消息队列中,当消息队列中的方法被处理的时候,又由把消息体加入消息队列的那个Handler去处理,所以,为了能确保,处理消息体的Handler就是发送消息体的Handler ,需要给Message加上一个标记,这个标记就是target。
  关于Loop()方法 我们稍后在下面单独做分析,这里先疏通好处理消息的流程。

  得出结论:

  现在,我们逆向推理,从handleMessage()--->dispatchMessage()----->Looper.loop()--------->得出结论:我们消息的处理是传递到Looper.loop()方法去触发的,而我们知道Looper.loop()方法是由ActivityThread的Main()方法去调用的,如下所示

    public static void More ...main(String[] args) {
        SamplingProfilerIntegration.start();

        // CloseGuard defaults to true and can be quite spammy.  We
        // disable it here, but selectively enable it later (via
        // StrictMode) on debug builds, but using DropBox, not logs.
        CloseGuard.setEnabled(false);

        Environment.initForCurrentUser();

        // Set the reporter for event logging in libcore
        EventLogger.setReporter(new EventLoggingReporter());

        Security.addProvider(new AndroidKeyStoreProvider());

        // Make sure TrustedCertificateStore looks in the right place for CA certificates
        final File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());
        TrustedCertificateStore.setDefaultUserDirectory(configDir);

        Process.setArgV0("<pre-initialized>");

        Looper.prepareMainLooper();

        ActivityThread thread = new ActivityThread();
        thread.attach(false);

        if (sMainThreadHandler == null) {
            sMainThreadHandler = thread.getHandler();
        }

        if (false) {
            Looper.myLooper().setMessageLogging(new
                    LogPrinter(Log.DEBUG, "ActivityThread"));
        }

       Looper.loop();

        throw new RuntimeException("Main thread loop unexpectedly exited");
    }
  在Main()方法里面我们找到了Looper.loop()方法,而在调用loop()方法之前,还执行了几行代码首先调用了Looper.prepareMainLooper(),创建ActivityThread实例,以及初始化MainThreadHandler对象,这几行代码的作用是什么?我们一个一个来分下,

  先看一下prepareMainLooper()方法,这个方法是初始化Looper对象,代码如下所示:

       public static void More ...prepareMainLooper() {
           prepare(false);
           synchronized (Looper.class) {
               if (sMainLooper != null) {
                   throw new IllegalStateException("The main Looper has already been prepared.");
               }
               sMainLooper = myLooper();
           }
       }
   首先prepare()去初始化looper,然后通过myLooper()方法返回Looper对象,看下初始化的过程:

     private static void More ...prepare(boolean quitAllowed) {
           if (sThreadLocal.get() != null) {
               throw new RuntimeException("Only one Looper may be created per thread");
           }
           sThreadLocal.set(new Looper(quitAllowed));
       }

  结论1:这里初始化可以看到,一个线程中 只能有一个Looper 实例存在。

  这里 new 了一个Looper,然后存储到了ThreadLocal里面,  由此我们可以推断出, 在myLooper() 方法 返回looper对象是通过sThreadLocal.get() 方法的。
  ThreadLocal是java lang包里的一个类:
  java.lang.ThreadLocal :这个类提供线程局部变量。这些变量不同于正常的同行,在每个线程访问一个(通过它的获取或设置方法)有它自己的,独立地初始化变量的副本。ThreadLocal实例通常是私有静态域类,希望副状态的线程(例如,一个用户ID或交易ID)。
例如,下面的类为每个线程生成唯一的标识符。一个线程的ID分配第一次调用就。get()仍然在后续调用不变。
  我理解的意思就是:维护了一些变量,不同的线程 ,对应不同的变量值,相互不干扰。
  在回到上面 初始化了Looper 我们看一下Looper 里面都有什么?

    private More ...Looper(boolean quitAllowed) {
           mQueue = new MessageQueue(quitAllowed);
           mThread = Thread.currentThread();
       }
     它里面有一个MessageQueue 和 当前的线程。

  结论2:因为前面 我们得出结论: 一个线程,只能有一个Looper(轮询器),而looper中有一个MessageQueue(消息队列),所以 一个线程对应一个Looper,一个MessageQueue。

  系统自己初始化的Handler(ActivityThread 内部的Handler)

  分析完了Looper.prepareMainLooper(),接着前面继续往下分析,初始化ActivityThread,然后 判断ThreadHandler 是不是null?如果是空 则初始化。

 final Handler More ...getHandler() {
   return mH;
 }
 final H mH = new H();
 
 private class More ...H extends Handler

   这里可以看出 在ActivityThread中 初始化了一个Handler,为什么系统自己初始化一个Handler呢?
  那是因为,系统内部进行数据传递,也是通过Handler来完成的,有很多动作,比如下面的图片所示:

   由此可见,不只是我们在更新UI的时候用到了Handler,系统本身也用到handler进行通信,比如Activity启动等等...因此 我们可以仿照系统的Handler工作过程,自己去实现自己的消息分发系统。
  我们可以看到,系统在完成以上步骤后,才调用了轮询器Looper的loop()方法,此时,消息轮询器就启动起来了。这里我们分析一下loop()方法的工作流程,这也是轮询器,取消息的过程,也是最关键的过程。
 public static void More ...loop() {
        final Looper me = myLooper(); //拿到looper对象,这里的looper对象,最终是由Looper.prepareMainLooper()方法初始化的 
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
        final MessageQueue queue = me.mQueue;//拿到Looper 身上的消息队列

        // 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();
		// 这里相当于while(true)
        for (;;) {
            Message msg = queue.next(); // might block   消息队列中取出一条消息,取出消息的过程也是一个while(true)类型的,
            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);
            }
			//回收,重复使用Message对象
            msg.recycleUnchecked();
        }
    }
   首先,拿到Looper对象,因为消息队列MessageQueue 在 Looper 内部,所以 拿到了Looper就相当于拿到了MessageQueue,紧接着,调用了MessageQueue的 next()方法,顾名思义:下一个,一个队列的下一个,那肯定就是下一条消息了,next()返回一个Message对象,我们看下next()方法 怎么返回的Message对象吧。
       Message More ...next() {
           // Return here if the message loop has already quit and been disposed.
           // This can happen if the application tries to restart a looper after quit
           // which is not supported.
           final long ptr = mPtr;
           if (ptr == 0) {
               return null;
           }
   
           int pendingIdleHandlerCount = -1; // -1 only during first iteration
           int nextPollTimeoutMillis = 0;
           for (;;) {//相当于while(true)
               if (nextPollTimeoutMillis != 0) {
                   Binder.flushPendingCommands();
               }
		// 根据下一条消息的时间 进行阻塞,到了时间 在执行,没有消息,或者没有到下一条消息的时间则阻塞等待。
               nativePollOnce(ptr, nextPollTimeoutMillis);
   
               synchronized (this) {
                   // Try to retrieve the next message.  Return if found.
                   final long now = SystemClock.uptimeMillis();
                   Message prevMsg = null;
                   Message msg = mMessages;
                   if (msg != null && msg.target == null) { //我们发送消息的时候都绑定了target 所以这里忽略
                       // Stalled by a barrier.  Find the next asynchronous message in the queue.
                       do {
                           prevMsg = msg;
                           msg = msg.next;
                       } while (msg != null && !msg.isAsynchronous());
                   }
                   if (msg != null) { //消息不为空
                       if (now < msg.when) { // 判断 当前时间 还没有到消息的执行时间,则计算需要等待的时间。
                           // Next message is not ready.  Set a timeout to wake up when it is ready.
                           nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                       } else {// 当前消息到了要执行的时间
                           // Got a message.
                           mBlocked = false;
                           if (prevMsg != null) {
                               prevMsg.next = msg.next;
                           } else {
                               mMessages = msg.next;
                           }
                           msg.next = null;
                           if (false) Log.v("MessageQueue", "Returning message: " + msg);
                           return msg;// 拿到此时要执行的Message 对象,并返回
                       }
                   } else {
                       // No more messages.
                       nextPollTimeoutMillis = -1;
                   }
   
                   // Process the quit message now that all pending messages have been handled.
                   if (mQuitting) {
                       dispose();
                       return null;
                   }
                   .........
               nextPollTimeoutMillis = 0;
           }
       }
   大体流程是 while(true), 在MessageQueue 中有消息,则 计算当前消息时候到了执行时间 now 与msg.when比较,如果当前时间小于msg.when 表示,还没有到下一条消息执行时间,计算出一个等待时间 nextPollTimeoutMillis, 此时执行到 nativePollOnce(ptr, nextPollTimeoutMillis);进行阻塞,阻塞的原理是用C写的 这个方法执行的过程 有兴趣的朋友可以看下这里的介绍:http://blog.csdn.net/ashqal/article/details/32107099

  loop()方法内部执行分析

  通过以上分析,我们可以得出结论:Loop()方法 内部是一个while(true)循环,当然,这个循环并非时刻不停执行的,,而是阻塞是的,原因在下面指出,在这个Loop()方法里,通过MessageQueue的next()方法,取下一条将要执行的消息,取消息的过程是计算下一条消息要执行的时间,与当前的时间做对比,如果msg.when=now 则去执行,如果 大于now 表示还没到这条消息要执行的时间,那么,计算出一个msg,when-now 需要等待的时间差,阻塞在这里,当时间过了这个时间差,继续去循环执行消息,这个阻塞的过程是通过调用C端的nativePollOnce方法去实现的。

  到了这里关于Handler的消息处理机制,我们分析的差不多了,分析了这么久,乱乱的,是时候上这张图了。

    跟大家开个玩笑,文章格式又长又烂,看的不舒服,自己我调侃下,轻松一下,是时候用一张图片总结一下我们分析的消息工作过程了。

  画图总结:


    这是在主线程中Handler的执行过程,如果,我们要在子线程中创建Handler,并在子线程中传递消息,我们需要仿照主线程中的步骤去做,首先要给子线程准备一个消息轮询器Looper对象,并且要将loop()方法执行起来。这样我们子线程中的Handler才能发收消息,否则会报错。
java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()
   在线程中创建Handler的时候要先执行Looper.prepare() 去初始化looper对象。
   当然有更简单地方法,谷歌的攻城狮们早为我们准备好了工具:HandlerThread,它就是一个包含looper的子线程。使用方法也简单:
	public void onHandlerTHread(View view){
//		创建一个HandlerThread,即创建了一个包含Looper的线程。

		HandlerThread handlerThread = new HandlerThread("leochin.com");
		handlerThread.start(); //创建HandlerThread后一定要记得start()
		
//		获取HandlerThread的Looper
		Looper looper = handlerThread.getLooper();
		
//		创建Handler,通过Looper初始化
		Handler handler = new Handler(looper){
			@Override
			public void handleMessage(Message msg) {
				super.handleMessage(msg);
				System.out.println("recieve time:"+getCurrentTime());
			}
		};
		Message message = Message.obtain();
		message.obj="子线程中发送消息";
		long delayMillis=2000;
		handler.sendMessageDelayed(message, delayMillis);
		Calendar now = Calendar.getInstance();
		System.out.println("send time:"+getCurrentTime());
//		通过以上三步我们就成功创建HandlerThread。通过handler发送消息,就会在子线程中执行。
//		如果想让HandlerThread退出,则需要调用handlerThread.quit();。
	}
  是的。无需你自己去创建子线程,无需你去自己去prepare(),仅仅四五行代码,就可以在使用。

  这篇文章比较长,自己总结一下都写了哪些东西。

  ① 首先,由在子线程中更新当前时间,显示在界面上的Demo引入,先分析了Handler 发送消息的过程。
    得出结论:sendMessageXXX()方法 最终 都是通过 MessagQqueue(消息队列) 的 enqueueMessage 方法 将Message 加入到 消息队列中。
  
  ② 如果在子线程中直接去更改UI界面会报错:
android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch it
    结论:只有创建视图层的线程(主线程),才可以去更改视图。
  
  ③ 逆推去分析消息的处理流程:
    得出结论:从handleMessage()--->dispatchMessage()----->Looper.loop()--------->得出结论,我们消息的处理是由Looper.loop()方法去触发的。
    同时得出 :一个线程,只能有一个Looper(轮询器),而looper中有一个MessageQueue(消息队列),所以 一个线程对应一个Looper,一个MessageQueue。
 
  ④ ActivityThread的Main方法中对Looper和Handler进行了初始化,供系统内部传递消息使用。
  
  ⑤ Handler与Message 、Looper(内部有 MessageQueue)共同组成了消息的处理系统。
 
  ⑥ 消息的分发是通过loop()方法去操作的,而取出消息是通过messageQueue的 next()方法 取出来的,在next()方法中,根据when,计算要处理消息的时间与当前时间now,的差值,去决定阻塞等待时间(这样做的目的是为了节省资源,否则 loop()方法一直不停的执行,是相当消耗资源的)。
 
  ⑦ 在子线程中创建Handler,首先要初始化Looper,并且要将轮询器开启起来(即调用Looper.prepare() Looper.loop()),否则报错 :
java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()
   
⑧也是最后一点,谷歌工程师为我们准备了HandlerThread,这个用起来很方便。
  
  谢谢认真观读本文的每一位小伙伴,衷心欢迎小伙伴给我指出文中的错误,也欢迎小伙伴与我交流学习。
  欢迎爱学习的小伙伴加群一起进步:230274309 。
  接下来打算,将自己修改后的 按钮水波纹效果,与大家分享一下。应该不会让大家失望的。微笑
  本文中用到的Demo 稍后上传。Demo 中有前面更改时间的Demo。也包含了后面的子线程中使用Handler,以及HandlerThread


评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值