这篇文章会从Service的一些小知识点,延伸到Android中几种经常使用进程间通讯方法。html
Service是一种不提供用户交互页面可是能够在后台长时间运行的组件,能够经过在AndroidManifest.xml设置Service的android:process=":remote"
属性,让Service运行另外一个进程中,也就是说,虽然你是在当前应用启动的这个Service,可是这个Service和这个应用并非同一个进程。android
四大组件都支持android:process=":remote"
这个属性。缓存
由于Service能够运行在不一样的进程,这里说一下Android中几种进程的优先级,当系统内存不足时候,系统会从优先级低的进程开始回收,下面根据优先级由高到低列出Android中几种进程。安全
前台进程,当前用户操做所须要的进程网络
这种进程基本不会被回收,只有当内存不足以支持前台进程同时运行时候,系统才回回收它们,主要关注前三个。app
可见进程,没有与用户交互所必须的组件,可是在屏幕上仍然可见其内容的进程ide
此外,一个进程的级别可能会由于其余进程对它的依赖而有所提升,即进程A服务于进程B(B依赖A),那么A的进程级别至少是和B同样高的。oop
和其余组件(Activity/ContentProvider/BroadcastReceiver)同样,Service须要在Androidmanifest.xml中声明。ui
<manifest ... > ... <application ... > <service android:name=".DemoService" /> ... </application> </manifest>
Service是运行在主线程中的,若是有什么耗时的操做,建议新建子线程去处理,避免阻塞主线程,下降ANR的风险。this
在另一篇文章中Intent以及IntentFilter详解提到过,为了确保应用的安全,不要为Service设置intent-filter,由于若是使用隐式Intent去启动Service时候,手机里面那么多应用,并不能肯定哪个Service响应了这个Intent,因此在项目中尽可能使用显式Intent去启动Service。在Android 5.0(API LEVEL 21)版本后的,若是传入隐式Intent去调用bindService()
方法,系统会抛出异常。
能够经过设置android:exported=false
来确保这个Service仅能在本应用中使用。
服务能够由其余组件启动,并且若是用户切换到其余应用,这个服务可能会继续在后台执行。到目前为止,Android中Service总共有三种启动方式。
startService()
启动的Service。经过这种方式启动的Service会独立的运行在后台,即便启动它的组件已经销毁了。例如Activity A使用startService()启动了Service B,过了会儿,Activity A执行onDestroy()被销毁了,若是Service B任务没有执行完毕,它仍然会在后台执行。这种启动方式启动的Service须要主动调用StopService()
中止服务。bindService()
启动的Service。经过这种方式启动Service时候,会返回一个客户端交互接口,用户能够经过这个接口与服务进行交互,若是这个服务是在另外一个进程中,那么就实现了进程间通讯,也就是Messenger和AIDL,这个会是下篇文章的重点。多个组件能够同时绑定同一个Service,若是全部的组件都调用unbindService()
解绑后,Service会被销毁。startService和bindService能够同时使用
Service是一个抽象类,使用须要咱们去实现它的抽象方法onBind()
,Service有且仅有这一个抽象方法,还有一些其余的生命周期回调方法须要复写帮助咱们实现具体的功能。
onStartCommand()
和onBind()
以前被调用。若是服务已经存在,调用startService()
启动服务时候这个方法不会调用,只会调用onStartCommand()
方法。startService()
启动服务时候会回调这个方法,这个方法执行后,服务会启动被在后台运行,须要调用stopSelf()
或者stopService()
中止服务。bindService()
绑定服务时候会回调的方法,这是Service的一个抽象方法,若是客户端须要与服务交互,须要在这个方法中返回一个IBinder
实现类实例化对象,若是不想其余客户端与服务绑定,直接返回null。若是组件仅经过startService()
启动服务,不论服务是否已经启动,都会回调onStartCommand()
方法,并且服务会一直运行,须要调用stopSelf
和stopService
方法关闭服务。
若是组件仅经过bindService()
绑定服务,则服务只有在与组件绑定时候运行,一旦全部的客户端所有取消绑定unbindService
,系统才会销毁该服务。
屡次启动同一个服务,只有在服务初次启动时候会回调onCreate
方法,可是每次都会回调onStartCommand
,能够利用这个向服务传递一些信息。
onStartCommand()的回调是在UI主线程,若是有什么耗时的操做,建议新启线程去处理。
启动服务:
JobScheduler.schedule()
startService(Intent)
bindService(Intent service, ServiceConnection conn, int flags)
关闭服务:
JobScheduler.cancel()
或者JobScheduler.cancelAll()
,对应JobScheduler.schedule()
stopSelf()
方法,组件的stopService(Intent)
方法,对应startService
启动方法unbindService(ServiceConnection conn)
,对应bindService
示例:
// 启动服务 Intent intent = new Intent(this, DemoService.class); startService(intent); // 中止服务 stopService(intent) // 绑定服务 ServiceConnection mConnection = ServiceConnection() { ... }; Intent intent = new Intent(this, DemoService.class); bindService(intent, mConnection, Context.BIND_AUTO_CREATE); // 解除绑定 unbindService(mConnection);
绑定服务bindService()
第三个参数数值:
Context.BIND_AUTO_CREATE
,绑定服务时候,若是服务还没有建立,服务会自动建立,在API LEVEL 14之前的版本不支持这个标志,使用Context.BIND_WAIVE_PRIORITY
能够达到一样效果Context.BIND_DEBUG_UNBIND
,一般用于Debug,在unbindService
时候,会将服务信息保存并打印出来,这个标记很容易形成内存泄漏。Context.BIND_NOT_FOREGROUND
,不会将被绑定的服务提高到前台优先级,可是这个服务也至少会和客户端在内存中优先级是相同的。Context.BIND_ABOVE_CLIENT
,设置服务的进程优先级高于客户端的优先级,只有当须要服务晚于客户端被销毁这种状况才这样设置。Context.BIND_ALLOW_OOM_MANAGEMENT
,保持服务受默认的服务管理器管理,当内存不足时候,会销毁服务Context.BIND_WAIVE_PRIORITY
,不会影响服务的进程优先级,像通用的应用进程同样将服务放在一个LRU表中Context.BIND_IMPORTANT
,标识服务对客户端是很是重要的,会将服务提高至前台进程优先级,一般状况下,即时客户端是前台优先级,服务最多也只能被提高至可见进程优先级,BIND_ADJUST_WITH_ACTIVITY
,若是客户端是Activity,服务优先级的提升取决于Activity的进程优先级,使用这个标识后,会无视其余标识。onStartCommand()
方法有一个int的返回值,这个返回值标识服务关闭后系统的后续操做。
返回值有如下几种:
Service.START_STICKY
,启动后的服务被杀死,系统会自动重建服务并调用on onStartCommand()
,可是不会传入最后一个Intent(Service可能屡次执行onStartCommand),会传入一个空的Intent,使用这个标记要注意对Intent的判空处理。这个标记适用于太依靠外界数据Intent,在特定的时间,有明确的启动和关闭的服务,例如后台运行的音乐播放。Service.START_NOT_STICKY
,启动后的服务被杀死,系统不会自动从新建立服务。这个标记是最安全的,适用于依赖外界数据Intent的服务,须要彻底执行的服务。Service.START_REDELIVER_INTENT
,启动后的服务被杀死,系统会从新建立服务并调用onStartCommand()
,同时会传入最后一个Intent。这个标记适用于可恢复继续执行的任务,好比说下载文件。Service.START_STICKY_COMPATIBILITY
,启动后的服务被杀死,不能保证系统必定会从新建立Service。Service生命周期(从建立到销毁)跟它被启动的方式有关系,这里只介绍startService
和bindService
两种启动方法时候Service的生命周期。
startService
启动方式,其余组件用这种方式启动服务,服务会在后台一直运行,只有服务调用自己的stopSelf()
方法或者其余组件调用stopService()
才能中止服务。bindService
启动方式,其余组件用这种方法绑定服务,服务经过IBinder与客户端通讯,客户端经过unbindService
接触对服务的绑定,当没有客户端绑定到服务,服务会被系统销毁。这两种生命周期不是独立的,组件能够同时用startService
启动服务同时用bindService
绑定服务,例如跨页面的音乐播放器,就能够在多个页面同时绑定同一个服务,这种状况下须要调用stopService()
或者服务自己的stopSelf()
而且没有客户端绑定到服务,服务才会被销毁。
<div align="center">
图-1Service生命周期图</div>
左图是使用startService()
所建立的服务的生命周期,右图是使用bindService()
所建立的服务的生命周期。
服务能够经过startForeground来使服务变成前台优先级。
public final void startForeground(int id, Notification notification) { try { mActivityManager.setServiceForeground( new ComponentName(this, mClassName), mToken, id, notification, true); } catch (RemoteException ex) { } }
第一个参数用于标识你应用中惟一的通知标识id,不能设为0,最终会传入NotificationManager.notify(int id, Notification notification)
取消通知须要用到,第二个参数是通知具体内容。
前台服务须要在状态栏中添加通知,例如,将音乐播放器的服务设置为前台服务,状态栏通知显示正在播放的歌曲,并容许其余组件与其交互。
// 设置Notification属性 Notification notification = new Notification(R.drawable.icon, getText(R.string.ticker_text),System.currentTimeMillis()); Intent notificationIntent = new Intent(this, ExampleActivity.class); PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0); notification.setLatestEventInfo(this, getText(R.string.notification_title), getText(R.string.notification_message), pendingIntent); startForeground(ONGOING_NOTIFICATION_ID, notification);
要将服务从前台移除,须要调用stopForeground(boolean removeNotification),参数是一个布尔值,用来标识服务从前台服务移除时候,是否须要移除状态栏的通知。若是服务在前台运行时候被中止,状态栏的通知也会被移除。
很少说,万能的通讯。
很少说,万能的通讯,例如ContentProvider/SharePreference等等。
使用这个方法启动的服务,再次调用startService()
传入Intent便可与服务通讯,由于这种方式启动的服务在完整的生命周期内onCreate()
只会执行一次,而onStartCommand()
会执行屡次,咱们再次调用startService()
时候,能够在oonStartCommand()
去处理。
使用这种方法启动的服务,组件有三种与服务通讯的方式。
下一篇文章具体介绍Messenger、AIDL,由于它们是属于Android进程间通讯。
若是一个服务Service只须要在本应用的进程中使用,不提供给其余进程,推荐使用第一种方法。
使用示例:
Service:
/** * 本地服务 * <br/> * 和启动应用属于同一进程 */ public class LocalService extends Service { /** * 自定的IBinder */ private final IBinder mBinder = new LocalBinder(); @Override public IBinder onBind(Intent intent) { return mBinder; } /** * 提供给客户端的方法 * * @return */ public String getServiceInfo() { return this.getPackageName() + " " + this.getClass().getSimpleName(); } /** * 自定义的IBinder */ public class LocalBinder extends Binder { public LocalService getService() { return LocalService.this; } } }
Activity:
/** * 绑定本地服务的组件 * * Created by KyoWang. */ public class BindLocalServiceActivity extends AppCompatActivity implements View.OnClickListener { private Button mShowServiceNameBtn; private LocalService mService; private boolean mBound = false; public ServiceConnection mConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { LocalService.LocalBinder binder = (LocalService.LocalBinder) service; mService = binder.getService(); mBound = true; } @Override public void onServiceDisconnected(ComponentName name) { mBound = false; } }; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.a_bind_local_service); mShowServiceNameBtn = (Button) findViewById(R.id.bind_local_service_btn); mShowServiceNameBtn.setOnClickListener(this); } @Override protected void onStart() { super.onStart(); Intent intent = new Intent(this, LocalService.class); bindService(intent, mConnection, Context.BIND_AUTO_CREATE); } @Override protected void onStop() { super.onStop(); if(mBound) { unbindService(mConnection); mBound = false; } } @Override public void onClick(View v) { int id = v.getId(); if(id == R.id.bind_local_service_btn) { if(mBound) { String info = mService.getServiceInfo(); Toast.makeText(BindLocalServiceActivity.this, info, Toast.LENGTH_SHORT).show(); } } } }
关于网上通用的提高服务优先级以保证服务长存后台,即保证服务不轻易被系统杀死的方法有如下几种。
设置android:persistent="true"
,这是application的一个属性,官方都不建议使用。
Whether or not the application should remain running at all times, "true" if it should, and "false" if not. The default value is "false". Applications should not normally set this flag; persistence mode is intended only for certain system applications.
设置android:priority
优先级,这个并非Service的属性。这个属性是在intent-filter
中设置的。官方解释,这个属性只对活动和广播有用,并且这个是接受Intent的优先级,并非在内存中的优先级,呵呵。
android:priority The priority that should be given to the parent component with regard to handling intents of the type described by the filter. This attribute has meaning for both activities and broadcast receivers。
onDestroy
中发送广播,而后重启服务,就目前我知道的,会出现Service的onDestroy
不调用的状况。startForeground
,这个上面提到过,是经过Notification
提高优先级。onStartCommand()
返回值,让服务被杀死后,系统从新建立服务,上面提到过。五个里面就两个能稍微有点用,因此啊,网络谣传害死人。
敲黑板时间,重点来了,官方强力推荐。
前面提到两点。
IntentService完美的帮咱们解决了这个问题,在内部帮咱们新建的线程,不须要咱们手动新建,执行完毕任务后会自动关闭。IntentService也是一个抽象类,里面有一个onHandleIntent(Intent intent)
抽象方法,这个方法是在非UI线程调用的,在这里执行耗时的操做。
IntentService使用非UI线程逐一处理全部的启动需求,它在内部使用Handler,将全部的请求放入队列中,依次处理,关于Handler能够看这篇文章,也就是说IntentService不能同时处理多个请求,若是不要求服务同时处理多个请求,能够考虑使用IntentService。
IntentService在内部使用HandlerThread
配合Handler
来处理耗时操做。
private final class ServiceHandler extends Handler { public ServiceHandler(Looper looper) { super(looper); } @Override public void handleMessage(Message msg) { onHandleIntent((Intent)msg.obj); stopSelf(msg.arg1); } } public int onStartCommand(Intent intent, int flags, int startId) { onStart(intent, startId); return mRedelivery ? START_REDELIVER_INTENT : START_NOT_STICKY; } public void onStart(Intent intent, int startId) { Message msg = mServiceHandler.obtainMessage(); msg.arg1 = startId; msg.obj = intent; mServiceHandler.sendMessage(msg); }
注意msg.arg1
它是请求的惟一标识,每发送一个请求,会生成一个惟一标识,而后将请求放入Handler
处理队列中,从源代码里面能够看见,在执行完毕onHandleIntent
方法后,会执行stopSelf
来关闭自己,同时IntentService中onBind()
方法默认返回null
,这说明启动IntetService的方式最好是用startService
方法,这样在服务执行完毕后才会自动中止;若是使用bindService
来启动服务,仍是须要调用unbindService
来解绑服务的,也须要复写onBind()
方法。
小盆宇:在
ServiceHandler
类的handleMessage
方法中,执行onHandleIntent
后紧接着执行stopSelf(int startId)
,把服务就给中止了,那第一个请求执行完毕服务就中止了,后续的请求怎么会执行?
注意stopSelf(int startID)方法做用是在其参数startId跟最后启动该service时生成的id相等时才会执行中止服务,当有多个请求时候,若是发现当前请求的startId不是最后一个请求的id,那么不会中止服务,因此只有当最后一个请求执行完毕后,才会中止服务。