Android應用程序都運行在一個dalvik虛擬機進程中,進程開始的時候會啓動一個主線程(MainThread),主線程負責處理和ui相關的事件,所以主線程一般又叫UI線程。而因爲Android採用UI單線程模型,因此只能在主線程中對UI元素進行操做, java
簡單的例子以下: android
package com.fangdo.android.ui; import org.slf4j.Logger; public class LoginActivity extends BaseActivity { Logger logger = LoggerFactory.getLogger(LoginActivity.class); /** Called when the activity is first created. */ private EditText phoneidET; private EditText passwordET; private Button logBT; private CheckBox savePasswordCB; private String phoneid; private String password; private Handler mHandler ; /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { System.setProperty("java.net.preferIPv6Addresses", "false"); super.onCreate(savedInstanceState); setContentView(R.layout.login); phoneidET = (EditText) findViewById(R.id.phoneidET); passwordET = (EditText) findViewById(R.id.passwordET); logBT = (Button) findViewById(R.id.logBT); savePasswordCB = (CheckBox) findViewById(R.id.savePasswordCB); savePasswordCB.setChecked(true);// 默認爲記住密碼 passwordET.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD); // 隱藏密碼爲InputType.TYPE_TEXT_VARIATION_PASSWORD,也就是0x81 // 顯示密碼爲InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD,也就是0x91 logBT.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { phoneid = phoneidET.getText().toString(); password = passwordET.getText().toString(); login(); } }); } public void login(){
mHandler = new Handler() { public void handleMessage (Message msg) {//此方法在ui線程運行 switch(msg.what) { case 1: Toast.makeText(getApplication(),msg.obj.toString(), Toast.LENGTH_SHORT).show(); break; } } };new Thread(){ public void run() { Message msg = new Message(); msg.what = 1 ; msg.obj = "顯示"; handler.sendMessage(msg); } }.start();} public Handler getmHandler() { return mHandler; } }
Android使用消息機制實現線程間的通訊,線程經過Looper創建本身的消息循環,MessageQueue是FIFO的消息隊列,Looper負責從MessageQueue中取出消息,而且分發到消息指定目標Handler對象。Handler對象綁定到線程的局部變量Looper,封裝了發送消息和處理消息的接口 ide
一、new Handler()源碼:當中重要的代碼:mLooper = Looper.myLooper(); 函數
mQueue = mLooper.mQueue;
oop
/** * Default constructor associates this handler with the queue for the * current thread. * * If there isn't one, this handler won't be able to receive messages. */ public Handler() { if (FIND_POTENTIAL_LEAKS) { 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(); if (mLooper == null) { throw new RuntimeException( "Can't create handler inside thread that has not called Looper.prepare()"); } mQueue = mLooper.mQueue; mCallback = null; }二、什麼先不說,看 Looper.myLooper()這個方法,Handler經過mLooper = Looper.myLooper();綁定到線程的局部變量Looper上去,同時Handler經過mQueue =mLooper.mQueue;得到線程的消息隊列。此時,Handler就綁定到建立此Handler對象的線程的消息隊列上了。
/** * Return the Looper object associated with the current thread. Returns * null if the calling thread is not associated with a Looper. */ public static Looper myLooper() { return sThreadLocal.get(); }
2.0 當你看到上面sThreadLocal.get()這行代碼,你會產生疑問,明明代碼中沒有建立怎麼會有值,詳細解釋一下: ui
Looper用於在android線程中進行消息處理,默認狀況下,一個線程並不和任何Looper綁定。當咱們調用Looper.prepare()時,若是當前線程尚未和任何Looper綁定,那麼將建立一個Looper讓它和當前線程綁定。當咱們調用Looper.loop()時,它將對當前線程所對應的Looper的消息進行處理,從消息隊列裏取消息,處理消息,一直循環直到對該Looper調用quit()函數。 this
注意:默認狀況下,線程是沒有Looper的,因此要調用 Looper.prepare()來給線程建立消息循環,而後再經過,Looper.loop()來使消息循環起做用。 spa
ctivity的MainUI線程已經新建並綁定了個Looper(在源碼,Main函數中你能夠看到)。因此在Activity中新建Handler時,不須要先調用Looper.prepare()
.net
public final class ActivityThread { 。。。。 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); Process.setArgV0("<pre-initialized>"); Looper.prepareMainLooper(); if (sMainThreadHandler == null) { sMainThreadHandler = new Handler(); } ActivityThread thread = new ActivityThread(); thread.attach(false); if (false) { Looper.myLooper().setMessageLogging(new LogPrinter(Log.DEBUG, "ActivityThread")); } Looper.loop(); throw new RuntimeException("Main thread loop unexpectedly exited"); } }2.0.1看到上面Looper.prepareMainLooper()和Looper.loop()方法了嗎?就是在這調用的。好了你們來看看 Looper.prepareMainLooper()的源碼:
public static void prepare() { if (sThreadLocal.get() != null) { throw new RuntimeException("Only one Looper may be created per thread"); } sThreadLocal.set(new Looper()); } public static void prepareMainLooper() { prepare(); setMainLooper(myLooper()); myLooper().mQueue.mQuitAllowed = false; } private synchronized static void setMainLooper(Looper looper) { mMainLooper = looper; }
2.1一個線程在調用Looper的靜態方法prepare()時,這個線程會新建一個Looper對象,並放入到線程的局部變量中,而這個變量是不和其餘線程共享的(關於ThreadLocal的介紹) 線程
那看看Looper(),建了一個消息隊列對象mQueue,斌企鵝獲取當前線程
private Looper() { mQueue = new MessageQueue(); mRun = true; mThread = Thread.currentThread(); }
三、在Looper.java 中也定義了sThreadLocal變量
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();四、
類須要覆蓋這個方法,實現接受到消息後的處理方法。
/** * Subclasses must implement this to receive messages. */ public void handleMessage(Message msg) { }
五、是時候啓動消息循環了!Looper的靜態方法loop()實現了消息循環。
public static void loop() { Looper me = myLooper(); if (me == null) { throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread."); } 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(); while (true) { Message msg = queue.next(); // might block if (msg != null) { if (msg.target == null) { // No target is a magic identifier for the quit message. return; } long wallStart = 0; long threadStart = 0; // 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); wallStart = SystemClock.currentTimeMicro(); threadStart = SystemClock.currentThreadTimeMicro(); } msg.target.dispatchMessage(msg); if (logging != null) { long wallTime = SystemClock.currentTimeMicro() - wallStart; long threadTime = SystemClock.currentThreadTimeMicro() - threadStart; logging.println("<<<<< Finished to " + msg.target + " " + msg.callback); if (logging instanceof Profiler) { ((Profiler) logging).profile(msg, wallStart, wallTime, threadStart, threadTime); } } // 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(); } } }
解釋:msg.target 在Message.java 中是這樣定義的:/*package*/ Handler target; (明白了吧)
再看看Handle中是樣定義的 msg.target.dispatchMessage(msg)這個方法。
/** * Handle system messages here. */ public void dispatchMessage(Message msg) { if (msg.callback != null) { handleCallback(msg); } else { if (mCallback != null) { if (mCallback.handleMessage(msg)) { return; } } handleMessage(msg); } }看到沒最後調用的是 handleMessage(msg);這個方法,這就是爲何步驟4要重寫 ha ndleMessage (msg)這個方法,就是最後把本身的夥計代碼加入其中
public boolean sendMessageAtTime(Message msg, long uptimeMillis) { boolean sent = false; MessageQueue queue = mQueue; if (queue != null) { msg.target = this; sent = queue.enqueueMessage(msg, uptimeMillis); } else { RuntimeException e = new RuntimeException( this + " sendMessageAtTime() called with no mQueue"); Log.w("Looper", e.getMessage(), e); } return sent; }這是就會取到Handler中的mQueue參數,而後放入其中,等待讀取這個信息