Android Framework框架之IBinder进程间通信

来源:互联网 发布:讲诚信知敬畏发言稿 编辑:程序博客网 时间:2024/05/22 15:11

前面一篇介绍了Android中一个进程中有一个VM,一个主线程,一个Looper和一个MessageQueue,这一篇重点讲一下利用IBinder实现进程间通信。首先进程间通信肯定至少要有两个进程嘛。我们就模拟下这个场景,写一个Demo,声明这个Demo要用到两个进程。然后进程A放一个MainActivity,放几个按钮,用来控制播放音乐,另一个进程B用来实现播放音乐。通过这个Demo穿插这讲进程间通信IPC到底是个什么东西。

首先看MainActivity的代码。

public class MainActivity extends AppCompatActivity {    private IBinder ib = null;    private Button bt_1;    private Button bt_2;    private Button bt_3;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        bt_1 = (Button) findViewById(R.id.bt_1);        bt_2 = (Button) findViewById(R.id.bt_2);        bt_3 = (Button) findViewById(R.id.bt_3);        bt_1.setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View v) {                Parcel parcel = Parcel.obtain();                Parcel parcelReply = Parcel.obtain();                parcel.writeString("play");                try {                    ib.transact(1, parcel, parcelReply, 0);                } catch (RemoteException e) {                    e.printStackTrace();                }            }        });        bt_2.setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View v) {                Parcel parcel = Parcel.obtain();                Parcel parcelReply = Parcel.obtain();                parcel.writeString("stop");                try {                    ib.transact(2, parcel, parcelReply, 0);                } catch (RemoteException e) {                    e.printStackTrace();                }            }        });        bt_3.setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View v) {                finish();            }        });        startService(new Intent(this, myService.class));        bindService(new Intent(this, myService.class), conn, BIND_AUTO_CREATE);    }    private ServiceConnection conn = new ServiceConnection() {        @Override        public void onServiceConnected(ComponentName name, IBinder service) {            ib = service;        }        @Override        public void onServiceDisconnected(ComponentName name) {        }    };

这里面很简单,只有三个按钮,每个按钮有一个点击事件,分别去做不同的事。(先不要管关于进程通信的内容,接下来会仔细的说明)

点击按钮bt_1让另一个进程的Service播放音乐,点击bt_2,让另一个进程的Service停止播放音乐,单击bt_3,finish掉Activity。然后根据上一篇讲的关于点击图标后怎么执行到MainActivity的onCreate()方法的问题,在此在重复一遍。App要启动,zygote孵化器就创建一个新的进程来执行这个App,进程里包含一个主线程,一个VM,Looper,MessageQueue。然后主线程在while循环里绕圈圈,框架把MainActivity对象new出来,发Message给主线程,让主线程执行MainActivity.onCreate();这个时候我们来看MainActivity.onCreate()最后两行,我们分别写了启动一个Service,绑定这个Service。然后根据前几篇讲的,App开发者是没有权利new一个Service对象的,App开发者只有在Manifest文件里声明一个Service,在我们通过Intent告知框架启动这个Service,然后框架根据我们在Manifest里写的Service的配置才知道要new一个什么样的Service。下面是我们对这个Service的配置

<?xml version="1.0" encoding="utf-8"?><manifest xmlns:android="http://schemas.android.com/apk/res/android"    package="com.example.gray_dog3.ibindertest">    <application        android:allowBackup="true"        android:icon="@mipmap/ic_launcher"        android:label="@string/app_name"        android:supportsRtl="true"        android:theme="@style/AppTheme">        <activity android:name=".MainActivity">            <intent-filter>                <action android:name="android.intent.action.MAIN" />                <category android:name="android.intent.category.LAUNCHER" />            </intent-filter>        </activity>        <service android:name=".myService" android:process=":remote">            <intent-filter>                <action android:name="com.example.gray_dog3.ibindertest.PLAY_MUSIC_SERVICE"/>            </intent-filter>        </service>    </application></manifest>

上面就是我们对Service的配置了,名字叫myService,而后面的
android:process=":remote"

这个则是告诉框架,我们想要启动的Service是在一个新的进程中的。所以框架便按照我们的要求,孵化出一个新进程,然后new一个myService放进去。然后这个进程的主线程在while循环里打转转,过一会框架给这个主线程一个Message,告诉他执行Service的onCreate方法。我们我们在在myService的写一些播放音乐的准备的代码,和前面的MainActivity.onCreate()一个性质。下面是我们的myService的代码。(先不要关注具体的业务逻辑后面会讲)

public class myService extends Service {    private IBinder iBinder = null;    private static ContrlSong contrlSong;    @Override    public void onCreate() {        iBinder = new myBinder();        contrlSong = new MySongContrler(getApplicationContext());    }    @Nullable    @Override    public IBinder onBind(Intent intent) {        return iBinder;    }    public static void play() {        contrlSong.play();    }    public static void stop() {        contrlSong.stop();    }}

现在要注意一个问题,就是框架已经为我们启动两个进程,两个进程各自有一个自己的主线程,一个主线程执行完MainActivity.onCreate()之后在它的while循环里转圈圈,另一个主线程在执行完myService.onCreate()之后在它自己的while循环里转圈圈。

顺着刚刚的MainActivity的主线程往下分析,刚刚是执行到startService这一步了,执行完后两个进程也有了,Service也有了。然后MainActivity想在点击按钮后让myService播放音乐。(为了方便下面用MA代表MainActivity,MS代表myService)如果MA和MS在一个进程中,我们只需要在myService里写一个静态方法play()我们直接调myService.play()就可以了。即便是为了不引起ANR,我们也可以在MS里面开一个子线程让它去播放音乐。但是对于MA来说,我不管你怎么去实现,我只要调一个方法就完事了。但是现在问题是MA和MS不在同一个进程里。MA是不能直接调用MS的方法的。为什么不能直接调用呢?关于这个,在这里在补充一些进程的知识。

进程是一个独立的执行空间以及它所拥有的资源巴拉巴拉。。。总归记住一点就可以了,每个进程的空间都是独立的,这个很重要。此时的MA和MS就好像是在一个是FaceBook用户,一个是QQ用户,它们要想交流,是没办法直接加好友的。但是问题又来了,我是一个APP的两个进程,你TM不让我交流,我还玩个卵子。所以这时候就要通过一个统一接口来把它们两个连起来了,比如QQ邮箱,和FaceBook邮箱(不知道FaceBook有没有邮箱,假设有哈)。Android就为我们准备了一个叫IBinder,用来让两个进程通信。

现在明白了IBinder的用途,让我们来看看IBinder怎么用。接着前面的状态开始执行,前面说到两个进程有了,MS也启动了,然后要通信了,先把两个连接起来。这时候MS的那个主线程刚执行完startService,然后开始执行bindService,我们来看MainActivity.onCreate()的最后一行代码

bindService(new Intent(this, myService.class), conn, BIND_AUTO_CREATE);
第一个参数Intent,不奇怪,说明这个操作是要交个框架来完成的。第二个conn

private ServiceConnection conn = new ServiceConnection() {        @Override        public void onServiceConnected(ComponentName name, IBinder service) {            ib = service;        }        @Override        public void onServiceDisconnected(ComponentName name) {        }

在MyActivity中,是类的属性,类加载后的时候就已经有这个对象了。这个ServiceConnection和OnClickListener是一个性质的东西。就是MA和MS绑定好了以后触发它执行它的绑定后的方法。前面说的bind动作是交由框架执行的,MA的主线程发出这个向框架的请求后就去转圈圈了。框架把bind这件事做好后再发一个Message告诉MA的主线程,我bindService已经bind好了,你执行bind后的动作把,然后MA的主线程就来执行conn.onServiceConnected了,注意这个时候Message里面还带的有一个IBinder的对象。这个IBinder的对象就是两个进程通信的工具 conn.onServiceConnected()里面的一行 ib = service;就是把这个对象的引用存下来。也就是bind成功后MA就拥有了回传的跟MS通信的工具了。看下MS里面的代码:

 @Override    public void onCreate() {        iBinder = new myBinder();        contrlSong = new MySongContrler(getApplicationContext());    }    @Nullable    @Override    public IBinder onBind(Intent intent) {        return iBinder;    }
在MS创建的时候我们new了一个Binder对象。然后当MS被绑定onBind()的时候(这个就是在MA中发出bindService操作后框架在这边调用onBind()),我们把这个iBinder对象return给框架,框架交给交给MA,所以MA和MS就拥有了"同一个"对象,然后如果这个对象里有一个写方法,一个读方法,我写你读,或者你写我读,这样不就可以通信了么。这里我们为什么给"同一个"加上引号呢?实际上它们并不是同一个对象,而是利用一个是Binder,一个是BinderProxy。也就是一个是return回去的那个对象,一个是那个对象的分身。因为框架在进程间通信是通过底层Linux来完成的,MS这边return回去iBinder对象,框架就"new"出一个分身给MA,使他们看起来像是一个对象,操作的时候也像操作同一个对象一样,由框架和底层来保证分身和本尊的同步问题,对上面是透明的。既然我们MA和MS拿到了"同一个"对象,就可以利用这个对象进行通信了,一个写一个读。

懂了这个大概的原理我们再来看IBinder这个接口,是Android框架为我们写好的用来进程间通信的接口。同时Android也写了这个接口两个实现类类Binder和BinderProxy。其实叫什么名字,一点都不重要,不管它叫IBinder还是叫狗蛋,目的只有一个嘛,就是有一个对象,让MA和MS同时持有,然后这个对象有一个写数据的方法,一个读数据的方法,就可以完成通信了。MA持有的对象是BinderProxy类型的,MA持有的对象是Binder类型的。但是这些是框架做的事对我们是透明的,现在我们认为MA和MS拿到了同一个对象。在这里我们可以认为MainActivity.ib和myService.iBinder两个引用指向同一个对象。然后我们来看MA里的代码:

bt_1.setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View v) {                Parcel parcel = Parcel.obtain();                Parcel parcelReply = Parcel.obtain();                parcel.writeString("play");                try {                    ib.transact(1, parcel, parcelReply, 0);                } catch (RemoteException e) {                    e.printStackTrace();                }            }        });
我们前面讲的,点击bt_1要让MS播放音乐。我们让它播放音乐的信号就是:
ib.transact(1, parcel, parcelReply, 0);

这里的Parcel就是一个序列化的工具类。序列化又是各什么东东呢?刚刚我们说的MA和MS拥有"同一个"对象的引用,MA里面ib.transact()就相当于写的动作,MS里面的iBinder.onTransact()就相当于读的动作。当MA写了以后由框架通知MS去读。(iBinder.onTransact()的代码写在MyBinder里):

public class myBinder extends Binder {    @Override    protected boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {        if (code == 1)            myService.play();        if (code == 2)            myService.stop();        return true;    }}
这里我们相当于在myBinder里解析了传过来的内容。实际有用的就是1和2,MA传1就让MS唱歌,MA传2就让MS停止。现在来讲什么是序列化,序列化就是把要传的数据转成一个大家约定的格式,反正都是二进制0和1表示的,我只要规定String怎么表示,类怎么表示等等,就可以做一次转换,到接收方再反着翻译回来就行了。为什么要这么搞呢,我直接传数据不就行了?不行,因为前面已经说了,我们并不在操作统一对象,而真正的IPC也并不在Java层,底层代码鬼知道用什么语言写的,有什么样的数据结构定义,底层面对一堆乱码oo**xx怎么搞,而且以类或者对象什么的存储可能占的空间也比较大,不利于快速的读写啊。所以就搞出序列化这么各东东。把要传的数据转一种规定的格式,到那边再翻译过来就ok了。

到这里差不多就扯完了。整个执行逻辑就是这样咯,最后贴上播放音乐部分的业务逻辑。

播放音乐接口:

public interface ContrlSong {    public void play();    public void stop();}


实现类。

public class MySongContrler implements ContrlSong {    private MediaPlayer mediaPlayer = null;    private Context context;    MySongContrler(Context context) {        this.context = context;    }    @Override    public void play() {        if (mediaPlayer != null) return;        mediaPlayer = MediaPlayer.create(context, R.raw.song);        mediaPlayer.start();    }    @Override    public void stop() {        if (mediaPlayer != null) {            mediaPlayer.stop();            mediaPlayer.release();            mediaPlayer = null;        }    }}

有时间把代码传到GitHub,拜!



0 0
原创粉丝点击