framework该这么学,原来系统服务这样设计的?

750次阅读  |  发布于1年以前

Client/Server架构,也就是大家最常听到的C/S架构在后端开发中可以说是祖师爷级别的概念了。其实安卓的系统服务的设计也是遵循C/S架构的,甚至和最近很流行的微服务micro service架构也有关联。

今天我想用通俗一点的语言稍微讲解一下安卓端的CS架构,每个组件设计的理念,让大家更清晰的理解一下安卓的客户端和服务器到底是个什么东西。

1Android的C/S 的客户端

简单的说,安卓的APP应用层可以当做是安卓C/S架构中的客户端。而安卓系统中,运行在系统进程system process的系统服务system service,被认为是服务器端。

2Manager API

客户端除了app本身之外,还包括安卓SDK的客户端公有API (public API)。基本上sdk里面带Manager字样的类,与之相对应的都会有一个安卓服务器端的实现。我们先抛开实现细节,教教大家怎么寻找客户端对应的服务器实现。

比如可以让app在运行时获取用户手机SIM卡等相关信息的TelephonyManager,大家可以看到它的其中和一个方法,用来获取手机当前的位置的方法。

public CellLocation getCellLocation() {
     try { 
         Bundle bundle = getITelephony().getCellLocation(); 
         return CellLocation.newFromBundle(bundle); 
         } 
     catch (RemoteException ex) { 
         return null; 
     }

它的调用了getITelehony()来获取一个ITelephony对象,大家一看这种带I开头的类就知道肯定要涉及IPC,跨进程调用了,它的服务器实现必然会继承ITelephony类的Stub。

所以在搜索该Manager的服务器端的实现的时候,直接搜索extends ITelephony的类就好。

public class PhoneInterfaceManager extends ITelephony.Stub {

@Override public CellIdentity getCellLocation(String callingPackage, String callingFeatureId) {
 .....
}

}

果不其然,整个framework就只有PhoneInterfaceManager 继承了该ITelephony类的Stub,并且也可以找打之前getCellLocation 的具体实现。

当然其他的各种manager也是照着这样的方式一一实现的。整个安卓的客户端public API都有与之对应的实现,都是实现以I开头的Stub类。

这个小节我先简单的教大家怎么找到manager类的对应服务器端的实现,至于安卓是具体怎么实现这样的一对一映射我们接下来的小节再详细说明。

3Android SDK

大家都知道编译一个安卓app需要android sdk,但是不少人没搞明白这个SDK到底是编译时用的还是运行时用的。

解答这个问题其实很简单,大家打开你的android studio,随便点击一个manager类的源码,就可以看出来。

很明显,这些SDK里面的类都不会最终打包进app的APK文件里面,如果真的用了这些类当做实现,运行时肯定都会crash(毕竟这里面全都是抛RuntimeException)。

所以SDK的类,都是没有具体实现的,我们把它们叫Stub类。他们只是用来帮助我们进行编译而已,目的是让app开发者能成功打包编译出APK。这个和gradle里面的compileOnly 关键字很像。而且这样设计也很有道理,毕竟如果每个app都需要把android framework的公有api类都打包进APK,那每个app安装文件都将变得巨大无比。

dependencies {
    compileOnly 'javax.servlet:servlet-api:2.5'
}

但是在之前的图里,我明明还是把Manager类画在了APP的进程里面,也就是说运行时我们还是需要Manager类的。那这些类到底从哪来的呢?

4zygote android

关于Zygote的细节我们就不聊那么多,网上资源挺多的。但是我们需要知道的重点就是,每一个APP都是Zygote复制出来的一个进程(具体的实现应该是linux里面的fork),这个进程Dalvik(或者是ART)都是一个独立的虚拟机,virtual machine。在最原始的进程里面,所有的public API, 也就是那些客户端的Manager类都已经被加载进该进程(虚拟机)了。所以当每个app被启动的时候,启动APP的dalvik或者ART已经加载过了所有的客户端API的类。这样,app在运行时就不会找不到对应的Manager类 (symbol not found),但是同时通过android的stub sdk又减少了apk大小,可以说是一举两得。

https://man7.org/linux/man-pages/man2/fork.2.html

5Hidden API

最后关于客户端再分享一个小知识,就是关于隐藏API。安卓的很多public manager class会有自己的隐藏api,可以看源码有没有带@hide的字样。比如AudioManager的getMasterStreamType方法,注释里面就加了hide。

/** * 
Get the stream type whose volume is driving the UI sounds volume.
UI sounds are screen lock/unlock, camera shutter, key clicks... 
@hide 
*/ 
public int getMasterStreamType() { 
}

加了这个注释之后,安卓在编译Stub sdk 的时候,就会把带这个注释的方法从stub类里面删掉,这样导致app开发者在编译阶段找不到该方法,从而达到隐藏的效果。大家通过android studio打开AudioManager的stub 就可以发现。

所以该隐藏也只是编译阶段隐藏而已,并没有做到真正的运行时隐藏。

也正因为该隐藏方式是在客户端的编译阶段隐藏,而且在运行时的dalvik和art虚拟机里面还是有对应类被加载,所以在运行时App还是可以通过java的反射来进行访问。。。。安全性并没有那么高。

有一些安全性要求比较高的API,会强制在服务器端run time检查调用者的进程ID,严格控制调用者身份。比如ActivityManager里面的一个方法,会检查callingUId。

void enforceShellRestriction(String restriction, int userHandle) 
{ 
    if (Binder.getCallingUid() == Process.SHELL_UID) 
        { if (userHandle < 0 || mUserManager.hasUserRestriction(restriction, userHandle)) 
        { throw new SecurityException("Shell does not have permission to access user " + userHandle); 
        } 
     } }

这个思想其实和真正的后端开发就很相似了,你想在客户端做安全性验证,怎么也会出现漏洞。比如游戏客户端可以用很多种方式绕过安全检查,或者甚至修改游戏端运行时代码等等方式作弊。

Android的C/S 的服务器端

说了这么多客户端的实现。是时候聊聊服务器端的实现了。服务器端的具体实现,我推荐大家看看这篇文章 如何实现一个 System Services?,这是博主吴小龙写的,步骤非常详细,大家可以先过一遍有个印象。

https://juejin.cn/post/6961760668705882142

不过再多的细节,服务器端的实现绕不过这三个概念:1. AIDL 2. Bound Service 3. ServiceManager and SystemService

6对AIDL的理解

大部分朋友应该对AIDL怎么用已经很了解了,这次我就不讲具体怎么用,而是应该怎么理解AIDL的设计初衷。

其实我们站在开发者的角度就可以很容易理解。当我的APP需要进行跨进程通信, 我作为开发者肯定希望有一个方便的客户端API,可以直接调用该API就间接的和system process通信.而不需要写一些非常复杂的C++的代码,比如我们总不能要求app开发者写jni文件去调用linux的系统调用来做这个事情。

站在服务器,也就是系统开发者的角度,我肯定希望当客户端调用我的某一个方法的时候,我不需要做额外的数据的序列化和反序列化。或者通俗一点说,就是客户端的调用的API的方法名,和我服务器端的方法名一致,接口长的样子要一样。比如在客户端TelephonyManager#isEmergencyNumber调用了ITelephony#isEmergencyNumber, 那么服务器端我也希望被调用服务器端实现的isEmergencyNumber方法。

AIDL机制就很好的解决了这个问题。AIDL可以帮助客户端和系统服务端开发者自动生成代码,包装了一些真正的跨进程通信的实现,让开发者不需要自己写linux的系统调用,同时还可以保持双方接口的一致(方法名一样等等)。

所以这一套设计方案说到底,就是为了开发者,尤其是app开发者更方便的开发而已。它的出现让跨进程变得更方便,但是这个跨进程也是在现有框架下进行,比如,客户端app开发要想和系统进行通信,必须通过系统现有的service与manager API进行通信,也还是有一定的限制。

7Service的注册

实现了一个系统级别的服务,总得先生成该服务的实例,并且保存在某处(确保不会被垃圾回收),才能保证客户端能正常的调用。

安卓的ServiceManager就提供了这样的功能。

@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) 

public static void addService(String name, IBinder service, boolean allowIsolated, int dumpPriority) 
{ 
    try { 
        getIServiceManager().addService(name, service, allowIsolated, dumpPriority); 
        } 
    catch (RemoteException e) { Log.e(TAG, "error in addService", e); } 
    }

这个方法最后会调用一个C++的底层代码,把服务实例注册。当前端的Manager api调用ServiceStub的方法的时候,安卓系统会通过IPC调用这些注册了的Service实例。

Android的SystemService

服务器端的服务可以以Service的的形式存在,也可以以SystemService的形式存在。Service就不用多说了,大家都知道怎么做bind service。

SystemService就不太一样了,大家可以看看这个类是个啥。

@SystemApi(client = Client.SYSTEM_SERVER)                                   
public abstract class SystemService { 

    public void onBootPhase(@BootPhase int phase) {}

}

这个抽象类并不以Service的形式存在,而是一个简单的类。它唯一不一样的是提供了很多和启动周期相关的回调(boot phase).很多朋友不太懂什么是启动周期,其实很好理解,通俗一点就是,安卓设备被设置了屏幕锁之后,设备启动之后会被分成不同的启动阶段。

比如设备启动但是没有解锁是一个阶段,设备被解锁之后是另一个阶段。每个阶段开始之后,安卓的system server会启动不同的进程。最明显的例子是打电话的进程,设备在没有解锁之前也需要能接收或者拨打紧急电话。

SystemService类暴露了这样一个回调就用意很明显了,这样可以让不同的系统服务在不同的启动阶段做不同的事情。

比如安卓监控设备温度的系统服务,他只有在ActivityManager被加载之后才进行一系列业务逻辑的执行。

class ThermalManagerService extends SystemService {

    @Override public void onBootPhase(int phase) {
    if (phase == SystemService.PHASE_ACTIVITY_MANAGER_READY) { 
            onActivityManagerReady(); 
        }
    }
}
Android的SystemServer

那么有了这么多服务,在哪里开始执行呢。就在SystemServer里面。

https://cs.android.com/android/platform/superproject/+/master:frameworks/base/services/core/java/com/android/server/power/ThermalManagerService.java;drc=b0193ccac5b8399f9b5ef270d102b5a50f9446ab;l=72

SystemServer.java的核心代码在这几行:

try { 
    t.traceBegin("StartServices"); 
    startBootstrapServices(t); 
    startCoreServices(t); 
    startOtherServices(t); 
    startApexServices(t); 
}

BootStrap service就是指我们上一节说到的SystemService,这些对象和bootstrap也就是启动周期紧密的相关,所以都是SystemService。Core Service就是系统需要的但是对bootstrap不敏感的服务。但是无论是哪种service最后都毫无例外的将自己注册在ServiceManager了。

ServiceManager.addService("sec_key_att_app_id_provider", new KeyAttestationApplicationIdProviderService(context));

就这样,安卓系统成功的创建了所有需要的服务的实例,并且将其注册了起来。到此为止,客户端已经可以尝试通过Manager API对这些系统服务进行IPC通信了。而且所有的这些Service的实例都被保存在一个List里面。

class SystemServiceManager {

private List<SystemService> mServices;

}

所以并不用担心这些Service被垃圾回收。到这里基本上安卓的前端和后端我们就都了解了。但是这些都只是一些概念的皮毛,有很多细节还没覆盖到。大家有兴趣可以再多查查资料。不过,安卓在架构上也在不断的进化。一个例子:很多后端的开发概念,微服务现在在安卓的架构中也开始被支持了。

Android自己的微服务-APEX

从安卓10开始,android 的系统开发者已经开始尝试把很多framework中的代码模块化。从framework/base移动到packages/modules/下面,变成独立的模块,从而将该模块编译成独立的APEX文件(一个类似APK的安装文件)。

最显著的就是蓝牙模块:

https://cs.android.com/android/platform/superproject/+/master:packages/modules/Bluetooth/

这个模块编译之后是一个类似APK的APEX文件,这样的好处是:

蓝牙模块可以自己单独的通过google playstore更新了!!!!

也就是说,以后安卓的某一个模块出了新的fix,用户不需要再下载完整庞大的img了,安卓只需要通过playstore下载对应的APEX文件实现更新!

APEX文件的具体格式可以看这:

https://source.android.com/docs/core/ota/apex

可以说,安卓在C/S的架构上的进化从来没停止过。系统开发者的学习脚步也不能停啊。。。

Copyright© 2013-2020

All Rights Reserved 京ICP备2023019179号-8