深刻探討安卓UI線程與子線程交互5大設計

什麼是UI線程

在一個Android 程序開始運行的時候,會單獨啓動一個Process。默認的狀況下,全部這個程序中的Activity或者Service(Service和 Activity只是Android提供的Components中的兩種,除此以外還有Content Provider和Broadcast Receiver)都會跑在這個Process
一個Android 程序默認狀況下也只有一個Process,但一個Process下卻能夠有許多個Thread。在這麼多Thread當中,有一個Thread,咱們稱之爲UI Thread。UI Thread在Android程序運行的時候就被建立,是一個Process當中的主線程Main Thread,主要是負責控制UI界面的顯示、更新和控件交互。在Android程序建立之初,一個Process呈現的是單線程模型,全部的任務都在一個線程中運行。java

UI線程和子線程分開的意義

UI Thread所執行的每個函數,所花費的時間都應該是越短越好。而其餘比較費時的工做(訪問網絡,下載數據,查詢數據庫等),都應該交由子線程去執行,以避免阻塞主線程。在android的設計思想中,爲了確保用戶順滑的操做體驗。一些耗時的任務不可以在UI線程中運行,像訪問網絡就屬於這類任務。所以咱們必需要從新開啓一個後臺線程運行這些任務。然而,每每這些任務最終又會直接或者間接的須要訪問和控制UI控件。例如訪問網絡獲取數據,而後須要將這些數據處理顯示出來。就出現了上面所說的狀況。本來這是在正常不過的現象了,可是android規定除了UI線程外,其餘線程都不能夠對那些UI控件訪問和操控。爲了解決這個問題,下面將探討這UI線程於子線程之間的五種交互方式。android

UI線程於子線程的交互方式

  1. handler數據庫

  2. Activity.runOnUIThread(Runnable)數組

  3. View.Post(Runnable)安全

  4. View.PostDelayed(Runnabe,long)網絡

  5. AsyncTask多線程

handler使用

解釋:當應用程序啓動時,Android首先會開啓一個主線程 (也就是UI線程) , 主線程爲管理界面中的UI控件, 進行事件分發, 好比說, 你要是點擊一個 Button ,Android會分發事件到Button上,來響應你的操做。 若是此時須要一個耗時的操做,例如: 聯網讀取數據, 或者讀取本地較大的一個文件的時候,你不能把這些操做放在主線程中,若是你放在主線程中的話,界面會出現假死現象, 若是5秒鐘尚未完成的話,會收到Android系統的一個錯誤提示 "強制關閉"。 這個時候咱們須要把這些耗時的操做,放在一個子線程中,由於子線程涉及到UI更新,,Android主線程是線程不安全的, 也就是說,更新UI只能在主線程中更新,子線程中操做是危險的。 這個時候,Handler就出現了。,來解決這個複雜的問題 ,因爲Handler運行在主線程中(UI線程中), 它與子線程能夠經過Message對象來傳遞數據, 這個時候,Handler就承擔着接受子線程傳過來的(子線程用sedMessage()方法傳弟)Message對象,(裏面包含數據) , 把這些消息放入主線程隊列中,配合主線程進行更新UIapp

Handler實例

子類須要繼承Hendler類,並重寫handleMessage(Message msg) 方法, 用於接受線程數據。
如下爲一個實例,它實現的功能爲:經過線程修改界面Button的內容異步

public class MyHandlerActivity extends Activity { 
    Button button; 
    MyHandler myHandler; 
 
    protected void onCreate(Bundle savedInstanceState) { 
        super。onCreate(savedInstanceState); 
        setContentView(R.layout.handlertest); 
 
        button = (Button) findViewById(R.id.button); 
        myHandler = new MyHandler(); 
        // 當建立一個新的Handler實例時, 它會綁定到當前線程和消息的隊列中,開始分發數據 
        // Handler有兩個做用, (1) : 定時執行Message和Runnalbe 對象 
        // (2): 讓一個動做,在不一樣的線程中執行。 
        // 它安排消息,用如下方法 
        // post(Runnable) 
        // postAtTime(Runnable, long) 
        // postDelayed(Runnable, long) 
        // sendEmptyMessage(int) 
        // sendMessage(Message); 
        // sendMessageAtTime(Message, long) 
        // sendMessageDelayed(Message, long) 
        // 以上方法以 post開頭的容許你處理Runnable對象 
        //sendMessage()容許你處理Message對象(Message裏能夠包含數據) 
        MyThread m = new MyThread(); 
        new Thread(m).start(); 
    } 
 
    /** 
    * 接受消息,處理消息 ,此Handler會與當前主線程一塊運行 
    * */ 
 
    class MyHandler extends Handler { 
        public MyHandler() { 
        } 
 
        public MyHandler(Looper L) { 
            super(L); 
        } 
 
        // 子類必須重寫此方法,接受數據 
        @Override 
        public void handleMessage(Message msg) { 
            // TODO Auto-generated method stub 
            Log.d("MyHandler", "handleMessage。。。。。。"); 
            super.handleMessage(msg); 
            // 此處能夠更新UI 
            Bundle b = msg.getData(); 
            String color = b.getString("color"); 
            MyHandlerActivity.this.button.append(color); 
 
        } 
    } 
 
    class MyThread implements Runnable { 
        public void run() { 
 
            try { 
                Thread.sleep(10000); 
            } catch (InterruptedException e) { 
                // TODO Auto-generated catch block 
                e.printStackTrace(); 
            } 
 
            Log.d("thread......", "mThread......"); 
            Message msg = new Message(); 
            Bundle b = new Bundle();// 存放數據 
            b.putString("color","個人"); 
            msg.setData(b); 
            MyHandlerActivity.this.myHandler.sendMessage(msg); 
            // 向Handler發送消息,更新UI 
        } 
    } 
}

Activity.runOnUIThread(Runnable)

  1. 編寫後臺線程,這回你能夠直接調用UI控件ide

  2. 建立後臺線程的實例

  3. 調用UI線程對應的Activity的runOnUIThread方法,將後臺線程實例做爲參數傳入其中。

注意:無需調用後臺線程的start方法

View.Post(Runnable)

該方法和方法二基本相同,只是在後臺線程中能操控的UI控件被限制了,只能是指定的UI控件View。方法以下

  1. 編寫後臺線程,這回你能夠直接調用UI控件,可是該UI控件只能是View

  2. 建立後臺線程的實例

  3. 調用UI控件View的post方法,將後臺線程實例做爲參數傳入其中。

如下是官方文檔對該方法的註釋及源碼。(postDelayed相似,再也不贅述)

Causes the Runnable to be added to the message queue.The runnable >will be run on the user interface thread.

public boolean post(Runnable action) {    
  final AttachInfo attachInfo = mAttachInfo;
  if (attachInfo != null) {       
      return attachInfo.mHandler.post(action);
  }    
  // Assume that post will succeed later       
  ViewRootImpl.getRunQueue().post(action); 
  return true;
}

意思是將任務添加到消息隊列中,保證在UI線程執行。從本質上說,它仍是依賴於以Handler、Looper、MessageQueue、Message爲基礎的異步消息處理機制。相對於新建Handler進行處理更加便捷。下面舉一個經常使用的例子,好比在onCreate方法中獲取某個view的寬高,而直接View#getWidth獲取到的值是0。要知道View顯示到界面上須要經歷onMeasure、onLayout和onDraw三個過程,而View的寬高是在onLayout階段才能最終肯定的,而在Activity#onCreate中並不能保證View已經執行到了onLayout方法,也就是說Activity的聲明週期與View的繪製流程並非一一綁定。那爲何調用post方法就能起做用呢?首先MessageQueue是按順序處理消息的,而在setContentView()後隊列中會包含一條詢問是否完成佈局的消息,而咱們的任務經過View#post方法被添加到隊列尾部,保證了在layout結束之後才執行。

View.PostDelayed(Runnabe,long)

該方法是方法三的補充,long參數用於制定多少時間後運行後臺進程
這是一種能夠建立多線程消息的函數
使用方法:

試例1:鬧鐘提醒延時

1,首先建立一個Handler對象
Handler handler=new Handler();
2,而後建立一個Runnable對象

Runnable runnable=new Runnable(){  
   @Override  
   public void run() {  
    // TODO Auto-generated method stub  
    //要作的事情,這裏再次調用此Runnable對象,以實現每兩秒實現一次的定時器操做  
    handler.postDelayed(this, 2000);  
   }   
};

3,使用PostDelayed方法,兩秒後調用此Runnable對象
handler.postDelayed(runnable, 2000);
實際上也就實現了一個2s的一個定時器
4,若是想要關閉此定時器,能夠這樣操做
handler.removeCallbacks(runnable);
固然,你也能夠作一個鬧鐘提醒延時的函數試試,好比,先用MediaPlayer播放鬧鐘聲音, 若是不想起,被中止播放以後,下次就5分鐘後再播放,再被中止的話,下次就4分鐘後播放, ……………… 只要更改延時的時間就能夠實現了,用一個static對象的話會比較容易操做

試例2:歡迎頁製做

public class SplanshActivity extends AppCompatActivity {
    private Handler handler;
    private Boolean isFirst = true;
    private SharedPreferences sp ;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_splansh);
        sp = getPreferences(MODE_PRIVATE);
        handler = new Handler();
        handler.postDelayed(new Runnable() {
            @Override
            public void run() {
                isFirst = sp.getBoolean("isFirst",true);
                Intent intent = new Intent();//意圖
                if(isFirst){
                    sp.edit().putBoolean("isFirst",false).commit();
                    //引導界面
                    intent.setClass(SplanshActivity.this,GuideActivity.class);
                }else{
                    //主界面
                    intent.setClass(SplanshActivity.this,MainActivity.class);
                }
                startActivity(intent);
                finish();
 
            }
        },3000);
    }
}

AsyncTask

AsyncTask是一個專門用來處理後臺進程與UI線程的工具。經過AsyncTask,咱們能夠很是方便的進行後臺線程和UI線程之間的交流。
那麼AsyncTask是如何工做的哪。
AsyncTask擁有3個重要參數

  1. Params

  2. rogress

  3. Result

Params是後臺線程所需的參數。在後臺線程進行做業的時候,他須要外界爲其提供必要的參數,就好像是一個用於下載圖片的後臺進程,他須要的參數就是圖片的下載地址。
Progress是後臺線程處理做業的進度。依舊上面的例子說,就是下載圖片這個任務完成了多少,是20%仍是60%。這個數字是由Progress提供。
Result是後臺線程運行的結果,也就是須要提交給UI線程的信息。按照上面的例子來講,就是下載完成的圖片。
AsyncTask還擁有4個重要的回調方法。
一、onPreExecute
二、doInBackground
三、onProgressUpdate
四、onPostExecute
onPreExecute運行在UI線程,主要目的是爲後臺線程的運行作準備。當他運行完成後,他會調用doInBackground方法。
doInBackground運行在後臺線程,他用來負責運行任務。他擁有參數Params,而且返回Result。在後臺線程的運行當中,爲了可以更新做業完成的進度,須要在doInbackground方法中調用PublishProgress方法。該方法擁有參數Progress。經過該方法能夠更新Progress的數據。而後當調用完PublishProgress方法,他會調用onProgressUpdate方法用於更新進度。
onProgressUpdate運行在UI線程,主要目的是用來更新UI線程中顯示進度的UI控件。他擁有Progress參數。在doInBackground中調用PublishProgress以後,就會自動調onProgressUpdate方法
onPostExecute運行在UI線程,當doInBackground方法運行完後,他會調用onPostExecute方法,並傳入Result。在onPostExecute方法中,就能夠將Result更新到UI控件上。
明白了上面的3個參數和4個方法,你要作的就是

  1. 編寫一個繼承AsyncTask的類,並聲明3個參數的類型,編寫4個回調方法的內容。

  2. 而後在UI線程中建立該類(必須在UI線程中建立)。

  3. 最後調用AsyncTask的execute方法,傳入Parmas參數(一樣必須在UI線程中調用)。

這樣就大功告成了。
另外值得注意的2點就是,千萬不要直接調用那四個回調方法。還有就是一個AsyncTask實例只能執行一次,不然就出錯哦

private class DownloadFilesTask extends AsyncTask<URL, Integer, Long> {
    //在這裏聲明瞭Params、Progress、Result參數的類型
    //由於這裏不須要使用onPreExecute回調方法,因此就沒有加入該方法
    //後臺線程的目的是更具URL下載數據
      protected Long doInBackground(URL... urls) {
          int count = urls.length;//urls是數組,不止一個下載連接
          long totalSize = 0;//下載的數據
          for (int i = 0; i < count; i++) {
          //Download是用於下載的一個類,和AsyncTask無關,你們能夠忽略他的實現
          totalSize += Downloader.downloadFile(urls[i]);
          publishProgress((int) ((i / (float) count) * 100));//更新下載的進度
              // Escape early if cancel() is called
              if (isCancelled()) break;
          }
         return totalSize;
    }
    //更新下載進度
    protected void onProgressUpdate(Integer... progress) {
         setProgressPercent(progress[0]);
    }
    //將下載的數據更新到UI線程
    protected void onPostExecute(Long result) {
         showDialog("Downloaded " + result + " bytes");
    }
}

有了上面的這個類,接下你要作的就是在UI線程中建立實例,並調用execute方法,傳入URl參數就能夠了。這上面的5種方法各有優勢。可是究其根本,其實後面四種方法都是基於handler方法的包裝。在通常的情形下後面四種彷佛更值得推薦。可是當情形比較複雜,仍是推薦使用handler

相關文章
相關標籤/搜索