参考: 2019校招Android面试题解1.0(上篇) 《Android开发艺术探索》

一.Linux相关知识

1.进程隔离

  • 为了保护操作系统中某些进程互不干扰,就设计了一个进程隔离的技术,避免进程A可以操作进程B的情况下所实现的;

    2.虚拟地址空间

  • 进程隔离技术使用到了虚拟地址空间,进程A的虚拟地址空间和进程B虚拟地址空间是不同的,这样就可以防止了进程A的数据写到进程B里面;

  • 操作系统中不同的虚拟地址空间数据是不共享的,每一个进程独享的空间实际上是虚拟的空间;

    3.系统调用

  • 在Linux内核当中,有一个非常重要的概念就做系统调用,因为内核会有某些保护机制,告诉应用程序只可访问某些许可的资源,不许可的资源是不可以被访问的,这也是把Linux内核层和应用层抽象分离开。也就是内核层和用户空间,用户可以通过系统调用在用户空间访问内核的某些程序;

    4.内核模块/驱动

  • 通过系统调用,用户空间可以访问内核空间,那么如果一个用户空间想与另外一个用户空间进行通信怎么办呢?很自然想到的是让操作系统内核添加支持;

  • 传统的Linux通信机制,比如Socket,管道等都是内核支持的;但是Binder并不是Linux内核的一部分,它是怎么做到访问内核空间的呢?

    Linux的动态可加载内核模块(Loadable Kernel Module,LKM)机制解决了这个问题; 模块是具有独立功能的程序,它可以被单独编译,但不能独立运行。它在运行时被链接到内核作为内核的一部分在内核空间运行; 这样,Android系统可以通过添加一个内核模块运行在内核空间,用户进程之间的通过这个模块作为桥梁,就可以完成通信了; 在Android系统中,这个运行在内核空间的,负责各个用户进程通过Binder通信的内核模块叫做Binder驱动;

  • 驱动程序一般指的是设备驱动程序(Device Driver),是一种可以使计算机和设备通信的特殊程序。相当于硬件的接口,操作系统只有通过这个接口,才能控制硬件设备的工作;

    • 驱动就是操作硬件的接口,为了支持Binder通信过程,Binder使用了一种“硬件”,因此这个模块被称之为驱动。

      二.Serializable和Parcelable

  • Serializable接口

  • Parcelable接口
    • Parcel:包装了可序列化的数据
    • 序列化功能由writeToParcel完成,最终是通过Parcel的一系列write方法完成;
    • 反序列化由Creator来完成,其内部标明了如何创建序列化对象和数组,并通过Parcel的一系列read方法来完成;
    • 内容描述功能由describeContents来完成,几乎所有情况下在,这个方法都应该返回0,仅当当前对象中存在文件描述符时,此方法返回1; 后半句未理解,暂时记下;
    • Intent,Bundle,Bitmap,List和Map也可以序列化,前提是它们中的每一个元素都是可序列化的;
  • Serializable与Parcelable对比
    • Serializable:Java平台提供,使用简单,但开销大,序列化和反序列化都需要大量的IO操作。
    • Parcelable:Android平台提供,使用起来较麻烦些,效率高,通常该方式作为首选,主要用于在内存序列化上;
    • 通过Parcelable将对象序列化或反序列化后存储到设备中和通过网络传输也是可以,但过程会稍显复杂,建议这两种情况下使用Serializable实现;
  • Binder

    • Binder的上层原理
    • IPC角度:跨进程通信的方式,可以把Binder理解为一种虚拟设备,它的设备驱动是/dev/binder;
    • 直观角度:是个实现了IBinder接口的类;
    • FrameWork层角度:是ServiceManager连接各种Manager(ActivityManager、WindowManager等)和相应ManagerService的桥梁;
    • 应用层角度:是客户端和服务端进行通信的媒介;
    • Android开发中,binder主要用于Service中,包括AIDL和Messenger
    • Binder的设计采用了面向对象的思想

      三.Aidl

      2.1.使用aidl原因

  • 进程间的通信

    2.2.Aidl的通信流程

    a.服务端

  • 1.创建一个service用来监听客户端的连接请求

  • 2.创建Aidl文件,将暴露给客户端的接口在这个Aidl文件中声明
  • 3.在服务中实现这个Aidl

    b.客户端

  • 1.绑定服务端的service

  • 2.将服务端返回的binder对象转成Aidl接口所属的类型
  • 3.调用Aidl的方法

    c.Aidl接口的创建

  • todo 示例

  • todo AIDL默认支持一些数据类型,在使用这些数据类型的时候是不需要导包的
    • AIDL文件中支持的数据类型
      • 基本数据类型
      • String和CharSequence
      • List
      • Map
      • Parcelable
      • AIDL
  • Aidl的创建
  • Aidl接口只支持方法,不支持静态常量

    d.远程服务service的实现

    三.从Aidl的角度去了解binder工作机制

  1. /*
  2. * This file is auto-generated. DO NOT MODIFY.
  3. */
  4. package com.jack.aidlservicedemo;
  5. //todo 1.所有可以在Binder中进行传输的接口都需要继承IInterface接口
  6. //todo 2.通过分析该类来了解binder的工作机制
  7. public interface IBookManager extends android.os.IInterface
  8. {
  9. /** Default implementation for IBookManager. */
  10. public static class Default implements com.jack.aidlservicedemo.IBookManager
  11. {
  12. //所有的返回值前都不需要加任何东西,不管是什么数据类型
  13. //todo 3.1.声明方法-getBooks
  14. @Override public java.util.List<com.jack.aidlservicedemo.Book> getBooks() throws android.os.RemoteException
  15. {
  16. return null;
  17. }
  18. //传参时除了Java基本类型以及String,CharSequence之外的类型
  19. //都需要在前面加上定向tag,具体加什么量需而定
  20. //todo 3.2.声明方法-addBook
  21. @Override public void addBook(com.jack.aidlservicedemo.Book book) throws android.os.RemoteException
  22. {
  23. }
  24. @Override
  25. public android.os.IBinder asBinder() {
  26. return null;
  27. }
  28. }
  29. /** Local-side IPC implementation stub class. */
  30. //todo 3.5.标明内部类 - Stub(这个类就是一个Binder类)
  31. /**
  32. * todo 当客户端和服务端都位于一个进程时,方法调用不会走跨进程的 transact 过程
  33. * 当两者位于不同进程时,方法调用需要走transact过程,这个过程由stub内部代理类
  34. */
  35. public static abstract class Stub extends android.os.Binder implements com.jack.aidlservicedemo.IBookManager
  36. {
  37. // 3.6.binder的唯一标识,一般用binder的类名表示
  38. private static final java.lang.String DESCRIPTOR = "com.jack.aidlservicedemo.IBookManager";
  39. /** Construct the stub at attach it to the interface. */
  40. public Stub()
  41. {
  42. this.attachInterface(this, DESCRIPTOR);
  43. }
  44. /**
  45. * Cast an IBinder object into an com.jack.aidlservicedemo.IBookManager interface,
  46. * generating a proxy if needed.
  47. */
  48. // 3.7.asInterface : 用于将服务端的对象转换成客户端所需的AIDL接口类型的对象
  49. //这种转换是区分进程的
  50. public static com.jack.aidlservicedemo.IBookManager asInterface(android.os.IBinder obj)
  51. {
  52. if ((obj==null)) {
  53. return null;
  54. }
  55. android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
  56. if (((iin!=null)&&(iin instanceof com.jack.aidlservicedemo.IBookManager))) {
  57. //3.7.1.如果客户端和服务端位于同一进程,那么此方法返回的就是Stub对象本身
  58. return ((com.jack.aidlservicedemo.IBookManager)iin);
  59. }
  60. //3.7.2.否则返回的是系统封装后的Stub.Proxy对象
  61. return new com.jack.aidlservicedemo.IBookManager.Stub.Proxy(obj);
  62. }
  63. //3.8.asBinder:用于返回当前binder对象
  64. @Override public android.os.IBinder asBinder()
  65. {
  66. return this;
  67. }
  68. //3.9.4.onTransact:运行在服务端的Binder线程池
  69. //当客户端发起跨进程请求,远程请求会通过系统底层封装后交由此方法来处理
  70. //服务端可以通过code来确定客户端所请求的方法是什么,接着从data中取出目标方法所需要的参数(如果有参数)
  71. //然后执行目标方法
  72. //当目标方法执行完,就向reply中写入返回值
  73. //需要注意的是:如果onTransact返回false,那么客户端的请求就会失败。利用该特定可以做权限验证。
  74. @Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException
  75. {
  76. java.lang.String descriptor = DESCRIPTOR;
  77. switch (code)
  78. {
  79. case INTERFACE_TRANSACTION:
  80. {
  81. reply.writeString(descriptor);
  82. return true;
  83. }
  84. case TRANSACTION_getBooks:
  85. {
  86. data.enforceInterface(descriptor);
  87. java.util.List<com.jack.aidlservicedemo.Book> _result = this.getBooks();
  88. reply.writeNoException();
  89. reply.writeTypedList(_result);
  90. return true;
  91. }
  92. case TRANSACTION_addBook:
  93. {
  94. data.enforceInterface(descriptor);
  95. com.jack.aidlservicedemo.Book _arg0;
  96. if ((0!=data.readInt())) {
  97. _arg0 = com.jack.aidlservicedemo.Book.CREATOR.createFromParcel(data);
  98. }
  99. else {
  100. _arg0 = null;
  101. }
  102. this.addBook(_arg0);
  103. reply.writeNoException();
  104. return true;
  105. }
  106. default:
  107. {
  108. return super.onTransact(code, data, reply, flags);
  109. }
  110. }
  111. }
  112. private static class Proxy implements com.jack.aidlservicedemo.IBookManager
  113. {
  114. private android.os.IBinder mRemote;
  115. Proxy(android.os.IBinder remote)
  116. {
  117. mRemote = remote;
  118. }
  119. @Override public android.os.IBinder asBinder()
  120. {
  121. return mRemote;
  122. }
  123. public java.lang.String getInterfaceDescriptor()
  124. {
  125. return DESCRIPTOR;
  126. }
  127. //所有的返回值前都不需要加任何东西,不管是什么数据类型
  128. //3.10.此方法运行在客户端,当客户端远程调用此方法,其内部实现如下:
  129. @Override public java.util.List<com.jack.aidlservicedemo.Book> getBooks() throws android.os.RemoteException
  130. {
  131. //3.10.1首先,创建该方法所需要的输入型Parcel对象_data,输出型Parcel对象_reply和返回值List对象_result;
  132. android.os.Parcel _data = android.os.Parcel.obtain();
  133. android.os.Parcel _reply = android.os.Parcel.obtain();
  134. java.util.List<com.jack.aidlservicedemo.Book> _result;
  135. try {
  136. //3.10.2.然后,把方法的参数写入到_data中(如果有参数的话),
  137. _data.writeInterfaceToken(DESCRIPTOR);
  138. //3.10.3.接着调用transact方法发起RPC(远程过程调用)请求,同时,当前线程挂起
  139. //3.10.4.然后服务端的onTransact方法会被调用,直到RPC过程返回后,当前线程继续执行
  140. boolean _status = mRemote.transact(Stub.TRANSACTION_getBooks, _data, _reply, 0);
  141. if (!_status && getDefaultImpl() != null) {
  142. return getDefaultImpl().getBooks();
  143. }
  144. _reply.readException();
  145. //3.10.4_.并从_reply中取出RPC过程的返回函数
  146. _result = _reply.createTypedArrayList(com.jack.aidlservicedemo.Book.CREATOR);
  147. }
  148. finally {
  149. _reply.recycle();
  150. _data.recycle();
  151. }
  152. //3.10.5.最后返回_reply中的数据
  153. return _result;
  154. }
  155. //传参时除了Java基本类型以及String,CharSequence之外的类型
  156. //都需要在前面加上定向tag,具体加什么量需而定
  157. //4.这个方法运行在客户端中,执行过程和getBooks是一样的,addBook方法没有返回值,所以不需要从_reply中取出返回值
  158. @Override public void addBook(com.jack.aidlservicedemo.Book book) throws android.os.RemoteException
  159. {
  160. android.os.Parcel _data = android.os.Parcel.obtain();
  161. android.os.Parcel _reply = android.os.Parcel.obtain();
  162. try {
  163. _data.writeInterfaceToken(DESCRIPTOR);
  164. if ((book!=null)) {
  165. _data.writeInt(1);
  166. book.writeToParcel(_data, 0);
  167. }
  168. else {
  169. _data.writeInt(0);
  170. }
  171. boolean _status = mRemote.transact(Stub.TRANSACTION_addBook, _data, _reply, 0);
  172. if (!_status && getDefaultImpl() != null) {
  173. getDefaultImpl().addBook(book);
  174. return;
  175. }
  176. _reply.readException();
  177. }
  178. finally {
  179. _reply.recycle();
  180. _data.recycle();
  181. }
  182. }
  183. public static com.jack.aidlservicedemo.IBookManager sDefaultImpl;
  184. }
  185. //todo 3.3.声明整型id 用于标识在 transact 过程中,客户端所请求的方法到底是哪一个
  186. static final int TRANSACTION_getBooks = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
  187. //todo 3.4.声明整型id
  188. static final int TRANSACTION_addBook = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
  189. public static boolean setDefaultImpl(com.jack.aidlservicedemo.IBookManager impl) {
  190. if (Stub.Proxy.sDefaultImpl == null && impl != null) {
  191. Stub.Proxy.sDefaultImpl = impl;
  192. return true;
  193. }
  194. return false;
  195. }
  196. public static com.jack.aidlservicedemo.IBookManager getDefaultImpl() {
  197. return Stub.Proxy.sDefaultImpl;
  198. }
  199. }
  200. //所有的返回值前都不需要加任何东西,不管是什么数据类型
  201. public java.util.List<com.jack.aidlservicedemo.Book> getBooks() throws android.os.RemoteException;
  202. //传参时除了Java基本类型以及String,CharSequence之外的类型
  203. //都需要在前面加上定向tag,具体加什么量需而定
  204. public void addBook(com.jack.aidlservicedemo.Book book) throws android.os.RemoteException;
  205. }
  206. //注意
  207. //当客户端发起请求时,由于当前线程会被挂起直至服务端进程返回数据,所以,如果一个远程方法是很耗时的,那么不能在UI线程中发起此远程请求;
  208. //其次:由于binder方法运行在binder线程池中,所以,不管binder方法是否耗时,都应采用同步的方式去实现,因为它已经运行在一个线程中了。 这一点,未明白。。。

四.问题收集

1.Android中进程和线程的关系?

  • 线程
    • 是一种有限的系统资源,CPU调用的最小单元;
    • 线程的创建和销毁都有一定的开销;
  • 进程

    • 指一个执行单元,在PC和移动设备上指一个程序或者一个应用;
    • 一个APP一般对应一个进程,当然,可以在AndroidMenifest中给四大组件指定属性android:process开启多进程模式;
    • 一个进程可以包含多个线程,因此,进程和线程是包含和被包含关系;

      2.为何需要进行IPC?多进程通信可能会出现什么问题?

  • 技术点:多进程通信

  • 场景:在多进程的情况下需要使用,比比如,使用混合开发会将WebView单独开启一个进程(好处多多,单独总结一篇混合开发的文章),这个时候就需要用到进程间通信;
  • 一般来说,使用多进程会造成如下几方面的问题:
    • (1)静态成员和单例模式完全失效。
    • (2)线程同步机制完全失效。
    • (3)SharedPreferences的可靠性下降。
    • (4)Application会多次创建。
  • 原因分析

    • 问题1:Android为每一个应用,或者说为每一个进程分配了一个独立的虚拟机,不同的虚拟机内存分配上有不同的地址空间,这就导致在不同的虚拟机中访问同一个类的对象会产生多个副本。所有运行在不同进程的四大组件,只要它们之间需要通过内存来共享数据,都会共享失败;
    • 问题2:本质通问题1类似,既然不在同一块内存了,那么不管是锁对象还是锁全局类都无法保证线程同步,因为不同进程的锁不是同一个对象;
    • 问题3:SharedPreferences不支持两个线程同时去执行写操作,否则会导致一定几率的数据丢失,这是因为SharedPreferences底层是通过读写XML文件来实现的,并发写显然是可能出问题的;
    • 问题4:系统在创建新的进程同时分配独立的虚拟机,运行在不同进程中的组件是属于两个不同的虚拟机和Application的;

      3.什么是序列化?Serializable接口和Parcelable接口的区别?为何推荐使用后者?

  • 技术点:序列化

  • 序列化表示将一个对象转换成可存储或可传输的状态。序列化后的对象可以在网络上进行传输,也可以存储到本地。
  • 应用场景:需要通过Intent和Binder等传输类对象就必须完成对象的序列化过程。
  • 两种方式:实现Serializable/Parcelable接口。不同点如图:

    111.webp

    4.Android中为何新增Binder来作为主要的IPC方式?

  • 技术点:Binder机制

  • Binder机制__优点
    • 传输效率高、可操作性强:传输效率主要影响因素是内存拷贝的次数,拷贝次数越少,传输速率越高。从Android进程架构角度分析:对于消息队列、Socket和管道来说,数据先从发送方的缓存区拷贝到内核开辟的缓存区中,再从内核缓存区拷贝到接收方的缓存区,一共两次拷贝,如图:

111.webp
而对于Binder来说,数据从发送方的缓存区拷贝到内核的缓存区,而接收方的缓存区与内核的缓存区是映射到同一块物理地址的,节省了一次数据拷贝的过程,如图:
111.webp
由于共享内存操作复杂,综合来看,Binder的传输效率是最好的。

  • 实现C/S架构方便:Linux的众IPC方式除了Socket以外都不是基于C/S架构,而Socket主要用于网络间的通信且传输效率较低。Binder基于C/S架构 ,Server端与Client端相对独立,稳定性较好。
  • 安全性高:传统Linux IPC的接收方无法获得对方进程可靠的UID/PID,从而无法鉴别对方身份;而Binder机制为每个进程分配了UID/PID且在Binder通信时会根据UID/PID进行有效性检测。

    5.使用Binder进行数据传输的具体过程?

  • 思路:通过AIDL实现方式解释Binder数据传输的具体过程
  • 服务端中的Service给与其绑定的客户端提供Binder对象,客户端通过AIDL接口中的asInterface()将这个Binder对象转换为代理Proxy,并通过它发起RPC请求。客户端发起请求时会挂起当前线程,并将参数写入data然后调用transact(),RPC请求会通过系统底层封装后由服务端的onTransact()处理,并将结果写入reply,最后返回调用结果并唤醒客户端线程(感觉这个答案不是最佳的)。

    111.webp

6.Binder框架中ServiceManager的作用?

  • 技术点:Binder机制(先记住标记为红色的部分,往后再补)
  • Binder框架定义了四个角色:Server,Client,ServiceManager和Binder驱动。其中Server、Client、ServiceManager运行于用户空间,Binder驱动运行于内核空间。关系如图:

111.webp111.webp
3.webp

  • Server&Client:服务器&客户端。在Binder驱动和Service Manager提供的基础设施上,进行Client-Server之间的通信。
  • ServiceManager服务的管理者,将Binder名字转换为Client中对该Binder的引用,使得Client可以通过Binder名字获得Server中Binder实体的引用。流程如图:

    1. ![4.webp](https://cdn.nlark.com/yuque/0/2020/webp/249982/1583998547752-9a34d0c6-121d-4b0f-a95b-5f3703e22fca.webp#align=left&display=inline&height=258&name=4.webp&originHeight=258&originWidth=556&size=7528&status=done&style=none&width=556)
  • Binder驱动

    • 与硬件设备没有关系,其工作方式与设备驱动程序是一样的,工作于内核态。
    • 提供open()mmap()poll()ioctl() 等标准文件操作。
    • 以字符驱动设备中的misc设备注册在设备目录/dev下,用户通过/dev/binder访问该它。
    • 负责进程之间binder通信的建立,传递,计数管理以及数据的传递交互等底层支持。
    • 驱动和应用程序之间定义了一套接口协议,主要功能由ioctl() 接口实现,由于ioctl()灵活、方便且能够一次调用实现先写后读以满足同步交互,因此不必分别调用write()和read()接口。
    • 其代码位于linux目录的drivers/misc/binder.c中。

      7.IPC方式对比(重新总结一下,参考艺术探索上的内容)

      5.webp

      8.是否了解AIDL?原理是什么?如何优化多模块都使用AIDL的情况?

  • AIDL(Android Interface Definition Language,Android接口定义语言):如果在一个进程中要调用另一个进程中对象的方法,可使用AIDL生成可序列化的参数,AIDL会生成一个服务端对象的代理类,通过它客户端实现间接调用服务端对象的方法。

  • AIDL的本质是系统提供了一套可快速实现Binder的工具。关键类和方法:
    • AIDL接口:继承IInterface
    • Stub类:Binder的实现类,服务端通过这个类来提供服务。
    • Proxy类:服务器的本地代理,客户端通过这个类调用服务器的方法。
    • asInterface():客户端调用,将服务端的返回的Binder对象,转换成客户端所需要的AIDL接口类型对象。返回对象:
      • 若客户端和服务端位于同一进程,则直接返回Stub对象本身;
      • 否则,返回的是系统封装后的Stub.proxy对象。
    • asBinder():根据当前调用情况返回代理Proxy的Binder对象。
    • onTransact():运行服务端的Binder线程池中,当客户端发起跨进程请求时,远程请求会通过系统底层封装后交由此方法来处理。
    • transact():运行在客户端,当客户端发起远程请求的同时将当前线程挂起。之后调用服务端的onTransact()直到远程请求返回,当前线程才继续执行。
  • 当有多个业务模块都需要AIDL来进行IPC,此时需要为每个模块创建特定的aidl文件,那么相应的Service就会很多。必然会出现系统资源耗费严重、应用过度重量级的问题。解决办法是建立Binder连接池,即将每个业务模块的Binder请求统一转发到一个远程Service中去执行,从而避免重复创建Service。
  • 工作原理:每个业务模块创建自己的AIDL接口并实现此接口,然后向服务端提供自己的唯一标识和其对应的Binder对象。服务端只需要一个Service,服务器提供一个queryBinder接口,它会根据业务模块的特征来返回相应的Binder对像,不同的业务模块拿到所需的Binder对象后就可进行远程方法的调用了。(我需要一个实实实在在的例子哟)流程如图:

7.webp