編輯:關於Android編程
在Android系統中的應用程序,與Java的應用程序相同,都是靠消息驅動,簡單的說就是:有一個消息隊列,我們可以不斷的向這個消息隊列中添加消息,並從中取出消息,處理消息。Android中與此工作相關的主要是由Handler,Looper以及Message來完成。
Looper類:為一個線程運行著一個消息循環,內部有一個消息隊列,每一個線程只允許最多存在一個Looper; Handler類:允許你向一個線程的消息隊列中發送消息,處理消息; Message類:消息類。首先,我們通過一個簡單的例子來學習如何使用,Looper和Handler,並通過這個例子來研究一下其工作原理;
1.我們在LooperThread中為消息循環做准備,並創建一個Handler用於處理消息,注意Handler的創建要在調用Looper.prepare()之後;
public class LooperThread extends Thread {
public Handler mHandler;
@Override
public void run() {
// TODO Auto-generated method stub
Looper.prepare();
synchronized (this) {
mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
// TODO Auto-generated method stub
super.handleMessage(msg);
Log.w("LooperThread", "handleMessage::Thread id---" + getId());
}
};
}
Looper.loop();
notifyAll();
}
}
2.接下來我們在主線程中創建一個新的線程,並通過LooperThread中的Handler向LooperThread線程發送消息;
final LooperThread mLooperThread = new LooperThread();
mLooperThread.start();
new Thread() {
@Override
public void run() {
// TODO Auto-generated method stub
while (mLooperThread.mHandler == null) {
try {
wait();//防止在發送消息時Handler還沒建立
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
mLooperThread.mHandler.sendEmptyMessage(0);
Log.w(TAG, "Send Message::Thread id ---" + getId());
}
}.start();
該例子主要是新創建一個Thread,並在該線程中向LooperThread發送消息。為了防止在發送消息時Handler還沒建立,進行了同步,在mHandler為null時,阻塞該線程以等待,當建立了Handler後喚醒該線程。可以在log中看到結果:
由上面的例子我們可以看到如果我們想在本線程中進行消息管理,首先需要調用Looper.prepare方法,那就讓我們一起首先來看一下這個方法:
public static void prepare() {
prepare(true);
}
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));
}
可以看到在prepare方法中首先判斷了該線程中是否已經創建了Looper,接著調用了ThreadLocal的set方法。ThreadLocal是Java中的線程局部變量類,它使得各線程能夠保持各自獨立的一個對象,一般情況下,通過ThreadLocal.set() 到線程中的對象是該線程自己使用的對象,其他線程是不需要訪問的,也訪問不到的。對於ThreadLocal機制在這裡不做過多贅述。
在這裡我們為該線程set了一個Looper,接下來然我們看看Looper的構造函數:
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
可以看到,在構造方法中,我們創建了一個新的消息隊列並設置了當前的線程。
接下來我們創建了一個Handler,關於Handler我們一會了分析,在最後我們調用了Looper的loop方法用來進行消息循環。讓我們來看看這個方法:
public static void loop() {
//myLooper()方法就是通過sThreadLocal.get()返回我們剛剛設置的Looper
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;
// 確保線程和本地進程是一致的,並且記錄這個identity token
Binder.clearCallingIdentity();
final long ident = Binder.clearCallingIdentity();
for (;;) {
Message msg = queue.next(); // 可能會阻塞
if (msg == null) {
// 沒有消息則表明這個消息隊列退出了.
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);
}
// 確保在分發的過程中該線程沒有崩潰
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();
}
}
可以看到,loop方法主要是從消息隊列中不斷的取出消息,並將該消息分發出去。
總結一下,Looper的工作:
封裝了一個消息隊列, 利用prepare將Looper和調用prepare方法的線程聯系起來 利用loop函數分發消息知道了Looper的工作,讓我們來看看如何把消息發送出去。首先來看看它的構造方法,
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;
}
無參數的構造方法調用了this(null, flase)構造方法,可以看到,在在Handler的成員變量中有一個Looper,首先獲取了當前創建Handler的線程的Looper,另外可以看到在Handler中也保存了一個消息隊列最終指向了Looper的消息隊列。
當我們調用了sendMessage方法之後就向Looper發送了一條消息,讓我們看看這個方法,消息是如何被傳遞的。sendMessage方法最終會調用到sendMessageAtTime方法來:
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);
}
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
可以看到,sendMessage方法最終調用了queue.enqueueMessage方法將消息加入到了Looper中的消息隊列。
在上面我們看到了我們分發消息是調用了dispatchMessage方法:
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
可以看到,dispatchMessage設置了一套消息處理的優先機制:
如果Message自帶了Callback,則交給Message的Callback處理; 如果Handler了設置了Callback,則交給Handler的Callback處理; 如果兩者都沒有,則調用handleMessage方法處理。在上面的例子中我們可以發現,Handler和Looper是存在同步問題的,如果在LooperThread中Handler還沒創建起來,在第二個線程中就發送了消息,這樣就會引發空指針異常。在上面,我參考了Android中的HandlerThread,利用了wait/notifyAll的方法解決了這個問題,在實際使用時我們完全可以使用HandlerThread來完成。
final HandlerThread mHandlerThread = new HandlerThread("LooperThread");
mHandlerThread.start();
final Handler mHandler = new Handler(mHandlerThread.getLooper()){
@Override
public void handleMessage(Message msg) {
// TODO Auto-generated method stub
super.handleMessage(msg);
Log.w("LooperThread", "handleMessage::Thread id---" + mHandlerThread.getId());
}
};
new Thread() {
@Override
public void run() {
// TODO Auto-generated method stub
mHandler.sendEmptyMessage(0);
Log.w(TAG, "Send Message::Thread id ---" + getId());
}
}.start();
我們可以來看一下HandlerThread的源碼:
@Override
public void run() {
mTid = Process.myTid();
Looper.prepare();
synchronized (this) {
mLooper = Looper.myLooper();
notifyAll();
}
Process.setThreadPriority(mPriority);
onLooperPrepared();
Looper.loop();
mTid = -1;
}
public Looper getLooper() {
if (!isAlive()) {
return null;
}
// If the thread has been started, wait until the looper has been created.
synchronized (this) {
while (isAlive() && mLooper == null) {
try {
wait();
} catch (InterruptedException e) {
}
}
}
return mLooper;
}
可以看到,這個的思想和我們剛剛例子中所用的是一致的。
今天給大家分享下自己用懸浮按鈕點擊實現翻頁效果的例子。首先,一個按鈕要實現懸浮,就要用到系統頂級窗口相關的WindowManager,WindowManager.Layo
我用的是cocos2d-2.0-x-2.0.3 之前弄了一天也沒成功 今天來了下載了最新的ndk8 更新了sdk 又重新是了一遍 居然成功了,不知道是工具的版本問題還是哪
Android4.4新特性,系統狀態欄一體化。 實現的步驟主要有以下幾點: 1.android4.4 以上版本 2.設置app全屏: 方法:在AndroidManifes
業余時間充足,於是想弄點自己的東西,找來找去還是回到當初感興趣的VR。目前好像沒有太多關於VR方面的教程,於是有了寫‘學習筆記’的想法。說干就干~
前面的博客中,我們已經分析過,當Android中的進程要使用電量時,需要