編輯:關於Android編程
Android是消息驅動的,實現消息驅動有幾個要素:
流程圖解析:相關名詞
- UI線程:就是我們的主線程,系統在創建UI線程的時候會初始化一個Looper對象,同時也會創建一個與其關聯的MessageQueue;
- Handler:作用就是發送與處理信息,如果希望Handler正常工作,在當前線程中要有一個Looper對象
- Message:Handler接收與處理的消息對象
- MessageQueue:消息隊列,先進先出管理Message,在初始化Looper對象時會創建一個與之關聯的MessageQueue;
- Looper:每個線程只能夠有一個Looper,管理MessageQueue,不斷地從中取出Message分發給對應的Handler處理!
簡單點說:
當我們的子線程想修改Activity中的UI組件時,我們可以新建一個Handler對象,通過這個對象向主線程發送信息;而我們發送的信息會先到主線程的MessageQueue進行等待,由Looper按先入先出順序取出,再根據message對象的what屬性分發給對應的Handler進行處理!
- voidhandleMessage(Message msg):處理消息的方法,通常是用於被重寫!
- sendEmptyMessage(int what):發送空消息
- sendEmptyMessageDelayed(int what,long delayMillis):指定延時多少毫秒後發送空信息
- sendMessage(Message msg):立即發送信息
- sendMessageDelayed(Message msg):指定延時多少毫秒後發送信息
- final booleanhasMessage(int what):檢查消息隊列中是否包含what屬性為指定值的消息 如果是參數為(int what,Object object):除了判斷what屬性,還需要判斷Object屬性是否為指定對象的消息
在主線程中,因為系統已經初始化了一個Looper對象,所以我們直接創建Handler對象,就可以進行信息的發送與處理了!
代碼示例:簡單的一個定時切換圖片的程序,通過Timer定時器,定時修改ImageView顯示的內容,從而形成幀動畫
運行效果圖:
實現代碼:
MainActivity.java:
public class MainActivity extends Activity { //定義切換的圖片的數組id int imgids[] = new int[]{ R.drawable.s_1, R.drawable.s_2,R.drawable.s_3, R.drawable.s_4,R.drawable.s_5,R.drawable.s_6, R.drawable.s_7,R.drawable.s_8 }; int imgstart = 0; final Handler myHandler = new Handler() { @Override //重寫handleMessage方法,根據msg中what的值判斷是否執行後續操作 public void handleMessage(Message msg) { if(msg.what == 0x123) { imgchange.setImageResource(imgids[imgstart++ % 8]); } } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); final ImageView imgchange = (ImageView) findViewById(R.id.imgchange); //使用定時器,每隔200毫秒讓handler發送一個空信息 new Timer().schedule(new TimerTask() { @Override public void run() { myHandler.sendEmptyMessage(0x123); } }, 0,200); } }
如果是Handler寫在了子線程中的話,我們就需要自己創建一個Looper對象了!創建的流程如下:
1 )直接調用Looper.prepare()方法即可為當前線程創建Looper對象,而它的構造器會創建配套的MessageQueue;2 )創建Handler對象,重寫handleMessage( )方法就可以處理來自於其他線程的信息了!3 )調用Looper.loop()方法啟動Looper
使用示例: 輸入一個數,計算後通過Toast輸出在這個范圍內的所有質數
實現代碼:main.xml:
MainActivity.java:
public class CalPrime extends Activity { static final String UPPER_NUM = "upper"; EditText etNum; CalThread calThread; // 定義一個線程類 class CalThread extends Thread { public Handler mHandler; public void run() { Looper.prepare(); mHandler = new Handler() { // 定義處理消息的方法 @Override public void handleMessage(Message msg) { if(msg.what == 0x123) { int upper = msg.getData().getInt(UPPER_NUM); Listnums = new ArrayList (); // 計算從2開始、到upper的所有質數 outer: for (int i = 2 ; i <= upper ; i++) { // 用i處於從2開始、到i的平方根的所有數 for (int j = 2 ; j <= Math.sqrt(i) ; j++) { // 如果可以整除,表明這個數不是質數 if(i != 2 && i % j == 0) { continue outer; } } nums.add(i); } // 使用Toast顯示統計出來的所有質數 Toast.makeText(CalPrime.this , nums.toString() , Toast.LENGTH_LONG).show(); } } }; Looper.loop(); } } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); etNum = (EditText)findViewById(R.id.etNum); calThread = new CalThread(); // 啟動新線程 calThread.start(); } // 為按鈕的點擊事件提供事件處理函數 public void cal(View source) { // 創建消息 Message msg = new Message(); msg.what = 0x123; Bundle bundle = new Bundle(); bundle.putInt(UPPER_NUM , Integer.parseInt(etNum.getText().toString())); msg.setData(bundle); // 向新線程中的Handler發送消息 calThread.mHandler.sendMessage(msg); } }
在上面我們簡單的說明了Handler是如何使用的。那麼現在我們就來看一下這個Handler是如何工作的。在Android的消息機制中主要是由Handler,Looper,MessageQueue,Message等組成。而Handler得運行依賴後三者。那麼我們就來看一下它們是如何聯系在一起的。
在一個Android應用啟動的時候,會創建一個主線程,也就是UI線程。而這個主線程也就是ActivityThread。在ActivityThread中有一個靜態的main方法。這個main方法也就是我們應用程序的入口點。我們來看一下這個main方法。
public static void main(String[] args) { ...... Looper.prepareMainLooper(); ActivityThread thread = new ActivityThread(); thread.attach(false); ...... Looper.loop(); throw new RuntimeException("Main thread loop unexpectedly exited"); }
在上面代碼中通過prepareMainLooper方法為主線程創建一個Looper,而loop則是開啟消息循環。從上面代碼我們可以猜想到在loop方法中應該存在一個死循環,否則給我們拋出RuntimeException。也就是說主線程的消息循環是不允許被退出的。下面我們就來看一下這個Looper類。
首先我們看一下Looper的構造方法。
private Looper(boolean quitAllowed) { mQueue = new MessageQueue(quitAllowed); mThread = Thread.currentThread(); }
在這個構造方法中創建了一個消息隊列。並且保存當前線程的對象。其中quitAllowed參數表示是否允許退出消息循環。但是我們注意到這個構造方法是private,也就是說我們自己不能手動new一個Looper對象。那麼我們就來看一下如何創建一個Looper對象。之後在Looper類中我們找到下面這個方法。
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對象,然後將這個對象保存在ThreadLocal中,當我們下次需要用到Looper的之後直接從這個sThreadLocal中取出即可。在這裡簡單說明一下ThreadLocal這個類,ThreadLocal它實現了本地變量存儲,我們將當前線程的數據存放在ThreadLocal中,若是有多個變量共用一個ThreadLocal對象,這時候在當前線程只能獲取該線程所存儲的變量,而無法獲取其他線程的數據。在Looper這個類中為我們提供了myLooper來獲取當前線程的Looper對象。從上面的方法還能夠看出,一個線程只能創建一次Looper對象。然後我們在看一下這個prepare在哪裡被使用的。
public static void prepare() { prepare(true); } public static void prepareMainLooper() { prepare(false); synchronized (Looper.class) { if (sMainLooper != null) { throw new IllegalStateException("The main Looper has already been prepared."); } sMainLooper = myLooper(); } }
prepareMainLooper方法:這個方法在上面的ActivityThread中的main方法中我們就已經見到過了。它是為主線程創建一個Looper,在主線程創建Looper對象中,就設置了不允許退出消息循環。並且將主線程的Looper保存在sMainLooper中,我們可以通過getMainLooper方法來獲取主線程的Looper。
在ActivityThread中的main方法中除了創建一個Looper對象外,還做了另外一件事,那就是通過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.recycleUnchecked(); } }
和我們剛才猜想的一樣,在loop中確實存在一個死循環,而唯一退出該循環的方式就是消息隊列返回的消息為空。然後我們通過消息隊列的next()方法獲得消息。msg.target是發送消息的Handler,通過Handler中的dispatchMessage方法又將消息交由Handler處理。消息處理完成之後便對消息進行回收處理。在這裡我們也能夠通過quit和quitSafely退出消息循環。
public void quit() { mQueue.quit(false); } public void quitSafely() { mQueue.quit(true); }
MessageQueue翻譯為消息隊裡,在這個消息隊列中是采用單鏈表的方式實現的,提高插入刪除的效率。
在這裡我們首先看一下Handler的構造方法。
public Handler(Callback callback, boolean async) { ...... 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 = callback; mAsynchronous = async; }
從這個構造方法中我們可以看出在一個沒有創建Looper的線程中是無法創建一個Handler對象的。所以說我們在子線程中創建一個Handler時首先需要創建Looper,並且開啟消息循環才能夠使用這個Handler。
在這裡我們重新整理一下我們的思路,看一下這個Handler的整個工作流程。在主線程創建的時候為主線程創建一個Looper,創建Looper的同時在Looper內部創建一個消息隊列。而在創鍵Handler的時候取出當前線程的Looper,並通過該Looper對象獲得消息隊列,然後Handler在子線程中發送消息也就是在該消息隊列中添加一條Message。最後通過Looper中的消息循環取得這條Message並且交由Handler處理。
本文主要介紹如何使用tcpdump和wireshark對Android應用程序進行抓包並分析,需要說明的是在抓包之前,你的Android設備必須root過了,另外你的電腦
Android開源框架庫分類,挑選出最常用,最實用的開源項目,本篇主要介紹的是優秀開源框架庫和項目,UI個性化控件會獨立介紹。UI個性化控件Dependency Inje
1G - 5G的介紹 Android的操作系統的介紹 Android版本 Android系統的架構 兩種虛擬機的不同 ART模式 模擬器的簡介 SDK目錄 Andro
Eclipse Android項目中XML文件格式化配置是本文要介紹的內容,主要是來了解並學習Eclipse Android的內容,具體關於Eclipse