Service是一個能夠在後臺執行長時間運行操做而不提供用戶界面的應用組件。服務可由其餘應用組件啓動,並且即便用戶切換到其餘應用,服務仍將在後臺繼續運行。 此外,組件能夠綁定到服務,以與之進行交互,甚至是執行進程間通訊 (IPC)。 例如,服務能夠處理網絡事務、播放音樂,執行文件 I/O 或與內容提供程序交互,而全部這一切都可在後臺進行。java
通常的咱們用兩種方法與service進行聯繫:startService()和bindService()。android
方法 | 啓動方式 | 中止方式 | 與啓動它的組件之間的通訊方式 | 生命週期 |
---|---|---|---|---|
startService | 在其餘組件中調用startService()方法後,服務即處於啓動狀態 | service中調用stopSelf()方法,或者其餘組件調用stopService()方法後,service將中止運行 | 沒有提供默認的通訊方式,啓動service後該service就處於獨立運行狀態 | 一旦啓動,service便可在後臺無限期運行,即便啓動service的組件已被銷燬也不受其影響,直到其被中止 |
bindService() | 在其餘組件中調用bindService()方法後,服務即處於啓動狀態 | 全部與service綁定的組件都被銷燬,或者它們都調用了unbindService()方法後,service將中止運行 | 能夠經過 ServiceConnection進行通訊,組件能夠與service進行交互、發送請求、獲取結果,甚至是利用IPC跨進程執行這些操做 | 當全部與其綁定的組件都取消綁定(多是組件被銷燬也有多是其調用了unbindService()方法)後,service將中止 |
生命週期 api
startService()->onCreate()->onStartCommand()->running->stopService()/stopSelf()->onDestroy()->stopped 其中,服務未運行時會調用一次onCreate(),運行時不調用。bash
bindService()->onCreate()->onBind()->running->onUnbind()->onDestroy()->stopped網絡
在Manifest裏面聲明注意:多線程
<service android:enabled="true"/"false"
android:exported="true"/"false"
android:icon="drawable resource"
android:isolatedProcess="true"/"false"
android:label="string resource"
android:name="string"
android:permission="string"
android:process="string" >
</service>
複製代碼
當一個service經過這種方式啓動以後,它的生命週期就已經不受啓動它的組件影響了,只要service自身沒有調用stopSelf()而且其餘的組件沒有調用針對它的stopService(),它能夠在後臺無限期的運行下去。另外,若是肯定了使用這種方式啓動service而且不但願這個service被綁定的話,除了傳統的建立一個類繼承service以外咱們有一個更好的選擇——繼承IntentService。併發
若是是擴建Service類的話,一般狀況下咱們須要新建一個用於執行工做的新線程,由於默認狀況下service將工做於應用的主線程,而這將會下降全部正在運行的Activity的性能。而IntentService就不一樣了。它是Service的子類,它使用工做線程來注意的處理全部的startService請求。若是不要求這個service要同時處理多個請求,那麼繼承這個類顯然要比直接繼承Service好不少。app
IntentService作了如下這些事:所以咱們只須要實現onHandleIntent()方法來完成具體的功能邏輯就能夠了。dom
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.
// 這裏根據Intent進行操做
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
// Restore interrupt status.
Thread.currentThread().interrupt();
}
}
}
複製代碼
注意:若是須要重寫其餘的方法,好比onDestroy()方法,必定不要刪掉它的超類實現!由於它的超類實現裏面也許包括了對工做線程還有工做隊列的初始化以及銷燬等操做ide
下面是一個官網的例子,提供了service 類實現的代碼示例,該類執行的工做與上述使用的IntentService示例徹底相同。也就是說,對於每一個啓動請求,它均使用工做線程執行做業,且每次僅處理一個請求。
public class HelloService extends Service {
private Looper mServiceLooper;
private ServiceHandler mServiceHandler;
private final class ServiceHandler extends Handler {
public ServiceHandler(Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message msg) {
long endTime = System.currentTimeMillis() + 5 * 1000;
while (System.currentTimeMillis() < endTime) {
synchronized (this) {
try {
wait(endTime - System.currentTimeMillis());
} catch (Exception e) {
}
}
}
stopSelf(msg.arg1);
}
}
@Override
public void onCreate() {
HandlerThread thread = new HandlerThread("ServiceStartArguments", Process.THREAD_PRIORITY_BACKGROUND);
thread.start();
mServiceLooper = thread.getLooper();
mServiceHandler = new ServiceHandler(mServiceLooper);
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Toast.makeText(this, "service starting", Toast.LENGTH_SHORT).show();
Message msg = mServiceHandler.obtainMessage();
msg.arg1 = startId;
mServiceHandler.sendMessage(msg);
return START_STICKY;
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onDestroy() {
Toast.makeText(this, "service done", Toast.LENGTH_SHORT).show();
}
}
複製代碼
若是讓service同時處理多個請求的需求,這個時候就只能去繼承Service了。這個時候就要本身去處理工做線程那些事。上面示例並未這樣作,但若是但願如此,則可爲每一個請求建立一個新線程,而後當即運行這些線程(而不是等待上一個請求完成)。
注意:onStartCommand()的返回值是用來指定系統對當前線程的行爲的。它的返回值必須是如下常量之一:
如需與 Activity 和其餘應用組件中的服務進行交互,或者須要經過進程間通訊 (IPC) 向其餘應用公開某些應用功能,則應建立綁定服務。要建立綁定服務,必須實現 onBind() 回調方法以返回IBinder,用於定義與服務通訊的接口。而後,其餘應用組件能夠調用bindService()來檢索該接口,並開始對服務調用方法。服務只用於與其綁定的應用組件,所以若是沒有組件綁定到服務,則系統會銷燬服務(您沒必要按經過onStartCommand()啓動的服務那樣來中止綁定服務)。
通常來說,咱們有三種方式能夠得到IBinder的對象:繼承Binder類,使用Messenger類,使用AIDL。
若是你的服務僅供本地應用使用,不須要跨進程工做,則能夠實現自有Binder類,讓你的客戶端經過該類直接訪問服務中的公共方法。
注意:此方法只有在客戶端和服務位於同一應用和進程內這一最多見的狀況下方纔有效。
引用官網例子:
public class LocalService extends Service {
// Binder given to clients
private final IBinder mBinder = new LocalBinder();
// Random number generator
private final Random mGenerator = new Random();
/**
* Class used for the client Binder. Because we know this service always
* runs in the same process as its clients, we don't need to deal with IPC. */ public class LocalBinder extends Binder { LocalService getService() { // Return this instance of LocalService so clients can call public methods return LocalService.this; } } @Override public IBinder onBind(Intent intent) { return mBinder;} /** method for clients */ public int getRandomNumber() {return mGenerator.nextInt(100)} } 複製代碼
LocalBinder 爲客戶端提供 getService() 方法,以檢索 LocalService 的當前實例。這樣,客戶端即可調用服務中的公共方法。 例如,客戶端可調用服務中的 getRandomNumber():
public class BindingActivity extends Activity {
LocalService mService;
boolean mBound = false;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
}
@Override
protected void onStart() {
super.onStart();
// Bind to LocalService
Intent intent = new Intent(this, LocalService.class);
bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
}
@Override
protected void onStop() {
super.onStop();
// Unbind from the service
if (mBound) {
unbindService(mConnection);
mBound = false;
}
}
/**
* Called when a button is clicked (the button in the layout file attaches to
* this method with the android:onClick attribute)
*/
public void onButtonClick(View v) {
if (mBound) {
// Call a method from the LocalService.
// However, if this call were something that might hang, then this request should
// occur in a separate thread to avoid slowing down the activity performance.
int num = mService.getRandomNumber();
Toast.makeText(this, "number: " + num, Toast.LENGTH_SHORT).show();
}
}
/**
* Defines callbacks for service binding, passed to bindService()
*/
private ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName className, IBinder service) {
// We've bound to LocalService, cast the IBinder and get LocalService instance LocalBinder binder = (LocalBinder) service; mService = binder.getService(); mBound = true; } @Override public void onServiceDisconnected(ComponentName arg0) { mBound = false; } }; } 複製代碼
如需讓服務與遠程進程通訊,則可以使用 Messenger 爲你的服務提供接口。利用此方法,你無需使用 AIDL 即可執行進程間通訊 (IPC)。
方法步驟以下:
用這種方式,客戶端並無像擴展Binder類那樣直接調用服務端的方法,而是採用了用Message來傳遞信息的方式達到交互的目的。
下面之因此建兩個工程是爲了經過Messenger實現IPC,固然你也能夠寫在一個工程
新建一個工程,做爲服務端。
public class MessagerService extends Service {
public static final int MSG = 0x0001;
private Messenger mMessenger = new Messenger(new Ihandler());
public class Ihandler extends Handler {
@Override
public void handleMessage(Message msg) {
if (msg.what == MSG) {
Toast.makeText(getApplicationContext(), "服務開啓了", Toast.LENGTH_SHORT).show();
}
super.handleMessage(msg);
}
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return mMessenger.getBinder();
}
}
複製代碼
服務端就一個Service,能夠看到代碼至關的簡單,只須要去聲明一個Messenger對象,而後在onBind方法返回mMessenger.getBinder();
<service
android:name=".service.MessagerService"
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="com.hw.playandroid.messenger"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</service>
複製代碼
說明:上述的 是爲了能讓其餘apk隱式bindService,經過隱式調用的方式來調起activity或者service,須要把category設爲default,這是由於,隱式調用的時候,intent中的category默認會被設置爲default。
主要是由於在這裏要跨進程通訊,因此在另一個進程裏面並無咱們的service的實例,此時必需要給其餘的進程一個標誌,這樣才能讓其餘的進程找到咱們的service。其實這裏的android:exported屬性不設置也能夠的,由於在有intent-filter的狀況下這個屬性默認就是true,
再新建一個工程,做爲客戶端
public class MessagerActivity extends AppCompatActivity {
Messenger mService;
boolean isBound;
ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mService = new Messenger(service);
isBound = true;
}
@Override
public void onServiceDisconnected(ComponentName name) {
mService = null;
isBound = false;
}
};
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_messager);
ButterKnife.bind(this);
}
@Override
protected void onStart() {
super.onStart();
Intent intent = new Intent();
intent.setAction("com.hw.playandroid.messenger");
intent.setPackage("com.hw.playandroid");
bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
}
@Override
protected void onStop() {
super.onStop();
if (isBound) {
unbindService(mConnection);
isBound = false;
}
}
@OnClick(R.id.tv)
public void onViewClicked() {
if (isBound) {
Message message = Message.obtain(null, MessagerService.MSG, 0, 0);
try {
mService.send(message);
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
}
複製代碼
(注意:intent.setPackage("com.hw.playandroid");這句必定要加上否則會奔潰)
首先bindService,而後在onServiceConnected中拿到回調的service(IBinder)對象,經過service對象去構造一個mService =new Messenger(service);而後就可使用mService.send(msg)發給服務端了。上面的例子只有客戶端對服務端單方面的通訊,而要實現雙向通訊其實也很簡單,只要客戶端裏也建立一個Handler實例,讓它接收來自服務端的信息,同時讓服務端在客戶端給它發的請求完成了以後再給客戶端發送一條信息便可。Message在其中起到了一個信使的做用,經過它客戶端與服務端的信息得以互通。
Messenger爲何能進行IPC呢? 其實Messenger的內部實現的,實際上也是依賴於aidl文件實現的。 服務端的onBind()中返回了mMessenger.getBinder()
public IBinder getBinder() { return mTarget.asBinder(); }
複製代碼
能夠看到返回的是mTarget.asBinder();而mTarget在兩處被賦值,這兩處分別是Messenger的兩個構造方法,而上面使用的構造mMessenger對象的代碼:new Messenger(new Ihandler());能夠肯定其調用的構造方法
public Messenger(Handler target) {
mTarget = target.getIMessenger();
}
複製代碼
由此能夠知道是Handler返回的,咱們繼續跟進Handler,全局搜索getIMessenger()
final IMessenger getIMessenger() {
synchronized (mQueue) {
if (mMessenger != null) {
return mMessenger;
}
mMessenger = new MessengerImpl(); return mMessenger;
}
}
複製代碼
看到這裏應該就知道了,mTarget是一個MessengerImpl對象,那麼asBinder其實是返回this,也就是MessengerImpl對象; 這是個內部類,繼承自IMessenger.Stub,而後實現了一個send方法,該方法就是將接收到的消息經過 Handler.this.sendMessage(msg);發送到handleMessage。 實際上,Messenger就是依賴IMessenger該aidl文件生成的類,繼承了IMessenger.Stub類,實現了send方法,send方法中參數會經過客戶端傳遞過來,最終發送給handler進行處理。
使用場景:引用一段官網原話 只有容許不一樣應用的客戶端用 IPC 方式訪問服務,而且想要在服務中處理多線程時,纔有必要使用 AIDL。 若是您不須要執行跨越不一樣應用的併發 IPC,就應該經過實現一個Binder 建立接口;或者,若是您想執行 IPC,但根本不須要處理多線程,則使用Messenger類來實現接口。不管如何,在實現 AIDL 以前,請您務必理解綁定服務。
AIDL 支持下列數據類型:
如何使用
新建一個工程做爲服務端,而後再在這個工程的main文件夾下aidl包用來存放.aidl文件,通常來講, aidl 包裏默認有着和 java 包裏的包結構。
建立一個實體類並使其實現Parcelable接口,實現序列化和反序列化。
public class Person implements Parcelable {
String name;
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public Person() {}
protected Person(Parcel in) { name = in.readString(); }
public static final Creator<Person> CREATOR = new Creator<Person>() {
@Override
public Person createFromParcel(Parcel in) { return new Person( in ); }
@Override
public Person[] newArray(int size) { return new Person[size]; }
};
@Override
public int describeContents() { return 0; }
@Override
public void writeToParcel(Parcel dest, int flags) { dest.writeString(name); }
}
複製代碼
(注意在建立IXxxAidl.aidl時,除了基本類型(int,long,char,boolean等),String,CharSequence,List,Map,其餘類型必須使用import導入,即便它們可能在同一個包裏,好比下面的Person,就是import進來的。另外,接口中方法的參數除了aidl支持的類型,其餘類型必須標識其方向:究竟是輸入仍是輸出或者是輸入輸出,用in,out或者inout來表示)
package com.example.com.test;
import com.example.com.test.bean.Person;
interface IMyAidl {
Person getPerson();
}
複製代碼
通常的,都把實體類和映射文件放到一個包下,這樣作的緣由是方便移植。實體類和映射文件也能夠再也不在同一個包裏面,也能夠把實體類放到java包下,可是注意這時候,實體類Person的這個 Person.aidl映射文件的包名要和實體類包名一致,可是移植的時候就不那麼方便了,須要把這個實體類Person單獨進行移植。
package com.example.com.test.bean;
parcelable Person;
複製代碼
注意在這一步可能會有一個坑等着你來踩,由於Android Studio 是使用 Gradle 來構建 Android 項目的,而 Gradle 在構建項目的時候會經過 sourceSets 來配置不一樣文件的訪問路徑,從而加快查找速度。Gradle 默認是將 java 代碼的訪問路徑設置在 java 包下的,這樣一來,若是 java 文件是放在 aidl 包下的話那麼系統是找不到這個 java 文件的。有兩種方法能夠解決
sourceSets { main { java.srcDirs = ['src/main/java', 'src/main/aidl'] } }
複製代碼
public class AidlService extends Service {
private IBinder mIBinder = new IMyAidl.Stub() {
@Override
public Person getPerson() throws RemoteException {
Random random = new Random();
Person mPerson = new Person();
mPerson.setName("你們好,個人名字叫" + random.nextInt(10));
return mPerson;
}
};
@Nullable
@Override
public IBinder onBind(Intent intent) {
return mIBinder;
}
}
複製代碼
記住在Manifest裏面註冊
<service android:name=".service.AidlService"
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="com.example.com.test.service.AidlService"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</service>
複製代碼
新建一個工程做爲客戶端,而後將服務端的aidl整個包複製到客戶端的main文件夾下,而後Rebuild一下
編寫客戶端代碼,這裏須要注意兩點。
public class AidlActivity extends Activity {
private IMyAidl mAidl;
private boolean isBound;
private ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mAidl = IMyAidl.Stub.asInterface(service);
isBound = true;
}
@Override
public void onServiceDisconnected(ComponentName name) {
mAidl = null;
isBound = false;
}
};
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_aidl);
ButterKnife.bind(this);
}
@OnClick(R.id.tv)
public void onViewClicked() {
if (isBound){
try {
String name = mAidl.getPerson().getName();
Toast.makeText(this,name,Toast.LENGTH_SHORT).show();
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
@Override
protected void onStart() {
super.onStart();
Intent intent = new Intent();
intent.setAction("com.example.com.test.service.AidlService");
intent.setPackage(IMyAidl.class.getPackage().getName());
bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
}
@Override
protected void onStop() {
super.onStop();
if (isBound) {
unbindService(mConnection);
isBound = false;
}
}
}
複製代碼
上面說到的service都是運行在後臺的,而這些運行在後臺的service的系統優先級相對較低,有可能會被殺死,好比系統內存不足就會回收掉正在後臺運行的service,想要service一直處在運行狀態的話就能夠將service設置爲前臺服務。
前臺服務被認爲是用戶主動意識到的一種服務,所以在內存不足時,系統也不會考慮將其終止。 前臺服務必須爲狀態欄提供通知,放在「正在進行」標題下方,這意味着除非服務中止或從前臺移除,不然不能清除通知。
很簡單和上面同樣先建立服務,再定義一個方法,在方法裏構建Notification,而後經過startForeground(110, notification);方法將服務設置到前臺這個方法裏的第一個參數不能夠爲0,中止的話用stopForeground(true);固然你能夠經過startService()直接開啓,或經過bindService()獲取service來直接調方法。開啓服務後咱們會收到一條通知,這條通知就是前臺服務提供的。注意在android8.0後 須要給otification設置一個channelId
注意開啓容許通知哦,否則收不到消息的
public class ForeGroundService extends Service {
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
//在android8.0後 須要給notification設置一個channelId,否則會報錯:
// Bad notification for startForeground: java.lang.RuntimeException: invalid channel for service notification
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
createNotificationChannel();
} else {
showNotification();
}
return START_STICKY;
}
@Override
public void onDestroy() {
super.onDestroy();
stopForeground(true);
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
public void showNotification() {
Intent clickIntent = new Intent(this, NotificationClickReceiver.class); //點擊通知以後要發送的廣播
int id = (int) (System.currentTimeMillis() / 1000);
PendingIntent pendingIntent = PendingIntent.getBroadcast(this, id, clickIntent, PendingIntent.FLAG_UPDATE_CURRENT);
Notification.Builder builder = new Notification.Builder(this)
.setSmallIcon(R.mipmap.ic_launcher)
.setContentTitle("今天是公元10000年1月1日")
.setContentText("天氣晴,溫度零下170度")
.setWhen(System.currentTimeMillis())
.setContentIntent(pendingIntent);
Notification notification = builder.build();
startForeground(1, notification);
}
@RequiresApi(Build.VERSION_CODES.O)
private void createNotificationChannel() {
String NOTIFICATION_CHANNEL_ID = "foreground_service";
String channelName = "ForeGroundService";
NotificationChannel chan = new NotificationChannel(NOTIFICATION_CHANNEL_ID, channelName, NotificationManager.IMPORTANCE_NONE);
chan.setLightColor(Color.BLUE);
chan.setLockscreenVisibility(Notification.VISIBILITY_PRIVATE);
NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
if (manager != null) {
manager.createNotificationChannel(chan);
} else {
stopSelf();
}
Intent clickIntent = new Intent(this, NotificationClickReceiver.class); //點擊通知以後要發送的廣播
int id = (int) (System.currentTimeMillis() / 1000);
PendingIntent pendingIntent = PendingIntent.getBroadcast(this, id, clickIntent, PendingIntent.FLAG_UPDATE_CURRENT);
NotificationCompat.Builder builder = new NotificationCompat.Builder(this, NOTIFICATION_CHANNEL_ID)
.setSmallIcon(R.mipmap.ic_launcher)
.setContentTitle("今天是公元10000年1月1日")
.setContentText("天氣晴,溫度零下170度")
.setWhen(System.currentTimeMillis())
.setContentIntent(pendingIntent);
Notification notification = builder.build();
startForeground(2, notification);
}
}
複製代碼
經過這幾句代碼能夠實現點擊通知以後要發送的廣播。
Intent clickIntent = new Intent(this, NotificationClickReceiver.class); //點擊通知以後要發送的廣播
int id = (int) (System.currentTimeMillis() / 1000);
PendingIntent pendingIntent = PendingIntent.getBroadcast(this.getApplicationContext(), id, clickIntent, PendingIntent.FLAG_UPDATE_CURRENT);
複製代碼
經過廣播進行一些操做,如跳轉頁面,關閉服務等等。
public class NotificationClickReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Toast.makeText(context,"我是點擊通知過來的,我要關閉服務了",Toast.LENGTH_SHORT).show();
context.stopService(new Intent(context, ForeGroundService.class));
context.startActivity(new Intent(context, NotificationActivity.class).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
}
}
複製代碼
運行結果以下