簡介
Service 是Android的四大組件之一,當應用程序希望在應用程序空閒的時候去運行一個耗時較長的操作,或者爲其他應用提供功能實現的時候可以使用Service來完成。每個service都必須在AndroidManifest.xml中有一個相應的聲明,Services 可以通過Context.bindService() 或者Context.startService()來啓動Service。
需要注意的是,Service與其他應用程序的對象(UI Activity...)一樣是運行在主進程的main Thread中,這就意味着,如果你的service打算做密集的CPU運算(比如播放媒體文件)或者做阻塞的操作(如聯網),都應該在Service中單獨開啓一個子線程來處理。而IntentService 繼承了Service,當你希望Service在一個單獨的線程中運行時,IntentService是一個標準的實現。
這篇文章涉及的主題:
- 什麼是Service?
- Service的生命週期
- Service 權限的定義與聲明
- 進程的聲明週期
- Local Service 範例
- Remote Messenger Service 範例
1.什麼是Service?
實際上,大多數的困惑都是在說Service 不是什麼:
1)Service不是一個單獨的進程,Service的對象實體並不一定運行在自己特有的進程中,它作爲應用程序的一部分與應用程序運行在同一個進程空間。
如果希望Service運行在自己獨立的進程:
a.可以在AndroidManifest.xml中通過android:process="" 爲Service 指定一個進程名稱,則Service會重新開啓一個進程。
b.即便是不在AndroidManifest.xml指定android:process="",如果某個應用程序A期望使用其他應用程序B中的Service,那麼B的中Service與A 肯定不在同一個進程中。
2)Service 不是一個線程,這就意味着它並不能脫離主線程(主要是爲了避免應用程序沒有反應的錯誤發生)
所以Service實際上非常簡單,提供了兩個主要的特性:
1)某個應用程序的功能告訴系統需要將一些事物放在後臺運行,只要調用了 Context.startService(),系統就會安排時間讓Service在後臺處理這些請求,除非你明確的去關閉Service,否則Service不會停止。
2)如果說應用程序A希望使用應用程序B中的Service完成某件事,需要調用Context.bindService()來建立連接,bindService()允許一種長期的連接來保證B中的Service能正常與它交互。
當Service這個組件已經創建,之後何時調用onCreate()和onBind()完全取決於Service自己,系統會讓Service在合適的時間點去調用這些方法
需要注意的是,因爲Service本身的設計非常簡單,你可以按照具體的需求來完善Service:讓它成爲一個本地的Java對象然後直接調用它的方法(可以參照下文中的 Local Service 範例),也可以通過AIDL提供一個完全遠程的接口。
2.Service 的生命週期
啓動Service的方式有兩種:
1)Context.startService()
context.startService() -> onCreate() -> onStartCommand(可能多次調用) -> Service running -> context.stopService() -> onDestroy() -> Service stop
如果Service尚未運行,Client執行context.startService()時,則調用吮吸onCreate() -> onStartCommand()。
如果Service已經運行,當Client多次執行context.startService(Intent service)時則只執行onStartCommand(),Client的數據可放在intent中傳遞到Service。
無論Client執行多少次context.startService(),Client只需要執行一次context.stopService()就能關閉Service(調用onDestroy())。
即使多次調用startService(Intent service),但是隻要你Intent中啓動的是同一個Service,則不會重新建立一個Service。
- onStartCommand() 方法是在2.0 以後才引入的,代替了已經不建議使用的onStart()。它提供了和onStart()一樣的功能,但是還允許你告訴系統,如果系統在顯示調用stopService或stopSelf之前終止了Service,該如何重啓Service。
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
startBackgroundTask(intent,startId);
// return Service.START_STICKY;
// or
// return Service.START_NOT_STICKY;
// or
return Service.START_REDELIVER_INTENT;
}
2)Context.bindService()
context.bindService() -> onCreate() -> onBind()(只執行一次) -> Service running -> onUnbind() -> onDestroy() -> Service stop
onBind()將返回給客戶端一個IBind接口實例,IBind 實際上可以簡單理解成某個定義在Service中的類A(這個接口至少需要extend IBind),它是通向Service的入口。當Client 拿到IBind之後可以將其向下轉型成類A,然後回調類A中的方法,類A中的方法就可以任意操作Service了。
bindService()會讓Client與Service綁定在一起,當所有連接到Service的Client退出時,Service也會退出。
Client接口IBind允許客戶端回調服務的方法,比如得到Service的實例、運行狀態或其他操作。這個時候把調用者(Context,例如Activity)會和Service綁定在一起,Context退出了,Srevice就會調用onUnbind->onDestroy相應退出。
3.Service 權限的定義與聲明
當Service 在它的AndroidManifest.xml <service> 標籤下聲明瞭android:permission="",則其他app在訪問該Service的時候必須在各自的AndroidManifest.xml <uses-permission>中聲明訪問權限,才能去start, stop, or bind service。
1)權限的定義
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
package="com.me.app.myapp" >
<permission
android:name="com.me.app.myapp.permission.DEADLY_ACTIVITY"
android:description="@string/permdesc_deadlyActivity"
android:label="@string/permlab_deadlyActivity"
android:permissionGroup="android.permission-group.COST_MONEY"
android:protectionLevel="dangerous" />
...
</manifest>
2)權限的聲明
<application>
...
<service
android:name="PrivService"
android:permission="com.me.app.myapp.permission.DEADLY_SERVICE" />
...
</application>
另外,Service也可以通過上述方式來保護它的IPC方法調用,在調用service 的某個方法前可以調用contex.checkCallingPermission()來檢查是否擁有執行權限。
4.進程的生命週期
只要一個進程的Service已經啓動或者有客戶端綁定到該Service,那麼Android 系統會儘可能保證該進程能正常運行。當系統的內存不足需要殺死一些現有的進程來釋放資源的時候,Service所在進程的優先級只有在下面幾種情況下才會更高一些:
- 如果Service正在執行onCreate(), onStartCommand(), or onDestroy()這些方法,Service所在的進程的優先級會提高成前臺進程,以保證這些代碼能正常執行。
- 如果Service 已經啓動,Service所在的進程的重要性只比用戶可見的進程低,但是比其他任何不可見的進程的優先級要高。因爲很少有進程對用戶來說是可見的(比如UI進程是可見的),所以只有內存極端缺乏的時候纔會終止Service所在的進程。
- 一旦有客戶端綁定到Service,只要綁定到service中的其中一個客戶端變成對用戶可見的進程,那麼也可以認爲service也是可見的。
一個已經啓動的service可以使用startForeground(int, Notification)方法將service切換到前臺線程狀態,這樣當系統內存不足時不會不會終止此service(但是理論上如果在內存極端缺乏的情況下還是會終止service進程)。 - 需要注意的是,如果你的service長時間運行,當系統出現內存緊張的情況下系統可能終止service的運行。如果發生這樣的情況,系統之後會重新啓動service。如果你重寫了onStartCommand()方法,並在此方法中去新開線程或者異步的執行一些操作,爲了避免service在運行的過程中被終止後重啓丟失intent數據,你可以在onStartCommand()中return Service.START_FLAG_REDELIVERY,這樣在系統重啓Service的時候會重新傳遞intent數據(否則重新啓動時inten數據的值爲null)。
5.Local Service 範例:
1)界面
package sunmedia.chenjian.localservice;
import android.app.Activity;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.widget.Toast;
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
doBindService();// 與service建立連接
}
private LocalService mBoundService;
private ServiceConnection mConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder service) {
mBoundService = ((LocalService.LocalBinder) service).getService();
Toast.makeText(MainActivity.this, R.string.local_service_connected,
Toast.LENGTH_SHORT).show();
}
public void onServiceDisconnected(ComponentName className) {
mBoundService = null;
Toast.makeText(MainActivity.this,
R.string.local_service_disconnected, Toast.LENGTH_SHORT)
.show();
}
};
private boolean mIsBound;
void doBindService() {
// 由於事先知道service與client運行在同一進程,所以可以直接指定類名來啓動service
bindService(new Intent(MainActivity.this, LocalService.class),
mConnection, Context.BIND_AUTO_CREATE);
mIsBound = true;
}
void doUnbindService() {
if (mIsBound) {
// 斷開連接
unbindService(mConnection);
mIsBound = false;
}
}
@Override
protected void onDestroy() {
super.onDestroy();
doUnbindService();
}
}
2)Service
package sunmedia.chenjian.localservice;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
import android.util.Log;
import android.widget.Toast;
public class LocalService extends Service {
public static final String NAME = "sunmedia.chen.jian.localservice";
private NotificationManager mNM;
// Unique Identification Number for the Notification.
// We use it on Notification start, and to cancel it.
private int NOTIFICATION = R.string.local_service_started;
/**
* 爲client提供訪問,因爲事先知道service與client運行在同一進程,所以不需要 考慮IPC的影響
*/
public class LocalBinder extends Binder {
LocalService getService() {
return LocalService.this;
}
}
@Override
public void onCreate() {
mNM = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
// 顯示service的啓動狀態,啓動時會在狀態現顯示一個圖標
showNotification();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.i("LocalService", "Received start id " + startId + ": " + intent);
// 由於我們希望service一直在運行,除非調用stop終止進程,所以返回值可以設置成
// START_STICKY
return START_STICKY;
}
@Override
public void onDestroy() {
// 取消在狀態欄的提示
mNM.cancel(NOTIFICATION);
Toast.makeText(this, R.string.local_service_stopped, Toast.LENGTH_SHORT)
.show();
}
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
// 用於與client交互,可以理解爲service的入口
private final IBinder mBinder = new LocalBinder();
/** * Show a notification while this service is running. */
private void showNotification() {
// 獲取用於狀態欄提示的字串
CharSequence text = getText(R.string.local_service_started);
// 設置狀態欄提示用的參數資源(圖片/文字/時間戳)
Notification notification = new Notification(R.drawable.stat_sample,
text, System.currentTimeMillis());
// PendingIntent 預先設置狀態被點擊時的動作
PendingIntent contentIntent = PendingIntent.getActivity(this, 0,
new Intent(this, Operation.class), 0);
// 設置狀態欄中持續顯示時的參數
notification.setLatestEventInfo(this,
getText(R.string.local_service_label), text, contentIntent);
// 發送Notification
mNM.notify(NOTIFICATION, notification);
}
}
3)operation
package sunmedia.chenjian.localservice;
import android.app.Activity;
import android.os.Bundle;
import android.widget.Toast;
//用於測試狀態欄被點擊時的動作
public class Operation extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.operation);
Toast.makeText(this, "Operation Create", Toast.LENGTH_LONG).show();
}
@Override
protected void onDestroy() {
// TODO Auto-generated method stub
super.onDestroy();
}
}
4)AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="sunmedia.chenjian.localservice"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk
android:minSdkVersion="17"
android:targetSdkVersion="17" />
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<activity
android:name="sunmedia.chenjian.localservice.MainActivity"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name="sunmedia.chenjian.localservice.Operation"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service
android:name="sunmedia.chenjian.localservice.LocalService"
android:enabled="true"
android:exported="false" >
<intent-filter android:name="LocalService" >
</intent-filter>
</service>
</application>
</manifest>
6.Remote Mesager Service範例
1)界面
package sunmedia.chenjian.remoteservice;
import android.app.Activity;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.Messenger;
import android.os.RemoteException;
import android.widget.TextView;
import android.widget.Toast;
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mCallbackText = (TextView)findViewById(R.id.TextView);
doBindService();
}
/** 用於與Service交互*/
Messenger mService = null;
/** 用於表示是否執行過bindService() */
boolean mIsBound;
/** 用於顯示當前連接的狀態 */
TextView mCallbackText;
/** 用於處理從Service 接收到的消息*/
class IncomingHandler extends Handler {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MessengerService.MSG_SET_VALUE:
mCallbackText.setText("Received from service: " + msg.arg1);
break;
default:
super.handleMessage(msg);
}
}
}
/** 用於發佈消息到客戶端. */
final Messenger mMessenger = new Messenger(new IncomingHandler());
/** 用於指示 Service 連接狀態 */
private ServiceConnection mConnection = new ServiceConnection() {
/** 當Service建立完後後會調用此方法,並將Service作爲參數傳遞進來,需要注意的是
* ServiceConnection中的方法都是在Activity所在的主線程調用的,也就是說其實是客
* 戶端的主線程不斷的輪詢Service的狀態再調用ServiceConnection中的方法。
* 導致不能連接的情況有幾種:
* 1)Service 沒有在AndroidManifest.xml中聲明或者啓動Service時名稱不正確
* 2)如果當前Activity所在的主線程被卡主,則無法正常調用此方法
* */
public void onServiceConnected(ComponentName className, IBinder service) {
mService = new Messenger(service);
mCallbackText.setText("Attached.");
//將mMessenger傳遞到Service方便Service發消息到客戶端
try {
Message msg = Message.obtain(null,
MessengerService.MSG_REGISTER_CLIENT);
msg.replyTo = mMessenger;
mService.send(msg);
//傳遞一個標識到Service,方便其測試連接
msg = Message.obtain(null, MessengerService.MSG_SET_VALUE,
this.hashCode(), 0);
mService.send(msg);
} catch (RemoteException e) {
//Service異常終止,可以選擇重新連接策略
}
Toast.makeText(MainActivity.this,
R.string.remote_service_connected, Toast.LENGTH_SHORT)
.show();
}
/**
* 當Service被異常終止的時,客戶端所在的主線程會調用此方法,之後系統會重新啓動
* Service並再次調用onServiceConnected()。
*/
public void onServiceDisconnected(ComponentName className) {
mService = null;
mCallbackText.setText("Disconnected.");
Toast.makeText(MainActivity.this,
R.string.remote_service_disconnected, Toast.LENGTH_SHORT)
.show();
}
};
void doBindService() {
Intent intent = new Intent(MessengerService.NAME);
bindService(intent,mConnection, Context.BIND_AUTO_CREATE);
mIsBound = true;
mCallbackText.setText("Binding.");
}
void doUnbindService() {
if (mIsBound) {
//如果客戶端正常退出,則需要註銷與Service的連接
if (mService != null) {
try {
Message msg = Message.obtain(null,
MessengerService.MSG_UNREGISTER_CLIENT);
msg.replyTo = mMessenger;
mService.send(msg);
} catch (RemoteException e) {
//Service已經異常終止
}
}
unbindService(mConnection);
mIsBound = false;
mCallbackText.setText("Unbinding.");
}
}
}
2)Service
package sunmedia.chenjian.remoteservice;
import java.util.ArrayList;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Intent;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.Messenger;
import android.os.RemoteException;
import android.widget.Toast;
public class MessengerService extends Service {
static final String NAME = "MyMessengerService";
/**用於在狀態欄顯示通知. */
NotificationManager mNM;
/** 用於記錄客戶端的連接情況*/
ArrayList<Messenger> mClients = new ArrayList<Messenger>();
/** 用於存儲最後一次連接的客戶端標識符 */
int mValue = 0;
/**
* 此命令用於註冊客戶端,Message的 replyTo欄位需要放置客戶端的Messenger,用於
* Service發消息到客戶端
* */
static final int MSG_REGISTER_CLIENT = 1;
/**
* 此命令用於註銷客戶端,Message的 replyTo欄位需要放置客戶端的Messenger,用於
* 將Messenger從記錄中移除。
*/
static final int MSG_UNREGISTER_CLIENT = 2;
/**
* 此命令主要用於測試Service與客戶端的連接
*/
static final int MSG_SET_VALUE = 3;
/** * Handler of incoming messages from clients. */
class IncomingHandler extends Handler {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_REGISTER_CLIENT:
mClients.add(msg.replyTo);
break;
case MSG_UNREGISTER_CLIENT:
mClients.remove(msg.replyTo);
break;
case MSG_SET_VALUE:
mValue = msg.arg1;
for (int i = mClients.size() - 1; i >= 0; i--) {
try {
mClients.get(i).send(
Message.obtain(null, MSG_SET_VALUE, mValue, 0));
} catch (RemoteException e) {
//如果發送消息給客戶端失敗,則認爲客戶端異常終止,將客戶端從
//記錄中移除
mClients.remove(i);
}
}
break;
default:
super.handleMessage(msg);
}
}
}
/** 用於與客戶端之間的信息交互 */
final Messenger mMessenger = new Messenger(new IncomingHandler());
@Override
public void onCreate() {
mNM = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
// Display a notification about us starting.
showNotification();
}
@Override
public void onDestroy() {
// Cancel the persistent notification.
mNM.cancel(R.string.remote_service_started);
// Tell the user we stopped.
Toast.makeText(this, R.string.remote_service_stopped,
Toast.LENGTH_SHORT).show();
}
/**
* 將服務端的Messenger發送給客戶端,方便客戶端傳遞消息給Service
*/
@Override
public IBinder onBind(Intent intent) {
return mMessenger.getBinder();
}
/** * Show a notification while this service is running. */
private void showNotification() {
// In this sample, we'll use the same text for the ticker and the
// expanded notification
CharSequence text = getText(R.string.remote_service_started);
// Set the icon, scrolling text and timestamp
Notification notification = new Notification(R.drawable.stat_sample,
text, System.currentTimeMillis());
// The PendingIntent to launch our activity if the user selects this
// notification
PendingIntent contentIntent = PendingIntent.getActivity(this, 0,
new Intent(this, Operation.class), 0);
// Set the info for the views that show in the notification panel.
notification.setLatestEventInfo(this,
getText(R.string.remote_service_label), text, contentIntent);
// Send the notification.
// We use a string id because it is a unique number. We use it later
// to cancel.
mNM.notify(R.string.remote_service_started, notification);
}
}
3)AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="sunmedia.chenjian.remoteservice"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk
android:minSdkVersion="8"
android:targetSdkVersion="17" />
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<activity
android:name="sunmedia.chenjian.remoteservice.MainActivity"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name="sunmedia.chenjian.remoteservice.Operation"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service
android:name="sunmedia.chenjian.remoteservice.MessengerService"
android:enabled="true"
android:exported="true"
android:process=":remote_service" >
<intent-filter>
<action android:name="MyMessengerService" />
</intent-filter>
</service>
</application>
</manifest>
個人原創:轉載請註明出處:http://blog.csdn.net/chenjianjk/article/details/9499271
Local Service、Remote Service代碼下載地址(中文的編碼格式爲UTF-8):
http://download.csdn.net/detail/chenjianjk/5826495