Handler、Thread和Runnable简单分析

  Handler、Thread和Runnable在开发中频繁使用,很多新手都因为概念不清而头绪全无,在这我来简单得缕缕这三者的联系与区别。

  Runnable是最简单的,它并没有什么包装,Android源码如下:

 1 /**
 2  * Represents a command that can be executed. Often used to run code in a
 3  * different {@link Thread}.
 4  */
 5 public interface Runnable {
 6 
 7     /**
 8      * Starts executing the active part of the class' code. This method is
 9      * called when a thread is started that has been created with a class which
10      * implements {@code Runnable}.
11      */
12     public void run();
13 }

  Runnable就是一个非常简单的接口,注释上说的是“代表一个能被执行的命令,总是用来在新的线程中运行”。
  我们再来看看Runnable的子类Thread,我们经常使用Thread来新建一个线程脱离原线程来单独跑,也经常把Runnable的实现类用Thread来包装成线程的主要执行内容:Thread thread = new Thread(Runnable)。我们就先来屡屡Thread thread = new Thread(Runnable)的过程。

 1     /**
 2      * Constructs a new {@code Thread} with a {@code Runnable} object and a
 3      * newly generated name. The new {@code Thread} will belong to the same
 4      * {@code ThreadGroup} as the {@code Thread} calling this constructor.
 5      *
 6      * @param runnable
 7      *            a {@code Runnable} whose method <code>run</code> will be
 8      *            executed by the new {@code Thread}
 9      *
10      * @see java.lang.ThreadGroup
11      * @see java.lang.Runnable
12      */
13     public Thread(Runnable runnable) {
14         create(null, runnable, null, 0);
15     }

  注释说用Runnable来构造一个线程实例,且创建的线程属于相同的ThreadGroup(是一种线程容器,create方法的第一个参数代表这个),我们来看看create方法都做了什么。

/**
     * Initializes a new, existing Thread object with a runnable object,
     * the given name and belonging to the ThreadGroup passed as parameter.
     * This is the method that the several public constructors delegate their
     * work to.
     *
     * @param group ThreadGroup to which the new Thread will belong
     * @param runnable a java.lang.Runnable whose method <code>run</code> will
     *        be executed by the new Thread
     * @param threadName Name for the Thread being created
     * @param stackSize Platform dependent stack size
     * @throws IllegalThreadStateException if <code>group.destroy()</code> has
     *         already been done
     * @see java.lang.ThreadGroup
     * @see java.lang.Runnable
     */
    private void create(ThreadGroup group, Runnable runnable, String threadName, long stackSize) {
        Thread currentThread = Thread.currentThread();
        if (group == null) {
            //前面我们说过了,用Runnable新建的线程和原线程属于同一线程容器
            group = currentThread.getThreadGroup();      
        }
        if (group.isDestroyed()) {
            throw new IllegalThreadStateException("Group already destroyed");
        }

        this.group = group;
        //此处用synchronized来保证新建的线程id+1并且使唯一的
        synchronized (Thread.class) {
            id = ++Thread.count;
        }
        //threadName 
        if (threadName == null) {
            this.name = "Thread-" + id;
        } else {
            this.name = threadName;
        }
        //相当于目标任务
        this.target = runnable;
        //线程开辟栈的大小,为0就是默认值8M
        this.stackSize = stackSize;
        //新线程的优先级和父线程是一样的
        this.priority = currentThread.getPriority();

        this.contextClassLoader = currentThread.contextClassLoader;

        // Transfer over InheritableThreadLocals.
        if (currentThread.inheritableValues != null) {
            inheritableValues = new ThreadLocal.Values(currentThread.inheritableValues);
        }
     
        // add ourselves to our ThreadGroup of choice
        this.group.addThread(this);
    }

  在新建完线程之后,我们有两种方法来启动线程任务:thread.run() ; thread.start()。这两者有啥区别嘞?都是这么说的:

  thread.run() ,实际上只是在UI线程执行了Runnable的任务方法并没有实现多线程,系统也没有新开辟一个线程。

  thread.start(),才是新开一个多线程,并且在新开的线程执行Thread你们的run()方法。

  对于thread.run(),由简单的java继承机制也知道,它只是执行了Runnable的run方法,我们来看看源码吧!

 1     /**
 2      * Calls the <code>run()</code> method of the Runnable object the receiver
 3      * holds. If no Runnable is set, does nothing.
 4      *
 5      * @see Thread#start
 6      */
 7     public void run() {
 8         if (target != null) {
 9             target.run();
10         }
11     }

  由上面的create方法我们知道target就是Runnable包装在thread中的实例,还没有做任何事情,我们知道线程的新建需要请求CPU,所以直接调用run方法确实没有新建线程,只是在currentThread中直接执行了一个方法而已。我们再来看看thread.start()方法的流程又是怎么样的。

 1     /**
 2      * Starts the new Thread of execution. The <code>run()</code> method of
 3      * the receiver will be called by the receiver Thread itself (and not the
 4      * Thread calling <code>start()</code>).
 5      *
 6      * @throws IllegalThreadStateException - if this thread has already started.
 7      * @see Thread#run
 8      */
 9     public synchronized void start() {
10         checkNotStarted();
11 
12         hasBeenStarted = true;
13 
14         nativeCreate(this, stackSize, daemon);
15     }
16 
17     private native static void nativeCreate(Thread t, long stackSize, boolean daemon);

  方法同样用synchronized关键字修饰,用来防止同一个线程阻塞。而方法的执行交给了nativeCreate方法,并且把当前Thread的实例自己传了进去,而this中就我们所知的,带了这么几个参数:

 1     volatile ThreadGroup group;
 2     volatile boolean daemon;
 3     volatile String name;
 4     volatile int priority;
 5     volatile long stackSize;
 6     Runnable target;
 7     private static int count = 0;
 8 
 9     /**
10      * Holds the thread's ID. We simply count upwards, so
11      * each Thread has a unique ID.
12      */
13     private long id;
14 
15     /**
16      * Normal thread local values.
17      */
18     ThreadLocal.Values localValues;
19 
20     /**
21      * Inheritable thread local values.
22      */
23     ThreadLocal.Values inheritableValues;

  那我们只好跟过去,看看在native中到底是怎么新建的新建的线程,资源又是如何请求的(新建线程的关键)。
  顺着nativeCreate方法,我们发现它换了方法名调用的/android/art/runtime/native/java_lang_Thread.cc里面方法,名字换成了CreateNativeThread:

 1 static JNINativeMethod gMethods[] = {
 2   NATIVE_METHOD(Thread, currentThread, "!()Ljava/lang/Thread;"),
 3   NATIVE_METHOD(Thread, interrupted, "!()Z"),
 4   NATIVE_METHOD(Thread, isInterrupted, "!()Z"),
 5   NATIVE_METHOD(Thread, nativeCreate, "(Ljava/lang/Thread;JZ)V"),
 6   NATIVE_METHOD(Thread, nativeGetStatus, "(Z)I"),
 7   NATIVE_METHOD(Thread, nativeHoldsLock, "(Ljava/lang/Object;)Z"),
 8   NATIVE_METHOD(Thread, nativeInterrupt, "!()V"),
 9   NATIVE_METHOD(Thread, nativeSetName, "(Ljava/lang/String;)V"),
10   NATIVE_METHOD(Thread, nativeSetPriority, "(I)V"),
11   NATIVE_METHOD(Thread, sleep, "!(Ljava/lang/Object;JI)V"),
12   NATIVE_METHOD(Thread, yield, "()V"),
13 };

  我们可以看到关于线程管理的一些方法全在里面,包括sleep、interrupted等,继续dig到CreateNativeThread的实现

 1 void Thread::CreateNativeThread(JNIEnv* env, jobject java_peer, size_t stack_size, bool is_daemon) {
 2   CHECK(java_peer != nullptr);//即为java层的thread实例,包裹着run方法的具体实现
 3   Thread* self = static_cast<JNIEnvExt*>(env)->self;
 4   Runtime* runtime = Runtime::Current();
 5 
 6   // Atomically start the birth of the thread ensuring the runtime isn't shutting down.
 7   bool thread_start_during_shutdown = false;//这段代码用来检测thread是否在runtime宕机时start的
 8   {
 9     MutexLock mu(self, *Locks::runtime_shutdown_lock_);
10     if (runtime->IsShuttingDownLocked()) {
11       thread_start_during_shutdown = true;
12     } else {
13       runtime->StartThreadBirth();
14     }
15   }
16   if (thread_start_during_shutdown) {//若runtime宕机了就抛出异常
17     ScopedLocalRef<jclass> error_class(env, env->FindClass("java/lang/InternalError"));
18     env->ThrowNew(error_class.get(), "Thread starting during runtime shutdown");
19     return;
20   }
21 
22   Thread* child_thread = new Thread(is_daemon);//新建子线程
23   // Use global JNI ref to hold peer live while child thread starts.
24   child_thread->tlsPtr_.jpeer = env->NewGlobalRef(java_peer);//把java层的run方法实体传递给子线程
25   stack_size = FixStackSize(stack_size);
26 
27   // Thread.start is synchronized, so we know that nativePeer is 0, and know that we're not racing to
28   // assign it.
29   env->SetLongField(java_peer, WellKnownClasses::java_lang_Thread_nativePeer,
30                     reinterpret_cast<jlong>(child_thread));
31 
32   pthread_t new_pthread;
33   pthread_attr_t attr;
34   CHECK_PTHREAD_CALL(pthread_attr_init, (&attr), "new thread");
35   CHECK_PTHREAD_CALL(pthread_attr_setdetachstate, (&attr, PTHREAD_CREATE_DETACHED), "PTHREAD_CREATE_DETACHED");
36   CHECK_PTHREAD_CALL(pthread_attr_setstacksize, (&attr, stack_size), stack_size);
37   //创建新线程的方法,返回一个标志
38   int pthread_create_result = pthread_create(&new_pthread, &attr, Thread::CreateCallback, child_thread);
39   CHECK_PTHREAD_CALL(pthread_attr_destroy, (&attr), "new thread");
40 
41   //线程创建如果失败则清除子线程信息,释放空间
42   if (pthread_create_result != 0) {
43     // pthread_create(3) failed, so clean up.
44     {
45       MutexLock mu(self, *Locks::runtime_shutdown_lock_);
46       runtime->EndThreadBirth();
47     }
48     // Manually delete the global reference since Thread::Init will not have been run.
49     env->DeleteGlobalRef(child_thread->tlsPtr_.jpeer);
50     child_thread->tlsPtr_.jpeer = nullptr;
51     delete child_thread;
52     child_thread = nullptr;
53     // TODO: remove from thread group?
54     env->SetLongField(java_peer, WellKnownClasses::java_lang_Thread_nativePeer, 0);
55     {
56       std::string msg(StringPrintf("pthread_create (%s stack) failed: %s",
57                                    PrettySize(stack_size).c_str(), strerror(pthread_create_result)));
58       ScopedObjectAccess soa(env);
59       soa.Self()->ThrowOutOfMemoryError(msg.c_str());
60     }
61   }
62 }

  创建新线程在pthread_create(&new_pthread, &attr, Thread::CreateCallback, child_thread)方法里由java Runtime实现,由于那里过于深入,我们就不往下挖了(往下我已经看不懂了)。但是我们有个一个创新线程过程的概念,进一步的了解了thread的id号,父线程等概念。也知道了只有通过thread.start()方法才会创建一个新的线程。
  说清了Thread和Runnable的关系,我们再来说说Handler,Handler是啥呢?当我们需要进行线程间通信的时候,我们就需要用到Handler,我们把Handler认为是一个维护消息循环队列的东西,书上都这么说,那么Handler是如何维护消息队列呢?要弄清楚这个还是得看源码。

  Google给出的关于Handler简介,大家感受下:

 1 /**
 2  * A Handler allows you to send and process {@link Message} and Runnable
 3  * objects associated with a thread's {@link MessageQueue}.  Each Handler
 4  * instance is associated with a single thread and that thread's message
 5  * queue.  When you create a new Handler, it is bound to the thread /
 6  * message queue of the thread that is creating it -- from that point on,
 7  * it will deliver messages and runnables to that message queue and execute
 8  * them as they come out of the message queue.
 9  * 
10 **/

  大意是三点:

  1:Handler可以在线程的帮助下用来发送和处理Message和Runnable实例;

  2:每个Handler实例都是和单个线程和此线程的消息队列绑定的;

  3:Handler负责的工作是传送message到消息队列中且在它们从队列中出来的时候对它们进行处理。

  那么Handler是怎么维护消息队列呢?在处理消息过程中,我们常用到的几个方法:sendMessage、sendMessageAtFrontOfQueue、sendMessageAtTime、sendMessageDelayed 以及 handleMessage 。我们来看看这几个方法的源码:

 1 public final boolean sendMessage(Message msg)
 2  {  3 return sendMessageDelayed(msg, 0);  4  }  5 public final boolean sendMessageDelayed(Message msg, long delayMillis)  6  {  7 if (delayMillis < 0) {  8 delayMillis = 0;  9  } 10 return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis); 11  } 12 public boolean sendMessageAtTime(Message msg, long uptimeMillis) { 13 MessageQueue queue = mQueue; 14 if (queue == null) { 15 RuntimeException e = new RuntimeException( 16 this + " sendMessageAtTime() called with no mQueue"); 17 Log.w("Looper", e.getMessage(), e); 18 return false; 19  } 20 return enqueueMessage(queue, msg, uptimeMillis); 21  } 22 public final boolean sendMessageAtFrontOfQueue(Message msg) { 23 MessageQueue queue = mQueue; 24 if (queue == null) { 25 RuntimeException e = new RuntimeException( 26 this + " sendMessageAtTime() called with no mQueue"); 27 Log.w("Looper", e.getMessage(), e); 28 return false; 29  } 30 return enqueueMessage(queue, msg, 0); 31 }

  我们可以看到除了设置的不同,发送message的每个方法都实现了enqueueMessage()方法,我们看看enqueueMessage()的源码:

1     private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
2         msg.target = this;
3         if (mAsynchronous) {
4             msg.setAsynchronous(true);
5         }
6         return queue.enqueueMessage(msg, uptimeMillis);
7     }

  追到MessageQueue里面,我们可以看到一个经典的队列add的操作:

 1     boolean enqueueMessage(Message msg, long when) {
 2         if (msg.target == null) {
 3             throw new IllegalArgumentException("Message must have a target.");
 4         }
 5         if (msg.isInUse()) {
 6             throw new IllegalStateException(msg + " This message is already in use.");
 7         }
 8 
 9         synchronized (this) {
10             if (mQuitting) {
11                 IllegalStateException e = new IllegalStateException(
12                         msg.target + " sending message to a Handler on a dead thread");
13                 Log.w("MessageQueue", e.getMessage(), e);
14                 msg.recycle();
15                 return false;
16             }
17 
18             msg.markInUse();
19             msg.when = when;
20             Message p = mMessages;
21             boolean needWake;
22             if (p == null || when == 0 || when < p.when) {
23                 // New head, wake up the event queue if blocked.
24                 msg.next = p;
25                 mMessages = msg;
26                 needWake = mBlocked;
27             } else {
28                 // Inserted within the middle of the queue.  Usually we don't have to wake
29                 // up the event queue unless there is a barrier at the head of the queue
30                 // and the message is the earliest asynchronous message in the queue.
31                 needWake = mBlocked && p.target == null && msg.isAsynchronous();
32                 Message prev;
33                 //经典的队列add操作
34                 for (;;) {
35                     prev = p;
36                     p = p.next;
37                     if (p == null || when < p.when) {
38                         break;
39                     }
40                     if (needWake && p.isAsynchronous()) {
41                         needWake = false;
42                     }
43                 }
44                 msg.next = p; // invariant: p == prev.next
45                 prev.next = msg;
46             }
47 
48             // We can assume mPtr != 0 because mQuitting is false.
49             if (needWake) {
50                 nativeWake(mPtr);
51             }
52         }
53         return true;
54     }

  可以看到Handler每次sendmessage时其实都是一个把message实例加到MessageQueue中的过程,无论延时还是不延时。
  发送message到消息队列的过程我们清楚了,那么Handler是怎么从消息队列中往外拿message的呢?这时候就是Looper出场的时候了,Looper 也是一个对应着一个线程,当程序运行时,系统会给主线程创建一个Looper,Looper也维护着一个MessageQueue,没错,这个messagequeue就是Handler发送的那个,Looper的责任就是一方面接收消息,另一方面不停地检查messagequeue里有没有message,如果有就调用Handler中的dispatchMessage方法进行处理,我们来看看源码是怎么写的。

 1     /**
 2      * Run the message queue in this thread. Be sure to call
 3      * {@link #quit()} to end the loop.
 4      */
 5     public static void loop() {
 6         final Looper me = myLooper();
 7         if (me == null) {
 8             throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
 9         }
10         final MessageQueue queue = me.mQueue;
11 
12         // Make sure the identity of this thread is that of the local process,
13         // and keep track of what that identity token actually is.
14         Binder.clearCallingIdentity();
15         final long ident = Binder.clearCallingIdentity();
16 
17         for (;;) {
18             Message msg = queue.next(); // might block
19             if (msg == null) {
20                 // No message indicates that the message queue is quitting.
21                 return;
22             }
23 
24             // This must be in a local variable, in case a UI event sets the logger
25             Printer logging = me.mLogging;
26             if (logging != null) {
27                 logging.println(">>>>> Dispatching to " + msg.target + " " +
28                         msg.callback + ": " + msg.what);
29             }
30 
31             msg.target.dispatchMessage(msg);
32 
33             if (logging != null) {
34                 logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
35             }
36 
37             // Make sure that during the course of dispatching the
38             // identity of the thread wasn't corrupted.
39             final long newIdent = Binder.clearCallingIdentity();
40             if (ident != newIdent) {
41                 Log.wtf(TAG, "Thread identity changed from 0x"
42                         + Long.toHexString(ident) + " to 0x"
43                         + Long.toHexString(newIdent) + " while dispatching to "
44                         + msg.target.getClass().getName() + " "
45                         + msg.callback + " what=" + msg.what);
46             }
47 
48             msg.recycleUnchecked();
49         }
50     }

  我们能看到有一句处理消息的代码: msg.target.dispatchMessage(msg);

  再看看enqueueMessage方法里:msg.target = this;

  对的,message的target就是handler:

 1    /**
 2      * Handle system messages here.
 3      */
 4     public void dispatchMessage(Message msg) {
 5         if (msg.callback != null) {
 6             handleCallback(msg);
 7         } else {
 8             if (mCallback != null) {
 9                 if (mCallback.handleMessage(msg)) {
10                     return;
11                 }
12             }
13             handleMessage(msg);
14         }
15     }

  一切的一切都回到了handleMessage()方法中,我们最熟悉的方法。
  我们用一张流程图来总结一下Handler、Looper和Message三者的关系。

  说了足够多的基础知识了,那么线程间如何进行通信的呢?其实我们只要稍微想想就知道了,根据上面的流程图,只要我们的Looper里面的MessageQueue是一致的,那么就能够通过message进行线程通信了。

  上面我们已经说过了Looper也是和线程对应的,当程序运行时,主线程会自动创建一个Looper对象,这时我们通过Looper.getMainLooper()或者Looper.myLooper()会得到属于主线程的Looper对象。但如果我们开启一个新线程,在新线程里面用Looper.myLooper()去获取属于当前线程的Looper对象,但是返回结果为null。起初很不理解,Looper对象里有个静态变量ThreadLocal,大家都知道静态变量存放于常量池里面,只有一份,按道理应该是能获取的到。

 

1     // sThreadLocal.get() will return null unless you've called prepare().
2     static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

  后来查了一下这个TheadLocal对象,当开启多个线程去访问数据库时,这时候共享数据库链接是不安全的,ThreadLocal保证了每个线程都有自己的session(数据库链接),首次访问为null,会创建自己的链接。Looper对象也一样,ThreadLocal保证了每个线程都有自己的Looper对象。在android中主线程启动会创建属于自己的looper对象,这时候如果我们在去调用prepare就会抛Only one Looper may be created per thread。

 

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

 

  但是子线程并不会自己创建looper对象,需要我们手动调用prepare创建属于该线程的looper对象。当然如果只是线程间通信我们完全可以在拿到主线程的Looper基础上直接通过message传递消息,下面是一个简单实例:

 1 public class Activity extends Activity implements OnClickListener{
 2        Button button = null ;
 3        TextView text = null ;
 4        MyHandler mHandler = null ;
 5        Thread thread ;
 6        @Override
 7        protected void onCreate(Bundle savedInstanceState) {
 8               super .onCreate(savedInstanceState);
 9               setContentView(R.layout. activity);        
10               button = (Button)findViewById(R.id. btn );
11               button .setOnClickListener( this );
12               text = (TextView)findViewById(R.id. content );
13        }
14        public void onClick(View v) {
15               switch (v.getId()) {
16               case R.id. btn :
17                      thread = new MyThread();
18                      thread .start();
19                      break ;
20               }            
21        }     
22        private class MyHandler extends Handler{             
23               public MyHandler(Looper looper){
24                      super (looper);
25               }
26               @Override
27               public void handleMessage(Message msg) { // 处理消息
28                      text .setText(msg. obj .toString());
29               }            
30        }
31        private class MyThread extends Thread{
32               @Override
33               public void run() {
34                      //拿到主线程的Looper,构造子线程的Handler
35                      Looper curLooper = Looper.myLooper ();
36                      Looper mainLooper = Looper.getMainLooper ();
37                      String msg ;
38                      if (curLooper== null ){
39                             mHandler = new MyHandler(mainLooper);
40                             msg = "curLooper is null" ;
41                      } else {
42                             mHandler = new MyHandler(curLooper);
43                             msg = "This is curLooper" ;
44                      }
45                      mHandler .removeMessages(0);
46                      //通过message进行线程间通信
47                      Message m = mHandler .obtainMessage(1, 1, 1, msg);
48                      mHandler .sendMessage(m);
49               }            
50        }
51 }

  我们来看看另外一种情况:通过子线程在UI线程中更新UI。利用上面这种方法当然是可以的,我们还可以通过往MessageQueue中post Runnable对象来做,要讲清楚这个,我们得先了解一个概念:有消息循环的线程。在 Android,线程分为有消息循环的线程和没有消息循环的线程,有消息循环的线程一般都会有一个Looper,通过Looper我们来定义线程中需要完成的任务。我们的主线程(UI线程)就是一个消息循环的线程。Looper所维护的消息队列里面不仅仅包括逻辑处理,同时也包含了UI任务更新。线程的运行是这样的,按照Looper维护的消息队列,从前往后遍历消息和任务,一一完成,我们之所以不能直接在子线程中更新UI,是因为我们无法知道主线程Looper中消息队列中的完成度,如果消息不为空,这时子线程更新UI,就会发生阻塞,所以我们要把需要更新的任务放到Looper中,让子线程的请求也加入到主线程的任务队列里面,一一完成。
  前面我们说了,Handler不仅可以发送message,也何以发送Runnable对象,我们也可以不同message的传递方式,直接把任务(run方法)post到Looper中,如下示例:

 1 1.在onCreate中创建Handler 
 2 public class HandlerTestApp extends Activity { 
 3         Handler mHandler; 
 4         TextView mText; 
 5         /** Called when the activity is first created. */ 
 6        @Override 
 7        public void onCreate(Bundle savedInstanceState) { 
 8            super.onCreate(savedInstanceState); 
 9            setContentView(R.layout.main); 
10            mHandler = new Handler();//创建Handler 
11            mText = (TextView) findViewById(R.id.text0);//一个TextView 
12        } 
13      /** 构建Runnable对象,在runnable中更新界面,此处,我们修改了TextView的文字.此处需要说明的是,Runnable对象可以再主线程中创建,也可以再子线程中创建。我们此处是在子线程中创建的。 
14 **/ 
15      Runnable mRunnable0 = new Runnable() 
16     { 
17                 @Override 
18                 public void run() { 
19                         mText.setText("This is Update from ohter thread, Mouse DOWN"); 
20                 } 
21     }; 
22 2.创建子线程,在线程的run函数中,我们向主线程的消息队列发送了一个runnable来更新界面。
23 
24     private void updateUIByRunnable(){ 
25           new Thread()  
26          {  
27                //Message msg = mHandler.obtainMessage();  
28               public void run()  
29              { 
30 
31                    //mText.setText("This is Update from ohter thread, Mouse DOWN");//这句将抛出异常 
32                    mHandler.post(mRunnable0);  
33              }  
34          }.start();
35 
36      }

  理解了有消息循环线程就很容易理解这个,把Runnable看做是Message也行。

转载于:https://www.cnblogs.com/simbachen/p/4165150.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值