欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

这一次,binder真正理解了(一) -----跨进程通信以及AIDL的使用

程序员文章站 2024-03-23 23:24:40
...

前言

网上有很多有关于binder文章的讲述,读了很多文章,有些直接讲源码,对初学的来说比较抽象,这系列文章先从使用背景,从运用上逐步深入去介绍知识点,希望能有一个更好的理解。这系列文章先从ipc通信讲起,通过AIDL的使用去探binder原理,这一次和我一起理清binder吧,有哪里讲的不好的点,欢迎指正补充,希望这一系列能让大家在以后的面试中能稳稳的回答出binder的相关问题。

概述

在日常的app开发当中,大家有没有跨进程的使用呢。还是一个进程走到底呢 。

这一次,binder真正理解了(一) -----跨进程通信以及AIDL的使用

在android里,一个应用启动对应着一个进程,那一个应用多创建进程又有什么好处呢,又有什么应用场景呢。

  • 分担主进程的内存压力

当应用越做越大,内存越来越多,将一些独立的组件放到不同的进程,它就不占用主进程的内存空间了。手机分配给每个进程的内存都是有限的,在一些webiview的优化文章里,让webview独自使用一个进程。

  • 使应用常驻后台,防止主进程被杀守护进程,守护进程和主进程之间相互监视,有一方被杀就重新启动它。

Android后台进程里有很多应用是多个进程的,因为它们要常驻后台,特别是即时通讯或者社交应用,不过现在多进程已经被用烂了。典型用法是在启动一个不可见的轻量级私有进程,在后台收发消息,或者做一些耗时的事情,或者开机启动这个进程,然后做监听等。坏处:消耗用户的电量,多占用了系统的空间,若所有应用都这样占用,系统内存很容易占满而导致卡顿,应用程序架构会变得复杂,因为要处理多进程之间的通信。

看来一个应用多进程还是有一些好处的,在创建多进程的同时,想必需要了解一些进程间通信,就比如上文提到的webview优化,那我们应用让webview打开指定文章,肯定需要传递url吧。还有一种进程通信的业务场景,大家应该经常遇到,就比如第三方登录,我们使用微信登陆和qq登陆,两个应用都是不同的进程,他们也需要使用进程间通信吧。看来,进程间通信所应用的业务场景比较多的。当然Android底层的实现用到进程间通信更多。

进程间通信方式(IPC通信方式)

每个进程都有自己的一部分独立的系统资源,彼此是隔离的。为了能使不同的进程互相访问资源并进行协调工作,才有了进程间通信。
每个Android的进程,只能运行在自己进程所拥有的虚拟地址空间。对应一个4GB的虚拟地址空间,其中3GB是用户空间,1GB是内核空间,当然内核空间的大小是可以通过参数配置调整的。对于用户空间,不同进程之间彼此是不能共享的,而内核空间却是可共享的。(换句话说,你A进程要和B进程通信,在用户空间两者无法通信,可以将两者转向内核通信)那么进程间通信都有哪几种方式呢,android是基于linux系统的,这边继承了linux系统的ipc通信方式,也有自己独有的通信方式。

  • 一 使用Bundle

    我们直到,四大组件中的三大组件(Activity、Service、Receiver)都是支持在Intent中传递Bundle数据的,由于Bundle实现了Parcelable接口,由于Bundle实现了Parcelable接口,所以它可以方便地在不同的进程间传输。

传输的数据必须能够序列化,比如基本类型、实现Parcelable接口的对象,实现了Serializable接口的对象以及一些Android支持的特殊对象。

除了直接传递数据这种典型的使用场景,它还有一种特殊的使用场景。这种方式的核心思想在于将原本需要在A进程的计算任务转移到B进程的后台Service中去执行,避免了A进程在计算,不能把计算结果传递给要启动的B进程里。

  • 二 使用文件共享

共享文件也是一种不错的进程间通信方式,两个进程通过读/写同一个文件来交换数据,比如A进程把数据写入文件,B进程通过读取这个文件来获取数据。由于Android系统基于Linux,使得其并发读/写文件可以没有限制地进行,甚至两个线程同时对同一个文件进行写操作都是允许的。

通过文件共享这种方式来共享数据对文件格式是没有具体要求的,比如可以是文本文件,也可以是XML文件,只要读/写双方约定数据格式即可。通过文件共享的方式也是有局限性的,比如并发读/写的问题。

  • 三 使用Socket

Socket也称为“套接字”,是网络通信的概念,它分为流失套接字和用户数据包套接字两种,分别对应于网络的传输控制层的TCP和UDP协议。

一般用于设计我们的聊天室程序,首先在远程Service建立一个TCP服务,然后在主界面中连接TCP服务,连接上了以后,就可以给服务端发消息了。使用Socket的工作机制。

  • 四 使用Messenger

通过Messenger可以在不同进程中传递Messenger对象,在Messenger中放入我们需要传递的数据,就可以轻松地实现数据的进程间传递了。Messenger是一种轻量级的IPC方案,它的底层实现是AIDL。

  • 五 使用AIDL

AIDL也是Messenger的底层实现,可以用来跨进称调用服务端,底层实现是binder。

  • 六 ContentProvider

ContentProvider的底层实现同样也是Binder。系统已封装好使用起来十分简单,(毕竟ContentProvider是用于和其他应用进行交互的)

系统预置了许多ContentProvider,比如通讯录信息、日程表信息等,要跨进程访问这些消息,只需要通过ContentResolver的query、update、insert和delet方法即可。

我们可以看到Messenger,AIDL,ContentProvider底层实际都是用Binder。

这一次,binder真正理解了(一) -----跨进程通信以及AIDL的使用

走错片场了,binder牛逼。

那Binder是什么? Binder作为Android系统提供的一种IPC机制。确实这句话就是他的定义,他就是Android系统来解决跨进程通信的一个机制,之后的系列文章会讲解他是如何运作的,从java层逐步往native层深入下去。

AIDL 实现进程间通信

github代码直通车

首先我们先从基于binder机制的AIDL讲起,如何实现进程间通信 ,现在我想实现这样一个功能,进程A的Activity传递数据给进程B的Service,进程B处理完数据再将数据返回给A (整个过程可以类比第三方登录)。可以参考下方流程图,首先进程A的clientActivity将MyData对象传给进程B的Service操作一通,等进程B的Service操作完在将处理后的MyData对象返回给进程A,大家可以试着想想该如何实现这种场景,再继续阅读下面的代码。

这一次,binder真正理解了(一) -----跨进程通信以及AIDL的使用

RemoteService

注册Service

   <service
            android:name=".binder.RemoteService"
            android:enabled="true"
            android:exported="true"
            android:process=":remote">
        </service>

编写AIDL文件

  • 要操作的MyData对象

MyData.aidl


package com.hugh.byteadvance.binder;

parcelable MyData;

  • IRemoteService.aidl 实际操作的函数,之后的Activity进程要调用这边的opration函数

IRemoteService.aidl

package com.hugh.byteadvance.binder;
import com.hugh.byteadvance.binder.MyData;
import com.hugh.byteadvance.binder.ICompletedListener;
interface IRemoteService {
      int getPid();
      MyData getMyData();
      void operation(in MyData parameter1 );
      void registerListener(in ICompletedListener listener);

      void unregisterListener(in ICompletedListener listener);
}

  • ICompletedListener 模仿耗时操作通过回调来通知

ICompletedListener.aidl

package com.hugh.byteadvance.binder;

import com.hugh.byteadvance.binder.MyData;

interface ICompletedListener {
   void onOperationCompleted(in MyData result);
}

Service部分代码编写

operation实现了具体操作,将数据相加,相乘最后通过回调返回给进程A Activity。
这里实现了同步和异步的操作,同步可以参考getPid(),getMyData()。
异步这边可以参考下operation(),注意这边的回调使用到了RemoteCallbackList。

public class RemoteService extends Service {

    private static final String TAG = "aaa";

    MyData mMyData;
    //声明
    private RemoteCallbackList<ICompletedListener> callbackList;

    @Override
    public void onCreate() {
        super.onCreate();
        initMyData();
        callbackList = new RemoteCallbackList<>();
    }


    /**
     * 初始化MyData数据
     **/
    private void initMyData() {
        mMyData = new MyData();
        mMyData.setData1(10);
        mMyData.setData2(20);
    }

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

    private final IRemoteService.Stub mBinder = new IRemoteService.Stub() {
        @Override
        public int getPid() throws RemoteException {
            Log.e(TAG, "android.os.Process.myPid()" + android.os.Process.myPid());
            return android.os.Process.myPid();
        }

        @Override
        public MyData getMyData() throws RemoteException {
            Log.i(TAG, "[RemoteService] getMyData()  " + mMyData.toString());
            return mMyData;
        }

        @Override
        public void operation(MyData parameter1 ) throws RemoteException {
            try {
                Log.e(TAG, "operation 被调用,延时3秒,模拟耗时计算");
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            int param1 = parameter1.getData1();
            int param2 = parameter1.getData2();
            MyData result = new MyData(param1 + param2,param1 *param2);
            //在操作 RemoteCallbackList 前,必须先调用其 beginBroadcast 方法
            //此外,beginBroadcast 必须和 finishBroadcast配套使用
            int count = callbackList.beginBroadcast();
            for (int i = 0; i < count; i++) {
                ICompletedListener listener = callbackList.getBroadcastItem(i);
                if (listener != null) {
                    listener.onOperationCompleted(result);
                }
            }
        }

        @Override
        public void registerListener(ICompletedListener listener) throws RemoteException {
            callbackList.register(listener);
            Log.e(TAG, "registerListener 注册回调成功");
        }

        @Override
        public void unregisterListener(ICompletedListener listener) throws RemoteException {
            callbackList.unregister(listener);
            Log.e(TAG, "unregisterListener 解除注册回调成功");
        }

        //此处可以用于权限拦截
        @Override
        public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
            return super.onTransact(code, data, reply, flags);
        }
    };

    @Override
    public boolean onUnbind(Intent intent) {
        Log.i(TAG, "[RemoteService] onUnbind");
        return super.onUnbind(intent);
    }


    @Override
    public void onDestroy() {
        Log.i(TAG, "[RemoteService] onDestroy");
        super.onDestroy();
    }

}

Activity代码编写

讲一下Activity的流程

  1. 首先新建ServiceConnection对象,在ServiceConnected和onServiceDisconnected写下相关的操作

  2. 启动Service,并绑定Service,在onServiceConnected的回调中,获取IRemoteService的代理对象

  3. mRemoteService.registerListener(completedListener); 注册监听后 , mRemoteService.operation(myData); 通过代理对象执行相应的方法。

  4. 最后 onOperationCompleted 再回调中获取相关结果。

重点:我们的操作都需要获取到IRemoteService的代理对象,就能进行进程间的通信。


public class ClientActivity extends AppCompatActivity implements View.OnClickListener {
    private static final String TAG = "aaa";
    private IRemoteService mRemoteService;
    private boolean mIsBound;
    private TextView mCallBackTv;
    private Button mBtnRegister;
    private Button mBtnOperate;
    private TextView mTvResult;
    private TextView mBtnUnRegister;

    private ICompletedListener completedListener = new ICompletedListener.Stub() {
        @Override
        public void onOperationCompleted(final MyData result) throws RemoteException {
            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    mTvResult.setText("运算结果: 加法:" + result.getData1() + "------运算结果: 乘法:" + result.getData2());
                }
            });
        }
    };

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_binder);
        mCallBackTv = (TextView) findViewById(R.id.tv_callback);
        mTvResult = findViewById(R.id.tv_operate_result);
        mBtnOperate = findViewById(R.id.btn_operate);
        mBtnRegister = findViewById(R.id.btn_register);
        mBtnUnRegister = findViewById(R.id.btn_un_register);
        mBtnRegister.setOnClickListener(this);
        mBtnOperate.setOnClickListener(this);
        mBtnUnRegister.setOnClickListener(this);
        mCallBackTv.setText("unattached");
    }


    private ServiceConnection mConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            mRemoteService = IRemoteService.Stub.asInterface(service);
            String pidInfo = null;
            MyData myData = null;
            try {
                myData = mRemoteService.getMyData();
                pidInfo = "pid=" + mRemoteService.getPid() +
                        ", data1 = " + myData.getData1() +
                        ", data2=" + myData.getData2();
            } catch (RemoteException e) {
                e.printStackTrace();
            }
            Log.i(TAG, "[ClientActivity] onServiceConnected  " + pidInfo);
            mCallBackTv.setText(pidInfo);
            Toast.makeText(ClientActivity.this, "connected", Toast.LENGTH_SHORT).show();
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            Log.i(TAG, "[ClientActivity] onServiceDisconnected");
            mCallBackTv.setText("disconnected");
            mRemoteService = null;
            Toast.makeText(ClientActivity.this, "disconnected", Toast.LENGTH_SHORT).show();
        }
    };

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.btn_register:
                /**
                 * 注册监听
                 */
                if (mRemoteService == null) {
                    Toast.makeText(ClientActivity.this, "请先点击bind,建立链接", Toast.LENGTH_SHORT).show();
                } else {
                    try {
                        Toast.makeText(ClientActivity.this, "注册监听成功", Toast.LENGTH_SHORT).show();
                        mRemoteService.registerListener(completedListener);
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }
                }
                break;
            case R.id.btn_operate:
                /**
                 * 调用Service异步方法
                 */
                if (mRemoteService == null) {
                    Toast.makeText(ClientActivity.this, "请先点击bind,建立链接", Toast.LENGTH_SHORT).show();
                } else {
                    try {
                        Log.e("aaa", "送入数据");
                        MyData myData = new MyData(2, 88);
                        mRemoteService.operation(myData);
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }
                }
                break;
            case R.id.btn_un_register:
                /**
                 * 取消监听
                 */
                if (mRemoteService == null) {
                    Toast.makeText(ClientActivity.this, "请先点击bind,建立链接", Toast.LENGTH_SHORT).show();
                } else {
                    try {
                        mRemoteService.unregisterListener(completedListener);
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }
                }
                break;
        }
    }


    public void clickHandler(View view) {
        switch (view.getId()) {
            case R.id.btn_bind:
                bindRemoteService();
                break;

            case R.id.btn_unbind:
                unbindRemoteService();
                break;

            case R.id.btn_kill:
                killRemoteService();
                break;
        }
    }

    /**
     * 绑定远程服务
     */
    private void bindRemoteService() {
        Log.i(TAG, "[ClientActivity] bindRemoteService");
        Intent intent = new Intent(ClientActivity.this, RemoteService.class);
        intent.setAction(IRemoteService.class.getName());
        bindService(intent, mConnection, Context.BIND_AUTO_CREATE);

        mIsBound = true;
        mCallBackTv.setText("binding中");
    }

    /**
     * 解除绑定远程服务
     */
    private void unbindRemoteService() {
        if (!mIsBound) {
            return;
        }
        Log.i(TAG, "[ClientActivity] unbindRemoteService ==>");
        unbindService(mConnection);
        mIsBound = false;
        mCallBackTv.setText("unbinding...");
    }


    /**
     * 杀死远程服务
     */
    private void killRemoteService() {
        Log.i(TAG, "[ClientActivity] killRemoteService");
        try {
            android.os.Process.killProcess(mRemoteService.getPid());
            mCallBackTv.setText("kill success");
        } catch (RemoteException e) {
            e.printStackTrace();
            Toast.makeText(ClientActivity.this, "kill failure", Toast.LENGTH_SHORT).show();
        }
    }

}

小结

大家可以参考demo和上面的流程图来理清下两进程间通信的流程。这样有助于后面的源码理解,这边为了方便阅读,只贴出了部分功能代码。在下一章为大家带来binder原理相关的解析。

工欲善其事,必先利其器。既然要读源码,那我们必须要先准备好相应的源码吧。

首先看下Android系统的架构图

这一次,binder真正理解了(一) -----跨进程通信以及AIDL的使用

我们可以看到binder驱动在kernel层,接下来的系列文章里,会讲解binder如何从应用层到framework层再到kernel层的整个链路。话不多说。给大家推荐下源码下载的地址。

注:Binder系列文章 framework 源码使用 android10 release 分支,kernel 部分使用 common 的 android-4.9-q-release 分支。

源码涉及目录

/framework/base/core/java/               (Java)
/framework/base/core/jni/                (JNI)
/framework/native/libs/binder            (Native)
/framework/native/cmds/servicemanager/   (Native)
/kernel/drivers/staging/android          (Driver)

Java framework

/framework/base/core/java/android/os/  
    - IInterface.java
    - IBinder.java
    - Parcel.java
    - IServiceManager.java
    - ServiceManager.java
    - ServiceManagerNative.java
    - Binder.java  


/framework/base/core/jni/    
    - android_os_Parcel.cpp
    - AndroidRuntime.cpp
    - android_util_Binder.cpp (核心类)
   

Native framework

/framework/native/libs/binder         
    - IServiceManager.cpp
    - BpBinder.cpp
    - Binder.cpp
    - IPCThreadState.cpp (核心类)
    - ProcessState.cpp  (核心类)

/framework/native/include/binder/
    - IServiceManager.h
    - IInterface.h

/framework/native/cmds/servicemanager/
    - service_manager.c
    - binder.c

Kernel

kernel/drivers/android
    - binder.c
  

源码下载

https://github.com/hughcoder/ByteAdvance

持续更新 敬请期待

参考

gityuan 的binder代码分析