Android开发中常常使用Handler来实现“跨越线程(Activity)更新UI”。本文将从源代码角度回答:为什么使用Handler可以跨线程更新UI?为什么跨线程更新UI一定要用Handler?

Demo

Demo1. 用Handler更新UI

以下这个Demo全然是为了演示“跨线程更新UI”而写的。

界面上仅仅有一个TextView和一个Button。按下Button创建一个后台线程。该后台线程每隔一秒更新一次TextView。连续更新10次。结束。

Activity的代码例如以下:

public class MainActivity extends Activity {
static final String TAG = "MainActivity"; Handler handler = null; @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main); final TextView text = (TextView)findViewById(R.id.txtHello);
Button button = (Button)findViewById(R.id.btnRun); button.setOnClickListener(new OnClickListener(){
@Override
public void onClick(View v) {
Log.d(TAG, "clicked!");
new Thread() {
public void run() {
for(int i=0; i<10; i++) {
Message msg = new Message();
msg.what = 1;
msg.obj = "item-"+i; handler.sendMessage(msg);
Log.d(TAG, "sended "+"item-"+i); try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}.start();
}
}); handler = new Handler() {
@Override
public void handleMessage(Message msg) {
String str = "unknow";
switch(msg.what) {
case 1:
str = (String)msg.obj;
break;
default:
break;
}
Log.d(TAG, "recv " + str);
text.setText(str);
super.handleMessage(msg);
}
};
} @Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
} }

布局文件较为简单:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context=".MainActivity" > <TextView
android:id="@+id/txtHello"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/hello_world" /> <Button
android:id="@+id/btnStart"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Start"
/>
</RelativeLayout>

这里展示的是Handler的典型使用方法——用来更新UI控件。

以下再展示一个非典型使用方法。不过为了后面的分析方便。

Demo2. 自制ActivityThread模拟Activity

本例是为了分析方便而创建的;使用一个线程LooperThread来模拟Activity。

后面阐述为什么要这么做,代码例如以下:

package com.example.handlerdemo;

import android.os.Bundle;
import android.os.Message;
import android.app.Activity;
import android.util.Log;
import android.view.Menu;
import android.view.View;
import android.widget.Button;
import android.widget.TextView; public class MainActivity extends Activity {
static final String TAG = "MainActivity"; ActivityThread acitivityThread = null; @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
setupViews();
} private void setupViews() {
TextView tv = (TextView)findViewById(R.id.txtHello);
Button bt = (Button)findViewById(R.id.btnStart); Log.d(TAG, String.format("[MainActivity] Thread %s(%d)",
Thread.currentThread().getName(), Thread.currentThread().getId()));
acitivityThread = new ActivityThread();
acitivityThread.start(); acitivityThread.waitForHandlerReady(); bt.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
new Thread() {
@Override
public void run() {
for(int i=0; i<10; i++) {
Message msg = new Message();
msg.what = i;
acitivityThread.mHandler.sendMessage(msg);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}.start();
}
});
} @Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
}

MainActivity.java

package com.example.handlerdemo;

import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.util.Log; public class ActivityThread extends Thread {
static final String TAG = "LooperThread"; public Handler mHandler = null; public ActivityThread() {
super("LooperThread");
} @Override
public void run() {
Looper.prepare(); synchronized(this) {
mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
Log.d(TAG, String.format("recv msg.what: %d in Thread: %s(%d)", msg.what,
  Thread.currentThread().getName(),Thread.currentThread().getId()));
}
};
this.notify();
} Looper.loop();
} public void waitForHandlerReady() {
try {
synchronized(this) {
while(mHandler == null)
this.wait();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

ActivityThread.java

这个Demo的布局文件非常easy。就不贴出来了。

为什么使用Handler可以跨线程更新UI?

概览

以Demo2为例,这个Demo至少涉及三个线程:GodActivity线程。ActivityThread线程(模拟UI)。匿名线程(GodActivity创建的。叫他aThread)。暂且把GodActivity当做上帝。把ActivityThread看做Demo1里的Activity。如今,我们先预览一下为什么aThread能够通过Handler来更新ActivityThread的UI(纯属虚构)。这两个线程的交互关系例如以下图所看到的:

(PS:此前的版本号画了非常多对象的生命线。结果非常混乱,删了一堆无关紧要的之后,立马清晰了,^_^)

这个序列图(Sequence Diagram)已经简洁明了地给出了答案:

  1. Activity线程的幕后另一个MessageQueue;MessageQueue故名思议是一个Message组成的Queue。
  2. aThread仅仅是将数据以Message的形式挂到了Activity幕后的MessageQueue上了;
  3. Activity线程从MessageQueue上取Message并调用Handler.handlerMessage,所以实际的“更新动作”还是发生在Activity线程内;

具体解释

以下将从Android 4.4.4源代码的角度分析Handler的“幕后黑手”。

几个关键类

Demo2中和Handler有关的类除了MessageQueue还有Message和Looper,这几个类的关系例如以下:

关键点:

  • MessageQueue通过Message.next维护链表结构(java引用即指针);
  • ActivityThread的消息循环被封装在Looper.loop()内,Looper.prepare()用于创建属于当前线程的Looper和MessageQueue。
  • 每一个Message能够通过target指向一个Handler,Handler实际上就是一个用来处理Message的callback。

接下来的代码,仅仅贴代码片段(方法),假设对各类的属性有所疑惑。能够回头查看此图。

Looper.prepare()

依据Looper的凝视能够看到,Looper线程“三部曲”:

  1. Looper.prepare()
  2. new Handler() { /* override handleMessage() */ }
  3. Looper.loop();

以下逐渐切入Looper.prepare():

    public static void prepare() {
prepare(true);
}

Looper.java

无參数版本号调用了有參数版本号:

    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.java

这段代码中引用了sThreadLocal。它被定义为ThreadLocal类型。即线程私有数据类型(或者叫做线程级别单例)

ThreadLocal<T>可以理解为Map<Thread,T>的一层包包装(实际上Android,JVM都是按Map实现的,感兴趣的同学可自行研究;set(value)时,以当前线程对象为key,所以每一个线程可以保存一份value。)

可见Looper.prepare()调用使得AcitivityThread通过Looper.sThreadLocal<Looper>持有了一个Looper对象。

继续看Looper的构造方法Looper(quitAllowed):

    private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread(); // 和当前线程关联
}

Handler.java

能够看到Looper的构造函数中创建了一个MessageQueue。

流程又转到了MessageQueue的构造函数MessageQueue(quitAllowed):

    MessageQueue(boolean quitAllowed) {
mQuitAllowed = quitAllowed;
mPtr = nativeInit();
}

MessageQueue.java

Handler()

首先看上面调用的默认构造方法:

    /**
* Default constructor associates this handler with the {@link Looper} for the
* current thread. 将当前线程的Looper与此handler关联。
* 假设当前线程没有looper,这个handler将不能接收消息,从而导致异常抛出
* If this thread does not have a looper, this handler won't be able to receive messages
* so an exception is thrown.
*/
public Handler() {
this(null, false);
}

Handler.java

默认构造方法又调用了还有一版本号的构造方法,例如以下:

    public Handler(Callback callback, boolean async) {
if (FIND_POTENTIAL_LEAKS) { // FIND_POTENTIAL_LEAKS 为 false;
final Class<? extends Handler> klass = getClass();
if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
(klass.getModifiers() & Modifier.STATIC) == 0) {
Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
klass.getCanonicalName());
}
} mLooper = Looper.myLooper(); // 获取当前线程(调用者)的Looper
if (mLooper == null) { // 假设当前线程没有Looper。则抛异常
throw new RuntimeException(
"Can't create handler inside thread that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue; // 这里引用的MessageQueue是Looper()中创建的
mCallback = callback;
mAsynchronous = async;
}

Handler.java

Handler()调用了Looper.myLooper():

    public static Looper myLooper() {
return sThreadLocal.get(); // 从该线程的“单例”中取出Looper对象
}

Looper.java

Looper.loop()

Looper.loop()封装了消息循环。所以我们如今看看Looper.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);
} // mLatencyLock is only initialized for non USER builds
// (e.g., USERDEBUG and ENG)
if ((!sLatencyEnabled) || (me != sMainLooper)) {
msg.target.dispatchMessage(msg); // 通过msg.target分派消息
}
else { // 记录性能数据
long t1 = SystemClock.uptimeMillis(); // 获得当前毫秒数(自启动)
msg.target.dispatchMessage(msg);
long t2 = SystemClock.uptimeMillis() - t1; // t2就是dispatchMessage(msg)所用时间
if (t2 < 50) {
// We don't care about these from a latency perspective
}
else if (t2 < 250) {
// Fast response that usually has low impact on user experience
sLatencyCountFast++;
sLatencySumFast += t2;
if (sLatencyCountFast >= 100) {
String name = getProcessName();
long avg = sLatencySumFast / sLatencyCountFast;
EventLog.writeEvent(2731, "mainloop2_latency1", name, avg);
sLatencyCountFast = 0;
sLatencySumFast = 0;
}
}
else if (t2 < 1000) {
sLatencyCountSlow++;
sLatencySumSlow += t2;
if (sLatencyCountSlow >= 10) {
String name = getProcessName();
long avg = sLatencySumSlow / sLatencyCountSlow;
EventLog.writeEvent(2731, "mainloop2_latency2", name, avg);
sLatencyCountSlow = 0;
sLatencySumSlow = 0;
}
}
else {
String name = getProcessName();
EventLog.writeEvent(2731, "mainloop2_bad", name, t2);
}
} 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();
}
}

Looper.java

能够看到。Looper.loop()的for循环实际上就是“消息循环”,它负责从消息队列(MessageQueue)中不断地取出消息(MessageQueue.next),然后通过msg.target来派发(dispatch)消息。

How to dispatch?

以下看看Message究竟是怎样被dispatch的:

    public void dispatchMessage(Message msg) {
if (msg.callback != null) { // 方法 1
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) { // 方法 2
return;
}
}
handleMessage(msg); // 方法 3
}
}

Handler.java

从这段代码能够看出,实现正常的Message处理有三种方式:

  1. 为Message.callback注冊一个Runnable实例。
  2. 为Handler.mCallback注冊一个Handler.Callback实例。
  3. 重写Handler的handleMessage方法。

另外。这三种方法优先级依次减少。且一个Message仅仅能有一种处理方式。

Message的发送与获取

对于一个后台线程,它要发出消息(Handler.sendMessage)。对于Activity线程,它要得到其它线程发来的消息(MessageQueue.next);而这两种工作都是以MessageQueue为基础的。以下。分别分析发送和接收的详细流程:

Handler.sendMessage()

Demo中后台线程正是通过Handler.sendMessage实现向Activity发消息的。Handler.sendMessage方法的代码例如以下:

    public final boolean sendMessage(Message msg)
{
return sendMessageDelayed(msg, 0);
}

Handler.java

    public final boolean sendMessageDelayed(Message msg, long delayMillis)
{
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}

Handler.java

当中,当中SystemClock.uptimeMillis()返回自启动以来CPU经过的毫秒数。

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

Handler.java

Handler.enqueMessage事实上仅仅是对MessageQueue.enqueueMessage的简单包装:

    private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this; // 将当前Handler(通常已重写handleMessage方法)与该Message绑定(通过target)
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis); // 调用MessageQueue.enqueueMessage
}

Handler.java

这里看到了Looper.loop()里引用的target的来源。

流程转到了MessageQueue.enqueueMessage(),看命名基本知道它是入队操作。代码例如以下:

    boolean enqueueMessage(Message msg, long when) {
if (msg.isInUse()) {
throw new AndroidRuntimeException(msg + " This message is already in use.");
}
if (msg.target == null) {
throw new AndroidRuntimeException("Message must have a target.");
} synchronized (this) { // 临界区
if (mQuitting) {
RuntimeException e = new RuntimeException(
msg.target + " sending message to a Handler on a dead thread");
Log.w("MessageQueue", e.getMessage(), e);
return false;
} msg.when = when;
Message p = mMessages; // 链表头
boolean needWake;
if (p == null || when == 0 || when < p.when) {
// p == null 队列为空
// when == 0 由 Handler.sendMessageAtFrontOfQueue() 发出
// when < p.when 新消息的when比队头要早
// New head, wake up the event queue if blocked.
msg.next = p; // 将msg放到队头,step 1
mMessages = msg; // 将msg放到队头,step 2
needWake = mBlocked;
} else {
// Inserted within the middle of the queue. Usually we don't have to wake 插到队列中间。 通常我们不必唤醒
// up the event queue unless there is a barrier at the head of the queue 事件(event)队列。除非队头有一个barrier,
// and the message is the earliest asynchronous message in the queue.且消息是队列中最早的同步消息。 needWake = mBlocked && p.target == null && msg.isAsynchronous();
Message prev;
for (;;) { // 遍历链表
prev = p;
p = p.next;
if (p == null || when < p.when) { // 到“尾部”了 或 新消息比当前消息更早
break;
}
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}
// 下面两行将msg插入prev和p之间
msg.next = p; // invariant: p == prev.next
prev.next = msg;
} // We can assume mPtr != 0 because mQuitting is false.
if (needWake) {
nativeWake(mPtr); // 通知前台线程“有消息来啦”
}
}
return true;
}

MessageQueue.java

依据这段代码可知,MessageQueue上的Message是依照when大小排列的。唯一可能让人疑惑的是最后的nativeWake。稍后讨论。

MessageQueue.next()

前文的Looper.loop方法通过MessageQueue.next()取出消息,如今看看它是怎样实现的:

    Message next() {
int pendingIdleHandlerCount = -1; // -1 only during first iteration
int nextPollTimeoutMillis = 0;
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
} // We can assume mPtr != 0 because the loop is obviously still running.
// The looper will not call this method after the loop quits.
nativePollOnce(mPtr, 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) {
// 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; // 将msg节点摘下
} else { // prevMsg == null, msg是链表头
mMessages = msg.next;
}
msg.next = null; // msg与MessageQueue“断绝关系”
if (false) Log.v("MessageQueue", "Returning message: " + msg);
msg.markInUse();
return msg; // 退出点1 到这为止。是常规逻辑
}
} else {
// No more messages.
nextPollTimeoutMillis = -1;
} // Process the quit message now that all pending messages have been handled.
if (mQuitting) {
dispose();
return null; // 退出点2
} // If first time idle, then get the number of idlers to run.
// Idle handles only run if the queue is empty or if the first message
// in the queue (possibly a barrier) is due to be handled in the future.
if (pendingIdleHandlerCount < 0
&& (mMessages == null || now < mMessages.when)) {
pendingIdleHandlerCount = mIdleHandlers.size();
}
if (pendingIdleHandlerCount <= 0) {
// No idle handlers to run. Loop and wait some more.
mBlocked = true;
continue;
} if (mPendingIdleHandlers == null) {
mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
}
mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
} // Run the idle handlers.
// We only ever reach this code block during the first iteration.
for (int i = 0; i < pendingIdleHandlerCount; i++) {
final IdleHandler idler = mPendingIdleHandlers[i];
mPendingIdleHandlers[i] = null; // release the reference to the handler boolean keep = false;
try {
keep = idler.queueIdle();
} catch (Throwable t) {
Log.wtf("MessageQueue", "IdleHandler threw exception", t);
} if (!keep) {
synchronized (this) {
mIdleHandlers.remove(idler);
}
}
} // Reset the idle handler count to 0 so we do not run them again.
pendingIdleHandlerCount = 0; // While calling an idle handler, a new message could have been delivered
// so go back and look again for a pending message without waiting.
nextPollTimeoutMillis = 0;
}
}

MessageQueue.java

MessageQueue.next()相同让人疑惑的是nativePollOnce,稍后也将见分晓。

小结

MessageQueue.next()和MessageQueue.sendMessage()分别被Activity线程、后台线程调用,而他们两个线程可能同一时候在调用这两个方法,所以他们共享并改动的成员变量须要加锁,这就是synchronized (this)出现的原因。

至此,已经可以完整的回答“为什么用Handler可以实现跨线程更新UI”。简单的说,Activity线程的背后都有一个消息队列(MessageQueue)。后台线程通过Handler的sendMessage方法向这个消息队列上放消息。Activity线程将消息从消息队列上取下来之后,通过详细Handler的handleMessage方法处理消息,而更新UI的代码就在这个handleMessage中。所以。后台线程并没有做实际的“更新”,仅仅是将要更新的内容以借助MessageQueue告诉了Activity线程,Activity线程才是实际做“更新”动作的人。

简言之。Handler并没有真正的实现“跨线程”更新UI,而是将要更新的数据(Message携带)和怎样更新(Handler携带)通过消息队列告诉了UI线程,UI线程才是真正的“幕后英雄”。

真正的ActivityThread

Demo2中的ActivityThread全然是虚构出来的,以下来看看Android的Activity究竟是不是想我虚构的那样有一个Looper。

经过上面的分析,能够从双方面验证:

  1. 看看Activity源代码中运行onCreate之前是否调用了Looper.prepare()。

  2. 运行onXXX方法时的CallStack上是否有Looper.loop();

第二点非常easy验证。仅仅需在随意onXXX方法中打一个断点。然后看程序的CallStack,就一面了然了:

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQveHVzaXdlaTEyMzY=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="">

依据这个调用栈。能够非常明显的看到有Looper.loop;同一时候还能看到是ActivityThread.main调用它的。所以能够看看ActivityThread.main的源代码:

    public static void 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()); Process.setArgV0("<pre-initialized>"); Looper.prepareMainLooper(); // 它和Looper.prepare相似 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(); throw new RuntimeException("Main thread loop unexpectedly exited");
}

ActivityThread.java

所以。上面提到的双方面都得到了验证。

即真正的ActivityThread是有Looper的。

Native浮云

细心的朋友可能会发现。上面MessageQueue的代码中还遗留几个native开头方法:nativeInit,nativePollOnce,nativeWake。

以下就来扫清这些“遮眼”的浮云。和这几个native方法直接相应的是:

static JNINativeMethod gMessageQueueMethods[] = {
/* name, signature, funcPtr */
{ "nativeInit", "()I", (void*)android_os_MessageQueue_nativeInit },
{ "nativeDestroy", "(I)V", (void*)android_os_MessageQueue_nativeDestroy },
{ "nativePollOnce", "(II)V", (void*)android_os_MessageQueue_nativePollOnce },
{ "nativeWake", "(I)V", (void*)android_os_MessageQueue_nativeWake },
{ "nativeIsIdling", "(I)Z", (void*)android_os_MessageQueue_nativeIsIdling }
};

android_os_MessageQueue.cpp

nativeInit

以下从adnroid_os_MessageQueue_nativeInit開始,顾名思义,nativeInit当然是完毕一些初始化工作的。

static jint android_os_MessageQueue_nativeInit(JNIEnv* env, jclass clazz) {
NativeMessageQueue* nativeMessageQueue = new NativeMessageQueue(); // 创建了NativeMessageQueue
if (!nativeMessageQueue) {
jniThrowRuntimeException(env, "Unable to allocate native queue");
return 0;
} nativeMessageQueue->incStrong(env);
return reinterpret_cast<jint>(nativeMessageQueue);
}

android_os_MessageQueue.cpp

看看NativeMessageQueue的声明:

class NativeMessageQueue : public MessageQueue {
public:
NativeMessageQueue();
virtual ~NativeMessageQueue(); virtual void raiseException(JNIEnv* env, const char* msg, jthrowable exceptionObj); void pollOnce(JNIEnv* env, int timeoutMillis); void wake(); private:
bool mInCallback;
jthrowable mExceptionObj;
};

android_os_MessageQueue.cpp

NativeMessageQueue继承了MessageQueue,再来看看MessageQueue的声明:

class MessageQueue : public RefBase {
public:
/* Gets the message queue's looper. */
inline sp<Looper> getLooper() const {
return mLooper;
} /* Checks whether the JNI environment has a pending exception.
*
* If an exception occurred, logs it together with the specified message,
* and calls raiseException() to ensure the exception will be raised when
* the callback returns, clears the pending exception from the environment,
* then returns true.
*
* If no exception occurred, returns false.
*/
bool raiseAndClearException(JNIEnv* env, const char* msg); /* Raises an exception from within a callback function.
* The exception will be rethrown when control returns to the message queue which
* will typically cause the application to crash.
*
* This message can only be called from within a callback function. If it is called
* at any other time, the process will simply be killed.
*
* Does nothing if exception is NULL.
*
* (This method does not take ownership of the exception object reference.
* The caller is responsible for releasing its reference when it is done.)
*/
virtual void raiseException(JNIEnv* env, const char* msg, jthrowable exceptionObj) = 0; protected:
MessageQueue();
virtual ~MessageQueue(); protected:
sp<Looper> mLooper;
};

android_os_MessageQueue.h

如今看看NativeMessageQueue的构造函数:

NativeMessageQueue::NativeMessageQueue() : mInCallback(false), mExceptionObj(NULL) {
mLooper = Looper::getForThread();
if (mLooper == NULL) {
mLooper = new Looper(false);
Looper::setForThread(mLooper);
}
}

android_os_MessageQueue.cpp

NativeMessageQueue的构造函数又调用了Looper::getForThread(),Looper::Looper()和Looper::setThread(),当中getForThread和setForThread都是静态函数:

sp<Looper> Looper::getForThread() {
int result = pthread_once(& gTLSOnce, initTLSKey);
LOG_ALWAYS_FATAL_IF(result != 0, "pthread_once failed"); return (Looper*)pthread_getspecific(gTLSKey);
}

Looper.cpp

这段代码中,在第一次运行pthread_once时将调用initTLSKey。

void Looper::initTLSKey() {
int result = pthread_key_create(& gTLSKey, threadDestructor);
LOG_ALWAYS_FATAL_IF(result != 0, "Could not allocate TLS key.");
}

Looper.cpp

void Looper::threadDestructor(void *st) {
Looper* const self = static_cast<Looper*>(st);
if (self != NULL) {
self->decStrong((void*)threadDestructor);
}
}

Looper.cpp

void Looper::setForThread(const sp<Looper>& looper) {
sp<Looper> old = getForThread(); // also has side-effect of initializing TLS if (looper != NULL) {
looper->incStrong((void*)threadDestructor);
} pthread_setspecific(gTLSKey, looper.get()); if (old != NULL) {
old->decStrong((void*)threadDestructor);
}
}

Looper.cpp

Looper::setForThread和getForThread中分别使用了pthread_setspecific,pthread_getsepcific,pthread_key_create,实现了线程私有的looper引用,这和Java层Looper类似。

Looper的构造函数例如以下:

Looper::Looper(bool allowNonCallbacks) :
mAllowNonCallbacks(allowNonCallbacks), mSendingMessage(false),
mResponseIndex(0), mNextMessageUptime(LLONG_MAX) {
int wakeFds[2];
int result = pipe(wakeFds);
LOG_ALWAYS_FATAL_IF(result != 0, "Could not create wake pipe. errno=%d", errno); mWakeReadPipeFd = wakeFds[0];
mWakeWritePipeFd = wakeFds[1]; result = fcntl(mWakeReadPipeFd, F_SETFL, O_NONBLOCK);
LOG_ALWAYS_FATAL_IF(result != 0, "Could not make wake read pipe non-blocking. errno=%d",
errno); result = fcntl(mWakeWritePipeFd, F_SETFL, O_NONBLOCK);
LOG_ALWAYS_FATAL_IF(result != 0, "Could not make wake write pipe non-blocking. errno=%d",
errno); mIdling = false; // Allocate the epoll instance and register the wake pipe.
mEpollFd = epoll_create(EPOLL_SIZE_HINT); // 用epoll实现IO多路复用,EPOLL_SIZE_HINT定义为8
LOG_ALWAYS_FATAL_IF(mEpollFd < 0, "Could not create epoll instance. errno=%d", errno); struct epoll_event eventItem;
memset(& eventItem, 0, sizeof(epoll_event)); // zero out unused members of data field union
eventItem.events = EPOLLIN;
eventItem.data.fd = mWakeReadPipeFd;
result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeReadPipeFd, & eventItem); // 将Wake管道的读端加入到mEpollFd上
LOG_ALWAYS_FATAL_IF(result != 0, "Could not add wake read pipe to epoll instance. errno=%d",
errno);
}

Looper.cpp

从Looper的构造函数能够看到,Looper的Wake是由管道+epoll实现的,且管道的两端fd都被设置为NONBLOCK的,并通过epoll实现IO多路复用。Looper的数据成员(data member)声明例如以下:

    struct Request {
        int fd;
        int ident;
        sp<LooperCallback> callback;
        void* data;
    };     struct Response {
        int events;
        Request request;
    };     struct MessageEnvelope {
        MessageEnvelope() : uptime(0) { }         MessageEnvelope(nsecs_t uptime, const sp<MessageHandler> handler,
                const Message& message) : uptime(uptime), handler(handler), message(message) {
        }         nsecs_t uptime;
        sp<MessageHandler> handler;
        Message message;
    }; const bool mAllowNonCallbacks; // immutable int mWakeReadPipeFd; // immutable
int mWakeWritePipeFd; // immutable
Mutex mLock; Vector<MessageEnvelope> mMessageEnvelopes; // guarded by mLock
bool mSendingMessage; // guarded by mLock // Whether we are currently waiting for work. Not protected by a lock,
// any use of it is racy anyway.
volatile bool mIdling; int mEpollFd; // immutable // Locked list of file descriptor monitoring requests.
KeyedVector<int, Request> mRequests; // guarded by mLock // This state is only used privately by pollOnce and does not require a lock since
// it runs on a single thread.
Vector<Response> mResponses;
size_t mResponseIndex;
nsecs_t mNextMessageUptime; // set to LLONG_MAX when none

Looper.h

Looper数据成员涉及的类型还有有:作为callback的LooperCallback,MessageHandler,以及Message:

class MessageHandler : public virtual RefBase {
protected:
virtual ~MessageHandler() { } public:
/**
* Handles a message.
*/
virtual void handleMessage(const Message& message) = 0;
};

Looper.h

class LooperCallback : public virtual RefBase {
protected:
virtual ~LooperCallback() { } public:
/**
* Handles a poll event for the given file descriptor.
* It is given the file descriptor it is associated with,
* a bitmask of the poll events that were triggered (typically ALOOPER_EVENT_INPUT),
* and the data pointer that was originally supplied.
*
* Implementations should return 1 to continue receiving callbacks, or 0
* to have this file descriptor and callback unregistered from the looper.
*/
virtual int handleEvent(int fd, int events, void* data) = 0;
};

Looper.h

struct Message {
Message() : what(0) { }
Message(int what) : what(what) { } /* The message type. (interpretation is left up to the handler) */
int what;
};

Looper.h

至此,android_os_MessageQueue_nativeInit分析完成。

nativeWake

接下来看看android_os_MessageQueue_nativeWake和android_os_MessageQueue_nativePollOnce。

static void android_os_MessageQueue_nativeWake(JNIEnv* env, jclass clazz, jint ptr) {
NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);
return nativeMessageQueue->wake();
}

android_os_MessageQueue.cpp

android_os_MessageQueue_nativeWake调用了NativeMessageQueue::wake:

void NativeMessageQueue::wake() {
mLooper->wake();
}

android_os_MessageQueue.cpp

NativeMessageQueue::wake直接将工作转交给了Looper::wake:

void Looper::wake() {
#if DEBUG_POLL_AND_WAKE
ALOGD("%p ~ wake", this);
#endif ssize_t nWrite;
do {
nWrite = write(mWakeWritePipeFd, "W", 1); // 向pipe的写段写入一个字节
} while (nWrite == -1 && errno == EINTR); if (nWrite != 1) {
if (errno != EAGAIN) {
ALOGW("Could not write wake signal, errno=%d", errno);
}
}
}

Looper.cpp

能够看到nativeWake很easy,仅仅是向pipe上写一个字节。

但这是怎样唤醒等待的线程的呢?猜想:等待线程必定通过epoll_wait等在mEpollFd上,稍后将得到验证。

nativePollOnce

static void android_os_MessageQueue_nativePollOnce(JNIEnv* env, jclass clazz,
jint ptr, jint timeoutMillis) {
NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);
nativeMessageQueue->pollOnce(env, timeoutMillis); // 调用NativeMessageQueue::pollOnce()
}

android_os_MessageQueue.cpp

android_os_MessageQueue_nativeWake调用了NativeMessageQueue::pollOnce:

void NativeMessageQueue::pollOnce(JNIEnv* env, int timeoutMillis) {
mInCallback = true;
mLooper->pollOnce(timeoutMillis);
mInCallback = false;
if (mExceptionObj) {
env->Throw(mExceptionObj);
env->DeleteLocalRef(mExceptionObj);
mExceptionObj = NULL;
}
}

android_os_MessageQueue.cpp

NativeMessageQueue::pollOnce调用了Looper::pollOnce:

    inline int pollOnce(int timeoutMillis) {
return pollOnce(timeoutMillis, NULL, NULL, NULL);
}

Looper.h

Looper::pollOnce(int)调用了还有一版本号的Looper::pollOnce:

int Looper::pollOnce(int timeoutMillis, int* outFd, int* outEvents, void** outData) {
int result = 0;
for (;;) {
while (mResponseIndex < mResponses.size()) {
const Response& response = mResponses.itemAt(mResponseIndex++); // 取出一个response
int ident = response.request.ident;
if (ident >= 0) {
int fd = response.request.fd;
int events = response.events;
void* data = response.request.data;
#if DEBUG_POLL_AND_WAKE
ALOGD("%p ~ pollOnce - returning signalled identifier %d: "
"fd=%d, events=0x%x, data=%p",
this, ident, fd, events, data);
#endif
if (outFd != NULL) *outFd = fd;
if (outEvents != NULL) *outEvents = events;
if (outData != NULL) *outData = data;
return ident;
}
} if (result != 0) {
#if DEBUG_POLL_AND_WAKE
ALOGD("%p ~ pollOnce - returning result %d", this, result);
#endif
if (outFd != NULL) *outFd = 0;
if (outEvents != NULL) *outEvents = 0;
if (outData != NULL) *outData = NULL;
return result;
} result = pollInner(timeoutMillis);
}
}

Looper.cpp

pollOnce的for(;;)循环里先查看是否还有没有取出的response,若有,取出一个马上返回;否则,调用Looper::pollInner,poll出一个IO事件(wake通知,后面可以看到):

int Looper::pollInner(int timeoutMillis) {
#if DEBUG_POLL_AND_WAKE
ALOGD("%p ~ pollOnce - waiting: timeoutMillis=%d", this, timeoutMillis);
#endif // Adjust the timeout based on when the next message is due.
if (timeoutMillis != 0 && mNextMessageUptime != LLONG_MAX) {
nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
int messageTimeoutMillis = toMillisecondTimeoutDelay(now, mNextMessageUptime);
if (messageTimeoutMillis >= 0
&& (timeoutMillis < 0 || messageTimeoutMillis < timeoutMillis)) {
timeoutMillis = messageTimeoutMillis;
}
#if DEBUG_POLL_AND_WAKE
ALOGD("%p ~ pollOnce - next message in %lldns, adjusted timeout: timeoutMillis=%d",
this, mNextMessageUptime - now, timeoutMillis);
#endif
} // Poll.
int result = ALOOPER_POLL_WAKE;
mResponses.clear();
mResponseIndex = 0; // We are about to idle.
mIdling = true; struct epoll_event eventItems[EPOLL_MAX_EVENTS];
int eventCount = epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis); // 关键。等待wake通知 // No longer idling.
mIdling = false; // Acquire lock.
mLock.lock(); // Check for poll error.
if (eventCount < 0) {
if (errno == EINTR) {
goto Done;
}
ALOGW("Poll failed with an unexpected error, errno=%d", errno);
result = ALOOPER_POLL_ERROR;
goto Done;
} // Check for poll timeout.
if (eventCount == 0) {
#if DEBUG_POLL_AND_WAKE
ALOGD("%p ~ pollOnce - timeout", this);
#endif
result = ALOOPER_POLL_TIMEOUT;
goto Done;
} // Handle all events.
#if DEBUG_POLL_AND_WAKE
ALOGD("%p ~ pollOnce - handling events from %d fds", this, eventCount);
#endif for (int i = 0; i < eventCount; i++) { // 处理全部事件
int fd = eventItems[i].data.fd;
uint32_t epollEvents = eventItems[i].events;
if (fd == mWakeReadPipeFd) {
if (epollEvents & EPOLLIN) {
awoken(); // 调用Looper::awoken(),运行实际的wake通知
} else {
ALOGW("Ignoring unexpected epoll events 0x%x on wake read pipe.", epollEvents);
}
} else {
ssize_t requestIndex = mRequests.indexOfKey(fd);
if (requestIndex >= 0) {
int events = 0;
if (epollEvents & EPOLLIN) events |= ALOOPER_EVENT_INPUT;
if (epollEvents & EPOLLOUT) events |= ALOOPER_EVENT_OUTPUT;
if (epollEvents & EPOLLERR) events |= ALOOPER_EVENT_ERROR;
if (epollEvents & EPOLLHUP) events |= ALOOPER_EVENT_HANGUP;
pushResponse(events, mRequests.valueAt(requestIndex)); // push到mRequest上
} else {
ALOGW("Ignoring unexpected epoll events 0x%x on fd %d that is "
"no longer registered.", epollEvents, fd);
}
}
}
Done: ; // Invoke pending message callbacks.调用等待的消息回调
mNextMessageUptime = LLONG_MAX;
while (mMessageEnvelopes.size() != 0) {
nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
const MessageEnvelope& messageEnvelope = mMessageEnvelopes.itemAt(0);
if (messageEnvelope.uptime <= now) {
// Remove the envelope from the list.
// We keep a strong reference to the handler until the call to handleMessage
// finishes. Then we drop it so that the handler can be deleted *before*
// we reacquire our lock.
{ // obtain handler
sp<MessageHandler> handler = messageEnvelope.handler;
Message message = messageEnvelope.message;
mMessageEnvelopes.removeAt(0);
mSendingMessage = true;
mLock.unlock(); #if DEBUG_POLL_AND_WAKE || DEBUG_CALLBACKS
ALOGD("%p ~ pollOnce - sending message: handler=%p, what=%d",
this, handler.get(), message.what);
#endif
handler->handleMessage(message); // 调用Message回调(MessageHandler)
} // release handler mLock.lock();
mSendingMessage = false;
result = ALOOPER_POLL_CALLBACK;
} else {
// The last message left at the head of the queue determines the next wakeup time.
mNextMessageUptime = messageEnvelope.uptime;
break;
}
} // Release lock.
mLock.unlock(); // Invoke all response callbacks.调用全部响应回调
for (size_t i = 0; i < mResponses.size(); i++) {
Response& response = mResponses.editItemAt(i);
if (response.request.ident == ALOOPER_POLL_CALLBACK) {
int fd = response.request.fd;
int events = response.events;
void* data = response.request.data;
#if DEBUG_POLL_AND_WAKE || DEBUG_CALLBACKS
ALOGD("%p ~ pollOnce - invoking fd event callback %p: fd=%d, events=0x%x, data=%p",
this, response.request.callback.get(), fd, events, data);
#endif
int callbackResult = response.request.callback->handleEvent(fd, events, data); // 调用事件回调(LooperCallback)
if (callbackResult == 0) {
removeFd(fd);
}
// Clear the callback reference in the response structure promptly because we
// will not clear the response vector itself until the next poll.
response.request.callback.clear();
result = ALOOPER_POLL_CALLBACK;
}
}
return result;
}

Looper.cpp

void Looper::awoken() {
#if DEBUG_POLL_AND_WAKE
ALOGD("%p ~ awoken", this);
#endif char buffer[16];
ssize_t nRead;
do {
nRead = read(mWakeReadPipeFd, buffer, sizeof(buffer)); // 读到暂时的buffer,
} while ((nRead == -1 && errno == EINTR) || nRead == sizeof(buffer));
}

Looper.cpp

Looper::awoken的read从mWakeReadFd上读出的消息被放在一个暂时的buffer上,这再次表明了这个pipe之作唤醒通知之用,并不关心实际内容。

nativeIsIdling 和 nativeDestroy

剩下的两个native方法的实现都很easy,先看nativeIdling:

static jboolean android_os_MessageQueue_nativeIsIdling(JNIEnv* env, jclass clazz, jint ptr) {
NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);
return nativeMessageQueue->getLooper()->isIdling();
}

android_os_MessageQueue.cpp

NativeMessageQueue::getLooper:

    inline sp<Looper> getLooper() const {
return mLooper;
}

android_os_MessageQueue.cpp

bool Looper::isIdling() const {
return mIdling;
}

Looper.cpp

再看nativeDestroy:

static void android_os_MessageQueue_nativeDestroy(JNIEnv* env, jclass clazz, jint ptr) {
NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);
nativeMessageQueue->decStrong(env);
}

android_os_MessageQueue.cpp

nativeDestroy将nativeMessageQueue的强引用减1,引用计数减为0时,对象会自己主动被析构并回收。

小结

隐藏在nativePollOnce和nativeWake背后起着重要作用的事实上是pipe。nativeWake向pipe的写端写一个字节,通知前台线程“有消息来了”。

总结

后台线程使用Handler更新UI的本质上是“生产者消费者问题”。后台线程扮演生产者,生产消息(Message),并放到消息队列上。前台线程扮演消费者。从消息队列上取消息。并处理(消费)它。

在这个过程中Handler扮演了两个角色:

  1. 消息队列的窗体,后台线程通过Handler.sendMessage()向消息队列放消息;
  2. 处理消息的回调,前台线程通过Handler.handleMessage()处理从队列上取下来的消息。

引申

本文开头所给的两个Demo都是“单生产者单消费者问题”。

这个问题中须要指出的是。消费者必定唯一。由于每一个线程最多仅仅能仅仅有一个Looper(通过Looper.prepare创建)。而MessageQueue是由Looper的构造方法创建的。所以每一个Looper相应一个MessageQueue;所以不可能有多个消费者线程共享一个MessageQueue。

但生产者能够不必唯一,比方本文开头的Demo1,按下Button之后,会创建一个后台线程。这个线程每一个1秒更新一次TextView,更新10次后结束。当你点下Button后不到10秒(比方5秒)时。再次点下Button,此时又创建了一个后台线程;这时两个后台线程都是生产者。感兴趣的朋友能够自己试试,看看实际执行的效果。

pipe是仅仅有两个端的结构。多生产者时,有多个线程向写端write,但始终仅仅有一个线程从读端read。

所以,nativePollOnce能够实现为堵塞的,即pipe的读端mWakeReadPipeFd能够不设为NONBLOCK(当然也就不须要要用epoll了)。

但因为可能存在多个生产者,所以pipe的写端设为NONBLOCK还是非常有必要的。

Android Handler 具体解释的更多相关文章

  1. Android Handler的使用示例:结合源码理解Android Handler机制(一)

    什么是Handler? Android 的官方解释: 文档分节1:A Handler allows you to send and process Message and Runnable objec ...

  2. Android Handler leak 分析及解决办法

    In Android, Handler classes should be static or leaks might occur, Messages enqueued on the applicat ...

  3. Android Handler练习

    package com.example.myact12; import java.util.Random; import android.support.v7.app.ActionBarActivit ...

  4. Android Handler简单使用

    package com.example.myhandlertest3; import android.os.Bundle; import android.os.Handler; import andr ...

  5. Android Handler的使用

    大家好我们这一节讲的是Android Handler的使用,在讲Handler之前,我们先提个小问题,就是如何让程序5秒钟更新一下Title. 首先我们看一下习惯了Java编程的人,在不知道Handl ...

  6. [Android]Handler的消息机制

    最经面试中,技术面试中有一个是Handler的消息机制,细细想想,我经常用到的Handler无非是在主线程(或者说Activity)新建一个Handler对象,另外一个Thread是异步加载数据,同时 ...

  7. 详解Android Handler的使用-别说你不懂handler

    我们进行Android开发时,Handler可以说是使用非常频繁的一个概念,它的用处不言而喻.本文就详细介绍Handler的基本概念和用法. Handler的基本概念         Handler主 ...

  8. Android handler Thread 修改UI Demo

    /********************************************************************** * Android handler Thread 修改U ...

  9. Android Handler的简单使用

    大家好我们这一节讲的是Android Handler的使用,在讲Handler之前,我们先提个小问题,就是如何让程序5秒钟更新一下Title. 首先我们看一下习惯了Java编程的人,在不知道Handl ...

随机推荐

  1. C - Puzzles

    Problem description The end of the school year is near and Ms. Manana, the teacher, will soon have t ...

  2. markdown 计算器

    计算器 分四种运算(加减乘除).括号.去除最后括号.验证等式是否计算完成 bracket = re.compile(r'\([^()]+\)') # 找括号 multiplys = re.compil ...

  3. Three学习之曲线

    曲线 属性 1. .arcLengthDivisions 当通过.getLengths计算曲线的累积段长度时,此值决定了分割的数量.为了确保在使用.getSpacedPoint等方法时的精度,如果曲线 ...

  4. showdialog

    在C#中窗口的显示有两种方式:模态显示(showdialog)和非模态显示(show). 区别: 模态与非模态窗体的主要区别是窗体显示的时候是否可以操作其他窗体.模态窗体不允许操作其他窗体,非模态窗体 ...

  5. 从实现HTML页面局部刷新到JSONP

    也可以用iframe方法发get请求,但是目前iframe基本已经被弃用,所以此处就不介绍这个方法. 方案一:用图片造 get 请求 button.addEventListener('click', ...

  6. 算法 之 3n+1问题

    卡拉兹(Callatz)猜想: 对任何一个自然数n,如果它是偶数,那么把它砍掉一半:如果它是奇数,那么把(3n+1)砍掉一半.这样一直反复砍下去,最后一定在某一步得到n=1.卡拉兹在1950年的世界数 ...

  7. Goldengate升级之目标端(replicat端)升级

    转自红黑联盟Goldengate升级之目标端(replicat端升级 要升级replicat端的原因为:目标端OGG软件版本与源端OGG软件版本不同,在实际生产应用中,经常发现replicat端事务丢 ...

  8. Sql Server 优化----SQL语句的执行方式与锁以及阻塞的关系

    阻塞原因之一是不同的Session在访问同一张表的时候因为不兼容锁的原因造成的, 当前执行的SQL语句是否被阻塞(或者死锁),不仅跟当前表上的已有的锁有关,也会跟当前执行的SQL语句的执行方式有关 简 ...

  9. 设计模式(C++实现)--一句话总结

    原文链接:http://blog.csdn.net/LCL_data/article/details/12117349 按照目的来分,设计模式可以分为创建型模式.结构型模式和行为型模式. 按照目的来分 ...

  10. SpringMVC(一) HelloWorld

    学习新东西的的第一个程序--HelloWorld,以下是SpringMVC的HelloWorld 第一步: 用MAVEN 创建webapp,并添加依赖.(强烈建议使用MAVEN,MAVEN学习书籍和视 ...