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

本文深入解析了Android中跨进程通信的多种方式,包括Service机制、Binder、AIDL、Messenger等,详细介绍了每种方法的工作原理及如何在服务端与客户端间实现数据交换。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

转://
https://blog.youkuaiyun.com/zsl_oo7/article/details/72084678

同进程使用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");
    }
}
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 客户端准备一个 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();
    }
}
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 以上程序写在同一个工程里可以正确运行,点击 play 按钮后 logcat 输出 playing。

三、小结

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

四、问题

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

同进程使用Service(二)

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

public interface MusicInterface {
    void play();
    void pause();
}
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

服务端

  • 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");
    }

    // 模拟暂停的业务逻辑
    public void pause() {
        Log.d("zsl", "Paused");
    }

    @Override
    public IBinder onBind(Intent intent) {
        return new MusicServiceProxy();
    }
}
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38

客户端

  • 客户端代码如下:
package com.zsl.musicclient;

import com.zsl.musicplayer0.R;
import com.zsl.musicservice.MusicInterface;
import com.zsl.musicservice.MusicService;

import android.app.Activity;
import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.view.View;

public class PlayerActivity extends Activity {

    private MusicInterface mMusicServiceProxy;

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

    class MusicServiceConn implements ServiceConnection{

        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            mMusicServiceProxy = (MusicInterface) service;
        }

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

    public void btn_play_clicked(View v){
        mMusicServiceProxy.play();
    }

    public void btn_pause_clicked(View v){
        mMusicServiceProxy.pause();
    }
}

 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47

小结

  • 服务端需要提供的有:

  • 接口类 MusicInterface
  • 继承自 Binder 并实现了 MusicInterface 的 proxy 类,即 MusicServiceProxy,它的业务函数,实际上只是调用 MusicService 的业务函数
  • 通过 onBind() 函数 return proxy 对象,即 new MusicInterface()
  • 客户端需要做的事情:

    • 搞个类 MusicServiceConn,实现 ServiceConnection 接口,用于接收 Service onBind() 返回的 IBinder,即 proxy
    • MusicServiceConn 实现 onServiceConnected() 方法时,把第二个参数(带出参数)数强转成 MusicInterface

    为何不能跨进程?

    • 客户端拿到 proxy 后,强转那句,不能跨进程。即:
    mMusicServiceProxy = (MusicInterface) service;
     
    • 1
    • 这个 service 是安卓OS传过来的,它的本质是Server进程里的一个对象,即一堆二进制数据。安卓OS拿他当 IBinder 类型看待。
    • 如果你的客户端是另一个进程,你没办法成功进行强转的。哪怕你把 MusicInterface.java 拷贝到了客户端进程。
    • 虽然这样客户端可以 import MusicInterface,并且使用它,编译的时候也没问题。但运行时,强转的时候还是会报错如下:
    java.lang.ClassCastException: 
    android.os.BinderProxy cannot be cast to com.zsl.musicclient.MusicInterface
     
    • 1
    • 2
    • 如果还是不服,把 MusicInterface 类的包也改成跟服务端一样的,还是报错:
    java.lang.ClassCastException: 
    android.os.BinderProxy cannot be cast to com.zsl.musicserver.MusicInterface
     
    • 1
    • 2
    • 其根本原因就是一个进程里的对象,是不可能被另一个进程认识。

    如果你想做实验,见证真的不能跨进程

    • 拆分成两个工程,一个 Sever(com.zsl.musicserver), 一个 Client(com.zsl.musicclient)
    • Server 工程包括如下源文件: MusicService.java,MusicInterface.java
    • Client 工程包括如下源文件: MainActivity.java,MusicInterface.java
    • Client 需要知道 Server 的包名和 Service 的类名:
    Intent intent = new Intent();
    intent.setClassName("com.zsl.musicserver",  "com.zsl.musicserver.MusicService");  
     
    • 1
    • 2
    • 问题:Client 绑定 Service 时报错,不让绑定
    java.lang.SecurityException: Not allowed to bind to service Intent
     
    • 1
    • 解决方式是在 Client 里先 startService(intent) 然后再 bindService(intent, ...)
    • 最后,你会遇到不让强转的问题

    跨进程使用Service(一):Binder

    • Binder 既然是被设计用来跨进程通信的,那么利用 Binder 一定可以跨进程。
    • 我们上面的例子之所以不能跨进程,是因为我们没有使用到 Binder 跨进程的核心函数:transact() 和 onTransact()

    一、Binder 跨进程原理

    • 对于安卓 OS 来说,Binder 对象是可以跨进程使用的,即一个进程里的 Binder 对象,可以被安卓 OS 传递到另一个进程里。
    • Binder 有两个函数:transact() 发送消息,onTransact() 接收消息。
    • Binder 实现了 IBinder 接口,transact() 是 IBinder 的,onTransact() 是 Binder 的。
    • 利用 transact() 和 onTransact(),我们可以实现跨进程通信。

    二、使用 Binder 跨进程调用 Service

    1. 服务端

    • 新建一个 Android 工程作为服务端。
    • 服务端 onBind() 返回的东西仍然继承自 Binder,不过我们这次不必给他添加个函数让客户端拿到 Service 实例了。
    • 我们只需要重写 Binder 的 onTransact() 即可,因为 onTransact() 能收到客户端发来的消息:
    package com.zsl.musicservicebinderipc;
    
    import android.app.Service;
    import android.content.Intent;
    import android.os.Binder;
    import android.os.IBinder;
    import android.os.Parcel;
    import android.os.RemoteException;
    import android.util.Log;
    
    public class MusicService extends Service {
    
        // 用于让 onBind() 返回的类
        // 重写 Binder 的 onTransact(), 用于接收客户端进程发来的消息
        public class MyBinder extends Binder {
    
            @Override
            protected boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
            switch(code){
                // 客户端的 transact() 第一个参数是 1 的时候,进行 play()
                case 1:{
                    play();
                    return true;
                }
                }
                return super.onTransact(code, data, reply, flags);
            }
        }
    
        // 重写 onBind 方法,返回一个 MyBinder 的实例,
        // 而 MyBinder 的 onTransact() 一旦收到 transact() 发来的 1,将会调用 play()
        @Override
        public IBinder onBind(Intent intent) {
            return new MyBinder();
        }
    
        public void play() {
            Log.d("testing", "playing");
        }
    }
     
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 我们让 onBind() 返回一个 MyBinder。客户端拿到 MyBinder 后,只需调用它的 transact() 方法,第一个参数传1,就能实现调用服务端 play() 的目的。

    2. 客户端

    • 新建一个 Android 工程作为客户端。
    • 客户端不再需要强转,它调用服务端 play() 的方式变成:调用从服务端得到的 IBinder 的 transact() 方法,并且第一个参数传1.
    • 这个 1 是服务端和客户端约定好的,代表是 play()。
    • 客户端代码如下:
    package com.zsl.musicclientbinderipc;
    
    import android.app.Activity;
    import android.content.ComponentName;
    import android.content.Intent;
    import android.content.ServiceConnection;
    import android.os.Bundle;
    import android.os.IBinder;
    import android.os.Parcel;
    import android.os.RemoteException;
    import android.view.View;
    
    public class MainActivity extends Activity {
        IBinder mService;
    
        // 准备一个 ServiceConnection,用来绑定 Service 并接收 Service onBind() 返回的那个 IBinder。
        private ServiceConnection mConnection = new ServiceConnection() {
            @Override
            public void onServiceConnected(ComponentName className,IBinder service) {
                // 把服务端进程返回的 IBinder 保存起来,后续调用它的 transact() 给服务端发消息
                mService = service;
            }
    
            @Override
            public void onServiceDisconnected(ComponentName name) {
            }
        };
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            Intent intent = new Intent();
            intent.setClassName("com.zsl.musicservicebinderipc", "com.zsl.musicservicebinderipc.MusicService");
            startService(intent);
            bindService(intent, mConnection, BIND_AUTO_CREATE);
        }
    
        // 让服务端 play()
        public void play(){
            // 调用 transact() 必须提供这两个参数,目前先不必理会
            Parcel data = Parcel.obtain();
            Parcel reply = Parcel.obtain();
            try {
                // IBinder 的 transact() ,给服务端发消息,服务端的 onTransact() 会处理消息
                mService.transact(1, data, reply, 0);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
    
        public void btn_play_clicked(View v) {
            play();
        }
    }
     
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55

    三、小结

    • 客户端程序员需要知道调用 play() 要 transact() 哪些参数,调用 pause() 要传递哪些。。。 要为每个需要用到的方法写一套 transact()
    • 不应该让客户端看到那么多细节,用起来不方便。服务端应该把一切都封装好,让客户端用起来方便。
    • 这样服务端需要封装几个类,把 transact()、onTransact() 包装起来。
    • 安卓为了简化这一个过程,为我们准备了 AIDL

    跨进程使用Service(二):AIDL

    一、AIDL 怎么用

    1.书写 AIDL 文件,定义接口

    • 新建一个 Android 工程,作为服务端。
    • 新建一个 Interface,起名叫 MusicInterface。MusicInterface.java 内容如下:
    package com.zsl.musicservice2aidl;
    
    public interface MusicInterface {
        void play();
        void pause();
    }
     
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 直接把 MusicInterface.java 改成 MusicInterface.aidl,并且把 public 去掉,因为 AIDL 语法不支持 public。MusicInterface.aidl 内容如下:
    package com.zsl.musicservice2aidl;
    
    interface MusicInterface {
        void play(String f);
        void pause();
    }
    
     
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    2. 实现服务端

    • 新建一个 MusicService。这次我们让 onBind() 返回的类不用自己搞一个 extends Binder implements MusicInterface 的内部类了,AIDL 已经为我们搞定了。我们只需要搞一个内部类,让它 extends Stub 即可。Stub 是 AIDL 为我们生成的一个类,它已经 extends Binder implements MusicInterface 了。
    • 服务端代码如下:
    package com.zsl.musicservice2aidl;
    
    // 把 AIDL 生成的 Stub 类 import 进来
    import com.zsl.musicservice2aidl.MusicInterface.Stub;
    
    import android.app.Service;
    import android.content.Intent;
    import android.os.IBinder;
    import android.util.Log;
    
    public class MusicService extends Service {
    
        // 用于让 onBind() 返回的类,只需要从 Stub 继承即可,比原来简单了些
        class MusicServiceProxy extends Stub {
    
            @Override
            public void play() {
                MusicService.this.play();
            }
    
            @Override
            public void pause() {
                MusicService.this.pause();
            }
        }
    
        // 模拟播放的业务逻辑
        public void play() {
            Log.d("zsl", "Playing");
        }
    
        // 模拟暂停的业务逻辑
        public void pause() {
            Log.d("zsl", "Paused");
        }
    
        @Override
        public IBinder onBind(Intent intent) {
            return new MusicServiceProxy();
        }
    }
     
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 记得在项目的 Manifest 文件里写上这个 Service。
    • 另外为了能被远程启动和绑定,最好把该 Service 的 export 搞成 true。

    3. 实现客户端

    • 新建一个 Android 工程,作为客户端。
    • 客户端也需要 MusicInterface 这个接口,把 MusicInterface.aidl 拷贝到客户端项目。注意!! 一定要在客户端建一个跟服务端一样的包,把 MusicInterface.aidl 放到这个包里。
    • 客户端的代码如下:
    package com.zsl.musicclient2aidl;
    
    // 注意包名,跟服务端是一致的
    import com.zsl.musicservice2aidl.MusicInterface;
    import com.zsl.musicservice2aidl.MusicInterface.Stub;
    
    import android.app.Activity;
    import android.content.ComponentName;
    import android.content.Intent;
    import android.content.ServiceConnection;
    import android.os.Bundle;
    import android.os.IBinder;
    import android.os.RemoteException;
    import android.view.View;
    
    public class MainActivity extends Activity {
    
        private MusicInterface mMusicServiceProxy;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            Intent intent = new Intent();
            intent.setClassName("com.zsl.musicservice2aidl", "com.zsl.musicservice2aidl.MusicService");
            startService(intent);
            bindService(intent, new MusicServiceConn(), BIND_AUTO_CREATE);
        }
    
        class MusicServiceConn implements ServiceConnection{
            @Override
            public void onServiceConnected(ComponentName name, IBinder service) {
                // 这里不直接强转了,而是使用 Stub 的 asInterface() 方法
                mMusicServiceProxy = Stub.asInterface(service);
            }
    
            @Override
            public void onServiceDisconnected(ComponentName name) {
            }
        }
    
        public void btn_play_clicked(View v){
            try {
                mMusicServiceProxy.play();
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
    }
     
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 可以看到客户端代码跟原来区别很小,最关键的一点就是: 在把 IBinder 强转成 MusicInterface 时,不再直接强转,而是使用 Stub 的 asInterface() 方法。
    • 部署服务端再部署客户端,实验,可以跨进程调用。
    • 可以看到,借助 AIDL 我们实现了跨进程通信,并且我们并没有显式的使用 transact() 和 onTransact() 方法。这就是 AIDL 带给我们的方便。
    • 下面我们看一下 AIDL 做了哪些包装来避免让程序员直接调用 transact() 、onTransact()

    二、AIDL的原理

    • MusicInterface.aidl 文件写好后,会在 gen 目录下生成 MusicInterface.java,其内容如下(经过精简):
    package com.zsl.musicservice2aidl;
    
    public interface MusicInterface extends IInterface {
    
        // 1. Stub 类继承了 Binder,实现了 MusicInterface,Service 的 onBind() 只需要返回 Stub 的子类即可
        // 2. Stub 类是 abstract 的,它并没有实现 MusicInterface 的业务函数 play() 和 pause(),这要交给 Service 去做
        // 3. Stub 类提供了一个 static 方法 asInterface(),我们在这个方法的注释中再细看,注意它是 static 的
        public static abstract class Stub extends Binder implements MusicInterface {
    
            // Stubd 类的 asInterfasce() 方法,这个方法是供客户端使用的,注意它是 static 的,它 return 一个 Proxy
            // 注意它需要一个参数 IBinder,它根据这个 IBinder new 一个 Proxy,返回给客户端
            public static MusicInterface asInterface(IBinder obj) {
                return new MusicInterface.Stub.Proxy(obj);
            }
    
            @Override
            // Stub 类实现 onTransact(),根据客户端传来的 code,调用 this.play()、this.pause()
            // 注意,Stub 的 this.play()、this.pause() 是虚函数,因为 Stub 不负责业务,这两函数交给Stub 的继承者去实现
            // 继承 Stub 的类,即 Service 设计者要实现的,用于让 onBind() 返回的类
            public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
                switch (code) {
                    case TRANSACTION_play: {
                        this.play();
                        return true;
                    }
                    case TRANSACTION_pause: {
                        this.pause();
                        return true;
                    }
                }
            }
    
            // Proxy 是客户端使用的类,它的 play() 函数其实就是用 mRemote 的 transact() 发消息
            // mRemote 就是从服务端得到的 IBinder,构造 Proxy 需要它
            private static class Proxy implements MusicInterface {
                private IBinder mRemote;
    
                Proxy(IBinder remote) {
                    mRemote = remote;
                }
    
                @Override
                public void play() throws android.os.RemoteException {
                    mRemote.transact(Stub.TRANSACTION_play, _data, _reply, 0);
                }
    
                @Override
                public void pause() throws android.os.RemoteException {
                    mRemote.transact(Stub.TRANSACTION_pause, _data, _reply, 0);
                }
            }
    
            static final int TRANSACTION_play = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
            static final int TRANSACTION_pause = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
        } // end of class Stub
    
        public void play() throws RemoteException;
        public void pause() throws RemoteException;
    }
     
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 我们把 AIDL 文件复制到客户端工程,客户端也会生成如上的 java 文件。
    • 其实拷贝 AIDL 文件就相当于拷贝了一个 .java 的接口文件。只不过 AIDL 的写法要简洁的多。
    • 服务端负责继承 Stub,实现它的业务函数。而 Stub 本身已经做好了 onTransact(),它把客户端的 play() 分发给继承者的 play().
    • 客户端只需要在绑定 Service 之后,利用安卓OS传回来的 IBinder 作为参数,调用 Stub.asInterface() 就能得到一个 MusicInterface。
    • 后续直接使用它的 play() 即可,感觉就像使用本地函数一样。
    • 客户端程序员不必知道,其实这个 MusicInterface 的 play(),底层调用的是 binder.transact(TRANSACTION_play,..)

    三、小结

    • AIDL 定义的是接口,服务端和客户端使用同样的接口。两端的工程里只要有相同的 AIDL 文件即可。
    • AIDL 书写简单,服务端程序员和客户端程序员都省去了很多麻烦。
    • 其本质上还是使用 Binder 的 transact() 和 onTransact() 进行跨进程通信。

    四、扩展:传递复杂数据类型:用 AIDL 定义类型

    • 我们上面的例子省去了跨进程传递参数的部分
    • 这部分不是本文关注的重点,作为扩展可以自行查资料

    五、AIDL 与 Native 层 的 Bp、Bn、呼应

    • AIDL 挺方便,是 Android 为我们准备的好东西。类似于 Native 层里那些 Android 为我们准备的好东西。
    • asInterface(),相当于 Native 层的 interface_cast,让客户端一调用,就能把一个 binder 转换成 IXXX 这种业务类。
    • 而 asInterface() 返回的 Proxy 类,其实就对应 Native 层的 BpXXX。
    • Stub 类则对于 Native 层的 BnXXX,它负责用 onTransact() 分发客户端请求。
    • AIDL 的方便之处在于,它连 Stub 类(即 BnXXX )的 onTransact() 都给你实现好了,包括用于辨别指令的 CODE 都自动给你生成了。
    • 而 Native 层的程序员一般需要自己写 onTransact()

    跨进程使用Service(二):Message

    一、回忆一下 Handler 和 Message

    • 在UI编程时,我们经常用 Handler 和 Message 处理高耗时任务。
    • 具体方法:
      • Activity 里弄个 Handler 类型的成员 mHandler。
      • mHandler 的 handleMessage() 负责处理 Message
      • 在 Activity 里起一个线程去做高耗时任务,该线程通过 mHandler.sendMessage() 给 mHander 发送 Message。
    • 要点: Activity 里的工作线程能拿到 mHander。这很容易,内部类可以通过类名访问外部类。

    二、 Messenger

    • UI 里只使用了两个类: Handler、Message。 UI 里都是调用 Handler 的 sendMessage(Message msg) 方法,发送一个 Message。然后 Handler 的 handleMessage() 会收到这个 Message 参数。
    • 说白了,这种传递 Message 的方式,是用同一个对象(mHandler)的两个方法(sedMessage(),handleMessage())在传递 Message。
    • 而同一个对象不可能横跨两个进程,所以我们需要一个新类:Messenger。
    • Messager 是基于 Binder 构建的,所以可以跨进程。 Messenger 的构造函数需要一个 IBinder 参数:
    Messenger(IBinder target)
    // Create a Messenger from a raw IBinder, which had previously been retrieved with getBinder(). 
     
    • 1
    • 2
    • 以上是 API 文档里对该构造函数的描述,他说那个 IBinder 参数必须是之前用 getBinder() 拿到的。这是啥意思?
    • Messenger 类有个 getBinder() 方法,能返回这个 Messenger 与对应的 Handler 通信用的 IBinder。
    • 对应的 Handler 在哪里? Messager 还有一个构造函数,以 Handler 为参数。
    Messenger(Handler target)
    //Create a new Messenger pointing to the given Handler. 
     
    • 1
    • 2
    • 用 Messenger 发送 Message 的时候,直接发给该 Handler。
    • 要点:
      1. Messenger 有两个构造函数,一个以 Handler 为参数,一个以 IBinder 为参数。
      2. Messenger 有个 getBinder() 方法,能返回与它对应的 Handler 通信用的 IBinder。
    • 这样就可在服务端弄一个 Handler,用来处理客户端发的 Message。然后基于它构建一个 Messenger,用其 getBinder() 方法得到 Messenger 与 Handler 通信的 IBinder。
    • 然后服务端的 onBind() 返回这个 IBinder。
    • 客户端得到这个 IBinder 后,再基于它构建一个自己的 Messenger,这个 Messenger 发的消息,就会被服务端收到了。
    • 注意: 服务端和客户端各有自己的 Messenger 对象,而不是同一个对象。服务端的 Messenger 是基于 Handler 构建,客户端的是基于 IBinder 构建。

    三、利用 Messenger 跨进程使用 Service

    1. 服务端

    • 服务端代码如下:
    package com.zsl.musicserver1message;
    
    public class MusicServer extends Service {
    
        static final int MSG_PLAY = 1;
    
        class ServiceHandler extends Handler  {
            @Override
            public void handleMessage(Message msg) {
                switch (msg.what) {
                    case MSG_PLAY:
                        MusicServer.this.play();
                        break;
                    default:
                        super.handleMessage(msg);
                }
            }
        }
    
        final Messenger mMessenger = new Messenger(new ServiceHandler());
    
        @Override
        public IBinder onBind(Intent intent) {
            //返回给客户端一个 IBinder 实例,客户端基于它构建自己的 Messager,然后发消息过来
            return mMessenger.getBinder();
        }
    
        public void play(){
            Log.d("Messenger Test", "Playing");
        }
    }
     
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • Service 的 Manifest 里要加上这个: android:exported="true"

    2. 客户端

    • 客户端代码如下:
    package com.zsl.musicclient1message;
    
    public class MainActivity extends Activity {
    
        static final int MSG_PLAY = 1;
    
        Messenger mService = null;
    
        private ServiceConnection mConnection = new ServiceConnection() {
            public void onServiceConnected(ComponentName className, IBinder service) {
                //接收onBind()传回来的IBinder,并用它构造Messenger
                mService = new Messenger(service);
            }
    
            @Override
            public void onServiceDisconnected(ComponentName name) {
            }
    
        };
    
        public void play() {
            Message msg = Message.obtain(null, MSG_PLAY, 0, 0);
            try {
                mService.send(msg);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            Intent intent = new Intent();
            intent.setClassName("com.zsl.musicserver1message", "com.zsl.musicserver1message.MusicServer");
            startService(intent);
            bindService(intent, mConnection, BIND_AUTO_CREATE);
        }
    
        public void btn_play_clicked(View v){
            play();
        }
    }
     
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43

    Messenger 和 AIDL 哪个好?

    • 本质都是 Binder
    • 都是 Android 推荐的方法
    • 孰优孰劣还是看需求
    • AIDL 相对来说使用起来更复杂,不过能传递的数据类型更丰富
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值