热门标签 | HotTags
当前位置:  开发笔记 > 运维 > 正文

Android中Binder详细学习心得

这篇文章主要介绍了Android中Binder详细学习心得,并分析了Binder的详细用法,需要的朋友参考下吧。

该文章是一个系列文章,是本人在Android开发的漫漫长途上的一点感想和记录,我会尽量按照先易后难的顺序进行编写该系列。该系列引用了《Android开发艺术探索》以及《深入理解Android 卷Ⅰ,Ⅱ,Ⅲ》中的相关知识,另外也借鉴了其他的优质博客,在此向各位大神表示感谢,膜拜!!!另外,本系列文章知识可能需要有一定Android开发基础和项目经验的同学才能更好理解,也就是说该系列文章面向的是Android中高级开发工程师。

前言

上一次还不如不说去面试了呢,估计是挂了,数据结构与算法方面虽然面试前突击了一波,但是时间太短,当时学的也不好。另外Android的一些知识也不是很了解。不过这也加大了我写博客的动力。许多知识总觉得自己掌握的还挺好,不过一问到比较细节的方面就不太清楚了。所以写这整个博客的目的也是加深自己的知识,培养自己的沟通能力,和大家一起学习吧。
好了,闲话少说,我们这一篇先解决上一篇中遗留的问题,之后有时间的话,我把这次的面试经历单写一篇博客,和大家共勉。
本篇我们来看一下ServiceManager。上一篇中没怎么说它,ServiceManager作为Android系统服务的大管家。我们还是有必要来看一下它的。

ServiceManager概述

ServiceManager是Android世界中所有重要系统服务的大管家。像前文提到的AMS(ActivityManagerService),还有许多以后可能分析到的PackageManagerService等等服务都需要像ServiceManager中注册。那么为何需要一个ServiceManager呢,其重要作用何在呢?私认为有以下几点:

ServiceManager能集中管理系统内的所有服务,它能施加权限控制,并不是任何进程都能注册服务的。 ServiceManager支持通过字符串名称来查找对应的Service。这个功能很像DNS。由于各种原因的影响,Server进程可能生死无常。 如果让每个Client都去检测,压力实在太大了。 现在有了统一的管理机构,Client只需要查询ServiceManager,就能把握动向,得到最新信息。

ServiceManager

[SystemServer.java]

public void setSystemProcess() {
  try {
    //注册服务,第二个参数为this,这里假设SystemServer通过“socket”与SM交互
    ServiceManager.addService(Context.ACTIVITY_SERVICE, this, true);
    ..........
  } catch (PackageManager.NameNotFoundException e) {
    ........
  }
}

 

我们SystemServer进程中的AMS通过SM的代理与SM进程交互(读者也可以把这个过程想象为你所能理解的进程间通信方式,例如管道、Socket等),并把自己注册在SM中。这个情况下,我们使用ServiceManager的代理与SM进程交互,既然有代理,那么也得有对应的服务端。那么根据我们之前博客的思路分析的话,就是如下的流程:

ServiceManager是如何启动的?

按照我们之前博客的思路,我们在SystemServer端有了个ServiceManager的代理,那么Android系统中应该提供类似AMS这样的继承或间接继承自java层Binder然后重写onTransact方法以处理请求。但是并没有,ServiceManager并没有使用如AMS这样复杂的Binder类结构。而是直接与Binder驱动设备打交道。所以我们上一篇说了ServiceManager不一样。我们来看具体看一下。

ServiceManager在init.rc配置文件中配置启动,是一个以c/c++语言编写的程序。init进程、SM进程等关系如下图


我们来看它的main方法。

int main(int argc, char **argv)
{
  struct binder_state *bs;
  //①应该是打开binder设备吧?
  bs = binder_open(128*1024);
  if (!bs) {
    ALOGE("failed to open binder driver\n");
    return -1;
  }
  //②成为manager
  if (binder_become_context_manager(bs)) {
    ALOGE("cannot become context manager (%s)\n", strerror(errno));
    return -1;
  }
  ......
  //③处理客户端发过来的请求
  binder_loop(bs, svcmgr_handler);
  return 0;
}

①打开Binder设备

[binder.c]

struct binder_state*binder_open(unsigned mapsize)
{
  struct binder_state*bs;
  bs=malloc(sizeof(*bs));
  ......
  //打开Binder设备
  bs->fd=open("/dev/binder",O_RDWR);
  ......
  bs->mapsize=mapsize;
  //进行内存映射
  bs->mapped=mmap(NULL,mapsize,PROT_READ,MAP_PRIVATE,bs->
  fd,0);
}

 

这一步的目的是把内核层的binder驱动映射到用户空间。我们知道进程之间是独立的,进程呢运行在用户空间内,内核层的Binder驱动可以看成是一个文件(实际上它也是,Linux上都是文件)。这一步呢,可以看成把一个文件映射到用户空间,我们的进程呢通过这个文件进行交互。

②成为manager

[Binder.c]

int binder_become_context_manager(struct binder_state*bs)
{
  //实现太简单了!这个有个0,什么鬼?
  return ioctl(bs->fd,BINDER_SET_CONTEXT_MGR,0);
}

 

③处理客户端发过来的请求

[Binder.c]

void binder_loop(struct binder_state *bs, binder_handler func)
{
  int res;
  struct binder_write_read bwr;
  uint32_t readbuf[32];
  bwr.write_size = 0;
  bwr.write_cOnsumed= 0;
  bwr.write_buffer = 0;
  readbuf[0] = BC_ENTER_LOOPER;
  binder_write(bs, readbuf, sizeof(uint32_t));
  for (;;) {//果然是循环
    bwr.read_size = sizeof(readbuf);
    bwr.read_cOnsumed= 0;
    bwr.read_buffer = (uintptr_t) readbuf;
    res = ioctl(bs->fd, BINDER_WRITE_READ, &bwr);
    if (res <0) {
      ALOGE("binder_loop: ioctl failed (%s)\n", strerror(errno));
      break;
    }
    //接收到请求交给binder_parse,最终会调用func来处理这些请求
    res = binder_parse(bs, 0, (uintptr_t) readbuf, bwr.read_consumed, func);
    if (res == 0) {
      ALOGE("binder_loop: unexpected reply&#63;!\n");
      break;
    }
    if (res <0) {
      ALOGE("binder_loop: io error %d %s\n", res, strerror(errno));
      break;
    }
  }
}

上面传入func的是svcmgr_ handler函数指针,所以会在svcmgr_handler中进行集中处理客户端的请求。

[service_manager.c]

ServiceManager的代理是如何获得的?

我们来回到最初的调用

[SystemServer.java]

public void setSystemProcess() {
  try {
    //注册服务,第二个参数为this,这里假设SystemServer通过“socket”与SM交互
    ServiceManager.addService(Context.ACTIVITY_SERVICE, this, true);
    ..........
  } catch (PackageManager.NameNotFoundException e) {
    ........
  }
}

上面的请求最终是通过SM服务代理发送的,那这个代理是怎么来的呢?我们来看

[ServiceManager.java]

public static void addService(String name, IBinder service) {
  try {
    getIServiceManager().addService(name, service, false);
  } catch (RemoteException e) {
    Log.e(TAG, "error in addService", e);
  }
}
private static IServiceManager getIServiceManager() {
  if (sServiceManager != null) {
    return sServiceManager;
  }
  // 是这里,没错了
  sServiceManager = ServiceManagerNative.asInterface(BinderInternal.getContextObject());
  return sServiceManager;
}

我们先来看BinderInternal.getContextObject()

[BinderInternal.java]

//好吧,它还是个native函数
public static final native IBinder getContextObject();

跟进[android_ util_Binder.cpp]

static jobject android_os_BinderInternal_getContextObject(JNIEnv* env, jobject clazz)
{
  sp b = ProcessState::self()->getContextObject(NULL);
  return javaObjectForIBinder(env, b);
}

跟进[ProcessState.cpp]

sp ProcessState::getContextObject(const sp& /*caller*/)
{
  return getStrongProxyForHandle(0);
}
/*这个函数是不是我们之前见过*/
sp ProcessState::getStrongProxyForHandle(int32_t handle)
{
  sp result;
  AutoMutex _l(mLock);
  handle_entry* e = lookupHandleLocked(handle);
  if (e != NULL) {
    IBinder* b = e->binder;
    if (b == NULL || !e->refs->attemptIncWeak(this)) {
      if (handle == 0) {//这里我们的handle为0
        Parcel data;
      //在handle对应的BpBinder第一次创建时
      //会执行一次虚拟的事务请求,以确保ServiceManager已经注册
        status_t status = IPCThreadState::self()->transact(
            0, IBinder::PING_TRANSACTION, data, NULL, 0);
        if (status == DEAD_OBJECT)
          return NULL;//如果ServiceManager没有注册,直接返回
      }
      //这里还是以handle参数创建了BpBinder
      b = new BpBinder(handle); 
      e->binder = b;
      if (b) e->refs = b->getWeakRefs();
      result = b;
    } else {
      
      result.force_set(b);
      e->refs->decWeak(this);
    }
  }
  return result;
}

我们再一步步返回

[android_ util_Binder.cpp]

static jobject android_os_BinderInternal_getContextObject(JNIEnv* env, jobject clazz)
{
  sp b = ProcessState::self()->getContextObject(NULL);
  //这里的b = new BpBinder(0);
  return javaObjectForIBinder(env, b);
}
/*这个函数我们上一篇是不是也见过*/
jobject javaObjectForIBinder(JNIEnv* env, const sp& val) {
  if (val == NULL) return NULL;
  //如果val是Binder对象,进入下面分支,此时val是BpBinder
  if (val->checkSubclass(&gBinderOffsets)) {
    // One of our own!
    jobject object = static_cast(val.get())->object();
    LOGDEATH("objectForBinder %p: it's our own %p!\n", val.get(), object);
    return object;
  }
  .........
  //调用BpBinder的findObject函数
  //在Native层的BpBinder中有一个ObjectManager,它用来管理在Native BpBinder上创建的Java BinderProxy对象
  //findObject用于判断gBinderProxyOffsets中,是否存储了已经被ObjectManager管理的Java BinderProxy对象
  jobject object = (jobject)val->findObject(&gBinderProxyOffsets);
  if (object != NULL) {
    jobject res = jniGetReferent(env, object);
    ............
    //如果该Java BinderProxy已经被管理,则删除这个旧的BinderProxy
    android_atomic_dec(&gNumProxyRefs);
    val->detachObject(&gBinderProxyOffsets);
    env->DeleteGlobalRef(object);
  }
  //创建一个新的BinderProxy对象
  object = env->NewObject(gBinderProxyOffsets.mClass, gBinderProxyOffsets.mConstructor);
  if (object != NULL) {
    env->SetLongField(object, gBinderProxyOffsets.mObject, (jlong)val.get());
    val->incStrong((void*)javaObjectForIBinder);
    jobject refObject = env->NewGlobalRef(
        env->GetObjectField(object, gBinderProxyOffsets.mSelf));
    //新创建的BinderProxy对象注册到BpBinder的ObjectManager中,同时注册一个回收函数proxy_cleanup
    //当BinderProxy对象detach时,proxy_cleanup函数将被调用,以释放一些资源
    val->attachObject(&gBinderProxyOffsets, refObject,
        jnienv_to_javavm(env), proxy_cleanup);
    // Also remember the death recipients registered on this proxy
    sp drl = new DeathRecipientList;
    drl->incStrong((void*)javaObjectForIBinder);
    //将死亡通知list和BinderProxy联系起来
    env->SetLongField(object, gBinderProxyOffsets.mOrgue, reinterpret_cast(drl.get()));
    // Note that a new object reference has been created.
    android_atomic_inc(&gNumProxyRefs);
    //垃圾回收相关;利用gNumRefsCreated记录创建出的BinderProxy数量
    //当创建出的BinderProxy数量大于200时,该函数将利用BinderInternal的ForceGc函数进行一个垃圾回收
    incRefsCreated(env);
    return object;
  }
}

接着返回到[ServiceManager.java]

private static IServiceManager getIServiceManager() {
  if (sServiceManager != null) {
    return sServiceManager;
  }
  // 是这里,没错了BinderInternal.getContextObject()是BinderProxy对象
  sServiceManager = ServiceManagerNative.asInterface(BinderInternal.getContextObject());
  return sServiceManager;
}

跟进[[ServiceManagerNative.java]]

static public IServiceManager asInterface(IBinder obj)
{
  if (obj == null) {
    return null;
  }
  我们知道这里的obj指向的是BinderProxy对象
  IServiceManager in =
    (IServiceManager)obj.queryLocalInterface(descriptor);
  if (in != null) {
    return in;
  }
  return new ServiceManagerProxy(obj);
}

跟进[Binder.java]

final class BinderProxy implements IBinder {
  public IInterface queryLocalInterface(String descriptor) {
    return null;
  }
}

跟进[ServiceManagerNative.java]

class ServiceManagerProxy implements IServiceManager {
  public ServiceManagerProxy(IBinder remote) {
    //这里的mRemote指向了BinderProxy,与我们上一篇博客中讲述的遥相呼应
    mRemote = remote;
  }
}

本节小结

我们详尽讲述了SM进程的启动以及它作为服务大管家的意义。结合上一篇的内容我们总算是把Binder讲述的比较清楚了。

Binder补充说明 AIDL

经过上面的介绍,你应该明白Java层Binder的架构中,Bp端可以通过BinderProxy的transact()方法与Bn端发送请求,而Bn端通过集成Binder重写onTransact()接收并处理来自Bp端的请求。这个结构非常清晰简单,在Android6.0,我们可以处处看到这样的设计,比如我们的ActivityManagerNavtive这个类,涉及到Binder通信的基本上都是这种设计。不过如果我们想要自己来定义一些远程服务。那这样的写法就比较繁琐,还好Android提供了AIDL,并且在Android8.0之后,我们可以看到与ActivityManagerNavtive相似的许多类已经被标注过时,因为Android系统也使用AIDL了。

AIDL的简单例子

AIDL的语法与定义一个java接口非常类似。下面我就定以一个非常简单的aidl

IMyAidlInterface.aidl

interface IMyAidlInterface {
  int getTest();
}

然后基本上就行了,我们重新build之后会得到一个
IMyAidlInterface.java文件,这个文件由aidl工具生成,我们现在使用的基本是AndroidStudio,即使你使用的是Eclipse也没关系,这个文件会自动生成,不需要你操心。但是我们还是得来看看我们生成的这个文件

public interface IMyAidlInterface extends android.os.IInterface {
  //抽象的Stub类,继承自Binder并实现我们定义的IMyAidlInterface接口
  //继承自Binder,重写onTransact方法,是不是感觉跟我们的XXXNative很像
  public static abstract class Stub extends android.os.Binder implements com.mafeibiao.testapplication.IMyAidlInterface {
    private static final java.lang.String DESCRIPTOR = "com.mafeibiao.testapplication.IMyAidlInterface";
    /**
     * Construct the stub at attach it to the interface.
     */
    public Stub() {
      this.attachInterface(this, DESCRIPTOR);
    }
    /**
     * Cast an IBinder object into an com.mafeibiao.testapplication.IMyAidlInterface interface,
     * generating a proxy if needed.
     */
    public static com.mafeibiao.testapplication.IMyAidlInterface asInterface(android.os.IBinder obj) {
      if ((obj == null)) {
        return null;
      }
      android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
      if (((iin != null) && (iin instanceof com.mafeibiao.testapplication.IMyAidlInterface))) {
        return ((com.mafeibiao.testapplication.IMyAidlInterface) iin);
      }
      return new com.mafeibiao.testapplication.IMyAidlInterface.Stub.Proxy(obj);
    }
    @Override
    public android.os.IBinder asBinder() {
      return this;
    }
    @Override
    public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
      switch (code) {
        case INTERFACE_TRANSACTION: {
          reply.writeString(DESCRIPTOR);
          return true;
        }
        case TRANSACTION_getTest: {
          data.enforceInterface(DESCRIPTOR);
          int _result = this.getTest();
          reply.writeNoException();
          reply.writeInt(_result);
          return true;
        }
      }
      return super.onTransact(code, data, reply, flags);
    }
    /*这个Proxy不用说肯定是代理了,其内部还有个mRemote对象*/
    private static class Proxy implements com.mafeibiao.testapplication.IMyAidlInterface {
      private android.os.IBinder mRemote;

      Proxy(android.os.IBinder remote) {
        mRemote = remote;
      }
      @Override
      public android.os.IBinder asBinder() {
        return mRemote;
      }
      public java.lang.String getInterfaceDescriptor() {
        return DESCRIPTOR;
      }
      @Override
      public int getTest() throws android.os.RemoteException {
        android.os.Parcel _data = android.os.Parcel.obtain();
        android.os.Parcel _reply = android.os.Parcel.obtain();
        int _result;
        try {
          _data.writeInterfaceToken(DESCRIPTOR);
          mRemote.transact(Stub.TRANSACTION_getTest, _data, _reply, 0);
          _reply.readException();
          _result = _reply.readInt();
        } finally {
          _reply.recycle();
          _data.recycle();
        }
        return _result;
      }
    }
    static final int TRANSACTION_getTest = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
  }
  public int getTest() throws android.os.RemoteException;
}

可见,AIDL的本质与XXXNative之类的类并没有什么本质的不同,不过他的出现使得构建一个Binder服务的工作大大简化了。

AIDL的使用详解

上面用一个非常简单的小例子来解密AIDL的本质,但是在实际使用AIDL的时候还有许多地方需要注意。

AIDL支持的数据类型 基本数据类型(int,long,charmboolean,double等) String和CharSequence List:只支持ArrrayList,并且里面每个元素的类型必须是AIDL支持的 Map:只支持HashMap,t,并且里面每个元素的类型必须是AIDL支持的 Parcelable:所有实现Parcelable接口的对象 AIDL:所有的AIDL接口本身也可以在AIDL文件中使用

以上6种数据类型就是AIDL所支持的所有类型,其中自定义的Parcel对象和AIDL对象必须要显示import进来,不管他们是否和当前的AIDL文件位于同一个包内。

另外一个需要注意的地方是如果我们在AIDL中使用了自定义的Parcelable接口的对象,那么我们必须新建一个和它同名的AIDL文件,并在其中声明它为Parcelable类型。

如下例

[IBookManager.aidl]

package com.ryg.chapter_2.aidl;
/*这里显示import*/
import com.ryg.chapter_2.aidl.Book;
interface IBookManager {
   //这里我们使用了自定义的Parcelable对象
   List getBookList();
   void addBook(in Book book);
}

这里我们新建一个与Book同名的AIDL文件并声明

[Book.aidl]

package com.ryg.chapter_2.aidl;
parcelable Book;

定向tag

定向tag:这是一个极易被忽略的点——这里的“被忽略”指的不是大家都不知道,而是很少人会正确的使用它。
AIDL中的定向 tag 表示了在跨进程通信中数据的流向,其中 in 表示数据只能由客户端流向服务端, out 表示数据只能由服务端流向客户端,而 inout 则表示数据可在服务端与客户端之间双向流通。其中,数据流向是针对在客户端中的那个传入方法的对象而言的。in 为定向 tag 的话表现为服务端将会接收到一个那个对象的完整数据,但是客户端的那个对象不会因为服务端对传参的修改而发生变动;out 的话表现为服务端将会接收到那个对象的的空对象,但是在服务端对接收到的空对象有任何修改之后客户端将会同步变动;inout 为定向 tag 的情况下,服务端将会接收到客户端传来对象的完整信息,并且客户端将会同步服务端对该对象的任何变动。

另外,Java 中的基本类型和 String ,CharSequence 的定向 tag 默认且只能是 in 。还有,请注意,请不要滥用定向 tag ,而是要根据需要选取合适的——要是不管三七二十一,全都一上来就用 inout ,等工程大了系统的开销就会大很多——因为排列整理参数的开销是很昂贵的。

所有的非基本参数都需要一个定向tag来指出数据的流向,不管是 in , out , 还是 inout 。基本参数的定向tag默认是并且只能是 in 。

本篇总结

我们本篇详细分析了ServiceManager,ServiceManager并没有使用复杂的类结构,他直接与Binder驱动设备交互达到IPC通信的目的。感谢你对的支持。


推荐阅读
  • 在创建新的Android项目时,您可能会遇到aapt错误,提示无法打开libstdc++.so.6共享对象文件。本文将探讨该问题的原因及解决方案。 ... [详细]
  • 深入解析Android自定义View面试题
    本文探讨了Android Launcher开发中自定义View的重要性,并通过一道经典的面试题,帮助开发者更好地理解自定义View的实现细节。文章不仅涵盖了基础知识,还提供了实际操作建议。 ... [详细]
  • 优化ListView性能
    本文深入探讨了如何通过多种技术手段优化ListView的性能,包括视图复用、ViewHolder模式、分批加载数据、图片优化及内存管理等。这些方法能够显著提升应用的响应速度和用户体验。 ... [详细]
  • 本文详细介绍如何使用arm-eabi-gdb调试Android平台上的C/C++程序。通过具体步骤和实用技巧,帮助开发者更高效地进行调试工作。 ... [详细]
  • 在计算机技术的学习道路上,51CTO学院以其专业性和专注度给我留下了深刻印象。从2012年接触计算机到2014年开始系统学习网络技术和安全领域,51CTO学院始终是我信赖的学习平台。 ... [详细]
  • Linux 系统启动故障排除指南:MBR 和 GRUB 问题
    本文详细介绍了 Linux 系统启动过程中常见的 MBR 扇区和 GRUB 引导程序故障及其解决方案,涵盖从备份、模拟故障到恢复的具体步骤。 ... [详细]
  • 1:有如下一段程序:packagea.b.c;publicclassTest{privatestaticinti0;publicintgetNext(){return ... [详细]
  • 本文介绍了一款用于自动化部署 Linux 服务的 Bash 脚本。该脚本不仅涵盖了基本的文件复制和目录创建,还处理了系统服务的配置和启动,确保在多种 Linux 发行版上都能顺利运行。 ... [详细]
  • 在Linux系统中配置并启动ActiveMQ
    本文详细介绍了如何在Linux环境中安装和配置ActiveMQ,包括端口开放及防火墙设置。通过本文,您可以掌握完整的ActiveMQ部署流程,确保其在网络环境中正常运行。 ... [详细]
  • 本文深入探讨了Linux系统中网卡绑定(bonding)的七种工作模式。网卡绑定技术通过将多个物理网卡组合成一个逻辑网卡,实现网络冗余、带宽聚合和负载均衡,在生产环境中广泛应用。文章详细介绍了每种模式的特点、适用场景及配置方法。 ... [详细]
  • 2023年京东Android面试真题解析与经验分享
    本文由一位拥有6年Android开发经验的工程师撰写,详细解析了京东面试中常见的技术问题。涵盖引用传递、Handler机制、ListView优化、多线程控制及ANR处理等核心知识点。 ... [详细]
  • 解决微信电脑版无法刷朋友圈问题:使用安卓远程投屏方案
    在工作期间想要浏览微信和朋友圈却不太方便?虽然微信电脑版目前不支持直接刷朋友圈,但通过远程投屏技术,可以轻松实现在电脑上操作安卓设备的功能。 ... [详细]
  • 解决JAX-WS动态客户端工厂弃用问题并迁移到XFire
    在处理Java项目中的JAR包冲突时,我们遇到了JaxWsDynamicClientFactory被弃用的问题,并成功将其迁移到org.codehaus.xfire.client。本文详细介绍了这一过程及解决方案。 ... [详细]
  • 深入解析 Apache Shiro 安全框架架构
    本文详细介绍了 Apache Shiro,一个强大且灵活的开源安全框架。Shiro 专注于简化身份验证、授权、会话管理和加密等复杂的安全操作,使开发者能够更轻松地保护应用程序。其核心目标是提供易于使用和理解的API,同时确保高度的安全性和灵活性。 ... [详细]
  • 创建项目:Visual Studio Online 入门指南
    本文介绍如何使用微软的 Visual Studio Online(VSO)创建和管理开发项目。作为一款基于云计算的开发平台,VSO 提供了丰富的工具和服务,简化了项目的配置和部署流程。 ... [详细]
author-avatar
手机用户敬怡
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有