关于Service的一些难点

作者: 大虾啊啊啊 | 来源:发表于2019-04-16 17:11 被阅读1次

    1、前言

    Service作为安卓四大组件之一,在开发中也是经常遇到的,顾明思议我们称为后台服务。当我们的业务不需要前台页面的时候,我们可以使用Service来实现。

    2、关于Service的启动方式以及生命周期

    Activity的生命周期我们也许背的滚瓜烂熟了,但是Service的生命周期我们可能往往很容易忽略。和Activity不一样的是Service的启动分为两种:startService和bindService。下面我们分别介绍:

    2.1、startService

    我们通过startService的方式来启动一个service,然后观察生命周期的变化
    1、创建MySevice类

    package com.example.administrator.workmanager;
    
    import android.app.Service;
    import android.content.Intent;
    import android.os.IBinder;
    import android.util.Log;
    
    public class MyService extends Service {
        private static final String TAG ="MyService" ;
    
        @Override
        public IBinder onBind(Intent intent) {
            Log.e(TAG, "onBind: ..." );
            return null;
        }
    
        @Override
        public void onCreate() {
            super.onCreate();
            Log.e(TAG, "onCreate: ..." );
        }
    
        @Override
        public int onStartCommand(Intent intent, int flags, int startId) {
            Log.e(TAG, "onStartCommand: ..." );
            return super.onStartCommand(intent, flags, startId);
        }
    
        @Override
        public void onDestroy() {
            Log.e(TAG, "onDestroy: ..." );
            super.onDestroy();
        }
    }
    
    
    

    2、配置AndroidManifest.xml

       <service android:name=".MyService"/>
    

    3、startService和stopService

       final Intent intent = new Intent(MyActivity.this,MyService.class);
            ivOne.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
    
                    startService(intent);
                }
            });
            ivTwo.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    stopService(intent);
                }
            });
    

    点击startService启动一个service,我们看打印结果:

    E/MyService: onCreate: ...
    E/MyService: onStartCommand: ...
    

    我们看到首次启动service的时候,首先会调用onCreate方法,然后在调用onStartCommand方法。那如果启动一个servcie之后再启动呢?我们在启动看看:

    E/MyService: onStartCommand: ...
    

    我们看到再次启动的时候,调用了onStartCommand方法,也就是说onCreate在第一次启动的时候会调用。接下来我们来看下通过stopService停止一个service。

       stopService(intent);
    
    E/MyService: onDestroy: ...
    

    通过stopService停止一个service。则会调用onDestroy方法。这个时候当我们再次startService,由于之前的service已经被销毁了,则会重新启动一个service

    E/MyService: onCreate: ...
    E/MyService: onStartCommand: ...
    

    那么就会重新调用onCreate和onStartCommand方法。

    2.2、Activity的生命周期会影响service的生命周期吗?

    我们看到我们是在Activity中通过startService的方式启动一个service,那么Activity的生命周期和service有关联吗?我们在做一个测试。
    我们启动service之后,分别点击Activity进行跳转,回到原来的Activity,关闭Activity等等。发现即使Activity被关闭了调用onDestroy方法。service的生命周期也不会有影响,也就是说service依然是存活在后台中的。因此我们得出结论,通过startService方式启动一个service,那么service的生命周期和Activity的生命周期是没有关系的。他们之间真的没关系吗?答案不是绝对的,下面我们介绍通过bindService的方式启动一个service。

    2.3、bindService

    首先我们观察到在MyServcie类中是不是有一个onBind方法。在上面我们没有提到,那么其实这个方法和bindService有关的。

       @Override
        public IBinder onBind(Intent intent) {
            Log.e(TAG, "onBind: ..." );
            return null;
        }
    
    

    下面我们通过bindService的方式来启动service

       //第一个参数:Intent意图
            //第二个参数:是一个接口,通过这个接口接收服务开启或者停止的消息,并且这个参数不能为null
            //第三个参数:开启服务时的操作,BIND_AUTO_CREATE代表自动创建service
            final Intent intent = new Intent(MyActivity.this,MyService.class);
            final  MyConnection conn = new MyConnection();
            ivOne.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
    
                    bindService(intent,conn,BIND_AUTO_CREATE);
                }
            });
            ivTwo.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    unbindService(conn);
                }
            });
    
    
     private class MyConnection implements ServiceConnection {
    
            @Override
            public void onServiceConnected(ComponentName name, IBinder service) {
                //只有当我们自己写的MyService的onBind方法返回值不为null时,才会被调用
                Log.e("call","onServiceConnected");
            }
    
            @Override
            public void onServiceDisconnected(ComponentName name) {
                //这个方法只有出现异常时才会调用,服务器正常退出不会调用。
                Log.e("call","onServiceDisconnected");
            }
        }
    

    我们来看打印结果:分别是onCreate、onBind。

     E/MyService: onCreate: ...
     E/MyService: onBind: ...
    

    当我们再次调用bindService的时候,不会有任何打印。然后我们来看下通过unbindService停止一个service。我们可以打印结果调用了onUnbind然后调用onDestroy方法

     E/MyService: onUnbind: ...
     E/MyService: onDestroy: ...
    

    我们得出结论:通过bindService的方式启动一个service首先会调用onCreate方法,然后在调用onBind方法,这种启动方式我们一般叫做绑定服务,也就是service和activity 绑定。当我们调用bindService服务之后,我们在再次调用,不会调用任何生命周期方法。我们通过unbindService方式停止解绑一个服务,分别会调用onUnbind和onDestroy方法。那这种方式启动的service和activity有没有关联呢?我们通过bindService启动一个service之后,然后退出当前activity,我们来看下面的生命周期的打印结果:分别调用了Activity的onPause和onDestroy方法,然后在调用了service的onUnbind和onDestroy方法

    E/MyService: Activity onPause: ....
     E/MyService: Activity onDestroy: ....
     E/MyService: onUnbind: ...
    E/MyService: onDestroy: ...
    

    我们得出结论就是:通过bindService方式启动一个service其实是和当前activity绑定的,当activity被销毁的时候,那么service也会相应的解绑和销毁。以上我们分析是在onBind返回值是null的情况下,那么如果onBind的返回值不为null呢?

    2.4、onBind返回值不为null情况下service生命周期的变化。

    我们对MyService和MyConnection进行修改。

    package com.example.administrator.workmanager;
    
    import android.app.Service;
    import android.content.Intent;
    import android.os.Binder;
    import android.os.IBinder;
    import android.util.Log;
    
    public class MyService extends Service {
        private static final String TAG ="MyService" ;
    
        @Override
        public IBinder onBind(Intent intent) {
            Log.e(TAG, "onBind: ..." );
            return new MyBinder();
        }
    
        @Override
        public void onCreate() {
            super.onCreate();
            Log.e(TAG, "onCreate: ..." );
        }
    
        @Override
        public int onStartCommand(Intent intent, int flags, int startId) {
            Log.e(TAG, "onStartCommand: ..." );
            return super.onStartCommand(intent, flags, startId);
        }
        @Override
        public void onDestroy() {
            Log.e(TAG, "onDestroy: ..." );
            super.onDestroy();
        }
    
        @Override
        public boolean onUnbind(Intent intent) {
            Log.e(TAG, "onUnbind: ..." );
            return super.onUnbind(intent);
    
        }
        public class MyBinder extends Binder {
            public void test(){
                serviceTest();
            }
        }
        private  void serviceTest(){
            Log.e(TAG, "serviceTest: 调用服务中的方法" );
        }
    }
    
    
       private class MyConnection implements ServiceConnection {
    
            @Override
            public void onServiceConnected(ComponentName name, IBinder service) {
                //只有当我们自己写的MyService的onBind方法返回值不为null时,才会被调用
                Log.e(TAG,"onServiceConnected");
                //调用服务中的方法
                ((MyService.MyBinder)service).test();
            }
    
            @Override
            public void onServiceDisconnected(ComponentName name) {
                //这个方法只有出现异常时才会调用,服务器正常退出不会调用。
                Log.e(TAG,"onServiceDisconnected");
            }
        }
    

    我们bindService启动然后看打印结果

     E/MyService: onCreate: ...
     E/MyService: onBind: ...
     E/MyService: onServiceConnected
     E/MyService: serviceTest: 调用服务中的方法
    

    然后在通过unbindeService解绑

    E/MyService: onUnbind: ...
     E/MyService: onDestroy: ...
    

    得出结论是:unbindeService解绑,生命周期的调用顺序还是一样的,但是bindService的时候,在MyConnection中会调用onServiceConnected方法。我们在onServiceConnected可以获取到IBinder对象,其实就是service中onBind返回的MyBiner对象,我们拿到IBinder对象,可以调用MyBiner中的方法,并可以在MyBiner中的方法中调用service的方法,这样也就完成了一次Activity和Service的通信,这种机制我们叫做binder机制。通过binder机制我们可以跨进程通信,假如这个service和我们的Activity不在同一个进程中,那么不就是完成了一次跨进程的通信吗?在启动一个安卓应用时,我们也许以为只是启动了一个应用进程,其实不是的,在内部还启动了很多个进程,我们来看一张图:

    image.png

    从架构图上我们可以看到,安卓系统中主要包含了这几个进程:Init进程、Zygite进程、System Server进程、和应用进程。那么进程之间的通信主要就是以Binder机制为主。

    3、小结

    我们针对以上的知识点进行总结:
    1、service的方式有startService和bindService
    2、startService方法启动,生命周期:onCreate,onStartCommand.
    停止用stopService方法,生命周期:onDestroy
    3、bindService方法启动,生命周期: onCreate, onBind.
    unbindService解绑,onUnbind,onDestroy。
    Activity被销毁的时候,
    Activity onPause
    Activity onDestroy
    Service onUnbind
    Service onDestroy
    4、onBind返回值不为null情况下,我们可以通过MyConnection中onServiceConnected方法获取onBind的返回值,调用服务service中的方法,实现Activity和Service的通信。

    注意

    以上提到的service都是在ui线程中执行的,我们之前的文章有提到,如果想在子线程中执行可以使用IntentService。

    4、关于Android O(8.0)后台service限制

    Android的每次平台更新,都一直在努力收紧应用权限。8.0也不例外,这次是限制应用后台启动service的权限。
    对于所有应用可以使用的解决方法。
    1). 平台区分,8.0以下,爱咋咋地,以前怎样还怎样。
    2). 8.0以上,统一一个常驻service,转为前台服务。方法为:将startService改成startForegroundService,并在对应的service创建的时候,使用startForeground注册自己的notification。
    3). 对,这个方法是带notification的,也就是说,如果想用常驻service,就得让用户知道。

    5、startForegroundService的使用

    我们知道startForegroundService启动的是作为一个前台服务,我们来看下具体的使用方式
    首先创建一个服务,和上面一样

    public class MusicPlayerService extends Service {
      
      private static final String TAG = MusicPlayerService.class.getSimpleName();
      
      @Override
      public void onCreate() {
          super.onCreate();
          Log.d(TAG, "onCreate()");
      }
      
      @Override
      public int onStartCommand(Intent intent, int flags, int startId) {
        Log.d(TAG, "onStartCommand()");
      }
      
      @Override
      public IBinder onBind(Intent intent) {
           Log.d(TAG, "onBind()");
           // TODO: Return the communication channel to the service.
        throw new UnsupportedOperationException("Not yet implemented");
      }
    }
    

    然后创建Notification:
    在Service的onStartCommand中添加如下代码:

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
      Log.d(TAG, "onStartCommand()");
      // 在API11之后构建Notification的方式
      Notification.Builder builder = new Notification.Builder
        (this.getApplicationContext()); //获取一个Notification构造器
      Intent nfIntent = new Intent(this, MainActivity.class);
      
      builder.setContentIntent(PendingIntent.
        getActivity(this, 0, nfIntent, 0)) // 设置PendingIntent
        .setLargeIcon(BitmapFactory.decodeResource(this.getResources(),
          R.mipmap.ic_large)) // 设置下拉列表中的图标(大图标)
        .setContentTitle("下拉列表中的Title") // 设置下拉列表里的标题
        .setSmallIcon(R.mipmap.ic_launcher) // 设置状态栏内的小图标
        .setContentText("要显示的内容") // 设置上下文内容
        .setWhen(System.currentTimeMillis()); // 设置该通知发生的时间
      
      Notification notification = builder.build(); // 获取构建好的Notification
      notification.defaults = Notification.DEFAULT_SOUND; //设置为默认的声音
    }
    

    在完成Notification通知消息的构建后,在Service的onStartCommand中可以使用startForeground方法来让Android服务运行在前台。

    // 参数一:唯一的通知标识;参数二:通知消息。
    startForeground(110, notification);// 开始前台服务
    

    如果需要停止前台服务,可以使用stopForeground来停止正在运行的前台服务。

    @Override
    public void onDestroy() {
      Log.d(TAG, "onDestroy()");
      stopForeground(true);// 停止前台服务--参数:表示是否移除之前的通知
      super.onDestroy();
    }
    

    到此为止,我们基本上就学会了如何使用前台服务了。

    5.1、前台服务与普通服务的区别

    • 前台Service的系统优先级更高、不易被回收;
    • 前台Service会一直有一个正在运行的图标在系统的状态栏显示,下拉状态栏后可以看到更加详细的信息,非常类似于通知的效果。并且图标和标题我们可以在onStartCommand方法中自行设置。

    相关文章

      网友评论

        本文标题:关于Service的一些难点

        本文链接:https://www.haomeiwen.com/subject/skubwqtx.html