Service是Android中實現程序後臺運行的解決方案,它非常適合用於去執行那些不需要和用戶交互而且還要求長期運行的任務。
Service的運行不依賴於任何用戶界面,即使程序被切換到後臺或者用戶打開了另外一個應用程序,Service仍然能夠保持正常運行,這也正是Service的使用場景。當某個應用程序進程被殺掉時,所有依賴於該進程的Service也會停止運行。
Service的生命週期相對Activity來說簡單的多,只有3個,分別爲 onCreate、onStartCommand、onDestory。
一但在項目的任何位置調用了Context的startService()函數,相應的服務就會啓動起來。
首次創建調用 onCreate 函數
然後回調onStartCommand()函數。
服務啓動了之後會一直保持運行狀態,知道 stopService() 或者 stopSelf()函數被調用。
雖然每調用一次 starService() 函數,onStartCommand()就會執行一次,但實際上每個服務都只會存在一個實例。
所以不管你調用了多少次 startService() 函數,只需調用一個 stopService() 或 stopSelf()函數,服務就會被停止。
通常Service大致如下:
package com.example.administrator.onceuponaday.Service;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.support.annotation.IntDef;
import android.support.annotation.Nullable;
public class MyService extends Service {
@Override
public int onStartCommand(Intent intent,int flags, int startId) {
return super.onStartCommand(intent, flags, startId);
}
private void doMyJob(Intent intent){
//從Intent中獲取數據
//執行相關操作
new Thread(){
@Override
public void run() {
//耗時操作
super.run();
}
}.start();
}
@Override
public IBinder onBind(Intent intent) {
// TODO: Return the communication channel to the service.
throw new UnsupportedOperationException("Not yet implemented");
}
}
與Activity一樣,Service也需要在AndroidManifest.xml中進行註冊,實例如下:
上述示例表示註冊一個在應用包service目錄下的MyService服務,註冊之後,當用戶調用 startService(new Intent(mClass,MyService))時,會調用 onStartCommand 函數,我們在該函數中調用 doMyJob,而在 doMyJob 中我們創建了一個線程來執行耗時操作,以避免阻塞UI線程。
當我們的Service完成使命時,需要調用stopService來停止該服務。
IntentService
完成一個簡單的後臺任務需要這麼麻煩,Android顯然早就“洞察”了這一點。因此,提供了一個IntentService,來完成這樣的操作,IntentService將用戶的請求執行在一個子線程中,用戶只需要複寫 onHandleIntent函數,並且在該函數中完成自己的耗時操作即可。需要注意的是,在任務執行完畢之後
IntentService 會調用 stopSelf 自我銷燬,因此,它適用於完成一些短期的耗時任務。
示例:
package com.example.administrator.onceuponaday.Service;
import android.app.IntentService;
import android.content.Intent;
import android.content.Context;
/**
* An {@link IntentService} subclass for handling asynchronous task requests in
* a service on a separate handler thread.
* <p>
* TODO: Customize class - update intent actions, extra parameters and static
* helper methods.
*/
public class MyIntentService extends IntentService {
// TODO: Rename actions, choose action names that describe tasks that this
// IntentService can perform, e.g. ACTION_FETCH_NEW_ITEMS
private static final String ACTION_FOO = "com.example.administrator.onceuponaday.Service.action.FOO";
private static final String ACTION_BAZ = "com.example.administrator.onceuponaday.Service.action.BAZ";
// TODO: Rename parameters
private static final String EXTRA_PARAM1 = "com.example.administrator.onceuponaday.Service.extra.PARAM1";
private static final String EXTRA_PARAM2 = "com.example.administrator.onceuponaday.Service.extra.PARAM2";
public MyIntentService() {
super("MyIntentService");
}
/**
* Starts this service to perform action Foo with the given parameters. If
* the service is already performing a task this action will be queued.
*
* @see IntentService
*/
// TODO: Customize helper method
public static void startActionFoo(Context context, String param1, String param2) {
Intent intent = new Intent(context, MyIntentService.class);
intent.setAction(ACTION_FOO);
intent.putExtra(EXTRA_PARAM1, param1);
intent.putExtra(EXTRA_PARAM2, param2);
context.startService(intent);
}
/**
* Starts this service to perform action Baz with the given parameters. If
* the service is already performing a task this action will be queued.
*
* @see IntentService
*/
// TODO: Customize helper method
public static void startActionBaz(Context context, String param1, String param2) {
Intent intent = new Intent(context, MyIntentService.class);
intent.setAction(ACTION_BAZ);
intent.putExtra(EXTRA_PARAM1, param1);
intent.putExtra(EXTRA_PARAM2, param2);
context.startService(intent);
}
@Override
protected void onHandleIntent(Intent intent) {
//耗時的操作here
if (intent != null) {
final String action = intent.getAction();
if (ACTION_FOO.equals(action)) {
final String param1 = intent.getStringExtra(EXTRA_PARAM1);
final String param2 = intent.getStringExtra(EXTRA_PARAM2);
handleActionFoo(param1, param2);
} else if (ACTION_BAZ.equals(action)) {
final String param1 = intent.getStringExtra(EXTRA_PARAM1);
final String param2 = intent.getStringExtra(EXTRA_PARAM2);
handleActionBaz(param1, param2);
}
}
}
/**
* Handle action Foo in the provided background thread with the provided
* parameters.
*/
private void handleActionFoo(String param1, String param2) {
// TODO: Handle action Foo
throw new UnsupportedOperationException("Not yet implemented");
}
/**
* Handle action Baz in the provided background thread with the provided
* parameters.
*/
private void handleActionBaz(String param1, String param2) {
// TODO: Handle action Baz
throw new UnsupportedOperationException("Not yet implemented");
}
}
運行在前臺的Service
Service默認是運行在後臺的,因此,它的優先級相對比較低,當系統出現內存不足的情況時,它可能就會比回收掉。
如果希望Service可以一直保持運行狀態,而不會由於系統內存不足被回收,可以將Service運行在前臺。
前臺服務不僅不會被系統無情地回收,它還會在通知欄顯示一條消息,下拉狀態欄後可以看到更加詳細的信息。
下面的程序實現了一個類似下拉狀態欄,顯示天氣信息的效果。首先定義一個服務:
package com.example.administrator.onceuponaday.Service; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; import android.app.Service; import android.app.TaskStackBuilder; import android.content.Context; import android.content.Intent; import android.os.IBinder; import android.support.annotation.Nullable; import android.support.v7.app.NotificationCompat; import com.example.administrator.onceuponaday.UI.MainActivity; public class WeatherService extends Service { private static final int NOTIFY_ID=123; @Override public void onCreate() { super.onCreate(); showNotification(); } /** * 在通知欄顯示天氣信息 */ private void showNotification() { NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(this) .setSmallIcon(R.drawable.weather) .setContentTitle(getText(R.string.weather)) .setContentText(getText(R.string.weather)); //創建通知被點擊時觸發的Intent Intent resultIntent = new Intent(this, MainActivity.class); //創建任務棧Builder TaskStackBuilder stackBuilder = TaskStackBuilder.create(this); stackBuilder.addParentStack(MainActivity.class); stackBuilder.addNextIntent(resultIntent); PendingIntent resultPendingIntent = stackBuilder.getPendingIntent(0,PendingIntent.FLAG_UPDATE_CURRENT); mBuilder.setContentIntent(resultPendingIntent); NotificationManager mNotifyMgr = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); //構建通知 final Notification notification = mBuilder.build(); //顯示通知 mNotifyMgr.notify(NOTIFY_ID,notification); //啓動爲前臺服務 startForeground(NOTIFY_ID,notification); } 1 @Nullable @Override public IBinder onBind(Intent intent) { return null; } }
我們在 onCreate 函數中調用了 showNotification 函數顯示通知,並且在最後調用 startForeground將服務設置爲前臺服務。在AndroidManifest.xml註冊之後我們就可以啓動該Service了。
AIDL是一種接口描述語言,通常用於進程間通信。編譯器根據AIDL文件生成一個系列對應的Java類,通過預先定義的接口以及Binder機制到達進程間通信的目的。說白了,AIDL就是定義一個接口,客戶端(調用端)通過bindService來與遠程服務端建立一個連接,在該連接建立時會返回一個IBinder對象,該對象是服務器端Binder的BinderProxy(代理對象),在建立連接時,客戶端通過 asInterface 函數將該 BinderProxy對象包裝成本地的Proxy,並將遠程服務端的BinderProxy對象賦值給Proxy類的mRemote字段,就是通過mRemote執行遠程函數調用。在SsoAuth.aidl文件中,會默認有一個basicTypes函數,我們在程序後面添加一個ssoAuth的函數用於 SSO 授權。下面是其代碼:因爲客戶端是調用端,因此,只需要定義AIDL文件,此時Rebuild 一下工程就會生成一個 SsoAuth.java 類,該類根據SsoAuth.aidl文件生成,包含了我們在AIDL文件中定義的函數。因爲AIDL通常用於進程間通信,因此,我們新建一個被調用端的工程,我們命名爲 aidl_server,然後將客戶端的AIDL文件夾複製到 aidl_server的 app/src/main目錄下。此時相當於在客戶端和被調用端都有同一份SsoAuth.aidl文件,它們 的包名、類名完全一致,生成的SsoAuth.java類也完全一致,這樣在遠程調用時它們就能擁有一致的類型。Rebuild被調用端工程之後就會生成SsoAuth.java文件,該文件中有一個Stub類實現了SsoAuth接口。我們首先需要定義一個Service子類, 然後再定義一個繼承自Stub的子類,並且在Service的onBind函數中返回這個Stub子類的對象。……