1. 程式人生 > >Activity與Service的那些事

Activity與Service的那些事

       服務作為Android的四大元件之一,它並不像Activity那樣高調(使用頻繁),它就是那個默默無聞的工作者。

       由於不怎麼用到,所以關於它的使用很容易忘記,現在有空就將它記錄下來,方便後期檢視。

       服務(Service)是Android中實現程式後臺執行的解決方案,它非常適合用於去執行那些不需要和使用者互動而且還要求長期執行的任務。服務的執行不依賴於任何使用者介面,即使當程式被切換到後臺,或者使用者打開了另外一個應用程式,服務仍然能夠保持正常執行。

       不過需要注意的是,服務並不是執行在一個獨立的程序當中的,而是依賴於建立服務時所在的應用程式程序。當某個應用程式程序被殺掉時,所有依賴於該程序的服務也會停止執行。

       另外,也不要被服務的後臺概念所迷惑,實際上服務並不會自動開啟執行緒,所有的程式碼都是預設執行在主執行緒當中的。也就是說,我們需要在服務的內部手動建立子執行緒,並在這裡執行具體的任務,否則就有可能出現主執行緒被阻塞住的情況。

1.建立一個服務

     新建一個服務測試類,讓其繼承自Service

public class MyService extends Service {
	
	@Override
	public IBinder onBind(Intent intent) {
		return null;
	}

}
     onBind()方法是Service中唯一的一個抽象方法,所以必須要在子類裡實現。後面會講到它的用處。

     建立服務,想必我們也是會讓其做一些事情的,那麼處理事情的邏輯程式碼段放置在哪裡呢?這時候就得需要重寫Service中的另外一些方法了。程式碼段如下所示:

public class MyService extends Service {

	@Override
	public IBinder onBind(Intent intent) {
		return null;
	}

	@Override
	public void onCreate() {
		super.onCreate();
	}

	@Override
	public int onStartCommand(Intent intent, int flags, int startId) {
		return super.onStartCommand(intent, flags, startId);
	}

	@Override
	public void onDestroy() {
		super.onDestroy();
	}

}

       上面程式碼中我們重寫了onCreate(), onStartCommand(), onDestroy()方法,它們是每個服務中最常用的三個方法。其中onCreate()方法會在服務建立的時候呼叫,onStartCommand()方法會在每次服務啟動的時候呼叫,onDestroy()方法會在服務銷燬的時候呼叫。那麼前2個方法有什麼區別了?

       onCreate()方法是在服務第一次建立的時候呼叫的,而onStartCommand()方法則在每次啟動服務的時候都會呼叫。

       通常情況下,如果我們希望服務一旦啟動就立刻去執行某個動作,就可以將邏輯寫在onStartCommand()方法裡。服務中的程式碼都是預設執行在主執行緒當中的,如果直接在服務裡去處理一些耗時的邏輯,就很容易出現ANR(Application Not Responding)的情況。所以在onStartCommand()方法裡寫邏輯程式碼時,最好開啟一個子執行緒,讓其在子執行緒中執行。

@Override
	public int onStartCommand(Intent intent, int flags, int startId) {
		new Thread(new Runnable() {  
	        @Override  
	        public void run() {  
	            // 處理具體的邏輯
	        }  
	    }).start();
		return super.onStartCommand(intent, flags, startId);
	}

       而當服務銷燬時,我們又應該在onDestroy()方法中去回收那些不再使用的資源。

注意:每一個服務都需要在AndroidManifest.xml檔案中進行註冊才能生效,這是Android四大元件共有的特點,所以平常我們也要養成建立元件即註冊的習慣。

<service android:name=".MyService" ></service>

2.啟動和停止服務

       服務的啟動和停止的方法和Activity類似,主要是藉助Intent來實現的。
       在Activity中啟動服務:

Intent startIntent = new Intent(this, MyService.class);
startService(startIntent); // 啟動服務

在Activity中停止服務:

Intent stopIntent = new Intent(this, MyService.class);
stopService(stopIntent); // 停止服務

注意:上面的停止服務,完全是由Activity來決定服務何時停止的,如果沒有上面的stopService()方法,服務就會一直處於執行狀態。

那服務有沒有什麼辦法讓自已停止下來呢?當然可以,只需要在MyService的任何一個位置呼叫stopSelf()方法就能讓這個服務停止下來了。

3.活動與服務的通訊
         雖然服務是在活動中啟動的,但是當在活動中startService()後,好像就變得不那麼容易控制我們的服務了,我們在活動裡呼叫了startService()方法來啟動MyService這個服務,然後MyService的onCreate()和onStartCommand()方法就會得到執行。之後服務會一直處於執行狀態,但具體執行的是什麼邏輯,活動就控制不了了,活動並不知道服務到底去做了什麼事情,以及完成的如何。

        這個時候前面說的那個抽象方法onBind()就可以幫助我們讓Activity和Service的聯絡更緊密一點。

       下面以在MyService裡實現下載功能為例,做到在活動中可以決定何時開始下載,以及隨時檢視下載進度。

        為此,我們要建立一個專門的Binder物件來對下載功能進行管理,修改MyService中的程式碼:

public class MyService extends Service {
	
	private DownloadBinder mBinder = new DownloadBinder();

	class DownloadBinder extends Binder {

		public void startDownload() {
			Log.d("MyService", "startDownload executed");
		}

		public int getProgress() {
			Log.d("MyService", "getProgress executed");
			return 0;
		}

	}

	@Override
	public IBinder onBind(Intent intent) {
		return mBinder;
	}

	……

}

         可以看到,這裡我們新建了一個DownloadBinder類,並讓它繼承自Binder,然後在它的內部提供了開始下載以及檢視下載進度的方法。具體的邏輯程式碼就不寫了。
接著,在MyService中建立了DownloadBinder的例項,然後在onBind()方法裡返回了這個例項,這樣MyService中的工作就全部完成了。
下面是如何在Activity中去呼叫服務裡的這些方法呢?
比如在MainActivity的佈局檔案中增加2個按鈕。一個用於繫結服務,一個用於取消服務。
activity_main.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >
    ……
    <Button
        android:id="@+id/bind_service"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Bind Service" />

    <Button
        android:id="@+id/unbind_service"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Unbind Service" />
</LinearLayout>

當一個活動和服務綁定了之後,就可以呼叫該服務裡的Binder提供的方法了。

public class MainActivity extends Activity implements OnClickListener {

	private Button startService;

	private Button stopService;

	private Button bindService;

	private Button unbindService;

	private MyService.DownloadBinder downloadBinder;

	private ServiceConnection connection = new ServiceConnection() {

		@Override
		public void onServiceDisconnected(ComponentName name) {
		}

		@Override
		public void onServiceConnected(ComponentName name, IBinder service) {
			downloadBinder = (MyService.DownloadBinder) service;
			downloadBinder.startDownload();
			downloadBinder.getProgress();
		}
	};

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		……
		bindService = (Button) findViewById(R.id.bind_service);
		unbindService = (Button) findViewById(R.id.unbind_service);
		bindService.setOnClickListener(this);
		unbindService.setOnClickListener(this);
	}

	@Override
	public void onClick(View v) {
		switch (v.getId()) {
		……
		case R.id.bind_service:
			Intent bindIntent = new Intent(this, MyService.class);
			bindService(bindIntent, connection, BIND_AUTO_CREATE); // 繫結服務
			break;
		case R.id.unbind_service:
			unbindService(connection); // 解綁服務
			break;
		default:
			break;
		}
	}

}

        這裡我們首先建立了一個ServiceConnection的匿名類,在裡面重寫了onServiceConnected()方法和onServiceDisconnected()方法,這兩個方法分別會在活動與服務成功繫結以及解除繫結的時候呼叫。在onServiceConnected()方法中,我們又通過向下轉型得到了DownloadBinder的例項,有了這個例項,活動和服務之間的關係就變得非常緊密了。現在我們可以在活動中根據具體的場景來呼叫DownloadBinder中的任何public方法,即實現了指揮服務幹什麼,服務就去幹什麼的功能。這裡仍然只是做了個簡單的測試,在onServiceConnected()方法中呼叫了DownloadBinder的startDownload()和getProgress()方法。

       當然,現在活動和服務其實還沒進行繫結,這個功能是在Bind Service按鈕的點選事件裡完成的。可以看到,這裡我們仍然是構建出了一個Intent物件,然後呼叫bindService()方法將MainActivity和MyService進行繫結。bindService()方法接收三個引數,第一個引數就是剛剛構建出的Intent物件,第二個引數是前面創建出的ServiceConnection的例項,第三個引數則是一個標誌位,這裡傳入BIND_AUTO_CREATE表示在活動和服務進行繫結後自動建立服務。這會使得MyService中的onCreate()方法得到執行,但onStartCommand()方法不會執行。

       然後如果我們想解除活動和服務之間的繫結該怎麼辦呢?呼叫一下unbindService()方法就可以了,這也是Unbind Service按鈕的點選事件裡實現的功能。

注意:任何一個服務在整個應用程式範圍內都是通用的,即MyService不僅可以和MainActivity繫結,還可以和任何一個其他的活動進行繫結,而且在繫結完成後它們都可以獲取到相同的DownloadBinder例項。

4.服務的生命週期

       服務也有自己的生命週期,前面我們使用到的onCreate()、onStartCommand()、onBind()和onDestroy()等方法都是在服務的生命週期內可能回撥的方法。

       一旦在專案的任何位置呼叫了Context的startService()方法,相應的服務就會啟動起來,並回調onStartCommand()方法。如果這個服務之前還沒有建立過,onCreate()方法會先於onStartCommand()方法執行。服務啟動了之後會一直保持執行狀態,直到stopService()或stopSelf()方法被呼叫。注意雖然每呼叫一次startService()方法,onStartCommand()就會執行一次,但實際上每個服務都只會存在一個例項(單例模式)。所以不管你呼叫了多少次startService()方法,只需呼叫一次stopService()或stopSelf()方法,服務就會停止下來了。

       另外,還可以呼叫Context的bindService()來獲取一個服務的持久連線,這時就會回撥服務中的onBind()方法。類似地,如果這個服務之前還沒有建立過,onCreate()方法會先於onBind()方法執行。之後,呼叫方可以獲取到onBind()方法裡返回的IBinder物件的例項,這樣就能自由地和服務進行通訊了。只要呼叫方和服務之間的連線沒有斷開,服務就會一直保持執行狀態。

       當呼叫了startService()方法後,又去呼叫stopService()方法,這時服務中的onDestroy()方法就會執行,表示服務已經銷燬了。類似地,當呼叫了bindService()方法後,又去呼叫unbindService()方法,onDestroy()方法也會執行,這兩種情況都很好理解。但是需要注意,我們是完全有可能對一個服務既呼叫了startService()方法,又呼叫了bindService()方法的,這種情況下該如何才能讓服務銷燬掉呢?根據Android系統的機制,一個服務只要被啟動或者被綁定了之後,就會一直處於執行狀態,必須要讓以上兩種條件同時不滿足,服務才能被銷燬。所以,這種情況下要同時呼叫stopService()和unbindService()方法,onDestroy()方法才會執行。

 5.儘量使用IntentService

為什麼要儘量使用IntentService呢?主要是由於以下2個原因:

1.服務是執行在主執行緒中的,當我們做一些比較耗時的操作時,容易造成程式ANR,前面說的,可以手動建立子執行緒,讓其處理耗時的邏輯;

2.當服務執行完後,我們要手動停止服務呼叫stopService()或者stopSelf()方法

使用IntentService的話,可以不用這麼麻煩,IntentService預設是開啟在子執行緒中的且會完成處理邏輯後自動停止服務

public class MyIntentService extends IntentService {

	public MyIntentService() {
		super("MyIntentService"); // 呼叫父類的有參建構函式
	}

	@Override
	protected void onHandleIntent(Intent intent) {
		// 列印當前執行緒的id
		Log.d("MyIntentService", "Thread id is " + Thread.currentThread(). getId());
	}

	@Override
	public void onDestroy() {
		super.onDestroy();
		Log.d("MyIntentService", "onDestroy executed");
	}

}

首先是要提供一個無參的建構函式,並且必須在其內部呼叫父類的有參建構函式。然後要在子類中去實現onHandleIntent()這個抽象方法,在這個方法中可以去處理一些具體的邏輯,而且不用擔心ANR的問題,因為這個方法已經是在子執行緒中執行的了。這裡為了證實一下,我們在onHandleIntent()方法中列印了當前執行緒的id。另外根據IntentService的特性,這個服務在執行結束後應該是會自動停止的,所以我們又重寫了onDestroy()方法,在這裡也列印了一行日誌,以證實服務是不是停止掉了。

在MainActivity中增加一個按鈕,並新增點選事件,新增如下程式碼:

// 列印主執行緒的id
Log.d("MainActivity", "Thread id is " + Thread.currentThread(). getId());
Intent intentService = new Intent(this, MyIntentService.class);
startService(intentService);

檢視log輸出,你會發現,不僅MyIntentService和MainActivity所在的執行緒id不一樣,而且onDestroy()方法也得到了執行,說明MyIntentService在執行完畢後確實自動停止了。IntentService和普通的service一樣,但是集開啟執行緒和自動停止於一身,比較實用。


6.服務的實戰

參考資料:

    郭霖:《第一行程式碼》第九章