Service详解及应用(二)-- 讲解应用篇

上篇我们讲解了Android中的5中等级的进程,分别是:前台进程、可见进程、服务进程、后台进程、空进程。系统会按照内存的需求先杀死等级较低的进程。其中,后台进程、空进程极容易被杀死,且杀死后不会重启。但是今天将的服务进程却不同, 服务进程被系统杀死后,内存充足时会自动重新启动,适合做一些后台的操作。今天我们就来讲讲Service。

这篇文章,我打算先直接从Google提供的Service的生命周期讲起,根据生命周期的各种回调方法一步步深入。

Service的生命周期

Service可以看成一种没有前台界面的Activity,生命周期与Activity有点类似。

Service生命周期

看看Service很NB啊,((o)/),既然可以做到被系统杀死后在内存充足的情况下重启,那么必有其特殊之处。不同的使用方式,有着不同的生命周期。现在看不懂没关系,后面会一个个讲的。这只是个药引子~

服务有两种使用方法:

  • 方式一:调用startService创建启动服务
  • 方式二:调用bindService创建绑定服务

无论使用哪一个方法,讲解这个之前都要先看看怎么创建一个Service。

声明一个Service

  • 清单文件配置:
    <service android:name=".RemoteService"/>

为了不让其他应用调用到我们自己的Service,为了安全,我们不要在Service中添加Intentfilter过滤器。也就是说尽量使用显示启动方法或者绑定Service。
更安全的防护罩是在Service的清单文件中添加android:exported="false"这样就将该Service与外界的APP隔绝起来,其他应用无权访问我们的服务。

  • 继承自Service或者其子类
public class RemoteService extends Service {

    @Override
    public IBinder onBind(Intent intent) {
        return null;    
    }
}

经过上面两个操作,我们便创建好了一个Service。

最显眼的莫过于需要实现一个IBinder onBind(Intent intent),这个方法,现在先不讲。先来看看如果启动一个服务。

MainActivity添加两个按钮,一个按钮启动服务,一个按钮停止服务。

声明过Service之后,下面就分创建启动的服务创建绑定的服务两个来讲解了,最后再总结讲解总的生命周期。

创建启动的服务

分两部分讲,启动服务和停止服务。

启动服务

还是先看看这部分生命周期方法。

通过startService(intent)创建启动的服务

public void startService(View view){
    Intent intent = new Intent();
    intent.setClass(this, RemoteService.class);
   intent.putExtra("sendData", "通过intent保存MainActivity组件中要传递的数据");
    startService(intent);//启动RemoteService服务
}
查看已经成功启动的RemoteService

前面讲过,可以将Service看出一个没有前台界面的Activity,看看现在在组件中(这里是Activity)启动一个Service也是如启动一个Activity一般如此简单。

讲解创建启动的Service的生命周期方法

先看下演示,看下整体概况:

public class RemoteService extends Service {
    @Override
    public void onCreate() {//Service被创建时回调
        super.onCreate();
        Log.i("hui", "------onCreate-----");
    }
     @Override
    public int onStartCommand(Intent intent, int flags, int startId) {//Service被启动时回调
        Log.i("hui", "--intent:"+ intent.getStringExtra("sendData")+ "flags="+flags+"---startId="+startId);
        return super.onStartCommand(intent, flags, startId);
    } 
   @Override
    public void onDestroy() {//Service被销毁时回调
        super.onDestroy();
        Log.i("hui", "------onDestroy-----");
    }
    @Override
    public IBinder onBind(Intent intent) {        return null;    }}

下面看看这种情况下的生命周期方法的具体流程。

通过startService创建启动的Service生命周期

onCreate()

当Service被创建的时候会回调这个方法。第一次启动Service的时候,由于Service没有被创建过,故:先创建Service,然后再启动Service。所以:onCreater->onStartCommand。只有当Service没有创建的时候才会回调这个方法,一旦Service被创建后再次startService启动Service也不会再次回调这个方法。

onStartCommand

返回值

默认返回super.onStartCommand(intent, flags, startId),看下这个方法。

public int onStartCommand(Intent intent, int flags, int startId) {
    onStart(intent, startId);
    return mStartCompatibility ? START_STICKY_COMPATIBILITY : START_STICKY;
}

默认情况mStartCompatibility 为false,返回START_STICKY。其实,这个返回值有三种。
还记得前面讲解Service的生命周期的时候说过,Service的生命周期很NB,因为它被系统杀死后,在内存充足的情况下回重新启动。
这里的重新启动,有三种重新启动方式,上面的返回的值就对应三种重新启动方式之一。下面就来看看者这三个具体的返回值。看起来很烦人,不过都很好理解的,实在不理解没关系,我们只需要返回默认的就可以了。

  • START_NO_STICKY
    sticky是“粘性的”,这里指的是非粘性启动。下面翻译Google文档的解释:
    如果返回START_NOT_STICKY,表示当Service运行的进程被Android系统强制杀掉之后,不会重新创建该Service,当然如果在其被杀掉之后一段时间又调用了startService,那么该Service又将被实例化。
    那什么情境下返回该值比较恰当呢?
    如果我们某个Service执行的工作被中断几次无关紧要或者对Android内存紧张的情况下需要被杀掉且不会立即重新创建这种行为也可接受,那么我们便可将 onStartCommand的返回值设置为START_NOT_STICKY。举个例子,某个Service需要定时从服务器获取最新数据:通过一个定时器每隔指定的N分钟让定时器启动Service去获取服务端的最新数据。当执行到Service的onStartCommand时,在该方法内再规划一个N分钟后的定时器用于再次启动该Service并开辟一个新的线程去执行网络操作。假设Service在从服务器获取最新数据的过程中被Android系统强制杀掉,Service不会再重新创建,这也没关系,因为再过N分钟定时器就会再次启动该Service并重新获取数据。
  • START_STICKY(默认返回值)
    这里指的是粘性启动。既然粘性启动,就不会向上面的非粘性启动那么的容易被干死而不启动。这种启动方式,启动后,如果在某一刻该服务被干死,那么系统会将这个Service标记为" started"状态,这意味着Service是已经启动的状态。然后系统会尝试着去重新启动该服务(try to re-create the service)的实例,然后再回调onStartCommand方法,但是此时intent的数据已经是null了。所以使用这种方式启动的服务在使用intent时需要进行非空判断。
    当我们使用服务时不需要intent时,以及可以在任何时间重启Service都没问题的话就可以使用这种方式启动服务。例如:后台播放音乐。
  • START_REDELIVER_INTENT
    redeliver是“再交付”的意思。也就是在交付intent的意思。这是基于START_STICKY启动方式的一种加强,使用这种方式启动的Service会保留intent的值。
    使用场景:需要重启,需要保留intent。
入参
  • intent
    这个intent就是刚才startService(intent)中的intent。在创建启动的服务所在的组件(这里是Activity)中如果需要传递给Service中数据,可以将要传递的额数据放到这个Intent里面

  • flags
    代表了创建启动Service的方式,是下面三种方式值的一种。这三种方式的值对应着上面的三种启动方式。

    • 0
      对应START_NO_STICKY启动方式
    • START_FLAG_RETRY(0x0002)
      对应START_STICKY启动方式
    • START_FLAG_REDELIVERY(0x0001)
      对应START_REDELIVER_INTENT启动方式
  • startId
    这个代表着每次创建启动Service的唯一身份ID,每次startService,这个startId均不相同。(可以从上面的图“通过startService创建启动的Service生命周期”看出来)。这个用于处理多个onStartCommand请求时,关闭Service时使用的。下面会对这个使用详细讲解的。

总结onStartCommand方法:

  • 这个方法在第一次创建启动Service的时候,由于没有创建过Service,所以会onCreate->onStartCommand。
  • 第二次乃至以后再重复启动Service的时候,Service已经被创建过,无需再被创建了。故:只回调了onStartCommand。所以,创建启动Service的时候,每次启动都会回调onStartCommand方法

onDestory

  • 服务停止的时候会回调onDestory方法。

总结创建启动服务:

  • 其他组件可以通过startService(intent)创建启动服务。该服务被启动后,服务与启动他的组件没有任何关系了。即使该组件被干掉,该Service任然不会被干掉。
  • 这种方式的服务一旦被启动后,基本不会被关闭。除非不足以支持优先级较高的进程(前台进程、可见进程)继续运行,系统才会停止该服务。系统一旦停止该服务后,当系统内存足够的时候会再次重启(具体依赖于onStartCommand的返回值)。但是如果手动关闭了该服务,该服务不会再次重启了(我记得好像是Android4.0以前的时候是会重启的,Android4.0以后不会重启了,具体哪个版本忘记了~~~)。
  • 可以将要传递给Service的数据放到Intent里面。服务被启动后回调onStartCommand方法,在这个方法中会给我们刚才传递的Intent,然后就可以为所欲为了。
  • Service默认情况下不会单独启动一个进程或者线程,默认情况下与主线程所在的进程在同一个进程中。所以,不能在Service中进行耗时操作,否则会引起ANR(响应时间超过5s)。鉴于这种情况,强烈建议在服务启动后,在onStartCommand方法中开启一个子线程去做具体的操作。相应的操作做完后去关闭服务,以免于多余的消耗系统资源给用户带来不适感。

下面就来讲讲如何优雅的关闭服务。

关闭服务

关闭服务有两种方法,stopService和stopSelf(),以及stopSelf(int startId)。

stopService

public void stopService(View view){
    Intent intent = new Intent();
    intent.setClass(this, RemoteService.class);
    stopService(intent);//停止RemoteService服务
}
启动服务后再停止服务

这种方法适用于第三方组件关闭该服务。

stopSelf()

public int onStartCommand(Intent intent, int flags, int startId) {//Service被启动时回调
    Log.i("hui", "--intent:"+ intent.getStringExtra("sendData")+ "flags="+flags+"---startId="+startId);
    new Thread(){
        @Override
        public void run() {
            super.run();
            try {
                sleep(6000);//6S后干掉自己
                stopSelf();//干掉自己
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }.start();
    return super.onStartCommand(intent, flags, startId);}
stopSelf,6s后干掉自己

上面例子中,在onStartCommand中开启一个子线程,6s后调用stopSelf方法干掉自己,停止服务。
那么问题来了。如果我要进行多次的下载图片,也就是说会多次调用onStartCommand方法。但是,不能每次下载完一张关闭一次,然后新的下载重新打开再重新下载,再停止服务在重新启动服务下载,这是不合理的。
下载完所有图片后需要关闭服务。可是我们不知道每张图片需要下载图片多久,不能再这次下载图片完成后就立即停止服务,而是在关闭服务的时候看看在自己下载图片的过程中是否有新的请求过来,如果没有新的下载图片的请求,那么我们就可以关闭服务了,这样就省了一次停止与启动服务。

那么就可以这样做,下载图片开始的时候,保存一个TAG标记。每次有新的下载请求的时候就更改TAG标记的值。而这个TAG标记是与下载的线程携带的。当下载图片的线程结束后,看看现在的TAG还是不是当初的TAG(现在的你还是以前的你么?O(∩_∩)O哈哈~)。如果lastTag != oldTag,说明已经有新的请求下载的Service,那么我们就不能关闭服务。关闭服务就由这个新的线程自己去关闭,关闭时候做同样的判断。
如果有一个子线程它存储的lastTag == oldTag,说明在自己下载的过程中没有新的下载需求。那么这是我就可以停止服务了。

Google就为我们提供这么一个方法,stopSelf(startId)。拿下面例子讲一讲:

stopSelf(int startId)

public int onStartCommand(Intent intent, int flags, final int startId) {//Service被启动时回调
    Log.i("hui", "--intent:"+ intent.getStringExtra("sendData")+ "flags="+flags+"---startId="+startId);
    new Thread(){
        @Override
        public void run() {
            super.run();
            try {
                sleep(1000 * startId);
                stopSelf(startId);//干掉自己
            } catch (InterruptedException e) {
                e.printStackTrace();
            }    
        
  }    
}.start();
    return super.onStartCommand(intent, flags, startId);
}
stopSelf(startId)
停止服务的时候的判断

每次一个onStartCommand请求的时候,都会传过来唯一的startId。这个startId被线程携带着的。当代码执行到stopSelf的时候,进行r.getLastStartId != startId(最近启动的Service的id跟startId进行是否相等判断)的判断,如果不等于就return掉,从而不会停止服务;如果最近启动的一个Service的id等于startId,说明在自己线程运行的过程中没有新的onStartCommand请求过来,这时就可以停止掉服务了。

小结创建启动的Service

  • 清单文件注册,继承Service(没什么说的,必须的)
  • 纯启动Service生命周期onCreate-onStartCommand-onDestory
  • 在onStartCommand方法中开启子线程进行估计耗时的操作
  • 子线程结束后stopSelf关闭Service(自己关闭自己)
  • 也可通过其他组件stopService(intent)来关闭指定的Service

对于这些总结有疑问的,坚持重新再看一遍,重新捋一下思路。

创建绑定的Service

我们知道创建启动的Service后,Service与启动它的组件不会有任何关系了。这时的Service就像一个断了线的风筝一样,不会受我们的控制了。
需求:如果我们需要开发一个音乐播放器,主页面有上一首(last)、下一首(next)、播放(play)、暂停(pause)这几个基本按钮。
分析

  • 那么,我们能把播放音乐的代码直接放在MainActivity中么?肯定不行的,那么就需要开启一个子线程运行播放音乐的代码。由于音乐播放器即使退到后台,还是要继续播音乐的,MainActivity退出的时候,子线程就变成了空进程,很容易被杀掉,所以播放音乐的代码不能放到MainActivity的子线程中。这里就用到了Service。
  • 那么把 play方法放到onStartCommand中?肯定不行,由于创建启动的Service后,Service与启动它的组件不会有任何关系,我们拿不到Service的对象,也就没办法调用Service中的play等方法了。。。。

如果我们想使用创建启动的Service中的方法,是没用办法的。怎么办呢?
这时,就可以使用Service的另一种姿势了:创建绑定的Service。
那么我们就来看看如何一步步使用Service中的方法。分两部分讲绑定服务解绑服务

绑定Service

一共有三步,打开冰箱门、把大象塞进去、关不上冰箱门。

  • Service中实现onBind方法
/** * 绑定的Service必须实现的方法,返回非null * @param intent * @return */
@Overridepublic IBinder onBind(Intent intent) {
    Log.i("hui", "-onBind-intent:"+ intent.getStringExtra("sendData"));
    return myBinder;
}

实现onBind方法有三种解决方案(先使用第一个方案详细讲解,再讲解后面的两个):
1.使用已经实现了IBinder的Binder子类
2.使用Messenger
3.使用AIDL

  • MainActivity中创建ServiceConnection的具体实现
/** * 服务连接对象 */
private ServiceConnection serviceConnection = new ServiceConnection() {
    /**
     * 当创建绑定Service与组件成功连接后,此方法会
     * 被调用。
     * @param name Service组件名称
     * @param service Service中返回的IBinder对象
     */ 
   @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        Log.d("hui", "---------onServiceConnected-----------");
    } 
   /**
     * 当创建绑定Service与组件意外中断后,此方法会被调用。
     * @param name
     */    @Override
    public void onServiceDisconnected(ComponentName name) {
        Log.d("hui", "---------onServiceConnected-----------");
    }
};
  • MainActivity中绑定Service
public void bindService(View view){
    Intent intent = new Intent();
    intent.putExtra("sendData", "通过bindService的intent保存MainActivity组件中要传递的数据"); 
     intent.setClass(this, RemoteService.class);
    /**
     * Intent service, ServiceConnection conn, int flags
     ** @param service  包装了要启动的Service及要携带的信息
     * @param conn
     * @param flags 启动Service的发送,一般为BIND_AUTO_CREATE,表示:如果服务不存在会自动建       
*/    
bindService(intent, serviceConnection, BIND_AUTO_CREATE);
}

创建绑定的Service

分析:使用bindService(Intent service, @NonNull ServiceConnection conn, @BindServiceFlags int flags)方法创建绑定的Service。

  • ServiceConnection
    类似于媒婆,是公公家(MainActivity)和丈母娘家(Service)交流的纽带,拿到的IBnder就是丈母娘家的闺女。
  • onServiceConnected
    * 当两家喜结连理后,也就是当创建绑定Service与组件成功连接后,此方法会
     * 被调用。
     * @param name Service组件名称[丈母娘家(Service)名称]
     * @param service 丈母娘家的闺女,Service中返回的IBinder对象
     */ 
   @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        Log.d("hui", "---------onServiceConnected-----------");
    }

  婚姻意外over
   public void onServiceDisconnected(ComponentName name) {
        Log.d("hui", "---------onServiceConnected-----------");
    }

四大组件中,只有Activity、Service、ContentProvider支持绑定Service。

解绑Service

解绑Service十分简单,干掉两家人之间的媒婆就行了。

public void unBindService(View view){
    unbindService(serviceConnection);
}
解绑Service

当第一次点击解绑时,unbindService方法解绑了MainActivity与RemoteService之间的纽扣--serviceConnection,之后MainActivity和RemoteService之间没有任何关系了。所以当第二次再次解绑时,crash了。但是stopService却可以多次被调用。两者之间是有区别的。

如何调用Service中的方法呢?

onServiceConnected(ComponentName name, IBinder service)方法中,这里的IBnder对象service就是启动Service的组件(这里是MainActivity)所连接的绑定的Service中的IBinder onBinder(Intent)方法返回的IBinder对象。既然我们都拿到了Service中的某个对象值(这里是返回的service),那么一切就简单了。。。

调用Service中方法思路

  • 创建一个实现了IBinder接口的对象并且返回
  • 这个对象中提供一个返回Service对象的方法
  • 拿到Service对象,为所欲为

RemoteService

public class RemoteService extends Service {
    private final MyBinder myBinder  = new MyBinder();
    @Override    public void onCreate() {//Service被创建时回调
        super.onCreate(); 
       Log.i("hui", "------onCreate-----");
    }
    /**
     * 
    * @param intent 传递过来的包装的intent数据
     * @param flags
     * @param startId 启动的Service的唯一标记
     * @return
     */ 
   @Override
    public int onStartCommand(Intent intent, int flags, final int startId) {//Service被启动时回调
        Log.i("hui", "-onStartCommand-intent:"+ intent.getStringExtra("sendData")+ "flags="+flags+"---startId="+startId);
           return super.onStartCommand(intent, flags, startId); 
   } 
   @Override
    public void onDestroy() {//Service被销毁时回调
        super.onDestroy();
        Log.i("hui", "------onDestroy-----");
    } 
   /**
     * 绑定的Service必须实现的方法,返回非null
     * @param intent
     * @return 
    */
    @Override
    public IBinder onBind(Intent intent) {
        Log.i("hui", "-onBind-intent:"+ intent.getStringExtra("sendData")); 
       return myBinder;
    }
    /**
     * Binder是实现了IBinder接口的对象
     */
    public class MyBinder extends Binder{
        /**
         * 拿到Service的对象
         * @return
         */ 
       public RemoteService getRemoteService(){
            return RemoteService.this; 
       }
    }
    public void playMusic() {
                 Log.i("hui", "--------playMusic------");
   }
      public void pauseMusic() {
             Log.i("hui", "--------pauseMusic------");
   }
}

MAinActivity

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
    
 public void bindService(View view){
        Intent intent = new Intent();
        intent.putExtra("sendData", "通过bindService的intent保存MainActivity组件中要传递的数据");        intent.setClass(this, RemoteService.class);
        /**
         * Intent service, ServiceConnection conn, int flags
         ** @param service  包装了要启动的Service及要携带的信息
         * @param conn
         * @param flags 启动Service的发送,一般为BIND_AUTO_CREATE,表示:如果服务不存在会自动创建         */ 
       bindService(intent, serviceConnection, BIND_AUTO_CREATE);
    } 
   //播放
public void play(View view){
    if(remoteService != null){
        remoteService.playMusic();
}
       }
//暂停
public void   pause(View view){
         if(remoteService != null){
        remoteService.pauseMusic();
   }
}


private RemoteService remoteService;
    /**
     * 服务连接对象 
    */
    private ServiceConnection serviceConnection = new ServiceConnection() {
        /**
         * 当创建绑定Service与组件成功连接后,此方法会 
        * 被调用。
         * @param name 组件名称
         * @param service Service中返回的IBinder对象
         */
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            Log.d("hui", "---------onServiceConnected-----------");
            //将拿到的Service中onBInder返回的对象也就是MyBInder对象进行强制转换
            RemoteService.MyBinder myBInder = (RemoteService.MyBinder) service;
           //调用MyBinder的方法,拿到RemoteService对象
           remoteService = myBInder.getRemoteService();
        }
        /**
         * 当创建绑定Service与组件意外中断后,此方法会被调用。
         * @param name
         */
        @Override
        public void onServiceDisconnected(ComponentName name) {
            Log.d("hui", "---------onServiceConnected-----------");
        }
    };
}

拿到Service对象

分析步骤:
当创建绑定Service后,由于RemoteService的onBinder返回非null,故RemoteService可以通过ServiceConnection与MainActivity成功建立连接,然后回调ServiceConnection的onServiceConnected方法。
同时,会把RemoteService的onBinder的返回值MyBinder传递给MainActivity。拿到service,也就是MyBinder对象,进行转型,拿到RemoteService中的MyBinder。
然后我们调用myBInder.getRemoteService()方法拿到RemoteService的对象,进而调用RemoteService中的方法。

创建绑定服务的生命周期

在刚才的“拿到Service对象”的GIF图中,当我退出APP的时候,MainActivity被销毁,这时的RemoteService方法的onDestory方法也被调用了,说明:MainActivity被销毁的时候RemoteService也被销毁了。
所以,创建绑定的Service与其绑定的Service同生共死。也就是说创建绑定的Service的存活与否依赖于其绑定的组件,如果该Service所绑定的所有的组件均已被销毁,那么该Service就被销毁了。

服务的混合启动

那么对于音乐播放器而言这是万万不可的,我Service不能因为你组件的销毁就被干掉(组件被销毁后 ,播放音乐的子线程所在进程就变成了空进程,很容易被干掉),怎么办?
使用创建启动的Service+创建绑定的Service的混合启动方案{【bindService+startService】}
也就是说我们在MainActivity上绑定Service后,再次启动Service。这样,对于创建启动的Service而言不会因为组件的销毁而销毁,创建绑定的Service后,组件可以与Service交互。

playMusic的方法中开启子线程,模拟播放音乐

public void playMusic() {
    new Thread(){
        @Override
        public void run() {
            super.run();
            int i = 0;
            while (true){
                Log.i("hui", "--------playMusic--i=" + (i++));
                try {
                    sleep(800);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }.start();}
startService+bindService混合启动模式

startService+bindService两个知识点:

  • 对于startService+bindService可以做一些实际的可控制的后台操作,也就是可以在Service中做我们指定的操作。使得组件与Service之间有了纽带关联。
  • 生命周期方法中,单独的解绑Service不会销毁Service,只有解绑加上stopService才会销毁Service
  • 被销毁后的Service中虽然任然运行着播放音乐的代码但是,这个进程已经变成了空进程,一不留神就被系统干掉了。这与运行服务进程是完全不同的。
  • Google建议:如果您的服务仅供本地应用使用,不需要跨进程工作,则可以实现自有Binder 类,让您的客户端通过该类直接访问服务中的公共方法。

上面我们几乎讲解完了绑定Service的整体流程,使用Binder或者子类实现onBinder方法的,但是这种方法有局限性,不支持跨进程通信。那么如果我们要跨进程通信,怎么办呢?这就是开头讲的绑定Service的实现onBinder方法的方案二(使用Messenger)和方案三(使用AIDL)。

跨进程知识补充

一般情况下一个APP的所有组件运行在唯一的一个进程中,除非你指定他运行的进程(例如使用“android:process”的属性,通过这个属性,我们可以指定某个组件运行的进程。我们可以通过设置这个属性,让每个组件运行在它自己的进程中,也可以只让某些组件共享一个进程。),否则都在自己的进程中运行者的。
两个APP是运行在不同的进程中的,默认无法共享资源的。如果让两个APP之间进行通信,就涉及到了跨进程通信(IPC)。

使用Messenger跨进程通信

总体思想,基于Handler处理方案,利用Handler在Service中创建一个Messenger对象,然后在客户端(这里是ClientMainActivity)拿到这个Messenger对象,然后使用这个Messenger对象将Message消息发送给Handler处理。按着这个理解,这种绑定方案理解起来就简单多了。
在原来的项目中新建一个MessengerService

public class MessengerService extends Service {
    private Handler handler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            Log.i("hui", "--MessengerService-----handleMessage-----");
        }
    };
//利用Handler构建一个Messenger对象
    private Messenger message = new Messenger(handler);
    @Override
    public IBinder onBind(Intent intent) {
        return message.getBinder();//返回Messenger中的binder,客户端拿到这个Messenger中的binder可以构建出唯一的Messenger。
    }
}
//清单文件
<service android:name=".MessengerService" >
    <intent-filter>
        <action android:name="com.example.asia.remoteservice.MessengerService"/>
    </intent-filter>
</service>

创建第三方APP-Client

public class ClientMainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
    public void startMessengerService(View view){ 
       Intent intent = new Intent();
//这里注意,MessengerService的清单文件配置action为"com.example.asia.remoteservice.MessengerService"
        intent.setAction("com.example.asia.remoteservice.MessengerService");
        bindService(intent, conn, BIND_AUTO_CREATE);
    }
    public void sendMsgService(View view){
        if(null != messenger){
            Message message = Message.obtain();
            try {
//使用messenger,将我们要传递的消息放到handler上,
                messenger.send(message);

            } catch (RemoteException e) {
                e.printStackTrace();
            } 
       }
    }
    private Messenger messenger;
    private ServiceConnection conn = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            Log.d("hui", "----client-----onServiceConnected-----------");
//客户端拿到这个binder(这个是Messenger中的Binder)可以构建出唯一的Messenger。
            messenger = new Messenger(service);
        }
        @Override
        public void onServiceDisconnected(ComponentName name) {
        }
    };
}
使用Messenger图示流程
Messenger实现绑定

与 AIDL 比较
当您需要执行 IPC 时,为您的接口使用[Messenger] 要比使用 AIDL 实现它更加简单,因为 [Messenger] 会将所有服务调用排入队列,而纯粹的 AIDL 接口会同时向服务发送多个请求,服务随后必须应对多线程处理。
由于对于大多数应用,服务不需要执行多线程处理,因此使用 Messenger可让服务一次处理一个调用。如果您的服务必须执行多线程处理,则应使用 AIDL 来定义接口。
---来自Google文档

好了,到这里为止。回顾一下知识点,如有疑问,重新看看:

  • Service两种使用方式

    • 创建启动Service
    • 创建绑定服务
  • 创建启动Service

    • startService
    • stopService
    • stopSelf
    • onStartCommand
  • 创建绑定服务

    • 实现IBinder,继承Binder
    • 使用Messenger
    • 使用AIDL
  • 生命周期

    • 创建启动Service:onCreate、onStartCommand、onDestory
    • 创建绑定Service : onCreate、onBind、onUnbind、onDestory
  • 混合启动

AIDL

这个单独一片讲解

对于以上讲解的,如有问题,不吝赐教!

END.

晚安
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269

推荐阅读更多精彩内容

  • Intent 是一个消息传递对象,您可以使用它从其他应用组件请求操作。尽管 Intent 可以通过多种方式促进组件...
    牧童遥指2000阅读 4,928评论 0 12
  • 前言:本文所写的是博主的个人见解,如有错误或者不恰当之处,欢迎私信博主,加以改正!原文链接,demo链接 Serv...
    PassersHowe阅读 1,335评论 0 5
  • [文章内容来自Developers] Service是一个可以在后台执行长时间运行操作而不提供用户界面的应用组件。...
    岳小川阅读 830评论 0 7
  • 想你时你在天边想你时你在眼前想你时你在脑海想你时你在心田 每次听到王菲“传奇”歌曲里这几句歌词,都会被其细腻的笔触...
    一心向荣阅读 369评论 2 0
  • 笑容是一场梦,只是这个梦境中,有了你,所以变得不太孤单,而我们都是梦中的主角,所以心中向往,无所畏惧。 笑容是一首...
    Hui_浅沫阅读 99评论 0 0