Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> android學習十八(Service服務的基本用法)

android學習十八(Service服務的基本用法)

編輯:關於Android編程

定義一個服務

在項目中定義一個服務,新建一個ServiceTest項目,然後在這個項目中新增一個名為MyService的類,並讓它繼承自Service,完成後的代碼如下所示:

 

package com.jack.servicetest;

import android.app.Service;
import android.content.Intent;
import android.os.IBinder;

public class MyService extends Service {

	@Override
	public IBinder onBind(Intent arg0) {
		// TODO Auto-generated method stub
		return null;
	}

}

目前MyService中只用一個onBind()方法,這個方法是Service中唯一的一個抽象方法,所以必須要在子類裡實現。既然定義了一個服務,自然應該在服務中去處理一些事情,那處理事情的邏輯應該寫在哪裡?這時我們就可以重寫Service中的另外一些方法了,如下所示:

 

 

package com.jack.servicetest;

import android.app.Service;
import android.content.Intent;
import android.os.IBinder;

public class MyService extends Service {

	@Override
	public IBinder onBind(Intent arg0) {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public void onCreate() {
		// TODO Auto-generated method stub
		super.onCreate();
	}

	@Override
	public void onDestroy() {
		// TODO Auto-generated method stub
		super.onDestroy();
	}

	@Override
	public int onStartCommand(Intent intent, int flags, int startId) {
		// TODO Auto-generated method stub
		return super.onStartCommand(intent, flags, startId);
	}
	
	

}


 

可以看到,這裡我們又重寫了onCreate(),onDestroy()和onStartCommand(Intent intent, int flags, int startId)這三個方法,它們是每個服務中最常用到的三個方法。其中onCreate方法會在服務創建的時候調用,onStartCommand方法會在每次服務啟動的時候調用。onDestroy()方法會在服務銷毀的時候調用。

通常情況下,如果我們希望服務一旦啟動就立刻去執行某個動作,就可以將邏輯寫在onStartCommand方法裡。而當服務銷毀時,我們又應該在onDestroy()方法中去回收那些不在使用的資源。

另外需要注意的是,沒一個服務都需要在AndroidManifest.xml文件中進行注冊才能生效,android四大組件都需要進行注冊。於是我們修改AndroidManifest.xml文件,代碼如下所示:

 




這樣的話,就已經將一個服務定義好了。

 

 

啟動和停止服務

定義好了服務後,接下來就應該考慮如何啟動以及停止這個服務。啟動服務和停止服務主要借助Intent來實現,下面我們在ServiceTest項目中嘗試去啟動已經停止MyService這個服務。

首先修改activity_main.xml中的代碼,如下所示:

 


上面的布局主要加入了2個按鈕,用來啟動和停止服務。

 

然後修改MainActivity中的代碼,如下所示:

 

package com.jack.servicetest;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.Menu;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;

public class MainActivity extends Activity implements OnClickListener{

	private Button startService;
	private Button stopService;
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		startService=(Button) findViewById(R.id.start_service);
		stopService=(Button) findViewById(R.id.stop_service);
		startService.setOnClickListener(this);
		stopService.setOnClickListener(this);
	}

	@Override
	public boolean onCreateOptionsMenu(Menu menu) {
		// Inflate the menu; this adds items to the action bar if it is present.
		getMenuInflater().inflate(R.menu.main, menu);
		return true;
	}

	@Override
	public void onClick(View v) {
		// TODO Auto-generated method stub
		switch(v.getId()){
		case R.id.start_service:
			Intent startIntent =new Intent(this,MyService.class);
			startService(startIntent);//啟動服務
			break;
		case R.id.stop_service:
			Intent stopIntent =new Intent(this,MyService.class);
			stopService(stopIntent);//停止服務
			break;
		default:
			break;
		}
	}

}


 

上面我們在onCreate方法中分別獲取到start service按鈕和stop service按鈕的實例,並給它們注冊了點擊
事件。然後在start service按鈕的點擊事件裡面,我們構建出了一個Intent對象,並調用startService()
方法來啟動MyService這個服務。在stop service按鈕的點擊事件裡,我們同樣構建出了一個Intent對象,並調用
stopService()方法來停止MyService這個服務。startService()和stopService()方法都是定義在Context
類中的,所以我們在活動裡可以直接調用這兩個方法。注意,這裡完全是由活動來決定服務何時停止的,如果沒有點擊stop service
按鈕,服務就會一直處於運行狀態。那服務有什麼辦法讓自己停下來了?只需要在MyService的任何一個位置調用shopSelf()
方法就能讓服務停止下來了。

 

接下來我們要考慮,如何才能證明服務已經成功啟動或者停止了?最簡單的方法就是在MyService的幾個方法中加入打印日志,如下所示:

 

package com.jack.servicetest;

import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.util.Log;

public class MyService extends Service {

	@Override
	public IBinder onBind(Intent arg0) {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public void onCreate() {
		// TODO Auto-generated method stub
		super.onCreate();
		Log.d(MyService, onCreate());
	}

	@Override
	public void onDestroy() {
		// TODO Auto-generated method stub
		super.onDestroy();
		Log.d(MyService, onDestroy());
	}

	@Override
	public int onStartCommand(Intent intent, int flags, int startId) {
		// TODO Auto-generated method stub
		
		Log.d(MyService, onStartCommand);
		
		return super.onStartCommand(intent, flags, startId);
		
	}
	
	

}

 

由此證明MyService服務確實已經成功停止下來了。

 

onCreate方法和onStartCommand方法的區別:onCreate方法是在服務第一次創建的時候調用的,而onStartCommand方法則在每次啟動服務的時候都會調用,由於剛才我們是第一次點擊start service按鈕,服務此時還未創建過,所以兩個方法都會執行,之後如果你在連續多點擊幾次start service按鈕,你就會發現只有onStartCommand方法可以得到執行了。

 

活動和服務進行通信

目前我們希望在MyService裡提供一個下載的功能,然後再活動中可以決定何時開始下載,以及隨時查看下載進。實現這個功能的思路是創建一個專門的Binder對象來對下載功能進行管理,修改MyService中的代碼:如下所示:

 

package com.jack.servicetest;

import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
import android.util.Log;

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) {
		// TODO Auto-generated method stub
		return mBinder;
	}

	@Override
	public void onCreate() {
		// TODO Auto-generated method stub
		super.onCreate();
		Log.d(MyService, onCreate());
	}

	@Override
	public void onDestroy() {
		// TODO Auto-generated method stub
		super.onDestroy();
		Log.d(MyService, onDestroy());
	}

	@Override
	public int onStartCommand(Intent intent, int flags, int startId) {
		// TODO Auto-generated method stub
		
		Log.d(MyService, onStartCommand);
		
		return super.onStartCommand(intent, flags, startId);
		
	}
	
	

}

這裡我們新建了一個DownloadBinder類,並讓它繼承自Binder,然後再它的內部提供開始下載以及查看下載進度的方法。當然這只是兩個模擬的方法,並沒有實現真正的功能,我們在這兩個方法中分別打印了一行日志。

 

接著,在MyService中創建了DownloadBinder的實例,然後再onBind()方法裡返回了這個實例,這樣MyService中的工作就全部完成了。

下面我們需要在活動中調用服務裡的方法,首先需要在布局文件中新曾兩個按鈕,修改activity_main.xml中的代碼,如下所示:

 


這兩個活動用於在活動中進行綁定和取消綁定服務,當一個活動和服務綁定了之後,就可以調用該服務裡的Binder提供的方法了,修改MainActivity中的代碼,如下所示:

 

 

package com.jack.servicetest;

import com.jack.servicetest.MyService.DownloadBinder;

import android.app.Activity;
import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.view.Menu;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;

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() {
		/*
		 * 這裡創建了一個ServiceConnection的匿名類,在這裡重寫了onServiceConnected方法和
		 * onServiceDisconnected方法,這兩個方法分別會在活動與服務成功綁定以及解除綁定的時候調用。
		 * 在onServiceConnected方法中,我們又通過向下轉型得到了DownloadBinder的實例,有了這個
		 * 實例,活動和服務之間的關系就變得非常緊密了,現在我們可以在活動中根據具體的場景來調用DownloadBinder
		 * 中的任何public方法,及實現了指揮服務干什麼,服務就干什麼的功能,這裡只做了簡單的測試,在onServiceConnected
		 * 中調用了DownloadBinder的startDownload(),getProgress()方法。
		 * */
		@Override
		public void onServiceDisconnected(ComponentName name) {
			// TODO Auto-generated method stub
			
		}
		
		@Override
		public void onServiceConnected(ComponentName name, IBinder service) {
			// TODO Auto-generated method stub
			downloadBinder=(MyService.DownloadBinder) service;
			downloadBinder.startDownload();
			downloadBinder.getProgress();
		}
	};
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		startService=(Button) findViewById(R.id.start_service);
		stopService=(Button) findViewById(R.id.stop_service);
		startService.setOnClickListener(this);
		stopService.setOnClickListener(this);
		
		bindService = (Button) findViewById(R.id.bind_service);
		unbindService = (Button) findViewById(R.id.unbind_service);
		bindService.setOnClickListener(this);
		unbindService.setOnClickListener(this);
		
	}

	@Override
	public boolean onCreateOptionsMenu(Menu menu) {
		// Inflate the menu; this adds items to the action bar if it is present.
		getMenuInflater().inflate(R.menu.main, menu);
		return true;
	}

	@Override
	public void onClick(View v) {
		// TODO Auto-generated method stub
		switch(v.getId()){
		case R.id.start_service:
			Intent startIntent =new Intent(this,MyService.class);
			startService(startIntent);//啟動服務
			break;
		case R.id.stop_service:
			Intent stopIntent =new Intent(this,MyService.class);
			stopService(stopIntent);//停止服務
			break;
		case R.id.bind_service:
			/*
			 *現在我們需要進行活動和服務的綁定,構建一個Intent對象,然後調用bindService()方法將
			 *MainActivity()和MyService進行綁定。 bindService方法接收三個參數,第一個參數就是
			 *上面創建出的Intent對象,第二個參數就是前面創建出的ServiceConnection的實例,第三個
			 *參數則是一個標志位,這裡傳入BIND_AUTO_CREATE表示在活動和服務進行綁定後自動創建服務。
			 *這會使得MyService中的onCreate()方法得到執行,但onStartCommand()方法不會執行。
			 * */
			Intent bindIntent=new Intent(this,MyService.class);
	        bindService(bindIntent, connection, BIND_AUTO_CREATE);//綁定服務
			break;
		case R.id.unbind_service:
			/*
			 * 如果我們想解除活動和服務之間的綁定,調用一下unbindService()方法就可以了。
			 * */
			unbindService(connection);//解綁服務
			break;
		default:
			break;
		}
	}

}


 

 

 

可以看到,首先是MyService的onCreate()方法得到了執行,然後startDownload和getProgeress方法得到了執行,說明我們確實已經在活動力成功的調用了服務裡提供的方法了。另外需要注意的,任何一個服務在整個應用程序范圍內都是通用的,即MyService不僅可以和MainActivity綁定,還可以和任何一個其他的活動進行綁定,而且在綁定完成之後他們都可以獲取到相同的DownloadBinder實例。

 

 

服務的生命周期

服務也有自己的生命周期,前面我們使用到的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()方法才會執行。

 

 

 

使用前台服務

服務幾乎都是在後台運行的,一直以來它都是默默的做著辛苦的工作。但是服務的系統優先級還是比較低的,當系統出現內存不足的情況時,就有可能會回收掉正在後台運行的服務。如果你希望服務可以一直 保持運行狀態,而 不會由於系統內存不足的原因導致被回收,就可以考慮使用前台服務。前台服務和普通服務最大的區別就在於,它會一直有一個正在運行的系統狀態欄顯示,下拉狀態欄後可以看到更加詳細的信息,非常類似於通知的效果。

下面我們創建一個前台服務吧,修改MyService中的代碼,如下所示:

 

package com.jack.servicetest;

import android.app.Notification;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
import android.util.Log;

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) {
		// TODO Auto-generated method stub
		return mBinder;
	}

	@Override
	public void onCreate() {
		// TODO Auto-generated method stub
		super.onCreate();
		@SuppressWarnings(deprecation)
		Notification notification=new Notification(R.drawable.ic_launcher,
				Notification comes,System.currentTimeMillis());
		Intent notificationIntent=new Intent(this,MainActivity.class);
		PendingIntent pendingIntent=PendingIntent.getActivity(this, 0,notificationIntent,
				0);
		notification.setLatestEventInfo(this, this is title, this is content,
				pendingIntent);
		startForeground(1, notification);
		/*
		 可以看到,這裡只是修改了onCreate()方法中的代碼,相信這部分代碼你會非常眼熟。這就是我們前面學習的
		 創建通知的方法。只不過這次在構建出Notification對象並沒有使用NotificationManager來將通知顯示
		 出來,而是調用了startForeground()方法。這個方法接收兩個參數,第一個參數是通知的id,類似於notify()方法
		 的第一個參數,第二個參數則是構建出來的Notification對象。調用startForeground()方法後就會讓MyService變成
		 一個前台服務,並在系統狀態顯示出來。
		 
		 */
		Log.d(MyService, onCreate());
	}

	@Override
	public void onDestroy() {
		// TODO Auto-generated method stub
		super.onDestroy();
		Log.d(MyService, onDestroy());
	}

	@Override
	public int onStartCommand(Intent intent, int flags, int startId) {
		// TODO Auto-generated method stub
		
		Log.d(MyService, onStartCommand);
		
		return super.onStartCommand(intent, flags, startId);
		
	}
	
	

}

現在重新運行下程序,並點擊start service或bind service按鈕,MyService就會以前台服務的模式開啟了,並且在系統狀態欄會顯示一個通知圖標,下拉狀態欄後可以看到該通知的詳細內容

 

使用IntentService

我們知道服務中的代碼都是默認運行在主線程當中,如果直接在服務裡去處理一些耗時的邏輯,就很容易出現ANR(Application Not Responding)的情況。

所以這個時候,就需要用到Android多線程編程的技術了,我們應該在服務的每個具體的方法裡開啟一個子線程,然後再這裡去處理那些耗時的邏輯。因此,一個比較標准的服務就可以寫成如下形式了:

 

package com.jack.servicetest;

import android.app.Notification;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
import android.util.Log;

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) {
		// TODO Auto-generated method stub
		//return mBinder;
		return null;
	}

	/*@Override
	public void onCreate() {
		// TODO Auto-generated method stub
		super.onCreate();
		@SuppressWarnings(deprecation)
		Notification notification=new Notification(R.drawable.ic_launcher,
				Notification comes,System.currentTimeMillis());
		Intent notificationIntent=new Intent(this,MainActivity.class);
		PendingIntent pendingIntent=PendingIntent.getActivity(this, 0,notificationIntent,
				0);
		notification.setLatestEventInfo(this, this is title, this is content,
				pendingIntent);
		startForeground(1, notification);
		
		 可以看到,這裡只是修改了onCreate()方法中的代碼,相信這部分代碼你會非常眼熟。這就是我們前面學習的
		 創建通知的方法。只不過這次在構建出Notification對象並沒有使用NotificationManager來將通知顯示
		 出來,而是調用了startForeground()方法。這個方法接收兩個參數,第一個參數是通知的id,類似於notify()方法
		 的第一個參數,第二個參數則是構建出來的Notification對象。調用startForeground()方法後就會讓MyService變成
		 一個前台服務,並在系統狀態顯示出來。
		 
		 
		Log.d(MyService, onCreate());
	}
*/
	/*@Override
	public void onDestroy() {
		// TODO Auto-generated method stub
		super.onDestroy();
		Log.d(MyService, onDestroy());
	}*/

	@Override
	public int onStartCommand(Intent intent, int flags, int startId) {
		// TODO Auto-generated method stub
		
		Log.d(MyService, onStartCommand);
		new Thread(new Runnable(){

			@Override
			public void run() {
				// TODO Auto-generated method stub
				//處理具體的邏輯
			}
			
		}).start();
		return super.onStartCommand(intent, flags, startId);
		
	}
	
	

}


 

但是,這種服務一旦啟動之後,就會一直處於運行狀態,必須調用stopService()或者stopSelf()方法才能讓服務停止下來。所以,如果想要實現一個服務執行完畢後自動停止的功能,就可以這樣寫:

 

package com.jack.servicetest;

import android.app.Notification;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
import android.util.Log;

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) {
		// TODO Auto-generated method stub
		//return mBinder;
		return null;
	}

	/*@Override
	public void onCreate() {
		// TODO Auto-generated method stub
		super.onCreate();
		@SuppressWarnings(deprecation)
		Notification notification=new Notification(R.drawable.ic_launcher,
				Notification comes,System.currentTimeMillis());
		Intent notificationIntent=new Intent(this,MainActivity.class);
		PendingIntent pendingIntent=PendingIntent.getActivity(this, 0,notificationIntent,
				0);
		notification.setLatestEventInfo(this, this is title, this is content,
				pendingIntent);
		startForeground(1, notification);
		
		 可以看到,這裡只是修改了onCreate()方法中的代碼,相信這部分代碼你會非常眼熟。這就是我們前面學習的
		 創建通知的方法。只不過這次在構建出Notification對象並沒有使用NotificationManager來將通知顯示
		 出來,而是調用了startForeground()方法。這個方法接收兩個參數,第一個參數是通知的id,類似於notify()方法
		 的第一個參數,第二個參數則是構建出來的Notification對象。調用startForeground()方法後就會讓MyService變成
		 一個前台服務,並在系統狀態顯示出來。
		 
		 
		Log.d(MyService, onCreate());
	}
*/
	/*@Override
	public void onDestroy() {
		// TODO Auto-generated method stub
		super.onDestroy();
		Log.d(MyService, onDestroy());
	}*/

	@Override
	public int onStartCommand(Intent intent, int flags, int startId) {
		// TODO Auto-generated method stub
		
		Log.d(MyService, onStartCommand);
		new Thread(new Runnable(){

			@Override
			public void run() {
				// TODO Auto-generated method stub
				//處理具體的邏輯
				stopSelf();
			}
			
		}).start();
		return super.onStartCommand(intent, flags, startId);
		
	}
	
	

}


 

雖然這種寫法並不復雜,但是總會有一些人忘記開啟線程,或者忘記調用stopSelf()方法。為了可以簡單地創建一個異步的,會自動停止的服務,android專門提供了一個IntentService類,這個類就很好的解決了前面所提到的兩種尴尬,下面我們來看下它的用法。

新建一個MyIntentService類繼承IntentService,代碼如下所示:

 

package com.jack.servicetest;

import android.app.IntentService;
import android.content.Intent;
import android.util.Log;

public class MyIntentService extends IntentService {

	/*
	 這裡首先是提供了一個無參的構造函數,並且必須在其內部調用父類的有參構造函數。然後要在子類中去實現
	 onHandleIntent()這個抽象方法,在這個方法中可以處理一些具體的邏輯,而且不用擔心ANR的問題,因為
	 這個方法已經是在子線程中運行的了。這裡為了證實一下,我們在onHandleIntent()方法中打印了當前線程的id。
	 另外根據IntentService的特性,這個服務在運行結束後應該是會自動停止的,所以我們又重寫了onDestroy()方法,在
	 這裡也打印l一行日志,以證實是不是停止掉了。
	 */
	public MyIntentService() {
		super(MyIntentService);//調用父類的有參構造函數
		// TODO Auto-generated constructor stub
	}

	@Override
	protected void onHandleIntent(Intent arg0) {
		// TODO Auto-generated method stub

		//打印當前線程的id
		Log.d(MyIntentService, Thread id is +Thread.currentThread().getId());
	}

	@Override
	public void onDestroy() {
		// TODO Auto-generated method stub
		super.onDestroy();
		Log.d(MyIntentService, onDestroy() executed);
	}

	
}


 

接下來修改activity_main.xml中的代碼,加入一個用於啟動MyIntentService這個服務的按鈕,如下所示:

 


然後修改MainActivity中的代碼,如下所示:

 

 

package com.jack.servicetest;

//import com.jack.servicetest.MyService.DownloadBinder;

import android.app.Activity;
import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.util.Log;
import android.view.Menu;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;

public class MainActivity extends Activity implements OnClickListener{

	private Button startService;
	private Button stopService;
	private Button bindService;
	private Button unbindService;
	private Button startIntentService;
	//private MyService.DownloadBinder downloadBinder;
	private ServiceConnection connection=new ServiceConnection() {
		/*
		 * 這裡創建了一個ServiceConnection的匿名類,在這裡重寫了onServiceConnected方法和
		 * onServiceDisconnected方法,這兩個方法分別會在活動與服務成功綁定以及解除綁定的時候調用。
		 * 在onServiceConnected方法中,我們又通過向下轉型得到了DownloadBinder的實例,有了這個
		 * 實例,活動和服務之間的關系就變得非常緊密了,現在我們可以在活動中根據具體的場景來調用DownloadBinder
		 * 中的任何public方法,及實現了指揮服務干什麼,服務就干什麼的功能,這裡只做了簡單的測試,在onServiceConnected
		 * 中調用了DownloadBinder的startDownload(),getProgress()方法。
		 * */
		@Override
		public void onServiceDisconnected(ComponentName name) {
			// TODO Auto-generated method stub
			
		}
		
		@Override
		public void onServiceConnected(ComponentName name, IBinder service) {
			// TODO Auto-generated method stub
			/*downloadBinder=(MyService.DownloadBinder) service;
			downloadBinder.startDownload();
			downloadBinder.getProgress();*/
		}
	};
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		startService=(Button) findViewById(R.id.start_service);
		stopService=(Button) findViewById(R.id.stop_service);
		startService.setOnClickListener(this);
		stopService.setOnClickListener(this);
		
		bindService = (Button) findViewById(R.id.bind_service);
		unbindService = (Button) findViewById(R.id.unbind_service);
		bindService.setOnClickListener(this);
		unbindService.setOnClickListener(this);
		
		
		startIntentService=(Button) findViewById(R.id.start_intent_service);
		startIntentService.setOnClickListener(this);
		
		
	}

	@Override
	public boolean onCreateOptionsMenu(Menu menu) {
		// Inflate the menu; this adds items to the action bar if it is present.
		getMenuInflater().inflate(R.menu.main, menu);
		return true;
	}

	@Override
	public void onClick(View v) {
		// TODO Auto-generated method stub
		switch(v.getId()){
		case R.id.start_service:
			Intent startIntent =new Intent(this,MyService.class);
			startService(startIntent);//啟動服務
			break;
		case R.id.stop_service:
			Intent stopIntent =new Intent(this,MyService.class);
			stopService(stopIntent);//停止服務
			break;
		case R.id.bind_service:
			/*
			 *現在我們需要進行活動和服務的綁定,構建一個Intent對象,然後調用bindService()方法將
			 *MainActivity()和MyService進行綁定。 bindService方法接收三個參數,第一個參數就是
			 *上面創建出的Intent對象,第二個參數就是前面創建出的ServiceConnection的實例,第三個
			 *參數則是一個標志位,這裡傳入BIND_AUTO_CREATE表示在活動和服務進行綁定後自動創建服務。
			 *這會使得MyService中的onCreate()方法得到執行,但onStartCommand()方法不會執行。
			 * */
			Intent bindIntent=new Intent(this,MyService.class);
	        bindService(bindIntent, connection, BIND_AUTO_CREATE);//綁定服務
			break;
		case R.id.unbind_service:
			/*
			 * 如果我們想解除活動和服務之間的綁定,調用一下unbindService()方法就可以了。
			 * */
			unbindService(connection);//解綁服務
			break;
		case R.id.start_intent_service:
			//打印主線程的id
			Log.d(MainActivity, Thread id is+Thread.currentThread().getId());
			Intent intentService=new Intent(this,MyIntentService.class);
			startService(intentService);
			break;
		default:
			break;
		}
	}

}

/*
 服務也有自己的生命周期,前面我們使用到的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()方法才會執行。
 */







 

可以看到,我們在start intentservice按鈕的點擊事件裡面去啟動MyIntentService這個服務,並在這裡打印了一下主線程的id,其實IntentService的用法和普通的服務沒什麼兩樣。

在AndroidManifest.xml裡注冊,如下所示:

 

 
        
        
    



重新運行程序,界面如下所示:

 

 

 

可以看到MyIntentService和MainActivity所在的線程id不一樣,而且onDestory()方法也得到了執行,說明MyIntentService在運行完畢後確實自動停止了。集開啟線程和自動停止於一身。

 

 

 

服務的最佳實踐---------後台執行的定時任務

Android中實現定時任務一般有兩種方式,一種是使用java api裡提供的Timer類,一種是使用android的Alarm機制。
這兩種方式在多數情況下都能實現類似的效果,但是Timer有一個明顯的短板,它並不太適用於那些需要長期在後台運行的定時任務。我們都知道,為了能讓電池更加耐用,每種手機都會有自己的休眠策略,andorid手機就會在長時間不操作的情況下自動讓cpu進入的到睡眠狀態,這就有可能導致Timer中的定時任務無法正常運行。而Alarm機制不存在這種情況,它具有喚醒cpu的功能,即可以保證每次需要執行定時任務的時候cpu都能正常工作。需要注意,這裡的喚醒cpu和喚醒屏幕完全不是同一個概念,不要弄混淆了。
我們來看看Alarm機制的用法吧,主要需要借助AlarmManager類來實現。這個類和NotificationManager有點類似,都是通過調用Context的getSystemService()方法來獲取實例的,只是這裡需要傳入的參數是Context.ALARM_SERVICE.
因此,獲取一個AlarmManager的實例就可以寫成:
AlarmManager manager=(AlarmManager)getSystemService(Context.ALARM_SERVICE);
接下來調用AarmManager的set()方法就可以設置一個定時任務了,比如說想要設定一個任務在10秒後執行,就可以寫成:
long triggerAtTime=SystemClock.elapsedRealtime()+10*1000;
manager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,triggerAtTime,pendingIntent);
第一個參數是一個整形參數,用於指定AlarmManager的工作類型,有四種值可選,分別是ELAPSED_REALTIME,ELAPSED_REALTIME_WAKEUP,
RTC 和 RTC_WAKEUP。其中ELAPSED_REALTIME表示讓定時任務的觸發從系統開機開始算起,但不會喚醒cpu。
ELAPSED_REALTIME_WAKEUP同樣表示讓定時任務的觸發時間從系統開機開始算起,但會喚醒cpu。
RTC表示讓定時任務的觸發時間從1970年1月1日0點開始算起,但不會喚醒cpu。

RTC_WAKEUP同樣表示讓定時任務的觸發時間從1970年1月1日0點開始算起,但會喚醒cpu。使用SystemClock.elapsedRealtime()方法
可以獲取到系統開機至今所歷經的毫秒數,使用System.currentTimeMillis()方法可以獲取到1970年1月1日0點
至今所經歷時間的毫秒數。
第二個參數就是定時任務觸發的時間,以毫秒為單位。如果第一個參數使用的是ELAPSED_REALTIME或ELAPSED_REALTIME_WAKEUP則這裡傳入開機至今的時間在加上延遲執行的時間。如果第一個參數使用的是RTC或RTC_WAKEUP,則這裡傳入1970年1月1日0點至今的時間再加上延遲執行的時間。
第三個參數是一個PendingIntent,對於它應該不會陌生了 吧。這裡我們一般會調用getBroadcast()方法來
獲取一個能夠執行廣播的PendingIntent。這樣當定時任務被觸發的時候,廣播接收器的onReceive()方法就可以得到執行。
了解了 set()方法的每個參數之後,你應該能想到,設定一個任務在10秒後執行還可以寫成:
long triggerAtTime=System.curentTimeMillis()+10*1000;
manager.set(AlarmManager.RTC_WAKEUP,triggerAtTime,pendingIntent);
現在已經掌握了Alarm機制的基本用法,下面我們就來創建一個可以長期在後台執行定時任務的服務。創建一個ServiceBestPractice項目,
然後新增一個LongRunningService類,代碼如下所示:

package com.jcak.servicebestpractice;

import java.util.Date;

import android.app.AlarmManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.SystemClock;
import android.util.Log;

public class LongRunningService extends Service {

	@Override
	public IBinder onBind(Intent intent) {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public int onStartCommand(Intent intent, int flags, int startId) {
		// TODO Auto-generated method stub
		new Thread(new Runnable(){

			@Override
			public void run() {
				// TODO Auto-generated method stub
				Log.d(LongRunningService,executed at +new Date().toString());
			}
			
		}).start();
		AlarmManager manager=(AlarmManager) getSystemService(ALARM_SERVICE);
		int anHour=10*1000;
		long triggerAtTime=SystemClock.elapsedRealtime()+anHour;
		Intent i=new Intent(this,AlarmReceiver.class);
		PendingIntent pi=PendingIntent.getBroadcast(this, 0, i, 0);
		manager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, 
				triggerAtTime, pi);
		
		return super.onStartCommand(intent, flags, startId);
	}

}


 

在onStartCommand()方法中開啟了一個子線程,然後在子線程裡就可以執行具體的邏輯操作了,這裡簡單的,只是打印了當前的時間。

創建線程之後的代碼就是上面講解的Alarm機制的用法,先是獲取到了AlarmManager的實例,然後定義任務的觸發時間為10秒,在使用PendingIntent指定處理定時任務的廣播接收器為AlarmReceiver,最後調用set()方法完成設定。顯然,AlarmReceiver不存在,我們就創建一個,代碼如下所示:

 

package com.jcak.servicebestpractice;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;

public class AlarmReceiver extends BroadcastReceiver {

	@Override
	public void onReceive(Context context, Intent intent) {
		// TODO Auto-generated method stub

		Intent i=new Intent(context,LongRunningService.class);
		context.startService(i);
	}

}

onReceiver()方法裡的代碼非常簡單,就是構建出了一個Intent對象,然後去啟動LongRunningService這個服務

 

。這就已經將一個長期服務在後台定時運行的服務完成了。因為一旦啟動了LongRunningService,就會在onStartCommand()方法裡設定一個定時任務,這樣10秒後AlarmReceiver的onReceive()方法就將得到執行了,然後我們在這裡再次啟動LongRunningService,這樣就形成了一個永久的循環,保證LongRunningService可以每隔10秒就會啟動一次,這個長期在後台運行的服務就完成了。

接下來,我們需要在打開程序的時候啟動一次LongRunningService,之後LongRunningService就可以一直運行了。修改MainActivity中的代碼,如下所示:

 

package com.jcak.servicebestpractice;

import android.os.Bundle;
import android.app.Activity;
import android.content.Intent;
import android.view.Menu;

public class MainActivity extends Activity {

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		Intent intent=new Intent(this,LongRunningService.class);
		startService(intent);
		
	}

	@Override
	public boolean onCreateOptionsMenu(Menu menu) {
		// Inflate the menu; this adds items to the action bar if it is present.
		getMenuInflater().inflate(R.menu.main, menu);
		return true;
	}

}

最後要注冊服務和廣播,代碼如下所示:

 

 




    

    
        
            
                

                
            
        
        
        
        
        
    



現在運行一下程序,然後觀察LogCat中打印的日志, 

 

 

 

可以看到LongRunningService每隔10秒打印一條日志。

另外需要注意的是,從android4.4版開始,Alarm任務的觸發時間將會變得不准確,有可能會延遲一段時間後任務才能得到執行。這並不是bug,而是系統在耗電方面進行的優化。系統會自動檢測目前有多少Alarm任務存在,然後將觸發時間將近的幾個任務存放在一起執行,這就可以大幅度減少cpu被喚醒的次數,從而有效延長電池的使用時間。

當然,如果要求Alarm任務的執行時間必須准確無誤,android仍然提供l解決方案。使用AlarmManager的setExact()方法來替代set()方法,就可以保證任務准時執行了。

 

 

 

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved