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));
}
}
复制代码
运行结果以下