基于 Binder 的跨进程通信以及 Service(二):Java 层

同进程使用Service(一)

  • 比如,你做了一个音乐播放 app,里面有一个 MusicService 负责后台播放音乐,对外提供 play(),pause() 的接口
  • 你在一个 Activity 里想调用这个 Service 的 play(),怎么搞?
  • 必须在这个 Activity 里拿到刚才启动的 MusicService 的实例,但这是不可能的。Service 实例是由安卓OS维护的,你拿不到。(启动 Service 可不是 new MusicService() new 出来的,而是调用安卓系统的 startService()/bindService(),这些函数不给你返回被启动的Service实例)
  • 我们可以想个办法把 Service 的实例返回去:首先要了解 Service 和客户端的回调机制,这里涉及一个重要的接口类:IBinder。

一、Service机制

  • 服务端都继承自 Service 类,Service 类有个 onBind() 方法。当有客户端来使用 Service 的时候,安卓OS会调用该 Service 的 onBinde()。不过因为 onBind() 的返回类型并不是 Service,所以我们不能直接把 Service 的实例返回去。
  • 并且,就算你返回去了也没用,因为客户端不是 onBind() 的调用者,安卓OS才是。客户端调的是 bindService()。
  • 客户端调用 bindService() 去绑定 Service,以便能够使用 Service 的功能。函数原型是:boolean bindService(Intent intent, ServiceConnection conn, int flags)
  • 第一个参数 intent 指定要绑定的是哪个 Service,我们这里就是: new Intent(this, MusicService.class);
  • 第三个参数 flags 一般用 BIND_AUTO_CREATE 即可。表示如果被绑定的Service还没启动,则安卓OS会自动启动它。
  • 重点是第二个参数 conn。
    • 它是 ServiceConnection 类型的
    • 当 Service 绑定好了之后(Service 的 onBind() 返回了),安卓OS会回调 conn.onServiceConnected(ComponentName name, IBinder service)
    • 安卓OS会在这个回调里,传来一个 IBinder 类型的对象(第二个参数),它就是 Service 端的 onBind() 方法 return 的东西。
  • 但这里还是没有拿到 Service 的实例,因为 onBind() 返回的并不是 Service 实例,而是一个 IBinder。
  • 不过既然知道了服务端和客户端之间交互的流程,就可以想办法让客户端拿到 Service 实例了。

二、让客户端拿到Service的实例

  • Service 端通过重写 onBind() 方法,让它返回一个可以拿到 Service 实例的 IBinder。比如给这个 IBinder 添加一个方法,getService().
public class MusicService extends Service {
   

    // 用于让 onBind() 返回的类,它从 Binder 继承,即实现了 IBinder 接口
    // 我们给它加了一个方法,getService(),用于返回 MusicService 的实例
    public class MyBinder extends Binder {
   
        MusicService getService() {
            return MusicService.this;
        }
    }

    // 重写 onBind 方法,返回一个 MyBinder 的实例,
    // 而 MyBinder 的 getService() 可以拿到 MusicService 实例
    @Override
    public IBinder onBind(Intent intent) {
        return new MyBinder();
    }

    public void play() {
      Log.d("testing", "playing");
    }
}
  • 客户端准备一个 ServiceConnection,用来绑定 Service 并接收 Service onBind() 返回的那个 IBinder。
  • 重写这个 ServiceConnection 的 onServiceConnected() 方法,把安卓OS回传过来的 IBinder 用起来,得到 Service 的实例。
import com.zsl.musicserver.MusicService.MyBinder;

public class MainActivity extends Activity {
   
    MusicService mService;

    // 准备一个 ServiceConnection,用来绑定 Service 并接收 Service onBind() 返回的那个 IBinder。
    private ServiceConnection mConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName className,IBinder service) {
            // 注意 !!!! 这里要进行强转,才能使用 getService() 方法 !!!!
            MyBinder binder = (MyBinder) service;
            mService = binder.getService();
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Intent intent = new Intent(this, MusicService.class);
        bindService(intent, mConnection, BIND_AUTO_CREATE);
    }

    public void onButtonClick(View v) {
        // 调用 Service 的业务函数
        mService.play();
    }
}
  • 以上程序写在同一个工程里可以正确运行,点击 play 按钮后 logcat 输出 playing。

三、小结

  • Service 重写 onBind() 方法,返回一个 IBinder 的子类。
  • 客户端用 conn 的 onServiceConnected() 方法接收到这个返回的对象。
  • 这样,客户端和 Service 的联系就建立起来了。

四、问题

  • 首先,上面这种做法很难做到跨进程,先不说原理,就从代码层面看也不行
  • 上例中客户端需要拿到Service的实例,这就要求客户端和服务端必须在一个工程里,如果是两个 app 根本没法搞:
MusicService mService; // 客户端代码里必须有 MusicService 这个类
  • 理论上服务端的 MusicService 类对客户端应该是透明的,客户端只需要使用 Service 的功能就可以了。
  • 所以我们稍微更进一步,把 MusicService 抽象出一个接口:MusicInterface。服务端和客户端都有接口类 MusicInterface,但分别实现,客户端是给服务端发消息,服务端真的干活。

同进程使用Service(二)

  • 这一节跟上一节没有本质区别,只是在上节基础上做了一点包装。
  • 让 Service 的 onBind() 函数返回的不光是一个 IBinder,还是一个 MusicInterface。
  • MusicInterface 是个接口,统一封装了 MusicService 的业务函数。客户端通过该接口使用业务函数。
package com.zsl.musicservice;

public interface MusicInterface {
    void play();
    void pause();
}

服务端

  • onBind() 返回的东西是 extends Binder implements MusicInterface
  • 下面例子中,MusicService 的内部类 MusicServiceProxy,就是用来返回给客户端的 proxy。
package com.zsl.musicservice;

import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
import android.util.Log;

public class MusicService extends Service {
   
    // proxy只负责分发,把活交给 Service 的业务函数干
    class MusicServiceProxy extends Binder implements MusicInterface {

        @Override
        public void play() {
            MusicService.this.play();
        }

        @Override
        public void pause() {
            MusicService.this.pause();
        }
    }

    // 模拟播放的业务逻辑
    public void play() {
        Log.d("zsl", "Playing");
    }

    // 模拟暂停的业务逻辑
    
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值