Android Service解析 轉http://zy77612.iteye.com/blog/1292649
什麼是Service?
解惑:
1、 Service不是分離開的進程,除非其他特殊情況,它不會運行在自己的進程,而是作爲啓動運行它的進程的一部分。
2、 Service不是線程,這意味着它將在主線程裏勞作。
啓動service有兩種方法:
1、 Context.startService()
調用者與服務之間沒有關聯,即使調用者退出,服務仍可運行
2、 Context.bindService()
調用者與服務綁定在一起,調用者一旦退出,服務也就終止
Service的生命週期
如果使用startService()啓動service,系統將通過傳入的Intent在底層搜索相關符合Intent裏面信息的service。如果服務沒有啓動則先運行onCreate,然後運行onStartCommand (可在裏面處理啓動時傳過來的Intent和其他參數),直到明顯調用stopService或者stopSelf纔將停止Service。無論運行startService多少次,只要調用一次stopService或者stopSelf,Service都會停止。使用stopSelf(int)方法可以保證在處理好intent後再停止。
控制service運行的主要方式有兩種,主要是根據onStartCommand方法返回的數值。方法:
2、START_NOT_STICKY
or START_REDELIVER_INTENT
這裏主要解釋這三個變量的意義:
1、 START_STICKY
在運行onStartCommand後service進程被kill後,那將保留在開始狀態,但是不保留那些傳入的intent。不久後service就會再次嘗試重新創建,因爲保留在開始狀態,在創建 service後將保證調用onstartCommand。如果沒有傳遞任何開始命令給service,那將獲取到null的intent
2、 START_NOT_STICKY
在運行onStartCommand後service進程被kill後,並且沒有新的intent傳遞給它。Service將移出開始狀態,並且直到新的明顯的方法(startService)調用才重新創建。因爲如果沒有傳遞任何未決定的intent那麼service是不會啓動,也就是期間onstartCommand不會接收到任何null的intent。
3、 START_REDELIVER_INTENT
在運行onStartCommand後service進程被kill後,系統將會再次啓動service,並傳入最後一個intent給onstartCommand。直到調用stopSelf(int)才停止傳遞intent。如果在被kill後還有未處理好的intent,那被kill後服務還是會自動啓動。因此onstartCommand不會接收到任何null的intent。
客戶端也可以使用bindService來保持跟service持久關聯。謹記:如果使用這種方法,那麼將不會調用onstartCommand(跟startService不一樣,下面例子註釋也有解析,大家可試試)。客戶端將會在onBind回調中接收到IBinder接口返回的對象。通常IBinder作爲一個複雜的接口通常是返回aidl數據。
Service也可以混合start和bind一起使用。
權限
要運行service,首先必須在AndroidManifest.xml
裏申明<service>標籤。
Service能夠保護個人的IPC調用,所以在執行實現該調用時前先使用checkCallingPermission(String)
方法
檢查是否有這個權限。
進程生命週期
當service運行在低內存的環境時,將會kill掉一下存在的進程。因此進程的優先級將會很重要:
1、 如果service當前正在執行onCreate、onStartCommand、onDestroy方法,主進程將會成爲前臺進程來保證代碼可以執行完成避免被kill
2、 如果service已經啓動了,那麼主進程將會比其他可見的進程的重要性低,但比其他看不見的進程高。因爲只有少部分進程始終是用戶可見的,因此除非在極度低內存的時候,不然 service是不會被kill的。
3、 如果有客戶端關聯到service,那麼service永遠比客戶端重要。也就是說客戶端可見,那麼service也可見(我理解這裏的可見並不是可以看到,而是重要性,因爲可見往往就表示重要性高)。
4、 Service可以使用startForeground API將service放到前臺狀態。這樣在低內存時被kill的機率更低,但是文檔後面又寫了,如果在極度極度低內存的壓力下,該service理論上還是會被kill掉。但這個情況基本不用考慮。
當然如果service怎麼保持還是被kill了,那你可以通過重寫onStartCommand返回變量來設置它的啓動方式。比如:START_STICKY、START_REDELIVER_INTENT等等,前面已經討論了它們的作用,這裏就不再累贅了
另外:
service 的onCreate和onStartCommand 是運行在主線程的,所以如果裏面有處理耗時間的任務。兩種處理:
1、 請將它們都挪到新的線程裏。
2、 用系統提供的IntentService,它繼承了Service,它處理數據是用自身新開的線程。
好了說了這麼多下面就是例子的時刻了。總共有兩個例子,第一個是本地調用,第二個是遠程調用.
口水快沒了,所以下面就直接進入代碼環節吧。代碼裏面已經有詳細的註解了,如果真的真的還是不明白,那就是我這篇二手鞋的失敗了。:(
public class LocalService extends Service {
private NotificationManager mNM;
// 通知唯一標示,在通知開始和結束使用
private int NOTIFICATION = R.string.local_service_started;
// 與界面交互的類,由於service跟界面總是運行在同一程序裏,所以不用處理IPC
public class LocalBinder extends Binder {
LocalService getService() {
return LocalService.this;
}
}
@Override
public void onCreate() {
mNM = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
// 在service開始時,將icon圖標放到通知任務欄
showNotification();
}
//
private void showNotification() {
CharSequence text = getText(R.string.local_service_started);
Notification notification = new Notification(R.drawable.icon, text,
System.currentTimeMillis());
// 當點擊通知時,啓動該contentIntent關聯的activity
PendingIntent contentIntent = PendingIntent.getActivity(this, 0,
new Intent(this, showActivity.class), 0);
// 在通知欄上顯示標題和內容
notification.setLatestEventInfo(this,
getText(R.string.local_service_label), text, contentIntent);
mNM.notify(NOTIFICATION, notification);
}
// 兼容2.0以前版本
@Override
public void onStart(Intent intent, int startId) {
}
// 在2.0以後的版本如果重寫了onStartCommand,那onStart將不會被調用,注:在2.0以前是沒有onStartCommand方法
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.i("Service", "Received start id " + startId + ": " + intent);
// 如果服務進程在它啓動後(從onStartCommand()返回後)被kill掉, 那麼讓他呆在啓動狀態但不取傳給它的intent.
// 隨後系統會重寫創建service,因爲在啓動時,會在創建新的service時保證運行onStartCommand
// 如果沒有任何開始指令發送給service,那將得到null的intent,因此必須檢查它.
// 該方式可用在開始和在運行中任意時刻停止的情況,例如一個service執行音樂後臺的重放
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;
}
private final IBinder mBinder = new LocalBinder();
}
public class LocalActivity extends Activity {
/** Called when the activity is first created. */
private LocalService mBoundService;
private boolean mIsBound;
private ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceDisconnected(ComponentName name) {
// 當進程崩潰時將被調用,因爲運行在同一程序,如果是崩潰將所以永遠不會發生
// 當解除綁定時也被調用
mBoundService = null;
Toast.makeText(LocalActivity.this,
R.string.local_service_disconnected, Toast.LENGTH_SHORT)
.show();
}
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
// service連接建立時將調用該方法
mBoundService = ((LocalService.LocalBinder) service).getService();
Toast.makeText(LocalActivity.this,
R.string.local_service_connected, Toast.LENGTH_SHORT)
.show();
}
};
void doBindService() {
// 建立service連接。因爲我們知道程序會運行在本地裏,因此使用顯示的類名來實現service
// (但是不支持跟其他程序交互)
// 兩種傳遞,一種是在manifest裏寫好intent-filter的action,一種是顯示傳遞
// bindService(new Intent("com.LocalService.LocalService"), mConnection,
// Context.BIND_AUTO_CREATE);
// bindService(new Intent(LocalActivity.this, LocalService.class),
// mConnection, Context.BIND_AUTO_CREATE);
//如果用這種方法將會調用onStartCommand方法
startService(new Intent(LocalActivity.this, LocalService.class));
mIsBound = true;
}
void doUnbindService() {
if (mIsBound) {
// Detach our existing connection.
stopService(new Intent(LocalActivity.this, LocalService.class));
// unbindService(mConnection);
mIsBound = false;
}
}
@Override
protected void onDestroy() {
doUnbindService();
super.onDestroy();
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
doBindService();
}
}
大家可以試試startService 和bindService這兩種區別。
輕鬆一下,Toast.makeText(this, "溫馨提示:\n代碼已經寫好了,如果想測試一下可以去掉註釋的喔", 2000).show(); ^-^
下面是遠程調用的例子,主要是用系統提供的Messenger,省去自己去寫複雜的aidl文件
<service android:name="MessengerService" android:process=":remote">
<intent-filter>
<action android:name="com.LocalService.MessengerService" />
</intent-filter>
</service>
如果加了android:process=":remote" ,那在調試時在service斷點是不會觸發的。
public class MessengerService extends Service {
private NotificationManager mNM;
// 保存所有跟服務連接的客戶端
ArrayList<Messenger> mClients = new ArrayList<Messenger>();
// 保存最後一次跟服務連接的客戶端的標誌
int mValue = 0;
// 註冊指令,Message's replyTo 字段值必須是client 的Messenger
static final int MSG_REGISTER_CLIENT = 1;
// 取消指令,Message's replyTo 字段值必須是先前給MSG_REGISTER_CLIENT的Messenger
static final int MSG_UNREGISTER_CLIENT = 2;
// 服務發送指令,可以在客戶端和服務直接交流
static final int MSG_SET_VALUE = 3;
// 處理客戶端傳送過來的消息
class IncomingHandler extends Handler {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_REGISTER_CLIENT:
// Optional Messenger where replies to this message can be sent.
// The semantics of exactly how this is used are up to the
// sender and receiver.
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) {
// 遠程客戶端出錯,從list中移除
// 遍歷列表以保證內部循環安全運行
mClients.remove(i);
}
}
break;
default:
super.handleMessage(msg);
}
Log.i("Service", "有" + mClients.size() + "客戶端");
}
}
// 創建一個新的Messenger跟已存在的Handler關聯
// 如果有任何消息發送到Messenger,將交給Handler處理
final Messenger mMessenger = new Messenger(new IncomingHandler());
@Override
public void onCreate() {
mNM = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
// 在service開始時,將icon圖標放到通知任務欄
showNotification();
}
//
private void showNotification() {
CharSequence text = getText(R.string.local_service_started);
Notification notification = new Notification(R.drawable.icon, text,
System.currentTimeMillis());
// 當點擊通知時,啓動該contentIntent關聯的activity
PendingIntent contentIntent = PendingIntent.getActivity(this, 0,
new Intent(this, showActivity.class), 0);
// 在通知欄上顯示標題和內容
notification.setLatestEventInfo(this,
getText(R.string.remote_service_label), text, contentIntent);
mNM.notify(R.string.remote_service_started, notification);
}
//
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.i("Service", "Received start id " + startId + ": " + intent);
return START_STICKY;
}
@Override
public void onDestroy() {
mNM.cancel(R.string.remote_service_started);
Toast.makeText(this, R.string.remote_service_stopped,
Toast.LENGTH_SHORT).show();
}
@Override
public IBinder onBind(Intent intent) {
return mMessenger.getBinder();
}
}
public class MessengerActivity extends Activity {
/** Called when the activity is first created. */
private Messenger mService = null;
private boolean mIsBound;
private TextView mCallbackText;
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());
private ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceDisconnected(ComponentName name) {
// 當進程崩潰時將被調用,因爲運行在同一程序,如果是崩潰將所以永遠不會發生
// 當解除綁定時也被調用
mService = null;
mCallbackText.setText("Disconnected.");
Toast.makeText(MessengerActivity.this,
R.string.remote_service_disconnected, Toast.LENGTH_SHORT)
.show();
}
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
// service連接建立時將調用該方法
// 返回IBinder接口以便我們可以跟service關聯。
// 我們可通過IDL接口來交流
mService = new Messenger(service);
mCallbackText.setText("Attached.");
// 只有我們連接着都監聽着服務
try {
// 註冊
Message msg = Message.obtain(null,
MessengerService.MSG_REGISTER_CLIENT);
msg.replyTo = mMessenger;
mService.send(msg);
// 例子
msg = Message.obtain(null, MessengerService.MSG_SET_VALUE,
11111111, 0);
mService.send(msg);
} catch (RemoteException e) {
// In this case the service has crashed before we could even
// do anything with it; we can count on soon being
// disconnected (and then reconnected if it can be restarted)
// so there is no need to do anything here.
}
Toast.makeText(MessengerActivity.this,
R.string.remote_service_connected, Toast.LENGTH_SHORT)
.show();
}
};
void doBindService() {
bindService(new Intent(MessengerActivity.this, MessengerService.class),
mConnection, Context.BIND_AUTO_CREATE);
mIsBound = true;
mCallbackText.setText("Binding.");
}
void doUnbindService() {
if (mIsBound) {
if (mService != null) {
try {
// 取消註冊
Message msg = Message.obtain(null,
MessengerService.MSG_UNREGISTER_CLIENT);
msg.replyTo = mMessenger;
mService.send(msg);
} catch (RemoteException e) {
// There is nothing special we need to do if the service
// has crashed.
}
}
// Detach our existing connection.
unbindService(mConnection);
mIsBound = false;
mCallbackText.setText("Unbinding.");
}
}
@Override
protected void onDestroy() {
doUnbindService();
super.onDestroy();
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.messenger);
mCallbackText = (TextView) findViewById(R.id.text);
doBindService();
}
}
測試遠程調用,我弄多一份項目來測試,主要是查看是否連接成功和有多少個客戶端連接上.