安卓服務Service詳解

service(服務)是安卓中的四大組件之一,它通常用作在後臺處理耗時的邏輯,與Activity一樣,它存在自己的生命週期,也需要在清單文件中配置相關信息,本博客將對Service的各個知識點進行詳細講解。



一Service的基本用法:

1使用本地服務

1)服務的啓動方式

1通過Context的startService()方法啓動服務:以該方法啓動的服務,開啓該服務的應用組件(如Activity)與該Service不存在關聯關係,即使開啓該服務的Activity被銷燬,Service任能夠一直在後臺運行。通常,開啓的服務執行一個單獨的操作且不需向調用者返回一個結果。比如,可能從網絡進行下載或者上傳一個文件。當任務完成,服務就該自我停止。使用服務於使用Activity非常相似,都是先繼承其對應的基類,然後重寫其中重要的方法,這些方法就是關於其生命週期回調的方法。代碼如下所示:

public class MyService extends Service {

	public static final String TAG = "MyService";

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

	@Override
	public int onStartCommand(Intent intent, int flags, int startId) {
		Log.d(TAG, "onStartCommand() executed");
		return super.onStartCommand(intent, flags, startId);
	}
	
	@Override
	public void onDestroy() {
		super.onDestroy();
		Log.d(TAG, "onDestroy() executed");
	}

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

}
然後再Activity中使用

 Intent startIntent = new Intent(this, MyService.class);  
 startService(startIntent); 
即可開啓該服務,程序運行結果如下:


從程序的運行結果來看,可以知道當啓動一個Service的時候,會調用該Service中的onCreate()和onStartCommand()方法。

當我們在次點擊啓動服務的按鈕,程序運行結果如下:


可以看到,此時只輸出onStartCommand() executed。這說明此時只執行了onStartCommand()方法,而未執行onCreate(),這說明onCreate()方法只會在Service第一次被創建的時候調用,如果當前Service已經被創建過了,則即使多次調用startService()方法,onCreate()方法都不會再執行,這一點非常類似數據庫操作中的open一個數據庫。

當然上述的例子僅僅只是爲了說明上述知識點,因爲Service中的代碼也僅僅只是打印出log而已,而事實上Service的使用是爲了處理一些耗時操作的,如網絡請求,文件上傳與下載,但都是重寫其某個生命週期函數,如onStart(Intent intent, int startId),onDestroy()在這些函數中完成自己的業務邏輯的處理,下面的代碼是使用服務來進行網絡通信的一個例子。

public class GetMsgService extends Service {
	private Client client;
	private boolean isStart;
	private SharePreferenceUserInfoUtil util;
	private ClientInputThread cit;
	@Override
	public IBinder onBind(Intent intent) {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public void onCreate() {
		// TODO Auto-generated method stub
		super.onCreate();
		client=((MyApplication) getApplication()).getClient();
	}

	@Override
	public void onStart(Intent intent, int startId) {
		// TODO Auto-generated method stub
		super.onStart(intent, startId);
		util = new SharePreferenceUserInfoUtil(getApplicationContext(),
				Constants.SAVE_USER);
	   
		new Thread(){
			   public void run()
			   {
					try {
						isStart=client.create();
					} catch (UnknownHostException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					} catch (IOException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
			//在服務中接受來自服務器端的消息,然後通過廣播的形式傳遞給相應的Activity處理。接受服務器端的消息一般在
			//服務中,因爲服務可以在後臺一直運行
									
				if(isStart)
				{
					cit=client.getClientInputThread();
					if(cit!=null)
					{
						cit.setMessageListener(new MessageListener() {
							
							public void getMessage(TransportObject msg) {
								
								if(msg!=null&&msg instanceof TransportObject)
								{
									//通過廣播向Activity傳遞消息
									Intent intent=new Intent();
									intent.setAction(Constants.ACTION_MSG);
									intent.putExtra(Constants.MSG, msg);
								    sendBroadcast(intent);
								}
							}
						});
					}
					else {
						Log.i("GetMsgService","服務器端連接暫時出錯");
					//	Toast.makeText(getApplicationContext(), "服務器端連接暫時出錯,請稍後重試!",0).show();
					}
				}
				  
			   }
		   }.start();
	}

	@Override
	public void onDestroy() {
		// TODO Auto-generated method stub
		super.onDestroy();
		ClientOutputThread out=client.getClientOutputThread();
		TransportObject<User> msg=new TransportObject<User>(TranObjectType.LOGOUT);
		User user=new User();
		user.setId(Integer.parseInt(util.getId()));
		msg.setObject(user);
		out.setMsg(msg);
		//關閉服務時關閉client
		out.setStart(false);
		client.getClientInputThread().setStart(false);
		
		
		
	}
	
	

}
可以看到,我們在getMsgService的onStart方法中開啓了一個線程用來進行客戶端從服務器端讀取信息的操作,在onDestroy()方法中關閉網絡請求的操作。

2通過Context的bindService()方法啓動服務:顧名思義,以該方法啓動的服務,開啓該服務的應用組件(如Activity)與該Service被綁定在一起,通常用這種方式開啓的服務是爲了與開啓該服務的應用組件(如Activity)進行消息通信。

首先我們來看一下bindService的簽名:

bindService(Intent service, ServiceConnection conn, int flags)
其中service參數是通過intent指定要啓動的Service。

conn參數是一個ServiceConnection對象,該對象用於監聽訪問者與service之間的連接情況,當訪問者與Service連接成功時會回調該類的onServiceConnected(ComponentName name, IBinder service)方法,然後將服務中創建的Ibinder對象(此時在Service的onBinder方法中需要返回該Ibinder對象)傳遞給第二個參數service,通過該Ibinder對象就能與Service進行通信。

第三個參數flags指定綁定時是否自動創建Service,一般我們指定爲BIND_AUTO_CREATE(自動創建,如果傳入0表示不自動創建)示例代碼如下:

Service中的代碼:

public class MyService extends Service {

	public static final String TAG = "MyService";

	private MyBinder mBinder = new MyBinder();

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

	@Override
	public int onStartCommand(Intent intent, int flags, int startId) {
		Log.d(TAG, "onStartCommand() executed");
		return super.onStartCommand(intent, flags, startId);
	}

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

	@Override
	public IBinder onBind(Intent intent) {//在onBind(Intent intent)中返回IBinder對象
		return mBinder;
	}

	class MyBinder extends Binder {//定義一個類實現IBinder接口( Binder實現了IBinder接口)

		public void doSomething() {
			Log.d("TAG", "doSomething() executed");
			
		}

	}

}
Activity中的代碼:

public class MainActivity extends Activity
{
  ...
private ServiceConnection connection = new ServiceConnection() {  
  
        @Override  
        public void onServiceDisconnected(ComponentName name) {  
        }  
  
        @Override  
        public void onServiceConnected(ComponentName name, IBinder service) {  
            myBinder = (MyService.MyBinder) service;  
            myBinder.doSomething();  
        }  
    };  
    protected void onCreate(Bundle savedInstanceState) {  
        super.onCreate(savedInstanceState);  
        setContentView(R.layout.activity_main);  
	Intent bindIntent = new Intent(this, MyService.class);  
        bindService(bindIntent, connection, BIND_AUTO_CREATE);//此時使用bindService開啓服務
    }
  ...

}
即在服務中定義一個IBinder的實例,然後在Service的public IBinder onBind(Intent intent)方法中將其返回,在Activity中定義一個ServiceConnection類的實例,在其onServiceConnected(ComponentName name, IBinder service)方法中獲取Service中 返回IBinder對象,利用該對象即可調用Service中的方法進行相互通信。

注意:一個服務在進程中的主線程運行——一個服務不會創建自己的線程,也不會在另外的進程運行(除非另外指定)。這意味着,如果服務需要做一些頻繁佔用CPU的工作或者會發生阻塞的操作,你需要在服務中另開線程執行任務。避免程序出現ANR。


2使用AIDL跨進程調用服務:

此種情況與上述介紹的使用bindService方法開啓的綁定本地的大的框架基本相同,只不過使用AIDL來實現跨進程調用,關於此種情況的介紹,請參看我的博客:安卓中不同APP之間的消息通信中相關的內容。



二Service與線程的關係及IntentService:

事實上Service與線程之間沒多大關係,我們之所以把Service與線程放在一起談論,是爲了更清楚的明白在哪些情況下用服務哪些情況下用線程,哪些情況下在服務中開啓一個線程,因爲Service默認在主線程中運行,不能進行耗時操作,這也是IntentService存在的原因。因爲IntentService會創建單獨的worker線程來處理intent請求,不需要自己創建一個子線程。

IntentService處理流程
創建默認的一個 worker 線程處理傳遞給 onStartCommand() 的所有 intent ,不佔據應用的主線程
創建一個工作隊列一次傳遞一個 intent 到你實現的 onHandleIntent() 方法,避免了多線程
在所有啓動請求被處理後自動關閉服務,不需要調用 stopSelf()
默認提供 onBind() 的實現,且返回 null
默認提供 onStartCommand() 的實現,實現發送intent到工作隊列再到onHandleIntent() 方法實現。

正因爲如此,所以使用IntentService無需重寫onBind(),onStartCommand(),只需重寫onHandleIntent() 即可。示例代碼如下:

public class HelloIntentService extends IntentService {
  /**
   * A constructor is required, and must call the super IntentService(String)
   * constructor with a name for the worker thread.
   */
  public HelloIntentService() {
    super("HelloIntentService");
  }
  /**
   * The IntentService calls this method from the default worker thread with
   * the intent that started the service. When this method returns, IntentService
   * stops the service, as appropriate.
   */
  @Override
  protected void onHandleIntent(Intent intent) {
    // Normally we would do some work here, like download a file.
    // For our sample, we just sleep for 5 seconds.
    long endTime = System.currentTimeMillis() + 5*1000;
    while (System.currentTimeMillis() < endTime) {
      synchronized (this) {
        try {
          wait(endTime - System.currentTimeMillis());
        } catch (Exception e) {
        }
      }
    }
  }
}


三Service的生命週期

我們先來看一下谷歌官方圖片:


從上述圖片可以看到兩種不同的啓動方式其生命週期也不同:

啓動的服務: 
startService()->onCreate()->onStartCommand()->running->stopService()/stopSelf()->onDestroy()->stopped 
其中,服務未運行時會調用一次onCreate(),運行時不調用。
綁定的服務: 
bindService()->onCreate()->onBind()->running->onUnbind()->onDestroy()->stopped
服務起始於 onCreate() ,終止於 onDestory()

服務的開關過程,只有 onStartCommand() 可多次調用,其他在一個生命週期只調用一次。

這兩個過程不是完全獨立,也可以綁定一個由 startService() 啓動過的服務



四如何創建不被系統殺死的服務

服務不被殺死包括三種情況

1.系統根據資源分配情況殺死服務

2.用戶通過 settings -> Apps -> Running -> Stop 方式殺死服務
3.用戶通過 settings -> Apps -> Downloaded -> Force Stop 方式殺死服務
第一種情況:

用戶不干預,完全靠系統來控制,辦法有很多。比如 onStartCommand() 方法的返回值設爲 START_STICKY ,服務就會在資源緊張的時候被殺掉,然後在資源足夠的時候再恢復。當然也可設置爲前臺服務,使其有高的優先級,在資源緊張的時候也不會被殺掉。

關於 onStartCommand() 方法的返回值做一下簡單的介紹:

START_STICKY:如果service進程被kill掉,保留service的狀態爲開始狀態,但不保留遞送的intent對象。隨後系統會嘗試重新創建service,由於服務狀態爲開始狀態,所以創建服務後一定會調用onStartCommand(Intent,int,int)方法。如果在此期間沒有任何啓動命令被傳遞到service,那麼參數Intent將爲null。
START_NOT_STICKY:“非粘性的”。使用這個返回值時,如果在執行完onStartCommand後,服務被異常kill掉,系統不會自動重啓該服務。
START_REDELIVER_INTENT:重傳Intent。使用這個返回值時,如果在執行完onStartCommand後,服務被異常kill掉,系統會自動重啓該服務,並將Intent的值傳入。
START_STICKY_COMPATIBILITY:START_STICKY的兼容版本,但不保證服務被kill後一定能重啓。

第二種情況:
用戶干預,主動殺掉運行中的服務。這個過程殺死服務會通過服務的生命週期,也就是會調用 onDestory() 方法,這時候一個方案就是在 onDestory() 中發送廣播開啓自己。這樣殺死服務後會立即啓動。如下:

在onCreate中註冊廣播,用來開啓服務,在服務的onDestroy()中發送廣播通知服務開啓自己,代碼如下:

public void onCreate() {
  // TODO Auto-generated method stub
  super.onCreate();
  mBroadcast = new BroadcastReceiver() {
    @Override
    public void onReceive(Context context, Intent intent) {
      // TODO Auto-generated method stub
      Intent a = new Intent(ServiceA.this, ServiceA.class);
      startService(a);
    }
  };
  mIF = new IntentFilter();
  mIF.addAction("listener");
  registerReceiver(mBroadcast, mIF);
}
@Override
public void onDestroy() {
  // TODO Auto-generated method stub
  super.onDestroy();
  Intent intent = new Intent();
  intent.setAction("listener");
  sendBroadcast(intent);
  unregisterReceiver(mBroadcast);
}

第三種情況:
強制關閉就不好解決。這個好像是從包的level去關的,不是走的Service的完整的生命週期。所以在服務里加代碼是無法被調用的。處理這個情況的唯一方法是屏蔽掉 force stop 和 uninstall 按鈕,讓其不可用。

好了,以上就是本人理解的關於Service的相關知識,看官如果覺得不錯請不要吝嗇點擊一下下方的“頂”按鈕給我一點鼓勵哦!微笑


發佈了98 篇原創文章 · 獲贊 330 · 訪問量 118萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章