Android的UI操作不是線程安全的(出于提高性能考慮,避免實現多線程同步等機制所引入的延時),若多個線程同時對UI元素進行操作,可能導致線程安全問題。因此,Android中做了嚴格的規定:只有UI主線程才能對UI進行設置與操作。
在實際編程中,為了避免UI界面長時間得不到響應而導致的ANR(Application Not Responding)異常,通常將網絡訪問、復雜運算等一些耗時的操作被放在子線程中執行。這就需要子線程在運行完畢后將結果返回到主線程并通過UI進行顯示。在Android中,是通過Handler+Loop+MessageQueue實現線程間通信的。
先看兩個實例:
實例1:模擬通過網絡下載數據并返回UI顯示。
操作過程為:1.UI線程獲得用戶請求。2.啟動子線程完成網絡數據下載(網絡下載過程通過強制子線程休眠若干秒來模擬)。3.子線程將下載的數據返回UI線程并顯示。
主要代碼如下:
public class MainActivity extends ActionBarActivity { private Button mButton; private TextView mTextView; private Handler mHandler; private Thread mNetAccessThread; private ProgressDialog mProgressDialog; private int mDownloadCount = 0; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.fragment_main); mButton = (Button) findViewById(R.id.btReqNet); mTextView = (TextView) findViewById(R.id.tvDownload); //設置按鈕的點擊事件監聽器 mButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { showProgressDialog("","正在下載..."); //啟動子線程進行網絡訪問模擬 mNetAccessThread = new ChildTread(); mNetAccessThread.start(); } }); //繼承Handler類并覆蓋其handleMessage方法 mHandler = new Handler(){ //覆蓋Handler類的handleMessage方法 //接收子線程傳遞的數據并在UI顯示 @Override public void handleMessage(Message msg) { switch (msg.what) { case 1: mTextView.setText((String) msg.obj); dismissProgressDialog(); break; //可以添加其他情況,如網絡傳輸錯誤 //case... default: break; } } }; } class ChildTread extends Thread { @Override public void run() { //休眠6秒,模擬網絡訪問延遲 try { Thread.sleep(6000); } catch (InterruptedException e) { e.printStackTrace(); } //將結果通過消息返回主線程 Message msg = new Message(); msg.what = 1; mDownloadCount ++; msg.obj = new String("第"+mDownloadCount+"次從網上下載的數據"); mHandler.sendMessage(msg); } }; /** * 開啟progressDialog. * * @param title 對話框標題. * @param content 對話框正文. */ protected void showProgressDialog(String title,String content) { mProgressDialog = new ProgressDialog(this); if(title != null) mProgressDialog.setTitle(title); if(content != null) mProgressDialog.setMessage(content); mProgressDialog.show(); } /** * 關閉progressDialog. * */ protected void dismissProgressDialog() { if(mProgressDialog != null) { mProgressDialog.dismiss(); } } }
程序運行效果:
點擊下載按鈕,UI線程通過handler.sendMessage()向子線程發送消息,子線程收到消息后啟動數據下載(通過休眠線程模擬)。
下載完畢,子線程將下載數據返回主線程并顯示。
實例2:模擬子線程向主線程發送消息。
操作過程為:1.UI線程獲得用戶輸入的消息內容。2.通過Handler將消息發送給子線程。3.子線程獲得消息并通過Toast將內容打印。
主要代碼如下:
public class MainActivity extends ActionBarActivity { private EditText mEditText; private Button mButton; private Handler mHandler; private Thread mChildTread; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.fragment_main); mEditText = (EditText) findViewById(R.id.etEditText); mButton = (Button) findViewById(R.id.btButton); // 設置按鈕的點擊事件監聽器 mButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { Message msg = new Message(); msg.what = 1; msg.obj = mEditText.getText(); //將消息發送到子線程 mHandler.sendMessage(msg); mEditText.setText(""); } }); //啟動子線程進行msg接收 mChildTread = new ChildTread(); mChildTread.start(); } /** * 子線程為內部類,可以直接訪問其外部類的mHandler變量 * */ class ChildTread extends Thread { @Override public void run() { //以下三步是handler looper機制工作的固定模式 Looper.prepare(); mHandler = new Handler() { public void handleMessage(Message msg) { // process incoming messages here switch (msg.what) { case 1: //子線程無權操作UI,只能通過Toast.makeText將收到的消息顯示 String st = msg.obj.toString(); if (st == null || st.equals("")) st = "收到的消息內容為空"; else st = "收到來自主線程的消息:" + st; Toast.makeText(MainActivity.this, st, 6000).show(); break; //可以添加其他情況,如傳輸錯誤 //case... default: break; } } }; Looper.loop(); } }; } 程序運行效果:
小結:Android通過Handler+Looper+MessageQueue機制實現線程間的通信,本文通過兩個簡單的實例分別基于該機制實現了UI線程到子線程和子線程到UI線程的消息傳遞。下一篇博文將會對Handler Looper機制的原理進行深入研究。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。