Service這部分,也算是Android中非常重要的知識了,尤其是做複雜應用的時候。至於非同步訊息和多執行緒,在功能複雜的同時用的也是越來越多,加上這幾天在寫Windows專案的部分裡面也涉及到了非同步套接字等等的非同步知識,發現,程式間的通訊等等也需要重新看看。知識可能就是這樣把,也許你當初把書摔在地上說沒用的東西,可能這個時候你就會後悔了。
Handler的非同步訊息處理機制
android開發當中,經常會有一些耗時操作,他們是不能放在主執行緒當中的。相信學過作業系統的人都知道『阻塞』是什麼意思,如果主執行緒中放了大量的耗時操作,那麼整個程式的使用者體驗勢必會變得很差。所以,我們很自然而然就想到了——多執行緒.
淺談多執行緒
多執行緒網上有很多資料解釋,在此不做贅述,只是簡單的把多執行緒實現的幾種方式說一下.
1.繼承Thread類
我們可以自己定義一個類,然後繼承Thread這個基類。重寫類中的run方法。run方法中,放入我們想要在子執行緒所做的耗時操作。在類的外部,可以建立一個我們自定義的類的物件,之後呼叫start成員方法,就可以自動執行run方法內的程式碼.
1 2 3 4 5 6 7 8 9 10 |
class myThread extends Thread{ public void run(){ ... } } Thread thread = new Thread(); thread.start(); |
2.重新實現runnable介面
1 2 3 4 5 6 7 8 9 10 |
class myThread implements Runnable{ public void run(){ ... } } Thread thread = new Thread(new myThread()); thread.start(); |
3.匿名類
這種方法相對來說,比較方便,簡單,還是比較好用的.
1 2 3 4 5 6 7 |
new Thread(new Runnable{ public void run(){ ... } }).start(); |
多執行緒不是萬能的
我們可能覺得,有了多執行緒之後,很多耗時操作就都能夠解決了,但是,假如我們現在一個activity中有一個button,一個textview,我們想通過button的點選,執行一系列的耗時操作之後,更新textview控制元件上面的內容。由於有了耗時操作的出現,我們很自然的想到多執行緒的例子,但是請注意,子執行緒裡面是不允許對UI控制元件進行操作的。不信的話,你可以自己寫一個子執行緒,然後在裡面呼叫textview的settext方法試試,是會報錯的。所以,這就用到了我們要說的非同步訊息處理機制。
在作業系統中,非同步就意味著我們不用阻塞在那些耗時的操作上,只需要發出一個耗時操作的請求就可以做接下來的事情,這樣,就不會發生程式的阻塞。等到耗時操作完成之後會用特定的方式來通知我們,已經完成。這一點在windows的非同步套接字程式設計上也體現的很好。那麼在android中,非同步任務機制還有更奇妙的運用。
非同步訊息處理機制
非同步訊息的處理在android中有以下應用:當我們要執行耗時操作的同時還要對UI控制元件進行操作的時候,因為UI的操作不能放在子執行緒中執行,我們就可以把UI的操作部分傳遞到主執行緒中進行處理,把耗時操作留在子執行緒中進行,這樣既不會阻塞程式,也完成了對UI的操作。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 |
public class HandlerActivity extends Activity { public static final int UPDATE_TEXT = 1; private Button mButton; private TextView mTextView; private Handler handler = new Handler(){ //處理訊息在主執行緒中 @Override public void handleMessage(Message msg) { // TODO Auto-generated method stub switch (msg.what) { case UPDATE_TEXT: mTextView.setText("it is changed"); break; default: break; } } }; @Override protected void onCreate(Bundle savedInstanceState) { // TODO Auto-generated method stub super.onCreate(savedInstanceState); setContentView(R.layout.layout_handler); mButton = (Button)findViewById(R.id.change_text); mTextView = (TextView)findViewById(R.id.handle_textView); mButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { // TODO Auto-generated method stub new Thread(new Runnable() { //子執行緒執行下面的程式碼 @Override public void run() { // TODO Auto-generated method stub Message message = new Message(); message.what = UPDATE_TEXT; handler.sendMessage(message); //子執行緒執行耗時操作,修改UI的操作放在handler的主執行緒中處理 try { Thread.sleep(1000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }).start(); } }); } } |
關於handler的非同步訊息處理機制的使用,上面的程式碼已經給出了一個比較完整的流程。想要理解Handler的非同步處理機制,首先要理解幾個概念.
- handler : 是整個非同步訊息處理機制中的控制著,處理者。它負責傳送和處理訊息
- message : 主要是在不同的執行緒之間傳遞訊息,作為一個訊息的載體,其中的what欄位等等,可以攜帶一定的資料資訊。
- MessageQueue: 訊息佇列,整個程式可能有很多非同步訊息需要處理,但是對於一個執行緒來說(主執行緒),只能有一個handler ,MessageQueue。所以我們需要把還沒有處理的訊息放入訊息佇列當中。
- looper:它相當於MessageQueue中的排程者,通過呼叫loop方法,進行一個無限迴圈,不斷的從訊息佇列中取出訊息,傳送給handler進行處理。 上面的程式碼大家也看到了,我要做的是兩件事,一個是要進行一個迴圈耗時操作,還有一個就是更新textview控制元件上面的內容。耗時操作是必須放在子執行緒中的,但是對UI的操作寫到了一個handlemessage的函式中。 上面提到的關於handler的非同步訊息處理機制的幾個要素當中,MessageQueue和Looper都是系統已經為我們實現好了的。所以我們首先要定義一個Hanlder的例項。並且要重寫父類的handlermessage的方法,用來處理到時候發來的訊息。接下來就是建立一個我們自定義的訊息了.
123Message message = new Message();message.what = UPDATE_TEXT;handler.sendMessage(message);
通過定義一個Message的物件,並且為what屬性設定好我們之前定義的常量UPDATE_TEXT,表明我們將要傳送的訊息附帶的一些資訊。通過handler呼叫sendmessage,我們就會把這個訊息傳送給MessageQueue中,之後,通過Looper的不斷迴圈處理,在訊息佇列當中取出該訊息之後,就會傳送給handlemessage函式,進行處理。因為Handler物件的定義是在主執行緒中間進行的,所以在handlermessage中進行UI控制元件的操作,是符合規定的。該函式接受一個message的引數,由於不同種類的訊息需要採取不同的處理方式,我們利用switch來進行分支,通過每個訊息所攜帶what屬性內容的不同來決定進行何種處理操作.
至此,handler非同步訊息處理機制就介紹完了。在android中的這種非同步機制,我覺得有兩個功能,一個是對耗時操作的支援,還有一個就是子執行緒和主執行緒之前的切換。它可以根據需求,把耗時操作放在子執行緒中,UI操作放在主執行緒中。
更簡單的AsyncTask
handler的非同步訊息處理機制可能需要我們自己實現和控制具體的操作細節,android為我們提供了更方便的形式,把handler的一些處理細節進行了封裝,創造了一個新的類—-AsyncTask。這是一個抽象類,我們在使用的時候,要重寫定義一個子類繼承於它,並且要指定三個泛型引數:傳遞給後臺任務的引數,顯示任務進度的單位,任務完成之後的結果返回值。
AsyncTask有幾個核心的函式,是實現非同步任務處理的關鍵。
- onPreExcute()在後臺任務開始之前呼叫,進行一些初始化的工作。
- doInBackground()這個函式內的程式碼都是在子執行緒中進行的,可以放入耗時操作,但是不能放入UI操作.
- publishProgress()用來進行子執行緒和主執行緒的切換,它呼叫之後會執行一個onProgressUpdate函式,這裡面可以進行UI的更新操作,這個函式顯然是在主執行緒中執行的。
- onPostExcute()主要是做一些收尾工作,當任務在doInBackgroud函式中處理完之後,通過return 返回值之後,這個函式就會被呼叫,可以進行一些資源的回收和銷燬工作,在這裡同樣可以進行UI操作。
整個類實現完成,AsyncTask的非同步訊息處理機制也就完成了,只需要定義一個這個類的物件,然後呼叫excute函式,這個任務就會開始執行。耗時操作放在了子執行緒中執行的doInBackground中,對UI的操作放在了onProgressUpdate函式中。實現了和handler一樣的效果,但是使用起來方便一些。
Service初探
service是四大元件之一比較重要的一部分,它一般在後臺執行,不予使用者互動,主要執行一些耗時操作。當然,如果你需要的話,也可以讓它變成前臺執行。
服務的種類(按啟動方式來分)
通過上面這張圖,我們可以清晰的看到,通過啟動服務的方式來分的的話,有兩種服務。
- 通過startservice啟動
- 通過bindservice啟動
Startservice的使用與生命週期
通過這種方式啟動的服務屬於比較標準的一類。一般來說,從定義一個服務到使用一個服務,要遵循一下幾個步驟:
- 自己實現一個類,該類繼承service類
- 在清單檔案中註冊自己的服務(實際上,四大元件的使用,都需要在清單檔案中進行註冊)
- 呼叫startservice來初始化並且啟動自己的服務
- 呼叫stopservice來停止我們已經執行的服務。
通過上面的圖,我們也可以觀察到,這種服務的生命週期是怎樣的:
首先,通過startservice來啟動服務,如果我們是第一次啟動服務的話,需要呼叫oncreate函式來做一些初始化的操作,並且呼叫start方法來開始執行服務的核心程式碼(新版本中start已經被替換成onstartcommand函式)。如果我們多次呼叫startservice方法的話,那麼oncreate也不會再被呼叫,重複呼叫的只有onstart方法,它會處理傳遞給startvice的intent.如果服務中有大量的耗時操作,比如在網路上下載圖片等,則可以在Onstart函式裡面單獨開一個執行緒來執行這些操作,這樣做可以不讓我們的主執行緒阻塞。最後,如果我們想停止這個服務,可以呼叫stopservice,這個函式第一次被呼叫的時候,會執行ondestory函式來釋放程式在oncreate函式中所建立的資源等等。當然,你也可以在服務的內部呼叫stopself函式來停止服務的執行。 其餘的,大家看下實現程式碼來具體理解吧:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 |
package com.demo.service; import com.demo.handler.HandlerActivity; import com.example.androiddemo.R; import android.app.Notification; import android.app.PendingIntent; import android.app.Service; import android.content.Intent; import android.os.IBinder; import android.util.Log; public class MyServiceActivity extends Service { @Override public IBinder onBind(Intent intent) { // TODO Auto-generated method stub return null; } @Override public void onCreate() { // TODO Auto-generated method stub super.onCreate(); Notification mNotification = new Notification(R.drawable.ic_launcher, "this is service ticker", System.currentTimeMillis()); Intent mIntent = new Intent(this,HandlerActivity.class); PendingIntent mPendingIntent = PendingIntent.getActivity(this, 0, mIntent, 0); mNotification.setLatestEventInfo(this, "this is title", "this is content", mPendingIntent); startForeground(1, mNotification); Log.i("xuran", "common service create"); } @Override public int onStartCommand(Intent intent, int flags, int startId) { // TODO Auto-generated method stub Log.i("xuran", "common service startcommand"); new Thread(new Runnable() { @Override public void run() { // TODO Auto-generated method stub for(int i = 0; i < 10; i++) { Log.i("xuran", "service in childthread " + i); try { Thread.sleep(1000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } }).start(); return super.onStartCommand(intent, flags, startId); } @Override public void onDestroy() { // TODO Auto-generated method stub Log.i("xuran", "common service destory"); super.onDestroy(); } } |
BindService與Binder
之前的服務,只要在活動中呼叫startservice之後,服務就自己去執行了,而服務具體做了什麼,在活動內是不知道的,在活動內,除了呼叫兩個API來啟動和停止服務之外,就不能再對服務有所幹涉了。如果我們想獲取到服務執行過程中的一些情況的話,就需要服務和活動進行通訊,這也就需要用到我們程式間通訊機制的一種—Binder.
在之前實現service類的時候,我們可以看到有一個抽象方法被重新實現—onBind()。service和activity的通訊大致是通過這樣的一種過程來實現的:
- 首先在service的類中,實現一個類,這個類繼承於Binder,這個類中你可以定義一些你自己的方法,這些方法就是以後要在服務中執行的方法,這個類是服務類的子類。然後,建立一個這個binder類的例項,在Onbind函式中返回這個binder物件。這個物件就像是一個開著的介面,向外提供了子類內部將要執行的操作。
- 服務對外通訊的介面做好之後,想讓活動和服務建立連線的話,就要在兩者之間建立一個連線點。這個連線點的工作由ServiceConnection類的物件來實現,我們要在activity中建立一個ServiceConnection的匿名類,類中要重寫兩個方法: onServiceDisconnected,onServiceConnected。之後在建立一個這個類的物件,到此為止,連線點也做好了,那麼如何讓服務和活動通過這個連線點連線起來進行通訊工作呢?
- 因為在實現服務的時候,給我們返回了一個Binder的物件作為介面,可以看見在重寫onServiceConnected方法的時候,裡面有一個引數就是Binder型別的引數,通過把這個Binder物件強制型別轉換成我們在服務中自己實現binder類的那個物件的類型別之後,就可以通過它來呼叫我們服務內部的一些操作。這就為活動來指揮服務內部的執行,提供了良好的介面和條件。
- 而對於acitivity來說的話,我們可以通過bindservice這個啟動服務的函式,在引數中把要啟動服務的intent和建立的連線點ServiceConnection物件傳遞過去,如此一來,在繫結成功之後就可以建立服務並且連線在一起,然後自動呼叫連線點中onServiceconnected方法,在這個方法的內部就可以通過引數中的binder物件來指揮服務。在服務和活動解除繫結的時候,自然也會呼叫連線點處的onServiceDisconnected方法。
- 我們在活動中,現在通過Binder物件所能執行的是service類的子類所提供的方法,如果想執行service類內部的方法,可以在這個介面中再定義一個函式,返回服務類的一個物件,便可以執行服務類中的成員方法了。
那麼BinderService的生命週期就由以下幾個部分構成:
onbindservice方法來建立一個持久的連線,它會在第一次呼叫的時候,呼叫oncreate來做一些服務的初始化的操作。之後自動呼叫onbind函式,向服務的外部返回Binder物件,這算是一個服務面向外部的介面。之後,通過serviceconnection物件的作用,活動和服務便能夠成功繫結並且可以執行相應的操作,如果想要停止服務,可以呼叫unbindservice函式來解除之前建立的連線,這會直接呼叫服務內部的ondestory函式。
優秀的結合—-IntentService
服務的執行一般來說關心兩個問題:
- 如果處理服務中的耗時操作
- 如果在服務中的邏輯執行完畢之後,自動關閉該服務。對於第一個問題來說,我們可以在服務的邏輯方法中新建一個子執行緒,讓它來完成耗時操作。 對於第二個問題來說,我們可以在服務的執行邏輯的末尾,加上stopself以便讓它在邏輯完成時,結束服務。
那麼能否有一種service可以不用我們自己手動單獨開闢執行緒,自動的來完成我們的耗時操作呢,並且在服務的邏輯處理完成之後,可以自動的幫我們結束服務,不用我們自己再呼叫stopself。這種服務就是intentservice。
intentservice的使用方法和普通的service是一樣的,但是在intentservice的實現中,我們需要繼承自intentservice的類,並且要為這個類過載一個沒有引數的構造方法。具體的實現步驟有以下幾個比較重要的:
- 繼承Intentservice並提供一個無引數的建構函式,在方法內部呼叫父類的建構函式。
- 重寫父類當中onhandleintent這個方法,把我們要在服務當中處理的邏輯過程都放在這個方法內完成
- 啟動這個service。 這個服務的特殊之處就在於onhandleintent這個方法,這個方法能夠代替我們手動進行的兩個工作:這個方法內部的程式碼都是在子執行緒中執行的,並且在這個方法執行到末尾的時候,會自動的幫我們關閉service。這樣一來,就不用我們再手動的開子執行緒和關閉服務了。其實在intentservice中也是通過開啟子執行緒來完成服務內的邏輯過程的執行的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
package com.demo.service; import android.app.IntentService; import android.content.Intent; import android.util.Log; public class MyIntentService extends IntentService { public MyIntentService(String name) { super(name); // TODO Auto-generated constructor stub } //使用使用intentservice必須要提供一個預設的建構函式 public MyIntentService(){ super("my service"); } @Override protected void onHandleIntent(Intent intent) { // TODO Auto-generated method stub for(int i = 0; i < 40; i++){ Log.i("xuran", "服務正在執行 " + i); try { Thread.sleep(1000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } @Override public void onDestroy() { // TODO Auto-generated method stub super.onDestroy(); Log.i("xuran", "服務已經關閉"); } } |
StartService和BindService的區別
- 如果是使用startservice的方式來啟動服務的話,即使啟動這個服務的activity可能處於destory狀態,但是服務本身還是在繼續執行的,除非這個服務所在程式被銷燬,那麼這個服務也會停止。
- 如果是使用bindservice來啟動服務的話,一旦啟動這個服務的activity變為destory的狀態的時候,那麼這個服務也隨之會銷燬,因為這個服務是和特定的activity進行繫結的。
- startservice一旦啟動之後,activity就不能再插手服務執行過程中的事情,比如我想了解服務執行的狀態等等,都不能夠實現,也就是說,activity只能是給服務發一個訊號來啟動他,但是並不能和它進行互動。
- bindservice就不一樣了,它是依靠binder程式間通訊的機制,在服務本身內部向外提供介面,以便讓activity能夠和服務進行互動操作,這樣一來,activity就可以在服務執行的過程當中來檢視服務執行的情況。
- bindservice雖然向外部提供了一個binder的物件,通過這個物件可以呼叫service內部的一些方法,但是在activity中,通過binder呼叫的函式並不是直接一步到位呼叫了服務中的相關成員函式,而只是得到一個類似指標和對映的東西,通過這些再去找在服務內部真正存在的方法。總的來說把,bindservice向外部提供的,只是內部資訊的一個對映,並不是真正的把內部的函式地址等等資訊提供出去。
- 如果有一個service和多個activity同時繫結的話,那麼即使一個activity解除了service的繫結,但是並不會回撥unbind和ondestory的方法,只不過在繫結多個actiity的時候要注意繫結的順序。> 其實bind和start兩種服務最本質的區別就是,Bind是一種伺服器/客戶端模型,能夠讓服務的啟動者和服務的執行者互動,但是start不行。
Service到底是什麼?程式 ?執行緒?
這個問題也是自己剛剛遇到的,一開始有點模糊不清,包括不明白service為什麼和呼叫他的activity是在同一個主執行緒中的。網上的資料,大家還是最好有一點甄別能力,尤其是論壇上面的,有些明顯的錯誤來誤導提問者。當然,我寫的東西可能也是有錯的,如果有不同意見,大家可以一起討論。 我對這個問題的理解是這樣的:<br> 首先,service不是一執行緒,也不是一個程式,它是一個元件。它的生存執行,和執行緒的生命週期沒有一點關係。也就是說,執行緒的死活和它是沒有關係的。但是,service的執行,畢竟需要一些條件,比如記憶體,CPU這些資源。然而,這些資源,作業系統只會分配給程式,這也就是為什麼,不管我們用什麼種類的service,只要我們把這個程式幹掉,服務神馬的也就都是浮雲了。所以,服務雖然是一個元件,但是它的執行還是要依賴於程式。
服務分為遠端服務和本地服務,本地服務就是在我們這個應用程式之內啟動並執行的。一般來說,服務的執行並不會新開一個執行緒,無論是哪一種service他們都是執行在程式中的主執行緒中。這也就是為什麼不能在服務中直接執行大量的耗時操作,要單獨新開一個子執行緒完成的原因。
還有一個問題:startservice在啟動之後,其實activity銷燬了,但是它還是繼續在執行,直到我們顯式的呼叫stopservice或者stopself,他才會停止。而且,即使我們在服務執行的過程中stopservice,但是服務還是會完成這一次的邏輯,也就是說,我們在一個服務執行的過程中,不能中斷本次執行的這個過程。這一點對於Bindservice和startservice都是一樣。
但是bindeservice在activity銷燬之後,它自己也會跟著銷燬,我們可能會覺得這是因為程式或者執行緒的生命週期的關係,其實,這是不正確的一個是,按下back鍵使activity銷燬,程式還有被殺死,還有一個就是,執行緒的生命週期和服務壓根一點關係沒有。之所以bindservice被銷燬了,是因為它繫結的是activity,而不是什執行緒,activity銷燬了,那麼繫結它上面的service也就會銷燬了。startservice沒有銷燬是因為它沒有和人和activity繫結。