當前位置:首頁 » 安卓系統 » android非同步多線程

android非同步多線程

發布時間: 2023-05-23 22:28:51

① [Android源碼分析] - 非同步通信Handler機制

一、問題:在Android啟動後會在新進程里創建一個主線程,也叫UI線程( 非線程安全 )這個線程主要負責監聽屏幕點擊事件與界面繪制。當Application需要進行耗時操作如網路請求等,如直接在主線程進行容易發生ANR錯誤。所以會創建子線程來執行耗時任務,當子線程執行完畢需要通知UI線程並修改界面時,不可以直接在子線程修改UI,怎麼辦?

解決方法:Message Queue機制可以實現子線程與UI線程的通信。

該機制包括Handler、Message Queue、Looper。Handler可以把消息/ Runnable對象 發給Looper,由它把消息放入所屬線程的消息隊列中,然後Looper又會自動把消息隊列里的消息/Runnable對象 廣播 到所屬線程里的Handler,由Handler處理接收到的消息或Runnable對象。

1、Handler

每次創建Handler對象時,它會自動綁定到創建它的線程上。如果是主線程則默認包含一個Message Queue,否則需要自己創建一個消息隊列來存儲

Handler是多個線程通信的信使。比如在線程A中創建AHandler,給它綁定一個ALooper,同時創建屬於A的消息隊列AMessageQueue。然後在線程B中使用AHandler發送消息給ALooper,ALooper會把消息存入到AMessageQueue,然後再把AMessageQueue廣播給A線程里的AHandler,它接收到消息會進行處理。從而實現通信。

2、Message Queue

在主線程里默認包含了一個消息隊列不需要手動創建。在子線程里,使用Looper.prepare()方法後,會先檢查子線程是否已有一個looper對象,如果有則無法創建,因為每個線程只能擁有一個消息隊列。沒有的話就為子線程創建一個消息隊列。

Handler類包含Looper指針和MessageQueue指針,而Looper里包含實際MessageQueue與當前線程指針。

下面分別就UI線程和worker線程講解handler創建過程:

首先,創建handler時,會自動檢查當前線程是否包含looper對象,如果包含,則將handler內的消息隊列指向looper內部的消息隊列,否則,拋出異常請求執行looper.prepare()方法。

 - 在 UI線程 中,系統自動創建了Looper 對象,所以,直接new一個handler即可使用該機制;

- 在 worker線程 中,如果直接創建handler會拋出運行時異常-即通過查『線程-value』映射表發現當前線程無looper對象。所以需要先調用Looper.prepare()方法。在prepare方法里,利用ThreadLocal<Looper>對象為當前線程創建一個Looper(利用了一個Values類,即一個Map映射表,專為thread存儲value,此處為當前thread存儲一個looper對象)。然後繼續創建handler, 讓handler內部的消息隊列指向該looper的消息隊列(這個很重要,讓handler指向looper里的消息隊列,即二者共享同一個消息隊列,然後handler向這個消息隊列發送消息,looper從這個消息隊列獲取消息) 。然後looper循環消息隊列即可。當獲取到message消息,會找出message對象里的target,即原始發送handler,從而回調handler的handleMessage() 方法進行處理。

 - handler與looper共享消息隊列 ,所以handler發送消息只要入列,looper直接取消息即可。

 - 線程與looper映射表 :一個線程最多可以映射一個looper對象。通過查表可知當前線程是否包含looper,如果已經包含則不再創建新looper。

5、基於這樣的機制是怎樣實現線程隔離的,即在線程中通信呢。 

核心在於 每一個線程擁有自己的handler、message queue、looper體系 。而 每個線程的Handler是公開 的。B線程可以調用A線程的handler發送消息到A的共享消息隊列去,然後A的looper會自動從共享消息隊列取出消息進行處理。反之一樣。

二、上面是基於子線程中利用主線程提供的Handler發送消息出去,然後主線程的Looper從消息隊列中獲取並處理。那麼還有另外兩種情況:

1、主線程發送消息到子線程中;

採用的方法和前面類似。要在子線程中實例化AHandler並設定處理消息的方法,同時由於子線程沒有消息隊列和Looper的輪詢,所以要加上Looper.prepare(),Looper.loop()分別創建消息隊列和開啟輪詢。然後在主線程中使用該AHandler去發送消息即可。

2、子線程A與子線程B之間的通信。

1、 Handler為什麼能夠實現不同線程的通信?核心點在哪?

不同線程之間,每個線程擁有自己的Handler、消息隊列和Looper。Handler是公共的,線程可以通過使用目標線程的Handler對象來發送消息,這個消息會自動發送到所屬線程的消息隊列中去,線程自帶的Looper對象會不斷循環從裡面取出消息並把消息發送給Handler,回調自身Handler的handlerMessage方法,從而實現了消息的線程間傳遞。

2、 Handler的核心是一種事件激活式(類似傳遞一個中斷)的還是主要是用於傳遞大量數據的?重點在Message的內容,偏向於數據傳輸還是事件傳輸。

目前的理解,它所依賴的是消息隊列,發送的自然是消息,即類似事件中斷。

0、 Android消息處理機制(Handler、Looper、MessageQueue與Message)

1、 Handler、Looper源碼閱讀

2、 Android非同步消息處理機制完全解析,帶你從源碼的角度徹底理解

謝謝!

wingjay

![](https://avatars0.githubusercontent.com/u/9619875?v=3&s=460)

② android從網路下載圖片listview每次滑動的時候圖片都會重新載入

要用緩存,推薦使用universal_image_loader第三方包 Universal Image Loader for Android的實現了非同步的網路圖片載入、緩存及顯示,支持多線程非同步載入。

③ Android中的線程狀態 - AsyncTask詳解

在操作系統中,線程是操作系統調度的最小單元,同時線程又是一種受限的系統資源,即線程不可能無限制地產生,並且 線程的創建和銷毀都會有相應的開銷。 當系統中存在大量的線程時,系統會通過會時間片輪轉的方式調度每個線程,因此線程不可能做到絕對的並行。

如果在一個進程中頻繁地創建和銷毀線程,顯然不是高效的做法。正確的做法是採用線程池,一個線程池中會緩存一定數量的線程,通過線程池就可以避免因為頻繁創建和銷毀線程所帶來的系統開銷。

AsyncTask是一個抽象類,它是由Android封裝的一個輕量級非同步類(輕量體現在使用方便、代碼簡潔),它可以在線程池中執行後台任務,然後把執行的進度和最終結果傳遞給主線程並在主線程中更新UI。

AsyncTask的內部封裝了 兩個線程池 (SerialExecutor和THREAD_POOL_EXECUTOR)和 一個Handler (InternalHandler)。

其中 SerialExecutor線程池用於任務的排隊,讓需要執行的多個耗時任務,按順序排列 THREAD_POOL_EXECUTOR線程池才真正地執行任務 InternalHandler用於從工作線程切換到主線程

1.AsyncTask的泛型參數

AsyncTask是一個抽象泛型類。

其中,三個泛型類型參數的含義如下:

Params: 開始非同步任務執行時傳入的參數類型;

Progress: 非同步任務執行過程中,返回下載進度值的類型;

Result: 非同步任務執行完成後,返回的結果類型;

如果AsyncTask確定不需要傳遞具體參數,那麼這三個泛型參數可以用Void來代替。

有了這三個參數類型之後,也就控制了這個AsyncTask子類各個階段的返回類型,如果有不同業務,我們就需要再另寫一個AsyncTask的子類進行處理。

2.AsyncTask的核心方法

onPreExecute()

這個方法會在 後台任務開始執行之間調用,在主線程執行。 用於進行一些界面上的初始化操作,比如顯示一個進度條對話框等。

doInBackground(Params...)

這個方法中的所有代碼都會 在子線程中運行,我們應該在這里去處理所有的耗時任務。

任務一旦完成就可以通過return語句來將任務的執行結果進行返回,如果AsyncTask的第三個泛型參數指定的是Void,就可以不返回任務執行結果。 注意,在這個方法中是不可以進行UI操作的,如果需要更新UI元素,比如說反饋當前任務的執行進度,可以調用publishProgress(Progress...)方法來完成。

onProgressUpdate(Progress...)

當在後台任務中調用了publishProgress(Progress...)方法後,這個方法就很快會被調用,方法中攜帶的參數就是在後台任務中傳遞過來的。 在這個方法中可以對UI進行操作,在主線程中進行,利用參數中的數值就可以對界面元素進行相應的更新。

onPostExecute(Result)

當doInBackground(Params...)執行完畢並通過return語句進行返回時,這個方法就很快會被調用。返回的數據會作為參數傳遞到此方法中, 可以利用返回的數據來進行一些UI操作,在主線程中進行,比如說提醒任務執行的結果,以及關閉掉進度條對話框等。

上面幾個方法的調用順序:

onPreExecute() --> doInBackground() --> publishProgress() --> onProgressUpdate() --> onPostExecute()

如果不需要執行更新進度則為onPreExecute() --> doInBackground() --> onPostExecute(),

除了上面四個方法,AsyncTask還提供了onCancelled()方法, 它同樣在主線程中執行,當非同步任務取消時,onCancelled()會被調用,這個時候onPostExecute()則不會被調用 ,但是要注意的是, AsyncTask中的cancel()方法並不是真正去取消任務,只是設置這個任務為取消狀態,我們需要在doInBackground()判斷終止任務。就好比想要終止一個線程,調用interrupt()方法,只是進行標記為中斷,需要在線程內部進行標記判斷然後中斷線程。

3.AsyncTask的簡單使用

這里我們模擬了一個下載任務,在doInBackground()方法中去執行具體的下載邏輯,在onProgressUpdate()方法中顯示當前的下載進度,在onPostExecute()方法中來提示任務的執行結果。如果想要啟動這個任務,只需要簡單地調用以下代碼即可:

4.使用AsyncTask的注意事項

①非同步任務的實例必須在UI線程中創建,即AsyncTask對象必須在UI線程中創建。

②execute(Params... params)方法必須在UI線程中調用。

③不要手動調用onPreExecute(),doInBackground(Params... params),onProgressUpdate(Progress... values),onPostExecute(Result result)這幾個方法。

④不能在doInBackground(Params... params)中更改UI組件的信息。

⑤一個任務實例只能執行一次,如果執行第二次將會拋出異常。

先從初始化一個AsyncTask時,調用的構造函數開始分析。

這段代碼雖然看起來有點長,但實際上並沒有任何具體的邏輯會得到執行,只是初始化了兩個變數,mWorker和mFuture,並在初始化mFuture的時候將mWorker作為參數傳入。mWorker是一個Callable對象,mFuture是一個FutureTask對象,這兩個變數會暫時保存在內存中,稍後才會用到它們。 FutureTask實現了Runnable介面,關於這部分內容可以看這篇文章。

mWorker中的call()方法執行了耗時操作,即result = doInBackground(mParams);,然後把執行得到的結果通過postResult(result);,傳遞給內部的Handler跳轉到主線程中。在這里這是實例化了兩個變數,並沒有開啟執行任務。

那麼mFuture對象是怎麼載入到線程池中,進行執行的呢?

接著如果想要啟動某一個任務,就需要調用該任務的execute()方法,因此現在我們來看一看execute()方法的源碼,如下所示:

調用了executeOnExecutor()方法,具體執行邏輯在這個方法裡面:

可以 看出,先執行了onPreExecute()方法,然後具體執行耗時任務是在exec.execute(mFuture),把構造函數中實例化的mFuture傳遞進去了。

exec具體是什麼?

從上面可以看出具體是sDefaultExecutor,再追溯看到是SerialExecutor類,具體源碼如下:

終於追溯到了調用了SerialExecutor 類的execute方法。SerialExecutor 是個靜態內部類,是所有實例化的AsyncTask對象公有的,SerialExecutor 內部維持了一個隊列,通過鎖使得該隊列保證AsyncTask中的任務是串列執行的,即多個任務需要一個個加到該隊列中,然後執行完隊列頭部的再執行下一個,以此類推。

在這個方法中,有兩個主要步驟。

①向隊列中加入一個新的任務,即之前實例化後的mFuture對象。

②調用 scheleNext()方法,調用THREAD_POOL_EXECUTOR執行隊列頭部的任務。

由此可見SerialExecutor 類僅僅為了保持任務執行是串列的,實際執行交給了THREAD_POOL_EXECUTOR。

THREAD_POOL_EXECUTOR又是什麼?

實際是個線程池,開啟了一定數量的核心線程和工作線程。然後調用線程池的execute()方法。執行具體的耗時任務,即開頭構造函數中mWorker中call()方法的內容。先執行完doInBackground()方法,又執行postResult()方法,下面看該方法的具體內容:

該方法向Handler對象發送了一個消息,下面具體看AsyncTask中實例化的Hanlder對象的源碼:

在InternalHandler 中,如果收到的消息是MESSAGE_POST_RESULT,即執行完了doInBackground()方法並傳遞結果,那麼就調用finish()方法。

如果任務已經取消了,回調onCancelled()方法,否則回調 onPostExecute()方法。

如果收到的消息是MESSAGE_POST_PROGRESS,回調onProgressUpdate()方法,更新進度。

InternalHandler是一個靜態類,為了能夠將執行環境切換到主線程,因此這個類必須在主線程中進行載入。所以變相要求AsyncTask的類必須在主線程中進行載入。

到此為止,從任務執行的開始到結束都從源碼分析完了。

AsyncTask的串列和並行

從上述源碼分析中分析得到,默認情況下AsyncTask的執行效果是串列的,因為有了SerialExecutor類來維持保證隊列的串列。如果想使用並行執行任務,那麼可以直接跳過SerialExecutor類,使用executeOnExecutor()來執行任務。

四、AsyncTask使用不當的後果

1.)生命周期

AsyncTask不與任何組件綁定生命周期,所以在Activity/或者Fragment中創建執行AsyncTask時,最好在Activity/Fragment的onDestory()調用 cancel(boolean);

2.)內存泄漏

3.) 結果丟失

屏幕旋轉或Activity在後台被系統殺掉等情況會導致Activity的重新創建,之前運行的AsyncTask(非靜態的內部類)會持有一個之前Activity的引用,這個引用已經無效,這時調用onPostExecute()再去更新界面將不再生效。

自己是從事了七年開發的Android工程師,不少人私下問我,2019年Android進階該怎麼學,方法有沒有?

沒錯,年初我花了一個多月的時間整理出來的學習資料,希望能幫助那些想進階提升Android開發,卻又不知道怎麼進階學習的朋友。【 包括高級UI、性能優化、架構師課程、NDK、Kotlin、混合式開發(ReactNative+Weex)、Flutter等架構技術資料 】,希望能幫助到您面試前的復習且找到一個好的工作,也節省大家在網上搜索資料的時間來學習。

④ Android 非同步任務中允許有多個線程同時進行嗎

允許的,非同步多線程下載就是這樣

⑤ Android進程間和線程間通信方式

        進程:是具有一定獨立功能的程序關於某個數據集合上的一次運行活動,進程是系統進行資源分配和調度的一個獨立單位。

  線程:是進程的一個實體,是CPU調度和分派的基本單位,它是比進程更小的能獨立運行的基本單位。線程自己基本上不擁有系統資源,只擁有一些在運行中必不可少的資源(如程序計數器,一組寄存器和棧),但是它可與同屬一個進程的其他的線程共享進程所擁有的全部資源。

  區別:

  (1)、一個程序至少有一個進程,一個進程至少有一個線程;

  (2)、線程的劃分尺度小於進程,使得多線程程序的並發性高;

  (3)、進程在執行過程中擁有獨立的內存單元,而多個線程共享內存,但線程之間沒有單獨的地址空間,一個線程死掉就等於整個進程死掉。

---------------------

一、Android進程間通信方式

1.Bundle

  由於Activity,Service,Receiver都是可以通過Intent來攜帶Bundle傳輸數據的,所以我們可以在一個進程中通過Intent將攜帶數據的Bundle發送到另一個進程的組件。

  缺點:無法傳輸Bundle不支持的數據類型。

2.ContentProvider

  ContentProvider是Android四大組件之一,以表格的方式來儲存數據,提供給外界,即Content Provider可以跨進程訪問其他應用程序中的數據。用法是繼承ContentProvider,實現onCreate,query,update,insert,delete和getType方法,onCreate是負責創建時做一些初始化的工作,增刪查改的方法就是對數據的查詢和修改,getType是返回一個String,表示Uri請求的類型。注冊完後就可以使用ContentResolver去請求指定的Uri。

3.文件

  兩個進程可以到同一個文件去交換數據,我們不僅可以保存文本文件,還可以將對象持久化到文件,從另一個文件恢復。要注意的是,當並發讀/寫時可能會出現並發的問題。

4.Broadcast

  Broadcast可以向android系統中所有應用程序發送廣播,而需要跨進程通訊的應用程序可以監聽這些廣播。

5.AIDL方式

  Service和Content Provider類似,也可以訪問其他應用程序中的數據,Content Provider返回的是Cursor對象,而Service返回的是java對象,這種可以跨進程通訊的服務叫AIDL服務。

         AIDL通過定義服務端暴露的介面,以提供給客戶端來調用,AIDL使伺服器可以並行處理,而Messenger封裝了AIDL之後只能串列運行,所以Messenger一般用作消息傳遞。

6.Messenger

  Messenger是基於AIDL實現的,服務端(被動方)提供一個Service來處理客戶端(主動方)連接,維護一個Handler來創建Messenger,在onBind時返回Messenger的binder。

  雙方用Messenger來發送數據,用Handler來處理數據。Messenger處理數據依靠Handler,所以是串列的,也就是說,Handler接到多個message時,就要排隊依次處理。

7.Socket

  Socket方法是通過網路來進行數據交換,注意的是要在子線程請求,不然會堵塞主線程。客戶端和服務端建立連接之後即可不斷傳輸數據,比較適合實時的數據傳輸

二、Android線程間通信方式

  一般說線程間通信主要是指主線程(也叫UI線程)和子線程之間的通信,主要有以下兩種方式:

1.AsyncTask機制

  AsyncTask,非同步任務,也就是說在UI線程運行的時候,可以在後台的執行一些非同步的操作;AsyncTask可以很容易且正確地使用UI線程,AsyncTask允許進行後台操作,並在不顯示使用工作線程或Handler機制的情況下,將結果反饋給UI線程。但是AsyncTask只能用於短時間的操作(最多幾秒就應該結束的操作),如果需要長時間運行在後台,就不適合使用AsyncTask了,只能去使用Java提供的其他API來實現。

2.Handler機制

  Handler,繼承自Object類,用來發送和處理Message對象或Runnable對象;Handler在創建時會與當前所在的線程的Looper對象相關聯(如果當前線程的Looper為空或不存在,則會拋出異常,此時需要在線程中主動調用Looper.prepare()來創建一個Looper對象)。使用Handler的主要作用就是在後面的過程中發送和處理Message對象和讓其他的線程完成某一個動作(如在工作線程中通過Handler對象發送一個Message對象,讓UI線程進行UI的更新,然後UI線程就會在MessageQueue中得到這個Message對象(取出Message對象是由其相關聯的Looper對象完成的),並作出相應的響應)。

三、Android兩個子線程之間通信

  面試的過程中,有些面試官可能會問Android子線程之間的通信方式,由於絕大部分程序員主要關注的是Android主線程和子線程之間的通信,所以這個問題很容易讓人懵逼。

  主線程和子線程之間的通信可以通過主線程中的handler把子線程中的message發給主線程中的looper,或者,主線程中的handler通過post向looper中發送一個runnable。但looper默認存在於main線程中,子線程中沒有Looper,該怎麼辦呢?其實原理很簡單,把looper綁定到子線程中,並且創建一個handler。在另一個線程中通過這個handler發送消息,就可以實現子線程之間的通信了。

  子線程創建handler的兩種方式:

  方式一:給子線程創建Looper對象:

new Thread(new Runnable() {

            public void run() { 

                Looper.prepare();  // 給這個Thread創建Looper對象,一個Thead只有一個Looper對象

                Handler handler = new Handler(){ 

                    @Override 

                    public void handleMessage(Message msg) { 

                        Toast.makeText(getApplicationContext(), "handleMessage", Toast.LENGTH_LONG).show(); 

                    } 

                }; 

                handler.sendEmptyMessage(1); 

                Looper.loop(); // 不斷遍歷MessageQueue中是否有消息

            }; 

        }).start();

---------------------

       方式二:獲取主線程的looper,或者說是UI線程的looper:

new Thread(new Runnable() {

            public void run() { 

                Handler handler = new Handler(Looper.getMainLooper()){ // 區別在這!!! 

                    @Override 

                    public void handleMessage(Message msg) { 

                        Toast.makeText(getApplicationContext(), "handleMessage", Toast.LENGTH_LONG).show(); 

                    } 

                }; 

                handler.sendEmptyMessage(1); 

            }; 

        }).start();

---------------------

⑥ android 上傳文件需要非同步線程嗎

一、handler的引入:

我們都知道,Android UI是線程不安全的,如果在子線程中嘗試進行UI操作,程序就有可能會崩潰。相信大家在日常的工作當中都會經常遇到這個問題,解決的方案應該也是早已爛熟於心,即創建一個Message對象,然後藉助Handler發送出去,之後在Handler的handleMessage()方法中獲得剛才發送的Message對象,然後在這里進行UI操作就不會再出現崩潰了。具體實現代碼如下:

復制代碼
1 package com.example.androidthreadtest;
2
3 import android.app.Activity;
4 import android.os.Bundle;
5 import android.os.Handler;
6 import android.os.Message;
7 import android.view.View;
8 import android.view.View.OnClickListener;
9 import android.widget.Button;
10 import android.widget.TextView;
11
12 public class MainActivity extends Activity implements OnClickListener {
13
14 public static final int UPDATE_TEXT = 1;
15 private TextView text;
16 private Button changeText;
17 private Handler handler = new Handler() {
18 public void handleMessage(Message msg) {
19 switch (msg.what) {
20 case UPDATE_TEXT:
21 text.setText("Nice to meet you");
22 break;
23 default:
24 break;
25 }
26 }
27 };
28
29 @Override
30 protected void onCreate(Bundle savedInstanceState) {
31 super.onCreate(savedInstanceState);
32 setContentView(R.layout.activity_main);
33 text = (TextView) findViewById(R.id.text);
34 changeText = (Button) findViewById(R.id.change_text);
35 changeText.setOnClickListener(this);
36 }
37
38 @Override
39 public void onClick(View v) {
40 switch (v.getId()) {
41 case R.id.change_text:
42 new Thread(new Runnable() {
43 @Override
44 public void run() {
45 Message message = new Message();
46 message.what = UPDATE_TEXT;
47 handler.sendMessage(message);
48 }
49 }).start();
50 break;
51 default:
52 break;
53 }
54 }
55 }
復制代碼
上方第45行代碼也可以換成:

Message msg = handler.obtainMessage();
上面的代碼中,我們並沒有在子線程中直接進行UI操作,而是創建了一個Message對象,並將它的what欄位的值指定為了一個整形常量UPDATE_TEXT,用於表示更新TextView這個動作。然後調用Handler的sendMessage()方法將這條Message發送出去。很快,Handler就會收到這條Message,並在handleMessage()方法,在這里對具體的Message進行處理(需要注意的是,此時handleMessage()方法中的代碼是在主線程中運行的)。如果發現Message的what欄位的值等於UPDATE_TEXT,就將TextView顯示的內容更新。運行程序後,點擊按鈕,TextView就會顯示出更新的內容。

二、非同步消息處理機制:

Handler是Android類庫提供的用於接受、傳遞和處理消息或Runnable對象的處理類,它結合Message、MessageQueue和Looper類以及當前線程實現了一個消息循環機制,用於實現任務的非同步載入和處理。整個非同步消息處理流程的示意圖如下圖所示:

根據上面的圖片,我們現在來解析一下非同步消息處理機制:

Message:消息體,用於裝載需要發送的對象。
handler:它直接繼承自Object。作用是:在子線程中發送Message或者Runnable對象到MessageQueue中;在UI線程中接收、處理從MessageQueue分發出來的Message或者Runnable對象。發送消息一般使用Handler的sendMessage()方法,而發出去的消息經過處理後最終會傳遞到Handler的handlerMessage()方法中。
MessageQueue:用於存放Message或Runnable對象的消息隊列。它由對應的Looper對象創建,並由Looper對象管理。每個線程中都只會有一個MessageQueue對象。
Looper:是每個線程中的MessageQueue的管家,循環不斷地管理MessageQueue接收和分發Message或Runnable的工作。調用Looper的loop()方法後,就會進入到一個無限循環中然後每當發現MessageQueue中存在一條消息,就會將它取出,並調用Handler的handlerMessage()方法。每個線程中也只會有一個Looper對象。
了解這些之後,我們在來看一下他們之間的聯系:

首先要明白的是,Handler和Looper對象是屬於線程內部的數據,不過也提供與外部線程的訪問介面,Handler就是公開給外部線程的介面,用於線程間的通信。Looper是由系統支持的用於創建和管理MessageQueue的依附於一個線程的循環處理對象,而Handler是用於操作線程內部的消息隊列的,所以Handler也必須依附一個線程,而且只能是一個線程。

我們再來對非同步消息處理的整個流程梳理一遍:

當應用程序開啟時,系統會自動為UI線程創建一個MessageQueue(消息隊列)和Looper循環處理對象。首先需要在主線程中創建一個Handler對象,並重寫handlerMessage()方法。然後當子線程中需要進行UI操作時,就創建一個Message對象,並通過Handler將這條消息發送出去。之後這條消息就會被添加到MessageQueue的隊列中等待被處理,而Looper則會一直嘗試從MessageQueue中取出待處理消息,並找到與消息對象對應的Handler對象,然後調用Handler的handleMessage()方法。由於Handler是在主線程中創建的,所以此時handleMessage()方法中的代碼也會在主線程中運行,於是我們在這里就可以安心地進行UI操作了。

通俗地來講,一般我們在實際的開發過程中用的比較多一種情況的就是主線程的Handler將子線程中處理過的耗時操作的結果封裝成Message(消息),並將該Message(利用主線程里的MessageQueue和Looper)傳遞到主線程中,最後主線程再根據傳遞過來的結果進行相關的UI元素的更新,從而實現任務的非同步載入和處理,並達到線程間的通信。

通過上一小節對Handler的一個初步認識後,我們可以很容易總結出Handler的主要用途,下面是Android官網總結的關於Handler類的兩個主要用途:

(1)線程間的通信:

在執行較為耗時的操作時,Handler負責將子線程中執行的操作的結果傳遞到UI線程,然後UI線程再根據傳遞過來的結果進行相關UI元素的更新。(上面已有說明)

(2)執行定時任務:

指定任務時間,在某個具體時間或某個時間段後執行特定的任務操作,例如使用Handler提供的postDelayed(Runnable r,long delayMillis)方法指定在多久後執行某項操作,比如當當、淘寶、京東和微信等手機客戶端的開啟界面功能,都是通過Handler定時任務來完成的。

我們接下來講一下post。

三、post:

對於Handler的Post方式來說,它會傳遞一個Runnable對象到消息隊列中,在這個Runnable對象中,重寫run()方法。一般在這個run()方法中寫入需要在UI線程上的操作。

Post允許把一個Runnable對象入隊到消息隊列中。它的方法有:post(Runnable)、postAtTime(Runnable,long)、postDelayed(Runnable,long)。詳細解釋如下:

boolean post(Runnable r):把一個Runnable入隊到消息隊列中,UI線程從消息隊列中取出這個對象後,立即執行。
boolean postAtTime(Runnable r,long uptimeMillis):把一個Runnable入隊到消息隊列中,UI線程從消息隊列中取出這個對象後,在特定的時間執行。
boolean postDelayed(Runnable r,long delayMillis):把一個Runnable入隊到消息隊列中,UI線程從消息隊列中取出這個對象後,延遲delayMills秒執行
void removeCallbacks(Runnable r):從消息隊列中移除一個Runnable對象。
下面通過一個Demo,講解如何通過Handler的post方式在新啟動的線程中修改UI組件的屬性:

復制代碼
1 package com.example.m03_threadtest01;
2
3 import android.app.Activity;
4 import android.os.Bundle;
5 import android.os.Handler;
6 import android.view.View;
7 import android.widget.Button;
8 import android.widget.TextView;
9
10 public class MainActivity extends Activity {
11 private Button btnMes1,btnMes2;
12 private TextView tvMessage;
13 // 聲明一個Handler對象
14 private static Handler handler=new Handler();
15
16 @Override
17 protected void onCreate(Bundle savedInstanceState) {
18 super.onCreate(savedInstanceState);
19 setContentView(R.layout.activity_main);
20
21 btnMes1=(Button)findViewById(R.id.button1);
22 btnMes2=(Button)findViewById(R.id.button2);
23 tvMessage=(TextView)findViewById(R.id.TextView1);
24 btnMes1.setOnClickListener(new View.OnClickListener() {
25
26 @Override
27 public void onClick(View v) {
28 // 新啟動一個子線程
29 new Thread(new Runnable() {
30 @Override
31 public void run() {
32 // tvMessage.setText("...");
33 // 以上操作會報錯,無法再子線程中訪問UI組件,UI組件的屬性必須在UI線程中訪問
34 // 使用post方式修改UI組件tvMessage的Text屬性
35 handler.post(new Runnable() {
36 @Override
37 public void run() {
38 tvMessage.setText("使用Handler.post在工作線程中發送一段執行到消息隊列中,在主線程中執行。");
39 }
40 });
41 }
42 }).start();
43 }
44 });
45
46 btnMes2.setOnClickListener(new View.OnClickListener() {
47
48 @Override
49 public void onClick(View v) {
50 new Thread(new Runnable() {
51 @Override
52 public void run() {
53 // 使用postDelayed方式修改UI組件tvMessage的Text屬性值
54 // 並且延遲3S執行
55 handler.postDelayed(new Runnable() {
56
57 @Override
58 public void run() {
59 tvMessage.setText("使用Handler.postDelayed在工作線程中發送一段執行到消息隊列中,在主線程中延遲3S執行。");
60
61 }
62 }, 3000);
63 }
64 }).start();
65
66 }
67 });
68 }
69
70 }
復制代碼
點擊按鈕,運行結果如下:

有一點值得注意的是,對於Post方式而言,它其中Runnable對象的run()方法的代碼,均執行在UI線程上(雖然是寫在子線程當中的),所以對於這段代碼而言,不能執行在UI線程上的操作,一樣無法使用post方式執行,比如說訪問網路。

四、Message:

Handler如果使用sendMessage的方式把消息入隊到消息隊列中,需要傳遞一個Message對象,而在Handler中,需要重寫handleMessage()方法,用於獲取工作線程傳遞過來的消息,此方法運行在UI線程上。

對於Message對象,一般並不推薦直接使用它的構造方法得到,而是建議通過使用Message.obtain()這個靜態的方法或者Handler.obtainMessage()獲取。Message.obtain()會從消息池中獲取一個Message對象,如果消息池中是空的,才會使用構造方法實例化一個新Message,這樣有利於消息資源的利用。並不需要擔心消息池中的消息過多,它是有上限的,上限為10個。Handler.obtainMessage()具有多個重載方法,如果查看源碼,會發現其實Handler.obtainMessage()在內部也是調用的Message.obtain()。

Handler中,與Message發送消息相關的方法有:

Message obtainMessage():獲取一個Message對象。
boolean sendMessage():發送一個Message對象到消息隊列中,並在UI線程取到消息後,立即執行。
boolean sendMessageDelayed():發送一個Message對象到消息隊列中,在UI線程取到消息後,延遲執行。
boolean sendEmptyMessage(int what):發送一個空的Message對象到隊列中,並在UI線程取到消息後,立即執行。
boolean sendEmptyMessageDelayed(int what,long delayMillis):發送一個空Message對象到消息隊列中,在UI線程取到消息後,延遲執行。
void removeMessage():從消息隊列中移除一個未響應的消息。

⑦ 怎麼准備Android面試

總體上來說可以分為兩種類型,小公司注重你的實踐能力,大公司看中你的綜合能力。小公司面試會比較集中安卓開發這一塊,比較在意你之前有沒有類似的項目經驗,有沒有遇到過目前他們所遇到的一些問題,准備這類公司面試除了基本的一些知識點還需要關注一些安卓開發實際中經常遇到的一些問題,諸如圖片緩存,常見的開源框架,oom問題等等。大公司面試比較在意你的綜合能力,無外乎是數據結構與演算法,計算機網路,操作系統,Java這。我想說在我列出的這么多考查點中,數據結構與演算法是一個分水嶺,這個的准備需要一個長時間的積累,真正的高手與一般人一試就知道,屢試不爽,所以很多公司都痴迷於考演算法,像是美團,網路,愛奇藝等公司,總共整個面試下來可以讓你手寫5個演算法題以上,我說這么多隻想告訴你這是一個門檻,能不能跨過去決定了你最終所找工作的水準,所以我都會建議每一個找程序員相關工作的人,這一塊要盡早准備,多刷一些leetcode或是其他類似的題。然後在剩下的幾塊知識中,我只想說你可以不用全部都懂,但是你必須要在某一塊知識點是有深入研究的,能夠回答面試官4到5個追加問題,如果所以知識點都只能答上一到兩個,面試官會覺得你的水平就是這樣了,另外也會覺得你沒有探究精神,關於後面一點是很恐怖的沒有探究精神做不了好的程序員。如果按照重要程度,面試安卓我覺得可以做一個這樣的排序:數據結構與演算法,java,安卓,計算機網路,操作系統,後面是資料庫或是設計模式之類的。java這一塊比較重要的是多線程同步非同步,java虛擬機內存回收,容器,面向對象一些知識點。就單單是多線程和虛擬機都可以看好幾本專門講解的書了,其他知識點就多啃啃java編程思想,遇到不懂在多翻翻博客加深理解。安卓這一塊,非同步機制,view的繪制,自定義控制項,四大組件都非常重要。不管是android或是java都最好看一些源碼,java可以看一些容器的源碼,android可以看handler,looper,message等非同步通信的源碼或是一些開源框架的源碼,會看源碼絕對是加分項。計算機網路可以注重看上面幾層,傳輸層注重研究。一般來說,前面這幾點掌握好了,足以聊好幾輪面試,一般面試官你都可以引導他去聊一些你擅長的知識點。後面幾塊知識點,你可以先掌握一些比較重要的,不一定每次面試都會遇到,但是也得准備。
我只提供一些大概的思路,具體到沒一塊知識點怎麼准備可以自己去搜集,大的方向對了加上好的執行力一般可以取得不錯的結果,祝好!

2016.03.14更新,加一些具體的准備建議:

上面這幾塊知識點,按照其重要程度排序,我個人認為可以這樣排:數據結構與演算法,Java,Android(如果不找安卓的就去掉這部分),計算機網路,操作系統,設計模式。下面我就按照這個順序給出一些准備建議。
Ø 數據結構與演算法
推薦書籍:大話數據結構,數據結構與演算法分析(java語言描述),劍指offer,進軍矽谷。
推薦在線刷題網站:Leetcode(LeetCode Online Judge),Lintcod(LintCode - 主頁),牛客網(在線編程_C++Java前端經典筆試面試題庫)
推薦方案:主要做三件事。
1, 先看一遍大話數據結構,熟悉一些基本概念,大話這本書只講了一些基本的數據結構,像是B樹,B+樹,紅黑樹需要自己看一些博客或是網站,推薦一個講解演算法的網站-七月演算法,另外海量數據處理也是一個經常考的專題,它結合了數據結構和操作系統的一些知識,這個需要進行針對性准備。
2,然後開始刷題,最推薦的肯定是leetcode,刷題建議按照標簽進行刷題(比如隊列,棧,哈希表),刷題從易到難,最好能夠刷一百題左右,如果一天一題,至少要三個月以上,所以這個耗時比較長,要從比較早開始准備。進軍矽谷這本書有按照專題進行編排,基本上的題目來自leetcode,可以作為一個參考。
2, 然後要分析一些常見數據結構的源碼實現(比如隊列,棧,hashmap之類的),數據結構與演算法分析這本書有講一些,可以結合一些博客進行准備。
Ø Java
找程序員工作,大多數情況下需要掌握好一門面向對象的語言,主流上分為Java和C++兩大陣營。這里講Java需要怎麼准備。
入門書籍:Java核心技術卷一,Head first in java(如果一點面向對象的概念都沒有那就看這本)
提高書籍:Java編程思想
Java虛擬機:深入理解Java虛擬機
Java多線程:Java多線程編程核心技術,Java並發編程實踐
其實我覺得如果有一定基礎建議直接看Java編程思想,面試中的考點經常都會在這本書中有講到。當然Java虛擬機和多線程這兩塊需要再看一些專門講解的書,每一次java面試考查內容幾乎都離不開這兩塊。
總體來說java考察內容包括以下這些:
1,面向對象的一些基本概念:繼承,多態之類的
2, 抽象類和介面
3, 靜態類,內部類
4, Java集合類,同步和非同步
5, Java類載入機制
6, Java內存模型和垃圾回收演算法
7, 線程同步機制(volatile,synchronized,重入鎖,threadlocal),線程間通信(wait,notify)
8, 異常處理
9, 多線程同步問題,生產者消費者,讀者寫者,哲學家就餐,用java實現
10,
了解java中設計模式的思想,用了哪些設計模式,有什麼好處
Ø Android
入門書籍:第一行代碼。
提高書籍:Android群英傳,Android開發藝術探索。
上面的書看好了,一般問題不大。主要問的一些問題:
1, Activity,Service,BrocastReceiver,Intent,Fragemnet,ContentProvider,這些組件的基礎知識,比如Activity兩種啟動方式,生命周期,四種啟動模式,Service則主要是:兩種啟動方式,生命周期。還有組件之間怎麼通信之類的問題。
2, Android的數據存儲方式,資料庫,sd卡,SharedPreferences 這些
3, Listview的優化,與scollview的區別
4, view狀態與重繪,view的繪制過程,view的事件分發機制,view的事件沖突處理
5, Android多線程非同步機制,AsyncTask工作原理與源碼實現,Handler,Message,Looper非同步實現機制與源碼分析
6, Android常見的開源框架(主要是網路通信,圖片載入這些),了解怎麼使用,分析源碼
7, Oom和anr異常引發的原因,怎麼解決
8, 了解一些常見的圖片緩存技術
Ø 計算機網路
推薦書籍:計算機網路自頂向下,tcp/ip協議詳解卷一
主要看:主要看應用層,傳輸層,網路層,其中傳輸層最為重要。主要的一些考察內容有:
1, TCP和UDP的區別
2, TCP的三次握手,和四次揮手,為什麼需要三次握手,為什麼要四次揮手(這兩個為什麼要考慮非常清楚)
3, TCP擁塞控制演算法(慢啟動,擁塞避免,快速恢復),流量控制演算法,滑動窗口協議,選擇重傳。
4, TCP可靠性是怎麼保證的(通過哪些機制保證)?
5, HTTP協議報文格式,請求方法欄位以及各個方法之間的區別,了解常見的響應狀態碼,了解cookie機制,了解WEB緩存技術(條件get方法)。
6, DNS協議的各層域名伺服器,兩種查詢方式(遞歸和迭代),dns緩存
7, Udp的特點,適用場景,校驗機制
8, 從輸入一個網址到最後網頁顯示整個過程發生了什麼?
9, IP地址組成,ARR,RARP,ICMP協議,路由選擇演算法

⑧ 安卓中非同步線程中可以包含非同步線程嗎

子線程沒有控制並發數量,當並發過多的時候非同步方法的作用就體現出來了。

非同步是相對於同步而言的,顧名思義,同步就是各個通訊節點之間有統一的時鍾,按照相同的時鍾工作,非同步相反,各節點之間沒有統一的時鍾,每個節點按照自己內部的時鍾工作。
android在所有Thread當中,有一個Thread,我們稱之為UI Thread。UI
Thread在Android程序運行的時候就被創建,是一個Process當中的主線程Main
Thread,主要是負責控制UI界面的顯示、更新和控制項交互。在Android程序創建之初,一個Process呈現的是單線程模型,所有的任務都在一個線程中運行。因此,我們認為,UI
Thread所執行的每一個函數,所花費的時間都應該是越短越好。而其他比較費時的工作(訪問網路,下載數據,查詢資料庫等),都應該交由子線程去執行,以免阻塞主線程。

熱點內容
android代碼搜索 發布:2025-02-12 15:45:36 瀏覽:778
矢量圖演算法 發布:2025-02-12 15:43:53 瀏覽:192
python量化投資入門 發布:2025-02-12 15:34:17 瀏覽:174
蘋果的天氣跟安卓的天氣哪個准 發布:2025-02-12 15:33:37 瀏覽:313
西安分布式存儲咨詢 發布:2025-02-12 15:33:24 瀏覽:179
我的世界伺服器怎麼獲得32k亂碼棒 發布:2025-02-12 15:25:15 瀏覽:545
hadoopftp 發布:2025-02-12 15:22:23 瀏覽:753
ftp怎麼增加 發布:2025-02-12 15:21:08 瀏覽:379
改裝車載中控什麼配置 發布:2025-02-12 15:10:00 瀏覽:861
資料庫體系結構 發布:2025-02-12 15:09:48 瀏覽:692