Andriod——IPC进程通信JAVA层工作原理简析

众所周知,Android每个APP都运行在各自的Dalvik虚拟机里面,每个虚拟机都等同于Linux的进程,这样做的原因是为了隔离进程,保证各个不同APP的安全,比如其中一个APP挂了不会影响到其他的正在运行的APP。而这样做APP各自的进程不能够直接进行通信。所以需要借助于Binder机制(当然还有socket和Linux原始的内存共享机制实现进程通信,不过都比较麻烦)。所以在这片文章主要结合自己学习Binder的一点体会简析Java层Binder的工作。(这里感谢高焕堂老师的Binder视频讲解)

首先,以最常见的例子绑定服务来举例子(简单是因为,在调用bindService(intent,serverconnection)方法时,我们可以直接在ServerConnection中自己复写的onServiceConnected(ComponentName name, IBinder service)中很容易得到一个客户端的IBInder对象,而这部分工作由AMS(ActivityManagerService)帮我们完成了)

这里写图片描述
主要流程步骤:
第一步:我们调用bindServcie(intent,connection,BIND_AUTO_CREATE)函数,其中第三个参数BIND_AUTO_CREATE参数告诉AMS要自动产生一个MyService,
第二步:调用bindService()函数后AMS要自动产生一个MyService,并且会调用MyService的onBind()函数
第三步:在MyService的onBind()函数中返回一个Binder对象。
第四部:AMS根据第三步中的Binder对象返回其IBinder类型对象,然后以IBinder对象为参数调用客户端的connection接口的onServiceConnection方法,从而我们在客户端获得了能与服务端Binder通信的IBinder客户端代理接口

现在从我们绑定了服务并直接获得客户端IBinder的基础上开始分析binder在Java层的工作,还是来看图:
这里写图片描述
左边的是客户端线程,右边的是服务端进程,上面是JAVA层实现,最底下是JNI层。
假设我们在界面上由两个按钮一个想要调用服务端的f1()函数,一个调用f2()函数,所以按照步骤:
第一步:我们为按钮设置了点击事件,并将监听对象设置为activity自己,所以在activity中重写onclick函数。根据不同的控件ID调用ib.transact()函数,注意transact()函数有四个参数,第一个为int类型的code字段,用来标识我们要调用哪个函数,在服务端解析调用函数时也要通过code来调用函数,第二个为Parcel类型的data字段,用来传递参数(关于Parcel知识简单了解就行了),第三个也是Parcel的repley字段,用来保存返回值,第四个是一个int类型的标志,使用默认值1即可。

第二步:执行ib.transact()函数我们会调用jni层的C++代码来进行通信,通信的基本原理是通过共享Linux的/dev/binder设备,这里我们主要分析JAVA层的工作所以不细说,就要知道Java层客户端IBinder的transact()函数最中跳入JNI层调用C++ 函数。

第三步:在第二步,我们通过transact函数进行跨进程通信,最终在服务端进程中会通过服务端进程的线程池新建出一个线程然后通过JNI方法调用JAVA层我们之前在MyService的onBind()中返回的Binder对象的execTransact()方法,并在execTransact方法中调用onTransact()方法。

第四步:我们实际创建的对象是MyBinder对象并且重写了onTransact函数,所以根据控制反转原则(依赖倒转)最终会调用我们自己写的onTransact()函数,同样onTransact()函数有四个参数,第一个为int类型的code字段,用来标识我们要调用哪个函数,第二个为Parcel类型的data字段,传递的参数(关于Parcel知识简单了解就行了),第三个Parcel的repley字段,用来保存返回值,第四个是一个int类型的标志。

第五步:在onTransact函数中根据code调用对应的函数,如果想发送一个消息给客户端,在客户端注册一个广播接收器,就可以通过发送广播的方式传递消息给客户端

客户端MainActivity布局:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <TextView
        android:id="@+id/tvDisp"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World!" />

    <Button
        android:id="@+id/btnStart"
        android:text="Start"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content" />
    <Button
        android:id="@+id/btnStop"
        android:text="Stop"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content" />
    <Button
        android:id="@+id/btnExit"
        android:text="Exit"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content" />
</LinearLayout>

客户端代码:

public class MainActivity extends AppCompatActivity implements ServiceConnection, View.OnClickListener {
    private TextView tvDisp;
    private IBinder ib;
    private Button btnStart, btnStop, btnExit;
    private static final String SERVICE_MEDIAPLAY = "com.sky.ipcdemo.service.medaiplay";
    private static final String BROADCAST_MEDIAPLAY = "com.sky.ipcdemo.broadcast.mediaplay";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        tvDisp = (TextView) findViewById(R.id.tvDisp);
        btnStart = (Button) findViewById(R.id.btnStart);
        btnStop = (Button) findViewById(R.id.btnStop);
        btnExit = (Button) findViewById(R.id.btnExit);
        btnStart.setOnClickListener(this);
        btnStop.setOnClickListener(this);
        btnExit.setOnClickListener(this);
        IntentFilter filter = new IntentFilter();
        filter.addAction(BROADCAST_MEDIAPLAY);
        registerReceiver(receiver, filter);
        Intent intent = new Intent(SERVICE_MEDIAPLAY);
        intent.setPackage("com.sky.serviceapp");//现在的编译版本只能显示调用Intent
        bindService(intent, this, BIND_AUTO_CREATE);
    }

    private BroadcastReceiver receiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            String instrcution = intent.getStringExtra("instruction");
            tvDisp.setText(instrcution);
        }
    };

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

    @Override
    public void onServiceDisconnected(ComponentName name) {

    }

    @Override
    public void onClick(View v) {
        try {
            Parcel data=Parcel.obtain();//获得一个Parcel对象data,用来传递参数
            Parcel reply=Parcel.obtain();//获得一个Parcel对象reply,用来返回结果
            switch (v.getId()) {
                case R.id.btnStart:
                    data.writeString("hello");//写入数据到Parcel中
                    ib.transact(1,data,reply,1);//code=1,表示调用服务端的start函数
                    break;
                case R.id.btnStop:
                    ib.transact(2, data, reply, 2);//code=1,表示调用服务端的stop函数
                    break;
                case R.id.btnExit:
                    ib.transact(3, data, reply,3);//code=1,表示调用服务端的exit函数
                    break;
            }
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }
}

服务端:另外一个工程或者同一工程下的另一个Module中

public class MyService extends Service {

    private static final String SERVICE_MEDIAPLAY = "com.sky.ipcdemo.service.medaiplay";
    private static final String BROADCAST_MEDIAPLAY = "com.sky.ipcdemo.broadcast.mediaplay";
    private Binder myBinder=new Binder(){
        @Override//重写Binder基类的方法
        public boolean onTransact(int code,Parcel data,Parcel reply,int flags) throws RemoteException {
            Intent intent=new Intent(BROADCAST_MEDIAPLAY);
            switch (code){
                case 1:
                    //执行start,这里模拟执行了start(),向客户端发送一个广播告诉客户端我start了。
                    //还记得我们在客户端的start情况传递了一个hello过来吗,现在我把它传递回去
                    intent.putExtra("instruction", "I\'am start and I\'m receive a string as" + data.readString());
                    sendBroadcast(intent);
                    break;
                case 2:
                    //还记得我们在客户端的start情况传递了一个hello过来吗,现在我把它传递回去
                    intent.putExtra("instruction","I\'am stop ");
                    sendBroadcast(intent);
                    break;
                case 3:
                    intent.putExtra("instruction","I\'am exit ");
                    sendBroadcast(intent);
                    break;
                default:
                    throw new RemoteException("invalid code");
            }
            return true;
        }
    };
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {//在服务绑定时触发
        return myBinder;//返回自己的Binder
    }
}

注意,小伙伴们一定要去修改Manifest.xml文件,在这里指定服务的action和可以导出属性

好了分析到这里,发现没?我们没有使用AIDL来进行远程通信诶,那么AIDL的工作到底是什么呢,其实很简单AIDL只是帮我们进行了数据的包装和解析,也就是客户端onTransact的工作和服务端onTransact的工作,AIDL生成的Java文件中包含一个Stub 和Proxy,其中Proxy包客户端包装的IBinder对象,并进行数据的传递,而Stub则帮我们进行了原本应该自己写的OnTransact函数进行数据解析,如图
包装前:
这里写图片描述
包装后:
这里写图片描述

比较上面两个图的不同之处:
1.在客户端,onServiceConnection中将IBinder对象ib包装成Proxy,将原来直接在onClick中写的transact包装到Proxy中去执行
2.在服务端原来MyBinder直接继承Binder自己处理onTransact(),现在继承自Stub,Stub负责解析code等参数,然后通过IOC控制反转调用f1(),f2().

好了我们可以自己来写Stub和Proxy,给f1()和f2()包成一个接口

public  interface ITest{
   void f1();
   void f2();
}

然后,Stub需要继承Binder类,并且继承ITest,重写Binder的onTransact方法

public  interface ITest{
   void f1();
   void f2();
   abstract class Stub extends Binder implements ITest{
       @Override
       public boolean onTransact(int code,Parcel d,Parcel r,int f){
           switch(code){
               //在这里f1()和f2()还没有实现,这是模板方法,最终通过IOC执行子类的f1和f2
               case 1:f1();break;
               case 2:f2();break;
           }
       }
   }
}

接下来要写Proxy,Proxy是用来包装客户端的IBinder对象的,它也需要继承ITest(因为我们直接通过proxy.f1()和proxy.f2()来使用)

public  interface ITest{
   void f1();
   void f2();
   abstract class Stub extends Binder implements ITest{
       @Override
       public boolean onTransact(int code,Parcel d,Parcel r,int f){
           switch(code){
               //根据code调用不同的方法,在这里f1()和f2()还没有实现,这是模板方法,最终通过IOC执行子类的f1和f2
               case 1:f1();break;
               case 2:f2();break;
           }
           return true;
       }

       //提供安全的Proxy包装获得方法
       public static Proxy asInterface(IBinder binder){
            return new Proxy(binder);
       } 

       private static class Proxy implements ITest{
       //需要包装一个IBinder
       private IBinder remote;
       public Proxy(IBinder binder){
           remote=binder;
       }
       //真正帮我们做原来自己在onClick中做的事情
       public void f1(){
           Parcel data=Parcel.obtain();
           Parcel reply=Parcel.obtain();
           remote.transact(1,data,reply,1);
       }
       public void f2(){
           Parcel data=Parcel.obtain();
           Parcel reply=Parcel.obtain();
           remote.transact(2,data,reply,1);//code变了
       }
   }
   }

}

好了应该注意到Proxy是Private的了,AIDL中通常是这样做的,可能是为了安全吧,所以在Stub中提供包装IBinder并返回Proxy的asInterface(IBinder binder)方法

public static Proxy asInterface(IBinder binder){
   return new Proxy(binder);
}

那么现在大家应该去看AIDL的时候就非常了解里面的代码了
记住两点即可:
1.Proxy用来包装客户端获得的IBinder,帮IBinder进行transact的工作
2.Stub是用来帮助服务端的MyBinder进行数据解析的工作

自己写的Stub和Proxy使用时,将两个接口文件在客户端APP和服务端APP中各放一个
代码:

public interface ITest {
    void start(String str) throws RemoteException;

    void stop() throws RemoteException;

    void exit() throws RemoteException;


    abstract class Stub extends Binder implements ITest {
        public static Proxy asInterface(IBinder binder) {
            return new Proxy(binder);
        }

        @Override
        public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
            switch (code) {
                case 1:
                    start(data.readString());
                    break;
                case 2:
                    stop();
                    break;
                case 3:
                    exit();
                    break;
                default:
                    throw new RemoteException("invalid code");
            }
            return true;
        }

        private static class Proxy implements ITest {
            private IBinder remote;

            public Proxy(IBinder binder) {
                remote = binder;
            }

            @Override
            public void start(String str) throws RemoteException {
                Parcel data = Parcel.obtain();
                data.writeString(str);
                Parcel reply = Parcel.obtain();
                remote.transact(1, data, reply, 1);
            }

            @Override
            public void stop() throws RemoteException {
                Parcel data = Parcel.obtain();
                Parcel reply = Parcel.obtain();
                remote.transact(2, data, reply, 1);
            }

            @Override
            public void exit() throws RemoteException {
                Parcel data = Parcel.obtain();
                Parcel reply = Parcel.obtain();
                remote.transact(3, data, reply, 1);
            }
        }
    }

然后我们之前在服务端MyService中的代码可以变成(注释的是原来的不用Stub的代码)

public class MyService extends Service {

    private static final String SERVICE_MEDIAPLAY = "com.sky.ipcdemo.service.medaiplay";
    private static final String BROADCAST_MEDIAPLAY = "com.sky.ipcdemo.broadcast.mediaplay";
/*    private Binder myBinder=new Binder(){
        @Override//重写Binder基类的方法
        public boolean onTransact(int code,Parcel data,Parcel reply,int flags) throws RemoteException {
            Intent intent=new Intent(BROADCAST_MEDIAPLAY);
            switch (code){
                case 1:
                    //执行start,这里模拟执行了start(),向客户端发送一个广播告诉客户端我start了。
                    //还记得我们在客户端的start情况传递了一个hello过来吗,现在我把它传递回去
                    intent.putExtra("instruction", "I\'am start and I\'m receive a string as" + data.readString());
                    sendBroadcast(intent);
                    break;
                case 2:
                    //还记得我们在客户端的start情况传递了一个hello过来吗,现在我把它传递回去
                    intent.putExtra("instruction","I\'am stop ");
                    sendBroadcast(intent);
                    break;
                case 3:
                    intent.putExtra("instruction","I\'am exit ");
                    sendBroadcast(intent);
                    break;
                default:
                    throw new RemoteException("invalid code");
            }
            return true;
        }
    };*/

    private Binder myBinder=new ITest.Stub() {
        @Override
        public void start(String str) throws RemoteException {
            Intent intent=new Intent(BROADCAST_MEDIAPLAY);
            intent.putExtra("instruction", "I\'am start and I\'m receive a string as" + str);
            sendBroadcast(intent);
        }

        @Override
        public void stop() throws RemoteException {
            Intent intent=new Intent(BROADCAST_MEDIAPLAY);
            intent.putExtra("instruction","I\'am stop ");
            sendBroadcast(intent);
        }

        @Override
        public void exit() throws RemoteException {
            Intent intent=new Intent(BROADCAST_MEDIAPLAY);
            intent.putExtra("instruction","I\'am exit ");
            sendBroadcast(intent);
        }
    };
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {//在服务绑定时触发
        return myBinder;//返回自己的Binder
    }
}

客户端代码变动为:注释为原来的代码

public class MainActivity extends AppCompatActivity implements ServiceConnection, View.OnClickListener {
    private TextView tvDisp;
  //  private IBinder ib;
    private ITest ib;
    private Button btnStart, btnStop, btnExit;
    private static final String SERVICE_MEDIAPLAY = "com.sky.ipcdemo.service.medaiplay";
    private static final String BROADCAST_MEDIAPLAY = "com.sky.ipcdemo.broadcast.mediaplay";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        tvDisp = (TextView) findViewById(R.id.tvDisp);
        btnStart = (Button) findViewById(R.id.btnStart);
        btnStop = (Button) findViewById(R.id.btnStop);
        btnExit = (Button) findViewById(R.id.btnExit);
        btnStart.setOnClickListener(this);
        btnStop.setOnClickListener(this);
        btnExit.setOnClickListener(this);
        IntentFilter filter = new IntentFilter();
        filter.addAction(BROADCAST_MEDIAPLAY);
        registerReceiver(receiver, filter);
        Intent intent = new Intent(SERVICE_MEDIAPLAY);
        intent.setPackage("com.sky.serviceapp");//现在的编译版本只能显示调用Intent
        bindService(intent, this, BIND_AUTO_CREATE);
    }

    private BroadcastReceiver receiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            String instrcution = intent.getStringExtra("instruction");
            tvDisp.setText(instrcution);
        }
    };

    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
       // ib = service;
        ib=ITest.Stub.asInterface(service);
    }

    @Override
    public void onServiceDisconnected(ComponentName name) {

    }

    @Override
    public void onClick(View v) {
        try {
            Parcel data=Parcel.obtain();//获得一个Parcel对象data,用来传递参数
            Parcel reply=Parcel.obtain();//获得一个Parcel对象reply,用来返回结果
            switch (v.getId()) {
                case R.id.btnStart:
                  /*  data.writeString("hello");//写入数据到Parcel中
                    ib.transact(1,data,reply,1);//code=1,表示调用服务端的start函数*/
                    ib.start("hello");
                    break;
                case R.id.btnStop:
                    //ib.transact(2, data, reply, 2);//code=1,表示调用服务端的stop函数
                    ib.stop();
                    break;
                case R.id.btnExit:
                    //ib.transact(3, data, reply,3);//code=1,表示调用服务端的exit函数
                    ib.exit();
                    break;
            }
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }

}

通过学习,深入了解了AIDL文件生成的JAVA文件的作用,自己也可以写

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值