参考: 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),是一种可以使计算机和设备通信的特殊程序。相当于硬件的接口,操作系统只有通过这个接口,才能控制硬件设备的工作;
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
-
2.2.Aidl的通信流程
a.服务端
1.创建一个service用来监听客户端的连接请求
- 2.创建Aidl文件,将暴露给客户端的接口在这个Aidl文件中声明
-
b.客户端
1.绑定服务端的service
- 2.将服务端返回的binder对象转成Aidl接口所属的类型
-
c.Aidl接口的创建
todo 示例
- todo AIDL默认支持一些数据类型,在使用这些数据类型的时候是不需要导包的
- AIDL文件中支持的数据类型
- 基本数据类型
- String和CharSequence
- List
- Map
- Parcelable
- AIDL
- AIDL文件中支持的数据类型
- Aidl的创建
- Aidl接口只支持方法,不支持静态常量
d.远程服务service的实现
三.从Aidl的角度去了解binder工作机制
/*
* This file is auto-generated. DO NOT MODIFY.
*/
package com.jack.aidlservicedemo;
//todo 1.所有可以在Binder中进行传输的接口都需要继承IInterface接口
//todo 2.通过分析该类来了解binder的工作机制
public interface IBookManager extends android.os.IInterface
{
/** Default implementation for IBookManager. */
public static class Default implements com.jack.aidlservicedemo.IBookManager
{
//所有的返回值前都不需要加任何东西,不管是什么数据类型
//todo 3.1.声明方法-getBooks
@Override public java.util.List<com.jack.aidlservicedemo.Book> getBooks() throws android.os.RemoteException
{
return null;
}
//传参时除了Java基本类型以及String,CharSequence之外的类型
//都需要在前面加上定向tag,具体加什么量需而定
//todo 3.2.声明方法-addBook
@Override public void addBook(com.jack.aidlservicedemo.Book book) throws android.os.RemoteException
{
}
@Override
public android.os.IBinder asBinder() {
return null;
}
}
/** Local-side IPC implementation stub class. */
//todo 3.5.标明内部类 - Stub(这个类就是一个Binder类)
/**
* todo 当客户端和服务端都位于一个进程时,方法调用不会走跨进程的 transact 过程
* 当两者位于不同进程时,方法调用需要走transact过程,这个过程由stub内部代理类
*/
public static abstract class Stub extends android.os.Binder implements com.jack.aidlservicedemo.IBookManager
{
// 3.6.binder的唯一标识,一般用binder的类名表示
private static final java.lang.String DESCRIPTOR = "com.jack.aidlservicedemo.IBookManager";
/** Construct the stub at attach it to the interface. */
public Stub()
{
this.attachInterface(this, DESCRIPTOR);
}
/**
* Cast an IBinder object into an com.jack.aidlservicedemo.IBookManager interface,
* generating a proxy if needed.
*/
// 3.7.asInterface : 用于将服务端的对象转换成客户端所需的AIDL接口类型的对象
//这种转换是区分进程的
public static com.jack.aidlservicedemo.IBookManager asInterface(android.os.IBinder obj)
{
if ((obj==null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin!=null)&&(iin instanceof com.jack.aidlservicedemo.IBookManager))) {
//3.7.1.如果客户端和服务端位于同一进程,那么此方法返回的就是Stub对象本身
return ((com.jack.aidlservicedemo.IBookManager)iin);
}
//3.7.2.否则返回的是系统封装后的Stub.Proxy对象
return new com.jack.aidlservicedemo.IBookManager.Stub.Proxy(obj);
}
//3.8.asBinder:用于返回当前binder对象
@Override public android.os.IBinder asBinder()
{
return this;
}
//3.9.4.onTransact:运行在服务端的Binder线程池
//当客户端发起跨进程请求,远程请求会通过系统底层封装后交由此方法来处理
//服务端可以通过code来确定客户端所请求的方法是什么,接着从data中取出目标方法所需要的参数(如果有参数)
//然后执行目标方法
//当目标方法执行完,就向reply中写入返回值
//需要注意的是:如果onTransact返回false,那么客户端的请求就会失败。利用该特定可以做权限验证。
@Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException
{
java.lang.String descriptor = DESCRIPTOR;
switch (code)
{
case INTERFACE_TRANSACTION:
{
reply.writeString(descriptor);
return true;
}
case TRANSACTION_getBooks:
{
data.enforceInterface(descriptor);
java.util.List<com.jack.aidlservicedemo.Book> _result = this.getBooks();
reply.writeNoException();
reply.writeTypedList(_result);
return true;
}
case TRANSACTION_addBook:
{
data.enforceInterface(descriptor);
com.jack.aidlservicedemo.Book _arg0;
if ((0!=data.readInt())) {
_arg0 = com.jack.aidlservicedemo.Book.CREATOR.createFromParcel(data);
}
else {
_arg0 = null;
}
this.addBook(_arg0);
reply.writeNoException();
return true;
}
default:
{
return super.onTransact(code, data, reply, flags);
}
}
}
private static class Proxy implements com.jack.aidlservicedemo.IBookManager
{
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;
}
//所有的返回值前都不需要加任何东西,不管是什么数据类型
//3.10.此方法运行在客户端,当客户端远程调用此方法,其内部实现如下:
@Override public java.util.List<com.jack.aidlservicedemo.Book> getBooks() throws android.os.RemoteException
{
//3.10.1首先,创建该方法所需要的输入型Parcel对象_data,输出型Parcel对象_reply和返回值List对象_result;
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
java.util.List<com.jack.aidlservicedemo.Book> _result;
try {
//3.10.2.然后,把方法的参数写入到_data中(如果有参数的话),
_data.writeInterfaceToken(DESCRIPTOR);
//3.10.3.接着调用transact方法发起RPC(远程过程调用)请求,同时,当前线程挂起
//3.10.4.然后服务端的onTransact方法会被调用,直到RPC过程返回后,当前线程继续执行
boolean _status = mRemote.transact(Stub.TRANSACTION_getBooks, _data, _reply, 0);
if (!_status && getDefaultImpl() != null) {
return getDefaultImpl().getBooks();
}
_reply.readException();
//3.10.4_.并从_reply中取出RPC过程的返回函数
_result = _reply.createTypedArrayList(com.jack.aidlservicedemo.Book.CREATOR);
}
finally {
_reply.recycle();
_data.recycle();
}
//3.10.5.最后返回_reply中的数据
return _result;
}
//传参时除了Java基本类型以及String,CharSequence之外的类型
//都需要在前面加上定向tag,具体加什么量需而定
//4.这个方法运行在客户端中,执行过程和getBooks是一样的,addBook方法没有返回值,所以不需要从_reply中取出返回值
@Override public void addBook(com.jack.aidlservicedemo.Book book) throws android.os.RemoteException
{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
if ((book!=null)) {
_data.writeInt(1);
book.writeToParcel(_data, 0);
}
else {
_data.writeInt(0);
}
boolean _status = mRemote.transact(Stub.TRANSACTION_addBook, _data, _reply, 0);
if (!_status && getDefaultImpl() != null) {
getDefaultImpl().addBook(book);
return;
}
_reply.readException();
}
finally {
_reply.recycle();
_data.recycle();
}
}
public static com.jack.aidlservicedemo.IBookManager sDefaultImpl;
}
//todo 3.3.声明整型id 用于标识在 transact 过程中,客户端所请求的方法到底是哪一个
static final int TRANSACTION_getBooks = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
//todo 3.4.声明整型id
static final int TRANSACTION_addBook = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
public static boolean setDefaultImpl(com.jack.aidlservicedemo.IBookManager impl) {
if (Stub.Proxy.sDefaultImpl == null && impl != null) {
Stub.Proxy.sDefaultImpl = impl;
return true;
}
return false;
}
public static com.jack.aidlservicedemo.IBookManager getDefaultImpl() {
return Stub.Proxy.sDefaultImpl;
}
}
//所有的返回值前都不需要加任何东西,不管是什么数据类型
public java.util.List<com.jack.aidlservicedemo.Book> getBooks() throws android.os.RemoteException;
//传参时除了Java基本类型以及String,CharSequence之外的类型
//都需要在前面加上定向tag,具体加什么量需而定
public void addBook(com.jack.aidlservicedemo.Book book) throws android.os.RemoteException;
}
//注意
//当客户端发起请求时,由于当前线程会被挂起直至服务端进程返回数据,所以,如果一个远程方法是很耗时的,那么不能在UI线程中发起此远程请求;
//其次:由于binder方法运行在binder线程池中,所以,不管binder方法是否耗时,都应采用同步的方式去实现,因为它已经运行在一个线程中了。 这一点,未明白。。。
四.问题收集
1.Android中进程和线程的关系?
- 线程
- 是一种有限的系统资源,CPU调用的最小单元;
- 线程的创建和销毁都有一定的开销;
进程
技术点:多进程通信
- 场景:在多进程的情况下需要使用,比比如,使用混合开发会将WebView单独开启一个进程(好处多多,单独总结一篇混合开发的文章),这个时候就需要用到进程间通信;
- 一般来说,使用多进程会造成如下几方面的问题:
- (1)静态成员和单例模式完全失效。
- (2)线程同步机制完全失效。
- (3)SharedPreferences的可靠性下降。
- (4)Application会多次创建。
原因分析
- 问题1:Android为每一个应用,或者说为每一个进程分配了一个独立的虚拟机,不同的虚拟机内存分配上有不同的地址空间,这就导致在不同的虚拟机中访问同一个类的对象会产生多个副本。所有运行在不同进程的四大组件,只要它们之间需要通过内存来共享数据,都会共享失败;
- 问题2:本质通问题1类似,既然不在同一块内存了,那么不管是锁对象还是锁全局类都无法保证线程同步,因为不同进程的锁不是同一个对象;
- 问题3:SharedPreferences不支持两个线程同时去执行写操作,否则会导致一定几率的数据丢失,这是因为SharedPreferences底层是通过读写XML文件来实现的,并发写显然是可能出问题的;
- 问题4:系统在创建新的进程同时分配独立的虚拟机,运行在不同进程中的组件是属于两个不同的虚拟机和Application的;
3.什么是序列化?Serializable接口和Parcelable接口的区别?为何推荐使用后者?
技术点:序列化
- 序列化表示将一个对象转换成可存储或可传输的状态。序列化后的对象可以在网络上进行传输,也可以存储到本地。
- 应用场景:需要通过Intent和Binder等传输类对象就必须完成对象的序列化过程。
两种方式:实现Serializable/Parcelable接口。不同点如图:
4.Android中为何新增Binder来作为主要的IPC方式?
技术点:Binder机制
- Binder机制__优点
- 传输效率高、可操作性强:传输效率主要影响因素是内存拷贝的次数,拷贝次数越少,传输速率越高。从Android进程架构角度分析:对于消息队列、Socket和管道来说,数据先从发送方的缓存区拷贝到内核开辟的缓存区中,再从内核缓存区拷贝到接收方的缓存区,一共两次拷贝,如图:
而对于Binder来说,数据从发送方的缓存区拷贝到内核的缓存区,而接收方的缓存区与内核的缓存区是映射到同一块物理地址的,节省了一次数据拷贝的过程,如图:
由于共享内存操作复杂,综合来看,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,最后返回调用结果并唤醒客户端线程(感觉这个答案不是最佳的)。
6.Binder框架中ServiceManager的作用?
- 技术点:Binder机制(先记住标记为红色的部分,往后再补)
- 在Binder框架定义了四个角色:Server,Client,ServiceManager和Binder驱动。其中Server、Client、ServiceManager运行于用户空间,Binder驱动运行于内核空间。关系如图:
- Server&Client:服务器&客户端。在Binder驱动和Service Manager提供的基础设施上,进行Client-Server之间的通信。
ServiceManager服务的管理者,将Binder名字转换为Client中对该Binder的引用,使得Client可以通过Binder名字获得Server中Binder实体的引用。流程如图:

Binder驱动:
- 与硬件设备没有关系,其工作方式与设备驱动程序是一样的,工作于内核态。
- 提供open()、mmap()、poll()、ioctl() 等标准文件操作。
- 以字符驱动设备中的misc设备注册在设备目录/dev下,用户通过/dev/binder访问该它。
- 负责进程之间binder通信的建立,传递,计数管理以及数据的传递交互等底层支持。
- 驱动和应用程序之间定义了一套接口协议,主要功能由ioctl() 接口实现,由于ioctl()灵活、方便且能够一次调用实现先写后读以满足同步交互,因此不必分别调用write()和read()接口。
- 其代码位于linux目录的drivers/misc/binder.c中。
7.IPC方式对比(重新总结一下,参考艺术探索上的内容)
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对象后就可进行远程方法的调用了。(我需要一个实实实在在的例子哟)流程如图: