天天看点

这一次,binder真正理解了(一) -----跨进程通信以及AIDL的使用前言注:Binder系列文章 framework 源码使用 android10 release 分支,kernel 部分使用 common 的 android-4.9-q-release 分支。

前言

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

概述

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

这一次,binder真正理解了(一) -----跨进程通信以及AIDL的使用前言注:Binder系列文章 framework 源码使用 android10 release 分支,kernel 部分使用 common 的 android-4.9-q-release 分支。

在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系列文章 framework 源码使用 android10 release 分支,kernel 部分使用 common 的 android-4.9-q-release 分支。

走错片场了,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的使用前言注:Binder系列文章 framework 源码使用 android10 release 分支,kernel 部分使用 common 的 android-4.9-q-release 分支。

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系列文章 framework 源码使用 android10 release 分支,kernel 部分使用 common 的 android-4.9-q-release 分支。

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

  • xref
  • github镜像链接
  • google source
  • 我这边整理好的binder相关代码

注: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代码分析

继续阅读