編輯:Android開發實例
在了解Android線程之前得先了解一下Android的進程。當一個程序第一次啟動的時候,Android會啟動一個LINUX進程和一個主線程。默認的情況下,所有該程序的組件都將在該進程和線程中運行。同時,Android會為每個應用程序分配一個單獨的LINUX用戶。Android會盡量保留一個正在運行進程,只在內存資源出現不足時,Android會嘗試停止一些進程從而釋放足夠的資源給其他新的進程使用, 也能保證用戶正在訪問的當前進程有足夠的資源去及時地響應用戶的事件。Android會根據進程中運行的組件類別以及組件的狀態來判斷該進程的重要性,Android會首先停止那些不重要的進程。按照重要性從高到低一共有五個級別:
Android 對進程的重要性評級的時候,選取它最高的級別。另外,當被另外的一個進程依賴的時候,某個進程的級別可能會增高。一個為其他進程服務的進程永遠不會比被服務的進程重要級低。因為服務進程比後台activity進程重要級高,因此一個要進行耗時工作的activity最好啟動一個service來做這個工作,而不是開啟一個子進程――特別是這個操作需要的時間比activity存在的時間還要長的時候。例如,在後台播放音樂,向網上上傳攝像頭拍到的圖片,使用service可以使進程最少獲取到“服務進程”級別的重要級,而不用考慮activity目前是什麼狀態。broadcast receivers做費時的工作的時候,也應該啟用一個服務而不是開一個線程。
當一個程序第一次啟動時,Android會同時啟動一個對應的主線程(Main Thread),主線程主要負責處理與UI相關的事件,如用戶的按鍵事件,用戶接觸屏幕的事件以及屏幕繪圖事件,並把相關的事件分發到對應的組件進行處理。所以主線程通常又被叫做UI線程。在開發Android應用時必須遵守單線程模型的原則: Android UI操作並不是線程安全的並且這些操作必須在UI線程中執行。
Android的UI是單線程(Single-threaded)的。為了避免拖住GUI,一些較費時的對象應該交給獨立的線程去執行。如果幕後的線程來執行UI對象,Android就會發出錯誤訊息 CalledFromWrongThreadException。以後遇到這樣的異常拋出時就要知道怎麼回事了!
在單線程模型下,為了解決類似的問題,Android設計了一個Message Queue(消息隊列), 線程間可以通過該Message Queue並結合Handler和Looper組件進行信息交換。下面將對它們進行分別介紹:
1. Message Message消息,理解為線程間交流的信息,處理數據後台線程需要更新UI,則發送Message內含一些數據給UI線程。 2. Handler Handler處理者,是Message的主要處理者,負責Message的發送,Message內容的執行處理。後台線程就是通過傳進來的Handler對象引用來sendMessage(Message)。而使用Handler,需要implement 該類的 handleMessage(Message)方法,它是處理這些Message的操作內容,例如Update UI。通常需要子類化Handler來實現handleMessage方法。 3. Message Queue Message Queue消息隊列,用來存放通過Handler發布的消息,按照先進先出執行。 每個message queue都會有一個對應的Handler。Handler會向message queue通過兩種方法發送消息:sendMessage或post。這兩種消息都會插在message queue隊尾並按先進先出執行。但通過這兩種方法發送的消息執行的方式略有不同:通過sendMessage發送的是一個message對象,會被Handler的handleMessage()函數處理;而通過post方法發送的是一個runnable對象,則會自己執行。 4. Looper Looper是每條線程裡的Message Queue的管家。Android沒有Global的Message Queue,而Android會自動替主線程(UI線程)建立Message Queue,但在子線程裡並沒有建立Message Queue。所以調用Looper.getMainLooper()得到的主線程的Looper不為NULL,但調用Looper.myLooper()得到當前線程的Looper就有可能為NULL。對於子線程使用Looper,API Doc提供了正確的使用方法:
- class LooperThread extends Thread {
- public Handler mHandler;
- public void run() {
- Looper.prepare(); //創建本線程的Looper並創建一個MessageQueue
- mHandler = new Handler() {
- public void handleMessage(Message msg) {
- // process incoming messages here
- }
- };
- Looper.loop(); //開始運行Looper,監聽Message Queue
- }
- }
這個Message機制的大概流程:
1. 在Looper.loop()方法運行開始後,循環地按照接收順序取出Message Queue裡面的非NULL的Message。
2. 一開始Message Queue裡面的Message都是NULL的。當Handler.sendMessage(Message)到Message Queue,該函數裡面設置了那個Message對象的target屬性是當前的Handler對象。隨後Looper取出了那個Message,則調用該Message的target指向的Hander的dispatchMessage函數對Message進行處理。
在dispatchMessage方法裡,如何處理Message則由用戶指定,三個判斷,優先級從高到低:
1) Message裡面的Callback,一個實現了Runnable接口的對象,其中run函數做處理工作;
2) Handler裡面的mCallback指向的一個實現了Callback接口的對象,由其handleMessage進行處理;
3) 處理消息Handler對象對應的類繼承並實現了其中handleMessage函數,通過這個實現的handleMessage函數處理消息。
由此可見,我們實現的handleMessage方法是優先級最低的!
3. Handler處理完該Message (update UI) 後,Looper則設置該Message為NULL,以便回收!
在網上有很多文章講述主線程和其他子線程如何交互,傳送信息,最終誰來執行處理信息之類的,個人理解是最簡單的方法——判斷Handler對象裡面的Looper對象是屬於哪條線程的,則由該線程來執行! 1. 當Handler對象的構造函數的參數為空,則為當前所在線程的Looper; 2. Looper.getMainLooper()得到的是主線程的Looper對象,Looper.myLooper()得到的是當前線程的Looper對象。現在來看一個例子,模擬從網絡獲取數據,加載到ListView的過程:
- public class ListProgressDemo extends ListActivity {
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.listprogress);
- ((Button) findViewById(R.id.load_Handler)).setOnClickListener(new View.OnClickListener(){
- @Override
- public void onClick(View view) {
- data = null;
- data = new ArrayList<String>();
- adapter = null;
- showDialog(PROGRESS_DIALOG);
- new ProgressThread(handler, data).start();
- }
- });
- }
- @Override
- protected Dialog onCreateDialog(int id) {
- switch(id) {
- case PROGRESS_DIALOG:
- return ProgressDialog.show(this, "",
- "Loading. Please wait...", true);
- default: return null;
- }
- }
- private class ProgressThread extends Thread {
- private Handler handler;
- private ArrayList<String> data;
- public ProgressThread(Handler handler, ArrayList<String> data) {
- this.handler = handler;
- this.data = data;
- }
- @Override
- public void run() {
- for (int i=0; i<8; i++) {
- data.add("ListItem"); //後台數據處理
- try {
- Thread.sleep(100);
- }catch(InterruptedException e) {
- Message msg = handler.obtainMessage();
- Bundle b = new Bundle();
- b.putInt("state", STATE_ERROR);
- msg.setData(b);
- handler.sendMessage(msg);
- }
- }
- Message msg = handler.obtainMessage();
- Bundle b = new Bundle();
- b.putInt("state", STATE_FINISH);
- msg.setData(b);
- handler.sendMessage(msg);
- }
- }
- // 此處甚至可以不需要設置Looper,因為Handler默認就使用當前線程的Looper
- private final Handler handler = new Handler(Looper.getMainLooper()) {
- public void handleMessage(Message msg) { // 處理Message,更新ListView
- int state = msg.getData().getInt("state");
- switch(state){
- case STATE_FINISH:
- dismissDialog(PROGRESS_DIALOG);
- Toast.makeText(getApplicationContext(),
- "加載完成!",
- Toast.LENGTH_LONG)
- .show();
- adapter = new ArrayAdapter<String>(getApplicationContext(),
- android.R.layout.simple_list_item_1,
- data );
- setListAdapter(adapter);
- break;
- case STATE_ERROR:
- dismissDialog(PROGRESS_DIALOG);
- Toast.makeText(getApplicationContext(),
- "處理過程發生錯誤!",
- Toast.LENGTH_LONG)
- .show();
- adapter = new ArrayAdapter<String>(getApplicationContext(),
- android.R.layout.simple_list_item_1,
- data );
- setListAdapter(adapter);
- break;
- default:
- }
- }
- };
- private ArrayAdapter<String> adapter;
- private ArrayList<String> data;
- private static final int PROGRESS_DIALOG = 1;
- private static final int STATE_FINISH = 1;
- private static final int STATE_ERROR = -1;
- }
這個例子,我自己寫完後覺得還是有點亂,要稍微整理才能看明白線程間交互的過程以及數據的前後變化。隨後了解到AsyncTask類,相應修改後就很容易明白了!
- ((Button) findViewById(R.id.load_AsyncTask)).setOnClickListener(new View.OnClickListener(){
- @Override
- public void onClick(View view) {
- data = null;
- data = new ArrayList<String>();
- adapter = null;
- //顯示ProgressDialog放到AsyncTask.onPreExecute()裡
- //showDialog(PROGRESS_DIALOG);
- new ProgressTask().execute(data);
- }
- });
- private class ProgressTask extends AsyncTask<ArrayList<String>, Void, Integer> {
- /* 該方法將在執行實際的後台操作前被UI thread調用。可以在該方法中做一些准備工作,如在界面上顯示一個進度條。*/
- @Override
- protected void onPreExecute() {
- // 先顯示ProgressDialog
- showDialog(PROGRESS_DIALOG);
- }
- /* 執行那些很耗時的後台計算工作。可以調用publishProgress方法來更新實時的任務進度。 */
- @Override
- protected Integer doInBackground(ArrayList<String>... datas) {
- ArrayList<String> data = datas[0];
- for (int i=0; i<8; i++) {
- data.add("ListItem");
- }
- return STATE_FINISH;
- }
- /* 在doInBackground 執行完成後,onPostExecute 方法將被UI thread調用,
- * 後台的計算結果將通過該方法傳遞到UI thread.
- */
- @Override
- protected void onPostExecute(Integer result) {
- int state = result.intValue();
- switch(state){
- case STATE_FINISH:
- dismissDialog(PROGRESS_DIALOG);
- Toast.makeText(getApplicationContext(),
- "加載完成!",
- Toast.LENGTH_LONG)
- .show();
- adapter = new ArrayAdapter<String>(getApplicationContext(),
- android.R.layout.simple_list_item_1,
- data );
- setListAdapter(adapter);
- break;
- case STATE_ERROR:
- dismissDialog(PROGRESS_DIALOG);
- Toast.makeText(getApplicationContext(),
- "處理過程發生錯誤!",
- Toast.LENGTH_LONG)
- .show();
- adapter = new ArrayAdapter<String>(getApplicationContext(),
- android.R.layout.simple_list_item_1,
- data );
- setListAdapter(adapter);
- break;
- default:
- }
- }
Android另外提供了一個工具類:AsyncTask。它使得UI thread的使用變得異常簡單。它使創建需要與用戶界面交互的長時間運行的任務變得更簡單,不需要借助線程和Handler即可實現。
1) 子類化AsyncTask 2) 實現AsyncTask中定義的下面一個或幾個方法 onPreExecute() 開始執行前的准備工作; doInBackground(Params...) 開始執行後台處理,可以調用publishProgress方法來更新實時的任務進度; onProgressUpdate(Progress...) 在publishProgress方法被調用後,UI thread將調用這個方法從而在界面上展示任務的進展情況,例如通過一個進度條進行展示。 onPostExecute(Result) 執行完成後的操作,傳送結果給UI 線程。 這4個方法都不能手動調用。而且除了doInBackground(Params...)方法,其余3個方法都是被UI線程所調用的,所以要求: 1) AsyncTask的實例必須在UI thread中創建; 2) AsyncTask.execute方法必須在UI thread中調用; 同時要注意:該task只能被執行一次,否則多次調用時將會出現異常。而且是不能手動停止的,這一點要注意,看是否符合你的需求! 在使用過程中,發現AsyncTask的構造函數的參數設置需要看明白:AsyncTask<Params, Progress, Result> Params對應doInBackground(Params...)的參數類型。而new AsyncTask().execute(Params... params),就是傳進來的Params數據,你可以execute(data)來傳送一個數據,或者execute(data1, data2, data3)這樣多個數據。 Progress對應onProgressUpdate(Progress...)的參數類型; Result對應onPostExecute(Result)的參數類型。 當以上的參數類型都不需要指明某個時,則使用Void,注意不是void。不明白的可以參考上面的例子,或者API Doc裡面的例子。 ---------------- 本文的相當內容摘錄於《淺析Android線程模型一 --- 轉》,但對於Message機制的流程理解則在參考《android中Message機制的靈活應用》後修改了!JSON代表JavaScript對象符號。它是一個獨立的數據交換格式,是XML的最佳替代品。本章介紹了如何解析JSON文件,並從中提取所需的信息。Android提供了四個
Android應用程序可以在許多不同地區的許多設備上運行。為了使應用程序更具交互性,應用程序應該處理以適合應用程序將要使用的語言環境方面的文字,數字,文件等。在本章中,我
Android提供了許多方法來控制播放的音頻/視頻文件和流。其中該方法是通過一類稱為MediaPlayer。Android是提供MediaPlayer類訪問內置的媒體播放
JSON代表JavaScript對象符號。它是一個獨立的數據交換格式,是XML的最佳替代品。本章介紹了如何解析JSON文件,並從中提取所需的信息。Android提供了四個