編輯:關於Android編程
一、Handler消息傳遞機制初步認識:
(一)、引入:
子線程沒有辦法對UI界面上的內容進行操作,如果操作,將拋出異常:CalledFromWrongThreadException
為了實現子線程中操作UI界面,Android中引入了Handler消息傳遞機制,目的是打破對主線程的依賴性。
什麼是Handler?
handler通俗一點講就是用來在各個線程之間發送數據的處理對象。在任何線程中,只要獲得了另一個線程的handler,則可以通過 handler.sendMessage(message)方法向那個線程發送數據。基於這個機制,我們在處理多線程的時候可以新建一個thread,這個thread擁有UI線程中的一個handler。當thread處理完一些耗時的操作後通過傳遞過來的handler向UI線程發送數據,由UI線程去更新界面。
主線程:運行所有UI組件,它通過一個消息隊列來完成此任務。設備會將用戶的每項操作轉換為消息,並將它們放入正在運行的消息隊列中。主線程位於一個循環中,並處理每條消息。如果任何一個消息用時超過5秒,Android將拋出ANR。所以一個任務用時超過5秒,應該在一個獨立線程中完成它,或者延遲處理它,當主線程空閒下來再返回來處理它。
(二)、常用類:(Handler、Looper、Message、MessageQueue)
(三)、Handler、Looper、Message、MessageQueue之間的關系:
Handler,Looper和MessageQueue的三角關系
【備注:】
Looper對象用來為一個線程開啟一個消息循環,從而操作MessageQueue;
默認情況下,Android創建的線程沒有開啟消息循環Looper,但是主線程例外。
系統自動為主線程創建Looper對象,開啟消息循環;
所以主線程中使用new來創建Handler對象。而子線程中不能直接new來創建Handler對象就會異常。
子線程中創建Handler對象,步驟如下:
Looper.prepare();
Handler handler = new Handler() {
//handlemessage(){}
}
Looper.loop();
(四)、Handler類中常用方法:
(五)、Message消息類中常用屬性:
【重點】:使用Message需要注意4點:
1、Message雖然也可以通過new來獲取,但是通常使用Message.obtain()或Handler.obtainMessage()方法來從消息池中獲得空消息對象,以節省資源;
2、如果一個Message只需要攜帶簡單的int型數據,應優先使用arg1和arg2屬性來傳遞數據,這樣比其他方式節省內存;
3、盡可能使用Message.what來標識信息,以便用不同的方式處理Message;
4、如果需要從工作線程返回很多數據信息,可以借助Bundle對象將這些數據集中到一起,然後存放到obj屬性中,再返回到主線程。
(六)、示例代碼一:【重點】
private Handler handler = null; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); text_main_info = (TextView) findViewById(R.id.text_main_info); pDialog = new ProgressDialog(MainActivity.this); pDialog.setMessage("Loading..."); image_main = (ImageView) findViewById(R.id.image_main); // 主線程中的handler對象會處理工作線程中發送的Message。根據Message的不同編號進行相應的操作。 handler = new Handler() { public void handleMessage(android.os.Message msg) { // 工作線程中要發送的信息全都被放到了Message對象中,也就是上面的參數msg中。要進行操作就要先取出msg中傳遞的數據。 switch (msg.what) { case 0: // 工作線程發送what為0的信息代表線程開啟了。主線程中相應的顯示一個進度對話框 pDialog.show(); break; case 1: // 工作線程發送what為1的信息代表要線程已經將需要的數據加載完畢。本案例中就需要將該數據獲取到,顯示到指定ImageView控件中即可。 image_main.setImageBitmap((Bitmap) msg.obj); break; case 2: // 工作線程發送what為2的信息代表工作線程結束。本案例中,主線程只需要將進度對話框取消即可。 pDialog.dismiss(); break; } } }; new Thread(new Runnable() { @Override public void run() { // 當工作線程剛開始啟動時,希望顯示進度對話框,此時讓handler發送一個空信息即可。 // 當發送這個信息後,主線程會回調handler對象中的handleMessage()方法。handleMessage()方法中 // 會根據message的what種類來執行不同的操作。 handler.sendEmptyMessage(0); // 工作線程執行訪問網絡,加載網絡圖片的任務。 byte[] data = HttpClientHelper.loadByteFromURL(urlString); // 工作線程將網絡訪問獲取的字節數組生成Bitmap位圖。 Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length); // 工作線程將要發送給主線程的信息都放到一個Message信息對象中。 // 而Message對象的構建建議使用obtain()方法生成,而不建議用new來生成。 Message msgMessage = Message.obtain(); // 將需要傳遞到主線程的數據放到Message對象的obj屬性中,以便於傳遞到主線程。 msgMessage.obj = bitmap; // Message對象的what屬性是為了區別信息種類,而方便主線程中根據這些類別做相應的操作。 msgMessage.what = 1; // handler對象攜帶著Message中的數據返回到主線程 handler.sendMessage(msgMessage); // handler再發出一個空信息,目的是告訴主線程工作線程的任務執行完畢。一般主線程會接收到這個消息後, // 將進度對話框關閉 handler.sendEmptyMessage(2); } }).start(); }
(七)、示例代碼二:圖片定時切換:
1、思路:利用多線程,子線程每隔2秒發送一個消息給主線程,主線程中Handler接收消息,並更新ImageView中的圖片。這樣就實現了循環切換的動態效果。
handler = new Handler() { @Override public void handleMessage(Message msg) { super.handleMessage(msg); switch (msg.what) { case 0: image_main_pic.setImageResource(imageId[position++]); if (position >= imageId.length) { position = 0; } break; default: break; } } }; // 第一種解決辦法:利用Thread和Thread的sleep // new Thread(new Runnable() { // @Override // public void run() { // while (flag) { // try { // Thread.sleep(2000); // } catch (InterruptedException e) { // e.printStackTrace(); // } // handler.sendEmptyMessage(0); // } // } // }).start(); // 第二種解決辦法:利用Timer定時器和定時器的schedule()方法。 //schedule()方法中有三個參數: /*第一個:表示定時任務TimerTask。 TimerTask 類實現了Runnable接口,所以要new TimerTask(),一定要實現run()方法。 第二個:表示第一次執行前的等待延遲時間; 第三個:表示兩次定時任務執行之間的間隔時間。*/ new Timer().schedule(new TimerTask() { @Override public void run() { handler.sendEmptyMessage(0); //sendEmptyMessage()方法等同於以下幾句話。所以。如果只發送一個what,就可以使用sendEmptyMessage()。這樣更簡單。 //Message message = Message.obtain(); // Message message2 = handler.obtainMessage(); //message.what = 0; //handler.sendMessage(message); } }, 1, 1500);
(八)、示例代碼三:打地鼠:
handler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case 0:
image_main_mouse.setVisibility(View.VISIBLE);
// 獲取0-8之間的隨機數[0,8),半閉合區間。目的是隨機獲取給定的8個坐標位置。
// 獲取隨機數有兩種辦法:
// 方法一:
//Math.random()*positionArr.length,注意偽隨機數是個半閉合區間。即隨機數不可能為positionArr.length
// 方法二:
//new Random().nextInt(positionArr.length);
position = (int) (Math.random() * positionArr.length);
image_main_mouse.setX(positionArr[position][0]);
image_main_mouse.setY(positionArr[position][1]);
break;
default:
break;
}
}
};
image_main_mouse = (ImageView) findViewById(R.id.image_main_mouse);
new Thread(new Runnable() {
@Override
public void run() {
while (flag) {
try {
// 獲取0-500之間的隨機數,再加上500,目的是讓老鼠出現的間隙時間也隨機,最短出現間隙為500毫秒,最長為999毫秒。
Thread.sleep(new Random().nextInt(500) + 500);
} catch (InterruptedException e) {
e.printStackTrace();
}
handler.sendEmptyMessage(0);
}
}
}).start();
image_main_mouse.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
image_main_mouse.setVisibility(View.GONE);
return false;
}
});
【備注:】
在案例《打地鼠》中使用到了橫豎屏幕切換,請參考以下代碼:
關於Android中Activity的橫豎屏切換問題可以通過AndroidManifest.xml文件中的Activity來配置:
android:screenOrientation=["unspecified" | "user" | "behind" |"landscape" | "portrait" | "sensor" | "nonsensor"]screenOrientation 用來指定Activity的在設備上顯示的方向,每個值代表如下含義:
unspecified
"
默認值 由系統來判斷顯示方向.判定的策略是和設備相關的,所以不同的設備會有不同的顯示方向.
"landscape
"
橫屏顯示(寬比高要長)
"portrait
"
豎屏顯示(高比寬要長)
"user
"
用戶當前首選的方向
"behind
"
和該Activity下面的那個Activity的方向一致(在Activity堆棧中的)
"sensor
"
有物理的感應器來決定。如果用戶旋轉設備這屏幕會橫豎屏切換。
"nosensor
"
忽略物理感應器,這樣就不會隨著用戶旋轉設備而更改了 ( "unspecified
"設置除外 )。
二、Handler、Looper源碼分析:
(一)、Handler的概念:
Handler的主要用途有兩個:
(1)、在將來的某個時刻執行消息或一個runnable;
(2)、為運行在不同線程中的多個任務排隊。
主要依靠以下方法來完成消息調度:
【備注:】
(二)、Handler的用法:
當你實例化一個Handler的時候可以使用Callback接口來避免寫自定義的Handler子類。這裡的機制類似與Thread與runable接口的關系。
在Handler裡面,子類要處理消息的話必須重寫handleMessage()這個方法,因為在handler裡面它是個空方法:
(三)、源碼分析:
A、Handler.java:(3個屬性,9個方法)
3個屬性:
9個方法:
B、Looper.JAVA:(4個屬性,4個方法)
每個ThreadLocal中只能有一個Looper,也就是說一個Thread中只有一個Looper
4個屬性:
4個方法:
C、Message.java:(8個屬性,5個方法)
8個屬性:
5個方法:
1 背景其實有點不想寫這篇文章的,但是又想寫,有些矛盾。不想寫的原因是隨便上網一搜一堆關於性能的建議,感覺大家你一總結、我一總結的都說到了很多優化注意事項,但是看過這些文
本文是想總結一些Android Studio的使用技巧,對於大多數習慣了使用eclipse的人來說,可能會需要一段時間,但是如果看過下面的一些介紹,你就能體會到Andr
一、問題描述 上一次我們使用百度地圖實現基本的定位功能,接下來我們繼續實現搜索和定位,並使用LocationOverlay繪制定位位置,同時展示如何使用自定義圖標繪制並
一、onWindowFocusChanged有時我們需要測量一個Activity多長時間才能顯示出來,那麼在代碼中打點計時的時機選在哪兒呢?在onCreate和onRes