編輯:關於Android編程
本文介紹了Android中的消息處理機制,給出了Android消息處理中的幾個重點類Handler、Message、MessageQueue、Looper、Runnable、Thread的詳細介紹,提供了兩個消息處理的實例代碼,並深入分析了使用Android消息機制應該遵循的幾個原則。
在具有java基礎的情況下,Android的學習比較輕松,很多人在沒有深刻了解Android消息處理機制的背景下,已經能夠開發出可用的app,很多人開始想學習Android消息處理機制的第一個動機是遇到了一個著名的bug“CalledFromWrongThreadException:Only the original thread that created a view hierarchy can touch its views.”。這個bug的含義即“只有創建一個view層次的線程能夠更新此view”,在更多情況下,它是想說“只有主線程能夠更新UI”。
本文即是來解釋如何利用Android的消息機制從主線程或者子線程中更新UI或完成其他操作。你可以學到:
1. 如何使用Android的消息實現同步、異步操作;
2. 如何在主線程和子線程發送並接收消息;
3. 消息是如何得到處理的;
4. 使用Android的消息處理機制應該遵循的幾個原則;
5. 兩個具體的消息處理實例源代碼。
你可能需要如下技術背景才能完全理解本文的內容,如何你沒有以下背景,建議先學習相關知識:
1. Java語言基礎
2. Java多線程技術
3. Android開發基本知識
先來看一個代碼,代碼地址為:
http://download.csdn.net/detail/logicteamleader/8827099
本例的作用是:創建一個Handler(處理消息的類),在界面上點擊按鈕就會向此Handler發送消息,Handler可以處理這些消息(後面會詳細解釋,消息的處理其實是多個類共同合作的結果),對UI進行修改。界面上有八個按鈕,從上至下其效果分別是:
1. 使用Handler的post來傳遞一個Runnable的實例,該實例的run方法會在按鈕點擊時運行;
2. 使用Handler的postDelayed(Runnable r, long delayMillis)來傳遞一個Runnable的實例,該實例的run方法會在一段時間delayMillis後執行;
3. 使用Handler的sendMessage方法傳遞一個消息,該消息在Handler的handleMessage方法中被解析執行;
4. 使用Message的sendToTarget方法向獲得該Message的Handler傳遞一個消息,該消息在Handler的handleMessage方法中被解析執行;
第5、6、7、8個方法與上面四個方法類似,不同的是它們都是在子線程中調用的。
源代碼如下:
package com.example.wxbhandlertest;
import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.os.MessageQueue;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
/**
* @author wxb
* Android中的消息處理實例之一
* * 一、在主線程內發送消息
* 1.使用post
* 2.使用postDelay
* 3.使用sendMessage
* 4.使用Message.sentToTarget
* 二、在子線程中使用Handler
* 1.使用post
* 2.使用postDelay
* 3.使用sendMessage
* 4.使用Message.sentToTarget
*/
public class MainActivity extends Activity {
private Runnable runnable=null;
private Runnable runnableDelay=null;
private Runnable runnableInThread=null;
private Runnable runnableDelayInThread=null;
private static TextView tv;
private static TextView tvOnOtherThread;
//自定義Message類型
public final static int MESSAGE_WXB_1 = 1;
public final static int MESSAGE_WXB_2 = 2;
public final static int MESSAGE_WXB_3 = 3;
public final static int MESSAGE_WXB_4 = 4;
public final static int MESSAGE_WXB_5 = 5;
private static Handler mHandler=new Handler(){
@Override
public void handleMessage(Message msg) {
switch(msg.what){
case MESSAGE_WXB_1:
tv.setText("invoke sendMessage in main thread");
break;
case MESSAGE_WXB_2:
tv.setText("Message.sendToTarget in main thread");
break;
case MESSAGE_WXB_3:
tvOnOtherThread.setText("invoke sendMessage in other thread");
break;
case MESSAGE_WXB_4:
tvOnOtherThread.setText("Message.sendToTarget in other thread");
break;
}
super.handleMessage(msg);
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
tv = (TextView) this.findViewById(R.id.tvOnMainThread);
tvOnOtherThread = (TextView) this.findViewById(R.id.tvOnOtherThread);
//方法1.post
runnable = new Runnable(){
public void run(){
tv.setText(getString(R.string.postRunnable));
}
};
Button handler_post = (Button) this.findViewById(R.id.btnHandlerpost);
handler_post.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
mHandler.post(runnable);
}
});
//方法2:postDelay
runnableDelay = new Runnable(){
public void run(){
tv.setText(getString(R.string.postRunnableDelay));
}
};
Button handler_post_delay = (Button) this.findViewById(R.id.btnHandlerPostdelay);
handler_post_delay.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
mHandler.postDelayed(runnableDelay, 1000); //1秒後執行
}
});
//方法3:sendMessage
Button btnSendMessage = (Button) this.findViewById(R.id.btnSendMessage);
btnSendMessage.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
Message msg = mHandler.obtainMessage();
msg.what = MESSAGE_WXB_1;
mHandler.sendMessage(msg);
}
});
//方法4:Message.sendToTarget
Button btnSendtoTarget = (Button) this.findViewById(R.id.btnSendtoTarget);
btnSendtoTarget.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
Message msg = mHandler.obtainMessage();
msg.what = MESSAGE_WXB_2;
msg.sendToTarget();
}
});
//在其他線程中發送消息
//1.post
runnableInThread = new Runnable(){
public void run(){
tvOnOtherThread.setText(getString(R.string.postRunnableInThread));
}
};
Button btnPost = (Button) this.findViewById(R.id.btnPost);
btnPost.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
new Thread(){
@Override
public void run() {
super.run();
mHandler.post(runnableInThread);
}
}.start();
}
});
//2.postDelay
runnableDelayInThread = new Runnable(){
public void run(){
tvOnOtherThread.setText(getString(R.string.postRunnableDelayInThread));
}
};
Button btnPostDelay = (Button) this.findViewById(R.id.btnPostDelay);
btnPostDelay.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
new Thread(){
@Override
public void run() {
super.run();
mHandler.postDelayed(runnableDelayInThread, 1000);
}
}.start();
}
});
//3.sendMessage
Button btnSendMessage2 = (Button) this.findViewById(R.id.btnSendMessage2);
btnSendMessage2.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
new Thread(){
@Override
public void run() {
super.run();
Message msg = mHandler.obtainMessage();
msg.what = MESSAGE_WXB_3;
mHandler.sendMessage(msg);
}
}.start();
}
});
//方法4:Message.sendToTarget
Button btnSendToTarget2 = (Button) this.findViewById(R.id.btnSendToTarget2);
btnSendToTarget2.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
new Thread(){
@Override
public void run() {
super.run();
mHandler.obtainMessage(MESSAGE_WXB_4).sendToTarget();
}
}.start();
}
});
}
}
看完代碼後,在解釋具體類之前,先了解幾個規則:
第一, 只有創建view的線程能夠更新此view;一般來說,創建UI的是Android主線程,因此只有在主線程中才能更新UI;
第二, 處理消息的類是Handler,它依附於創建自己的線程;如果在主線程中創建Handler mHandler,則向mHandler發送的消息會在主線程中被解析;如果在子線程中創建Handler sHandler,則向sHandler發送的消息會在子線程中被解析;
第三, 發送消息有三類方法,Handler的post方法,Handler的sendMessage方法,以及Message的sentToTarget方法,它們最終其實都調用了Handler的sendMessage方法;
第四, 消息的方法可以即時也可以延時,代表就是post和postDelay,以及sendMessage和sendMessageDelayed;延時發送消息可以實現很多用戶需要的界面延時效果,例如最常用的SplashWindow。
第五, post類型的方法只需要實例化一個Runnable接口,且不需要重載Handler. handleMessage方法,比較簡單易用;
第六, sendMessage和sentToTarget需要重載Handler. handleMessage方法,實現對不同消息的解析。
Handler是Android消息處理中最重要的一個類,不管什麼編程語言或者框架,叫Handler的類一般都很重要,也都很復雜,當年的Windows句柄類更是讓人一頭霧水。
幸好,Android的Handler很容易理解和使用。它就是一個消息處理的目標類,其主要作用有兩個:1)它安排要執行的消息和runnable在未來某個時間點執行;2)處理來自不同線程的消息並執行操作。
使用Handler要注意以下幾點:
1. Handler屬於創建它的線程,並與線程的消息循環關聯,同時與此線程的消息隊列(MessageQueue)關聯。
2. Handler發送消息的方法有兩類:post類和sendMessage類,用法見例子;
3. 如果使用post發送消息,則消息中包含的Runnable會在解析消息時執行;
4. 如果使用sendMessage發送消息,則需要重載Handler. handleMessage方法,實現對不同消息的解析
Message是消息類,它有幾個重要的屬性:
public int what:消息類型
public int arg1:參數1
public int arg2:參數2
public Object obj:對象參數
以上幾個參數用戶可以隨意定制。
值得注意的有幾點:
1. Android使用消息池模式來提高消息的效率,因此一般不適用new來創建消息,而是使用obtain方法來從消息池中獲取一個Message的實例;Handler類也有obtain方法;
2. Message有一個Handler作為Target,一般來說就是獲取該消息的所在線程的關聯Handler;因此使用sendToTarget方法發送消息時,將消息發給它的Target;
MessageQueue是消息隊列,一個線程最多擁有一個消息隊列,這個類一般不會被程序員直接使用。它的入隊方法enqueueMessage用來將一個Message加入隊列,這個方法是線程安全的,因此才保證了Android的消息處理機制是線程安全的。
MessageQueue的next方法用來獲取下一個Message,沒有任何消息時,主線程常常在此方法處等待。
Java的接口,它代表一個可執行的代碼片段,如下所示:
public interface Runnable {
/**
* Starts executing the active part of the class' code. This method is
* called when a thread is started that has been created with a class which
* implements {@code Runnable}.
*/
public void run();
}
Java的線程類,大家都應該很熟悉了。
第一個例子描述了如何在多個線程中發送消息,並在主線程中統一接收和處理這些消息;第二個例子則描述如何在子線程中建立一個消息循環,並從主線程發送消息給子線程,讓子線程處理這些消息。
第二個例子中有兩個消息循環,兩個Handler,主線程首先向子線程發送一個消息,子線程的收到消息後再向主線程發送一個消息,主線程收到消息後更新UI。
例子代碼如下:
http://download.csdn.net/detail/logicteamleader/8827401
源代碼如下:
package com.example.wxbloopinthread;
import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;
public class MainActivity extends Activity {
private static TextView tv = null;
//自定義Message類型
public final static int MESSAGE_WXB_1 = 1;
//主線程中創建Handler
private static Handler mHandler = new Handler(){
@Override
public void handleMessage(Message msg) {
switch(msg.what){
case MESSAGE_WXB_1:
tv.setText("主線程發送,子線程接收消息後回發,主線程修改UI");
break;
}
super.handleMessage(msg);
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
tv = (TextView) this.findViewById(R.id.textView1);
//創建子線程
new LooperThread().start();
//點擊按鈕向子線程發送消息
Button btn = (Button) this.findViewById(R.id.btnSendMessage);
btn.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
LooperThread.sHandler.sendEmptyMessage(MESSAGE_WXB_1);
}
});
}
//定義子線程
static class LooperThread extends Thread {
public static Handler sHandler = null;
public void run() {
//創建消息循環
Looper.prepare();
sHandler = new Handler() {
public void handleMessage(Message msg) {
switch(msg.what){
case MESSAGE_WXB_1:
mHandler.sendEmptyMessage(MESSAGE_WXB_1);
break;
}
}
};
//開啟消息循環
Looper.loop();
}
}
}
第二個例子使用了另一個重要的類Looper,它是代表Android中的消息循環處理類。
Looper被用來創建一個線程的消息循環。線程默認情況下是沒有消息循環的,要創建消息循環,必須先調用Looper.prepare,然後在合適的地方調用Looper.loop,這個loop方法就開始循環處理本線程接收到的消息,直到loop循環被停止。
在大部分情況下,Looper都是和Handler一起使用,它通過Handler接收和處理消息。
使用Looper要注意以下幾點:
1. Android的主線程是默認已經創建了Looper對象的,因此不能在主線程中調用Looper.prepare;
2. Looper.prepare和Looper.loop都是靜態方法,調用時要注意,不要使用new來創建一個Looper;
使用Android消息的方法有以下幾種:
1. 使用Handler和Runnable,即時或延時發送一個消息;
2. 使用Handler和Message,即時或延時發送一個消息,需重載Handler. handleMessage方法;
需要注意的規則有以下幾條:
1. 只有創建view的線程能夠更新此view;一般來說,創建UI的是Android主線程,因此只有在主線程中才能更新UI;
2. 處理消息的類是Handler,它依附於創建自己的線程;如果在主線程中創建Handler mHandler,則向mHandler發送的消息會在主線程中被解析;如果在子線程中創建Handler sHandler,則向sHandler發送的消息會在子線程中被解析;
3. Looper被用來創建一個線程的消息循環,要創建消息循環,必須先調用Looper.prepare,然後在合適的地方調用Looper.loop。
具體的體會,還是要多看代碼才行。
(1)布局文件layout (2)需要彈出的Toast布局文件 (3)類的文件 package com
很久以前寫過兩篇Fragment的介紹,主要就是介紹其功能:Android Fragment 真正的完全解析(上)和Android Fragment 真正的
實現目標 相信大家都知道毛玻璃效果是怎樣的,也可以說是高斯模糊效果。效果圖如下: 這是一個透明,且會對背景進行高斯模糊的效果,看起來就像是毛玻璃一樣,其實不光是側滑
SharedPreferences是Android中存儲簡單數據的一個工具類。可以想象它是一個小小的Cookie,它通過用鍵值對的方式把簡單數據類型(boolean、in