最近因为还没找到工作所以也趁着现在有时间,将以前的只是整理下,要不然总容易遗忘,今天就来讲解下Service的用法。作为Android的四大组件之一,其重要性可想而知。在应用中我们主要是用来进行一些后台操作,不需与应用UI进行交互,执行耗时任务等。
官方文档中这样说:
Service 是一个可以在后台执行长时间运行操作而不提供用户界面的应用组件。服务可由其他应用组件启动,而且即使用户切换到其他应用,服务仍将在后台继续运行。
此外,组件可以绑定到服务,以与之进行交互,甚至是执行进程间通信 (IPC)。 例如,服务可以处理网络事务、播放音乐,执行文件 I/O
或与内容提供程序交互,而所有这一切均可在后台进行。
Service的用途:
1.在后台执行耗时操作,但不需要与用户进行交互。2.一个应用暴露出来的一些供其他应用使用的功能。
这里需要声明一点,Service是运行在主线程中,因而如果需要进行耗时操作或者访问网络等操作,需要在Service中再开启一个线程来执行(使用IntentService的话则不需要在自己手动开启线程)。
启动Service
启动一个Service有两种方式:
- Context.startService()
- Context.bindService()
(图片截取自官方文档:https://developer.android.com...)
startService()方式启动Service,我们启动之后是没有办法再对Service进行控制的,而且启动之后该Service是一直在后台运行的,即使它里面的一些代码执行完毕,我们要想终止该Service,就需要在他的代码里面调用stopSelf()方法或者直接调用stopService() 方法。而通过bindService()方法启动的Service,客户端将获得一个到Service的持久连接,客户端会获取到一个由Service的onBind(Intent)方法返回来的IBinder对象,用来供客户端回调Service中的回调方法。
我们无论使用那种方法,都需要定义一个类,让它继承Service类,并重写其中的几个方法,如果我们是采用startService()方式启动的话,只需要重写onCreate() 、onStartCommand(Intent intent, int flags, int startId)、onDestroy()方法即可(其实我们也可以重写),而如果采用的是bindService()方法启动的话,我们就需要重写onCreate() 、onBind(Intent intent)、 onUnbind(Intent intent)方法.注意,作为四大组件之一,Service使用之前要在清单文件中进行配置。
- <application>
- ......
- <service
- android:name=".MyService">
- </service>
- </application>
Context.startService()
MyService.java的代码:
- public class MyService extends Service {
- public MyService() {
- }
- @Override
- public void onCreate() {
- super.onCreate();
- Log.i("test","onCrete executed !");
- }
- @Override
- public int onStartCommand(Intent intent, int flags, int startId) {
- Log.i("test","onStartComand executed !");
- return super.onStartCommand(intent, flags, startId);
- }
- @Override
- public void onDestroy() {
- super.onDestroy();
- Log.i("test","onDestroy executed !");
- }
- }
MainActivity.java的代码如下:
- public class MainActivity extends AppCompatActivity implements View.OnClickListener{
- Button btnStart,btnStop;
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_main);
- btnStart = (Button) findViewById(R.id.btn_start);
- btnStop = (Button) findViewById(R.id.btn_stop);
- btnStart.setOnClickListener(this);
- btnStop.setOnClickListener(this);
- }
- @Override
- public void onClick(View view) {
- Intent mIntent = new Intent(MainActivity.this,MyService.class);
- switch (view.getId()){
- case R.id.btn_start:
- startService(mIntent);
- break;
- case R.id.btn_stop:
- stopService(mIntent);
- break;
- }
- }
- }
主界面就两个按钮,一个用来启动Service,一个用来停止Service:
下面我们先点击START按钮,Log信息如下:
可以看出,onCreate()方法先执行,然后onStartCommand()方法紧接着执行,那么如果我们再次点击启动按钮呢?结果如下图:
我们可以看到,这次onCreate()方法没有再执行,而是直接执行了onStartCommand()方法,这是因为Service只在***次创建的时候才执行onCreate()方法,如果已经创建了,那之后再次调用startService()启动该Service的时候,只会去执行onStartCommand()方法方法,而不会再执行onCreate()方法。
接下来我们点击停止按钮,可以看到,onDestroy()方法被执行了:
注意,如果我们不点击停止按钮手动停止该Service的话,该Service会一直在后台运行,即使它的onStartCommand()方法中的代码已经执行完毕,在下图中我们可以看到:
这时候我们的这个Service是一直在后台执行的,即使它的onStartCommand()方法中的代码已经执行完了。如果我们想要它自动停止的话,可以将onStartCommand()方法中的代码修改如下:
- @Override
- public int onStartCommand(Intent intent, int flags, int startId) {
- Log.i("test","onStartComand() executed !");
- stopSelf();
- return super.onStartCommand(intent, flags, startId);
- }
Context.bindService()
采用该方法的代码就稍微比以前的多了,因为我们需要在客户端对Service进行控制,因而会在MainActivity中创建一个匿名内部类ServiceConnection,然后会在bindService()方法和unbindService()方法中将其传入。MyService.java 中的代码如下:
- public class MyService extends Service {
- private MyBinder myBinder = new MyBinder();
- public MyService() {
- }
- @Override
- public void onCreate() {
- super.onCreate();
- Log.i("test","onCreate() executed !");
- }
- @Override
- public void onDestroy() {
- super.onDestroy();
- Log.i("test","onDestroy() executed !");
- }
- @Override
- public boolean onUnbind(Intent intent) {
- Log.i("test","onUnbind executed !");
- return super.onUnbind(intent);
- }
- @Override
- public IBinder onBind(Intent intent) {
- Log.i("test","onBind() executed !");
- return myBinder;
- }
- class MyBinder extends Binder{
- public void startDownload(){
- Log.i("test", "MyBinder中的startDownload() executed !");
- // 执行具体的下载任务,需开启一个子线程,在其中执行具体代码
- }
- }
- }
MainActivity.java 的代码如下:
- public class MainActivity extends AppCompatActivity implements View.OnClickListener{
- Button btnBind,btnUnBind;
- MyService.MyBinder myBinder ;
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_main);
- btnBind = (Button) findViewById(R.id.bind);
- btnUnBind = (Button) findViewById(R.id.btn_unBind);
- btnBind.setOnClickListener(this);
- btnUnBind.setOnClickListener(this);
- }
- ServiceConnection mServiceConnection = new ServiceConnection() {
- @Override
- public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
- // 将IBinder向下转型为我们的内部类MyBinder
- myBinder = (MyService.MyBinder) iBinder;
- // 执行下载任务
- myBinder.startDownload();
- }
- @Override
- public void onServiceDisconnected(ComponentName componentName) {
- }
- };
- @Override
- public void onClick(View view) {
- Intent mIntent = new Intent(MainActivity.this,MyService.class);
- switch (view.getId()){
- case R.id.bind:
- // 绑定Service
- bindService(mIntent,mServiceConnection,BIND_AUTO_CREATE);
- break;
- case R.id.btn_unBind:
- // 取消绑定Service
- unbindService(mServiceConnection);
- break;
- }
- }
- }
点击绑定按钮;
点击取消绑定按钮:
注意,如果我们没有先点击绑定,而是直接点击的取消绑定,程序会直接crash,报以下错误:
- java.lang.IllegalArgumentException: Service not registered: com.qc.admin.myserializableparceabledemo.MainActivity$1@8860e28
- at android.app.LoadedApk.forgetServiceDispatcher(LoadedApk.java:1120)
- at android.app.ContextImpl.unbindService(ContextImpl.java:1494)
- at android.content.ContextWrapper.unbindService(ContextWrapper.java:616)
- at com.qc.admin.myserializableparceabledemo.MainActivity.onClick(MainActivity.java:71)
细心的你也许早就发现了,Log中并没有打印"onServiceDisconnected executed !"这句,也就是说没有调用onServiceDisconnected()方法?从字面理解,onServiceConnected()方法是在Service建立连接的时候调用的,onServiceDisconnected()不就应该是在Service断开连接的时候调用的吗?其实不然,我们查看该方法的文档就知道了:
Called when a connection to the Service has been lost. This typically happens when the process hosting the service has crashed or been killed. This does not remove the ServiceConnection itself -- this binding to the service will remain active, and you will receive a call to onServiceConnected(ComponentName, IBinder) when the Service is next running.
意思就是:当绑定到该Service的连接丢失的时候,该方法会被调用,典型的情况就是持有该Service的进程crash掉了,或者被杀死了。但是这并不会移除ServiceConnection 自身--它仍然是保持活跃状态,当Service下次被执行的时候,onServiceConnected(ComponentName, IBinder) 方法仍然会被调用。
但是要注意,如果我们按照刚才说的,不是先点击 bindService()方法,而是直接点击unbindService()方法,程序虽然也是crash掉了,但onServiceDisconnected()方法并不会被调用,这个很容易理解,毕竟都没有建立连接呢,谈何断开连接啊。但是如果我们已经绑定了Service,然后在后台直接终止该Service呢?结果会怎样?答案是onServiceDisconnected()方法仍然不会调用。这里我觉得应该是只有在意外的情况下进程结束,是由系统自动调用的,而非我们手动停止的。我们可以查看该方法内部的注释:
This is called when the connection with the service has been
unexpectedly disconnected -- that is, its process crashed.Because it
is running in our same process, we should never see this happen.
这段文字清楚的说明了该方法执行的场景:异常情况下导致断开了连接。也就是进程crash掉了。因为它运行在我们应用程序所在的进程中,因而我们将永远不希望看到这种情况发生。
Context.startService()和Context.bindService()同时使用
这两种方式是可以同时使用的,但是要注意,startService()和stopService()方法是对应的,而bindService()和unBind()方法是对应的,也就是说如果我们先调用startService()之后调用bindService()方法,或者相反,那么我们如果只调用stopService()或者只调用bindService()都无法停止该Service,只有同时调用才可以。
下面来看下具体代码:
MainActivity.java
- public class MainActivity extends AppCompatActivity implements View.OnClickListener{
- Button btnStart,btnStop,btnBind,btnUnBind;
- MyService.MyBinder myBinder ;
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_main);
- btnStart = (Button) findViewById(R.id.btn_start);
- btnStop = (Button) findViewById(R.id.btn_stop);
- btnBind = (Button) findViewById(R.id.btn_bind);
- btnUnBind = (Button) findViewById(R.id.btn_unBind);
- btnStart.setOnClickListener(this);
- btnStop.setOnClickListener(this);
- btnBind.setOnClickListener(this);
- btnUnBind.setOnClickListener(this);
- }
- ServiceConnection mServiceConnection = new ServiceConnection() {
- @Override
- public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
- // 将IBinder向下转型为我们的内部类MyBinder
- myBinder = (MyService.MyBinder) iBinder;
- // 执行下载任务
- myBinder.startDownload();
- }
- @Override
- public void onServiceDisconnected(ComponentName componentName) {
- Log.i("test","onServiceDisconnected executed !");
- }
- };
- @Override
- public void onClick(View view) {
- Intent mIntent = new Intent(MainActivity.this,MyService.class);
- switch (view.getId()){
- case R.id.btn_start:
- // 启动Service
- startService(mIntent);
- break;
- case R.id.btn_stop:
- // 终止Service
- stopService(mIntent);
- break;
- case R.id.btn_bind:
- // 绑定Service
- bindService(mIntent,mServiceConnection,BIND_AUTO_CREATE);
- break;
- case R.id.btn_unBind:
- // 取消绑定Service
- unbindService(mServiceConnection);
- break;
- }
- }
- }
MyService.java的代码:
- public class MyService extends Service {
- private MyBinder myBinder = new MyBinder();
- public MyService() {
- }
- @Override
- public void onCreate() {
- super.onCreate();
- Log.i("test","onCreate() executed !");
- }
- @Override
- public int onStartCommand(Intent intent, int flags, int startId) {
- Log.i("test","onStartComand() executed !");
- return super.onStartCommand(intent, flags, startId);
- }
- @Override
- public void onDestroy() {
- super.onDestroy();
- Log.i("test","onDestroy() executed !");
- }
- @Override
- public boolean onUnbind(Intent intent) {
- Log.i("test","onUnbind executed !");
- return super.onUnbind(intent);
- }
- @Override
- public IBinder onBind(Intent intent) {
- Log.i("test","onBind() executed !");
- return myBinder;
- }
- class MyBinder extends Binder{
- public void startDownload(){
- Log.i("test", "MyBinder中的startDownload() executed !");
- // 执行具体的下载任务
- }
- }
- }
a.下面是依次点击start、bind、stop、unBind 按钮的输出结果:
b.下面是依次点击start、bind、unbind、stop 按钮时的输出结果:
在前台运行服务
我们上面一直说Service一般是用来在后台执行耗时操作,但是要知道,Service也是可以运行在前台的。后台Service的优先级比较低,容在内存不足等情况下被系统杀死,通过将其设置为前台,可以大大降低其被杀死的机会。前台Service会在系统通知栏显示一个图标,我们可以在这里进行一些操作。前台Service比较常见的场景有音乐播放器和天气预报等:
那么接下来我们就直接上代码:
- @Override
- public void onCreate() {
- super.onCreate();
- Log.i("test", "onCreate() executed !");
- Intent mIntent = new Intent(this, SecondActivity.class);
- PendingIntent mPendingIntent = PendingIntent.getActivity(this, 0, mIntent, 0);
- Notification mNotification = new NotificationCompat.Builder(this)
- .setSmallIcon(R.mipmap.ic_launcher)
- .setContentTitle("My Notification ")
- .setContentText("Hello World ! ")
- .setContentIntent(mPendingIntent)
- .build();
- // 注意:提供给 startForeground() 的整型 ID 不得为 0。
- // 要从前台移除服务,请调用 stopForeground()。此方法采用一个布尔值,指示是否也移除状态栏通知。
- // 然而stopForeground()不会停止服务。 但是,如果您在服务正在前台运行时将其停止,则通知也会被移除。
- startForeground(1, mNotification);
- }
其实这里的实现很简单,就是将一个Notification通过startForeground(1, mNotification);传进去,从而将Notification与 Service建立起关联。我们点击这个通知,就会跳转到第二个Activity(但是该Notification并不会消失),截图如下: