在一個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 Thread所執行的每個函數,所花費的時間都應該是越短越好。而其餘比較費時的工做(訪問網絡,下載數據,查詢數據庫等),都應該交由子線程去執行,以避免阻塞主線程。在android的設計思想中,爲了確保用戶順滑的操做體驗。一些耗時的任務不可以在UI線程中運行,像訪問網絡就屬於這類任務。所以咱們必需要從新開啓一個後臺線程運行這些任務。然而,每每這些任務最終又會直接或者間接的須要訪問和控制UI控件。例如訪問網絡獲取數據,而後須要將這些數據處理顯示出來。就出現了上面所說的狀況。本來這是在正常不過的現象了,可是android規定除了UI線程外,其餘線程都不能夠對那些UI控件訪問和操控。爲了解決這個問題,下面將探討這UI線程於子線程之間的五種交互方式。android
handler數據庫
Activity.runOnUIThread(Runnable)數組
View.Post(Runnable)安全
View.PostDelayed(Runnabe,long)網絡
AsyncTask多線程
解釋:當應用程序啓動時,Android首先會開啓一個主線程 (也就是UI線程) , 主線程爲管理界面中的UI控件, 進行事件分發, 好比說, 你要是點擊一個 Button ,Android會分發事件到Button上,來響應你的操做。 若是此時須要一個耗時的操做,例如: 聯網讀取數據, 或者讀取本地較大的一個文件的時候,你不能把這些操做放在主線程中,若是你放在主線程中的話,界面會出現假死現象, 若是5秒鐘尚未完成的話,會收到Android系統的一個錯誤提示 "強制關閉"。 這個時候咱們須要把這些耗時的操做,放在一個子線程中,由於子線程涉及到UI更新,,Android主線程是線程不安全的, 也就是說,更新UI只能在主線程中更新,子線程中操做是危險的。 這個時候,Handler就出現了。,來解決這個複雜的問題 ,因爲Handler運行在主線程中(UI線程中), 它與子線程能夠經過Message對象來傳遞數據, 這個時候,Handler就承擔着接受子線程傳過來的(子線程用sedMessage()方法傳弟)Message對象,(裏面包含數據) , 把這些消息放入主線程隊列中,配合主線程進行更新UIapp
子類須要繼承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 } } }
編寫後臺線程,這回你能夠直接調用UI控件ide
建立後臺線程的實例
調用UI線程對應的Activity的
runOnUIThread
方法,將後臺線程實例做爲參數傳入其中。注意:無需調用後臺線程的start方法
該方法和方法二基本相同,只是在後臺線程中能操控的UI控件被限制了,只能是指定的UI控件View。方法以下
編寫後臺線程,這回你能夠直接調用UI控件,可是該UI控件只能是View
建立後臺線程的實例
調用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結束之後才執行。
該方法是方法三的補充,long參數用於制定多少時間後運行後臺進程
這是一種能夠建立多線程消息的函數
使用方法:
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對象的話會比較容易操做
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是一個專門用來處理後臺進程與UI線程的工具。經過AsyncTask,咱們能夠很是方便的進行後臺線程和UI線程之間的交流。
那麼AsyncTask是如何工做的哪。
AsyncTask擁有3個重要參數
Params
rogress
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個方法,你要作的就是
編寫一個繼承AsyncTask的類,並聲明3個參數的類型,編寫4個回調方法的內容。
而後在UI線程中建立該類(必須在UI線程中建立)。
最後調用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