架构·微服务架构·ANDROID 源码分析(二)

1、上篇概述

上一篇的文章架构·微服务架构详细描述微服务架构相关的理论基础,为这一篇文章打好了理论基础。这篇文章将站在 Android Framework 设计者的角度上,剖析在 Android 中应用的微服务架构。

因为只有理论结合实践,才能帮助我们更好的理解微服务架构这一难点。

2、假设由你主导架构设计

微服务架构向客户端暴露服务,而客户端可以通过远程访问协议去访问服务端暴露的服务。当下,我们先不考虑 Android 是如何实现微服务架构,仅以我们的角度去思考:我们利用微服务架构时会遇到哪些问题?

2.1、设计应用场景

1、当程序启动时,所有的服务会预先注册。
2、客户端可以通过查询服务的注册中心,查询到服务的注册状态。
3、客户端主动 connect 服务端,打开通讯的管道。
4、客户端可以向服务端 push 数据。
5、服务端可以向客户端 push 数据。
6、客户端可以主动 disconnect 服务端,或当服务端不可用时客户端应知晓。

2.2、角色划分

1、服务注册中心:用于注册服务端的所有服务,并向客户端提供服务的唯一标识符,以便于客户端能正确发现服务。
2、客户端:……。
3、服务端:独立的业务单元。
4、包裹对象:封装可序列化的数据,用于客户端与服务端的双向数据推送。
5、通讯机制:用于将包裹对象丢到服务端或客户端。

直到目前为止,我们并没有接触 ANDROID 的源码。所以上述的构想都是源于自己的设计,与 ANDROID 本身的设计并不相同。

因为我们对场景已经有了一番思索,所以接下去在源码中遨游的时候就不容易迷失方向。上述的思考将成为分析源码的主线索,可以预料的是:这些思索与源码中的设计必然存在大量的差异。但相比迷失在源码里面,它给我们带来的益处远大于弊处。

3、分析 ANDROID 源码

由于之前在分析View·InputEvent 的事件派发时, 接触到 WindowManagerService这个类。在当时的文章中 WindowManagerService并不是主线索,因而就没有对此展开分析,那么此刻是旧事重提的时候了。

3.1、WindowManagerService的创建历程

老样子从构造器作为起点,发现是私有构造器。所以对象一定是经由内部创建的,多半情况服务是单例的。

// WindowManagerService.class
private WindowManagerService(Context context, InputManagerService inputManager,
            boolean haveInputMethods, boolean showBootMsgs, boolean onlyCore) {

}

经由main方法的验证,发现其提供了容量为 1 的 WindowManagerService[]。通过静态main()方法创建了WindowManagerService的实例。

这里使用数组的原因可能是 ANDROID 的设计者们认为服务可能存在多个。

// WindowManagerService.class
    public static WindowManagerService main(final Context context,
            final InputManagerService im,
            final boolean haveInputMethods, final boolean showBootMsgs,
            final boolean onlyCore) {
        final WindowManagerService[] holder = new WindowManagerService[1];
        DisplayThread.getHandler().runWithScissors(new Runnable() {
            @Override
            public void run() {
                holder[0] = new WindowManagerService(context, im,
                        haveInputMethods, showBootMsgs, onlyCore);
            }
        }, 0);
        return holder[0];
    }

3.2、WindowManagerService注册到服务中心

此刻我们先不关注内部的实现,转而观察 main()方法是在哪里被调用的,最终在SystemServer中发现了下面代码。

SystemServer在启动其他服务时将创建WindowManagerService实例,并将其注册到ServiceManager中。

// SystemServer.class
private void startOtherServices() {
 //  省略无关代码
WindowManagerService wm = null;
wm = WindowManagerService.main(context, inputManager,
                    mFactoryTestMode != FactoryTest.FACTORY_TEST_LOW_LEVEL,
                    !mFirstBoot, mOnlyCore);
            ServiceManager.addService(Context.WINDOW_SERVICE, wm);
 //  省略无关代码
}

继续跟进startOtherServices ()方法,观察其服务注册的流程是怎样的。

// SystemServer.class
private void run() {

        //  省略无关代码
        try {
            startBootstrapServices();
            startCoreServices();
            startOtherServices();
        } catch (Throwable ex) {
            Slog.e("System", "******************************************");
            Slog.e("System", "************ Failure starting system services", ex);
            throw ex;
        }
         //  省略无关代码
}

分析上段代码,我们得到了几个信息:
1、run()是私有方法
2、服务分为3种:启动项服务、核心服务、其它服务。(WindowManagerService 属于其它服务)

依然我们不脱离主线索,不去分析服务的种类等等,感兴趣的同学自行阅读源码。

// SystemServer.class
public static void main(String[] args) {
        new SystemServer().run();
    }

好吧,看来我们已经追述到尽头了。这是 Java 的入口函数,由 zygote 发起调用。

关于 Native 层如何启动服务,不再本章的范畴。

到此为止我们发现了两件事情,这两件事情与我们之前的构想几乎没有出入。
1、服务是被 SystemServer 创建并注册到服务中心的。
2、SystemServer是开机时即会启动的。

3.3、服务中心的日常(ServiceManager)

服务中心提供了管理服务的基础方法,基于类结构我们关注addService() 的流程

ServiceManager的类结构
    // ServiceManager.java
    /**
     * Place a new @a service called @a name into the service
     * manager.
     * 
     * @param name 新服务的名称
     * @param service 服务对象
     */
    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;
        }

        // Find the service manager
        sServiceManager = ServiceManagerNative.asInterface(BinderInternal.getContextObject());
        return sServiceManager;
    }

观察到sServiceManager对象是由ServiceManagerNative.asInterface()方法创建的,而传入的参数正是 IBinder

由此可见ServiceManager对象本身也是一个远程服务,它与客户端通过Binder进行通讯。

IServiceManager 的接口

IServiceManager提供了:

  • 添加 —— addService
  • 检查 ——checkService
  • 获取与具体服务通讯的 Binder ——getService
  • 列举已注册的服务 —— listServices
  • 设置权限控制器功能。—— setPermissionController

由此可见客户端访问服务是通过访问IServiceManager的实例(即ServiceManager),来获取到具体服务的通讯对象(Binder)的实例来达到通讯的目的的。

所以我们可以将 ServiceManager 理解为 DNS 服务。

3.4、如何连接服务中心

由 3.3 章节,我们发现WindowManagerService其实是通过向�ServiceManager查询服务得到Binder对象的。

所以问题的关键是如何连接上服务中心?

在一番思索后我们将问题定位到Binder对象,ServiceManager 中的 Binder 对象从BinderInternal.getContextObject()中得到的。

// com.android.internal.os.BinderInternal
    /**
     * 返回系统的全局“上下文对象”。 
     * 这通常是IServiceManager的实现,
     * 您可以使用它来查找其他服务。
     */
    public static final native IBinder getContextObject();

Binder对象的创建来源于 Native 层,那么我们不得不中断对Binder底层创建的分析。那么后续对于 ServiceManager的创建时机都将无从考究。(因为我并不打算深入到 Native 层)

3.5、Binder 的数据传输过程

    /**
     * 使用对象执行一个通用操作。
     * 
     * @param code 待执行的行为码。范围限定在 0x0与0xffffff之间。
     * @param data 编组数据(非空)发送到目标。如果您不发送任何数据,您必须创建一个空的Parcel在这里给出。
     * @param reply 要从目标接收的已编组数据。如果你对返回值不感兴趣,可以为null。
     * 
     * @param flags 附加操作标志,0表示RPC, or {@link #FLAG_ONEWAY} 表示单向RPC.
     *
     */
    public boolean transact(int code, Parcel data, Parcel reply, int flags)
        throws RemoteException;

Bindertransact()方法真正的触发了数据的传输,传输的具体实现可以参考BinderProxy

final class BinderProxy implements IBinder {

public boolean transact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
        Binder.checkParcel(this, code, data, "Unreasonably large binder buffer");
        return transactNative(code, data, reply, flags);
    }

 public native boolean transactNative(int code, Parcel data, Parcel reply,
            int flags) throws RemoteException;
}

数据传输细节也被掩藏在 Native 之下,看来 Android 的设计者们,很关心 Binder 的性能。因为 Android 的服务端与客户端,其实都是部署到机器本地的,而这也恰恰是 Android 不重用其他的远程访问协议,而重复造轮子的缘由吧。

如何连接上服务中心,目前我们暂且没法分析源头。那么暂且把这藏于 Native 之下的这部分过程略过,因为通过其他资料我们可以判定我们猜想的正确性。

3.6、数据如何互相推送

绕了一大圈我们重新回到WindowManagerService这个类,我们在早前一直认为它是服务,那么此刻我们深入源码看看它是否真的是服务。

public class WindowManagerService extends IWindowManager.Stub
        implements Watchdog.Monitor, WindowManagerPolicy.WindowManagerFuncs {

}
public interface IWindowManager extends android.os.IInterface {
    public static abstract class Stub extends android.os.Binder implements android.view.IWindowManager{

    private static final java.lang.String DESCRIPTOR = "android.view.IWindowManager";

        // 构造时就将其附加到接口。
        public Stub() {
            this.attachInterface(this, DESCRIPTOR);
        }

        // 将IBinder对象转换为android.view.IWindowManager接口,如果需要,生成代理。
        public static android.view.IWindowManager asInterface(android.os.IBinder obj) {
            if ((obj == null)) {
                return null;
            }
            //  尝试检索此Binder对象的接口的本地实现。
            //   如果返回null,则需要实例化一个代理类,以通过transact()方法调用。
            android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
            if (((iin != null) && (iin instanceof android.view.IWindowManager))) {
                return ((android.view.IWindowManager) iin);
            }
            // 查不到构建代理对象,访问远程对象
            return new android.view.IWindowManager.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{
              // 省略 代码
        }
    }
}

1、首先WindowManagerService继承自IWindowManager.Stub。
2、而IWindowManager.Stub继承自android.os.Binder并实现了android.view.IWindowManager。
3、IWindowManager.Stub提供了asInterface方法,用于返回Binder对象。** 如果本地已有 Binder 对象则复用,若没有则调用 mRemote 的 transact()方法获取到 Binder 对象。(此处单独在下面分析)**
4、拿到 Binder 对象后,可用于两端之间的通讯。

3.6.1、获取到 Binder 对象

// Binder.java

    private IInterface mOwner;
    private String mDescriptor;

    public void attachInterface(IInterface owner, String descriptor) {
        mOwner = owner;
        mDescriptor = descriptor;
    }

    public IInterface queryLocalInterface(String descriptor) {
        if (mDescriptor.equals(descriptor)) {
            return mOwner;
        }
        return null;
    }

attachInterface()方法用于将Binder 对象与descriptor 绑定,所以查询descriptor 可以获得Binder 对象。

而我们看到在Stub的构造器中就默认将Stub对象与descriptor绑定,所以只要初始化了Stub对象则默认都是使用的Stub作为 Binder

如果认真的思索到此,一定会存在疑惑:构造器中已经默认attachInterface(),那为什么还要多此一举检查是否存在Binder 对象呢?

public static android.view.IWindowManager asInterface(android.os.IBinder obj) {
}

我们发现上面的asInterface对象是静态方法,所以它可能在IWindowManager.Stub未构造前就传入 Binder,且该 Binder 一定不是IWindowManager.Stub对象,具体是什么我们在下面分析!

而传入之后发现Stub并没有持有 Binder 对象,于是就去以传入的Binder对象为基础创建了android.view.IWindowManager.Stub.Proxy(obj);对象。

 private static class Proxy implements android.view.IWindowManager {
            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 android.view.IWindowSession openSession(android.view.IWindowSessionCallback callback, com.android.internal.view.IInputMethodClient client, com.android.internal.view.IInputContext inputContext) throws android.os.RemoteException {
            
                      // 省略大量代码

            }
}

观察到Proxy对象的asBinder()返回的是mRemote本身。另一个让人注意的方法是openSession ()返回的类型是android.view.IWindowSession,而IWindowSession类继承自IInterface

IWindowSession的结构

这个结构与IWindowManager是不是惊人的相似?

IWindowManager的结构

3.6.2、WindowManagerService到底是不是服务?

见下面代码sWindowManagerService是通过ServiceManager拿到的Binder对象。 所以严格意义说sWindowManagerService并不是微服务架构中的服务,而是用于连接真正服务的黏合剂(或者可以理解为 Binder Server)。

    public static IWindowManager getWindowManagerService() {
        synchronized (WindowManagerGlobal.class) {
            if (sWindowManagerService == null) {
                sWindowManagerService = IWindowManager.Stub.asInterface(
                        ServiceManager.getService("window"));
                try {
                    sWindowManagerService = getWindowManagerService();
                    ValueAnimator.setDurationScale(sWindowManagerService.getCurrentAnimatorScale());
                } catch (RemoteException e) {
                    Log.e(TAG, "Failed to get WindowManagerService, cannot set animator scale", e);
                }
            }
            return sWindowManagerService;
        }
    }

IWindowSession、IWindow,这两个数据结构都是标准的aidl接口,用于进程之间的同步通信。 IWindowSession负责ViewRootImpl到WMS的单向请求,IWindow则用于WMS回调ViewRootImpl。

窗口服务类图

参考资料:Android窗口管理](http://www.cppblog.com/fwxjj/archive/2013/01/14/197252.html)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,214评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,307评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,543评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,221评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,224评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,007评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,313评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,956评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,441评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,925评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,018评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,685评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,234评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,240评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,464评论 1 261
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,467评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,762评论 2 345

推荐阅读更多精彩内容