Android应用程序窗口设计之建立与WMS服务之间的通信过程

   Android应用程序窗口设计之建立与WMS服务之间的通信过程


Android应用程序窗口设计系列博客:

Android应用程序窗口设计之Window及WindowManager的创建
Android应用程序窗口设计之setContentView布局加载的实现
普法Android的Token前世今生以及在APP,AMS,WMS之间传递
Android应用程序窗口设计之窗口的添加
Android应用程序窗口设计之建立与WMS服务之间的通信过程
Android窗口设计之Dialog、PopupWindow、系统窗口的实现
Android应用程序建立与AMS服务之间的通信过程


本篇博客编写思路总结和关键点说明:

在这里插入图片描述
为了更加方便的读者阅读博客,通过导读思维图的形式将本博客的关键点列举出来,从而方便读者取舍和阅读!



引言

  对Android窗口实现有一定了解的读者都应该知道WindowManagerService做为Android窗口管理的核心服务其在Android中的地位是毋庸置疑的也是无可替代的(感觉有点废话啊,核心服务当然很重要啊!)。而WMS(这里为了后续简述方便将WindowManagerService简称为WMS)服务却是运行在system_server进程中的,而通常我们的窗口的需求最开始的发起端通常是在Android应用程序进程端的,而做为窗口最最具体的载体Android应用程序中关于窗口的各种操作都离不开和WMS服务之间的跨进程交互得操作。那么这两个进程之间是怎么进行跨进程通信的呢?我想对于Android有一定了解的读者肯定会脱口而出通过Binder通信,是的应用程序和WMS服务之间正是通过Binder进行通信的!而我们今天的博客将会重点分析Android应用程序以及对应的窗口实现过程中二者之间是如何建立Binder通信逻辑的,即:

我们知道Binder通信有一个特点就是通常只能Binder客户端请求Binder服务端,而不能反过来(注意这里的措辞是通常)!所以通常Binder客户端和服务端之间想建立相互通信的关系,会借助匿名Binder,而我们这里的WMS和应用程序之间也是如此!

  • Android应用程序以及关联的四大组件建立和WMS的Binder通信

  • WMS建立和应用程序窗口的Binder通信,从而使窗口能通知WMS服务进行相关的处理

注意:本篇的介绍是基于Android 7.xx平台为基础的,其中涉及的代码路径如下:

frameworks/base/core/java/android/view/
	--- WindowManager.java
 	--- View.java
	--- ViewManager.java
	--- ViewRootImpl.java
	--- Window.java
	--- Display.java
	--- WindowManagerImpl.java
	--- WindowManager.java
	--- WindowManagerGlobal.java
	--- IWindowManager.aidl
	--- IWindow.aidl
	--- IWindowSession.aidl

frameworks/base/services/core/java/com/android/server/wm/
	---WindowManagerService.java
	---AppWindowToken.java
	---WindowState.java
	---Session.java
	---WindowToken.java

在正式开始分析前,我们先奉上Android应用程序以及相关窗口和WMS之间跨进程Binde通信交互图,也许下面的图就能给读者灵感,不待我分析就知道了今天的主题Android应用程序窗口设计之建立与WMS服务之间的通信过程了。

在这里插入图片描述

扫描二维码关注公众号,回复: 12395583 查看本文章


一.Android应用程序建立和WMS的Binder通信

在正式开始相关的的分析之前,将要涉及到涉及到Context的继承关系类图,从下面的类图中可以看出,Context是一个接口(提供了很多的接口方法),ContextImp和ContextWrapper都是其实现类,我们常用的Activity、Service、Application都直接或间接继承自ContextWrapper。

在这里插入图片描述


1.1 Android应用层获取Framework层WMS核心服务对外接口类ActivityManager

  WMS做为Android Framework层的Binder核心服务,它被注册到了servicemanager服务大管家里面,并且也和其它核心服务一样在Android Framework的框架层提供了对应的SDK接口供应用程序调用WMS服务,而我们这里以在应用程序的Activity或者其子类中获取AMS服务对外提供接口类为例说明:

WindowManager mWindowManager = (WindowManager)getSystemService(Context.WINDOW_SERVICE);

这里我们先看看Activity类中的getSystemService()方法,如下:

//[Activiyt.java]
    @Override
    public Object getSystemService(@ServiceName @NonNull String name) {
    
    
        if (getBaseContext() == null) {
    
    
            throw new IllegalStateException(
                    "System services not available to Activities before onCreate()");
        }

        if (WINDOW_SERVICE.equals(name)) {
    
    //这个地方比较特殊,会对WMS服务对外接口进行缓存,
            return mWindowManager;
        } else if (SEARCH_SERVICE.equals(name)) {
    
    
            ensureSearchManager();
            return mSearchManager;
        }
        return super.getSystemService(name);
    }

这里对获取WMS服务对外接口比较特殊啊,它会使用前面Activity创建过程中已经获取的mWindowManager代理端,然后直接使用缓存(注意,这个地方比较特殊,比较特殊,比较特殊!)此处涉及到Android应用程序窗口中Activity对应窗口的创建流程,读者可以详见博客Android应用程序窗口设计之Window及WindowManager的创建,总之它会在Activity的attach()方法中被初始化,如下:

//[Activity.java]
    final void attach(Context context, ActivityThread aThread,
            Instrumentation instr, IBinder token, int ident,
            Application application, Intent intent, ActivityInfo info,
            CharSequence title, Activity parent, String id,
            NonConfigurationInstances lastNonConfigurationInstances,
            Configuration config, String referrer, IVoiceInteractor voiceInteractor,
            Window window) {
    
    

		mWindow = new PhoneWindow(this, window);		...

        mWindow.setWindowManager(
                (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
                mToken, mComponent.flattenToString(),
                (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
            /**********************************************************************/
            //这里为了简述方便,将代码就地展开了
		    public void setWindowManager(WindowManager wm, IBinder appToken, String appName,
		            boolean hardwareAccelerated) {
    
    
				...
		        if (wm == null) {
    
    
		            wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
		        }
				//注意此处的this指向PhoneWIndow
		        mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
		    }	
		    	
		mWindowManager = mWindow.getWindowManager();
			/**********************************************************************/
            //这里为了简述方便,将代码就地展开了
		    public WindowManager getWindowManager() {
    
    
		        return mWindowManager;
		    }
	}

从上面看到,最终还是通过Context的上下文调用getSystemService()方法最终会调用到ContextThemeWrapper类的getSystemService中,源码如下:

//[ContextThemeWrapper.java]
    @Override
    public Object getSystemService(String name) {
    
    
        if (LAYOUT_INFLATER_SERVICE.equals(name)) {
    
    
            if (mInflater == null) {
    
    
                mInflater = LayoutInflater.from(getBaseContext()).cloneInContext(this);
            }
            return mInflater;
        }
        return getBaseContext().getSystemService(name);
    }

	//[ContextWrapper.java]
    public Context getBaseContext() {
    
    
        return mBase;//注意这里的mBase指向了ContextImpl类
    }

从上面的代码可以看到getSystemService()方法最终都调用到了ContextImpl类中,而至于mBase为什么指向了ContextImpl实例,这里就不过多篇幅分析了可以参见博客初始化目标Activity并执行相关生命周期流程的2.3章节,这里我们直接来看ContextImpl中getSystemService()的实现,如下:

//[ContextImpl.java]
    @Override
    public Object getSystemService(String name) {
    
    
        return SystemServiceRegistry.getSystemService(this, name);
    }

这里又来了一个SystemServiceRegistry类,我们接着继续分析看看它的处理逻辑!

//[SystemServiceRegistry.java]
    private static final HashMap<Class<?>, String> SYSTEM_SERVICE_NAMES =
            new HashMap<Class<?>, String>();
    private static final HashMap<String, ServiceFetcher<?>> SYSTEM_SERVICE_FETCHERS =
            new HashMap<String, ServiceFetcher<?>>();
    public static Object getSystemService(ContextImpl ctx, String name) {
    
    
        ServiceFetcher<?> fetcher = SYSTEM_SERVICE_FETCHERS.get(name);
        return fetcher != null ? fetcher.getService(ctx) : null;
    }

好像很简单额,直接从SYSTEM_SERVICE_FETCHERS哈希列表中根据服务名称进行查找,这里我们看下SYSTEM_SERVICE_FETCHERS是啥时候被填充的,我们接着查找:

//[SystemServiceRegistry.java]
    private static <T> void registerService(String serviceName, Class<T> serviceClass,
            ServiceFetcher<T> serviceFetcher) {
    
    
        SYSTEM_SERVICE_NAMES.put(serviceClass, serviceName);
        SYSTEM_SERVICE_FETCHERS.put(serviceName, serviceFetcher);
    }

可以看到在registerService()方法中注册了一系列的服务,我接着继续查找看看那里调用了它!

//[SystemServiceRegistry.java]
    static {
    
    
	    // Not instantiable.
	    private SystemServiceRegistry() {
    
     }
    	...
    	registerService(xxx);
        registerService(Context.WINDOW_SERVICE, WindowManager.class,
                new CachedServiceFetcher<WindowManager>() {
    
    
            @Override
            public WindowManager createService(ContextImpl ctx) {
    
    
            	//是不是很熟悉了
                return new WindowManagerImpl(ctx);
            }});       
}

这里我们可以看到SystemServiceRegistry类的static静态方法区中注册了一系列的服务,而我们的WindowManagerImpl实例对象也被注册到了里面,至此我们就可以使用WMS服务了。而其它的Android Framework层核心服务也数通过上述方法进行注册,然后对外提供的。

不知道细心的读者注意到了没有这里的SystemServiceRegistry类,只有一个私有构造方法,那么说明他不能被实例化,那它是怎么被实例化的呢,或者说是怎么被加载然后执行static的静态区的注册方法呢,这个就要说我们我们的zygote预加载机制了,在Android Zygote进程启动源码分析指南的的2.3章节我们知道zygote进程会调用preload()方法中会通过反射预加载一些类,而这其中就包括我们的SystemServiceRegistry,而我们的Android应用程序进程是由zygote进程孵化的所以继承了zygote进程资源,所以它预加载的SystemServiceRegistry也被我们继承到了。

而我们知道zygote进程预加载的claess被定义在frameworks/base/preloaded-classes中,我们简单看下就找到了SystemServiceRegistry身影,如下:

在这里插入图片描述

好了getSystemService()分析清楚了,但是从前面的逻辑我们可以看到获取Activity对应的WMS服务对外接口并没有结束!

注意如果是调用其它的Context上下文获取WMS对外接口类已经结束了(譬如Service中),但是Activity中获取WMS对外接口类比较特殊,还没有结束

我们接着来分析分析setWindowManager方法干了些啥,可以看到它主要是实例化了wWindowManager变量。它实例化的前提是将前面传递过来的参数wm强制转换为WindowManagerImpl,然后调用createLocalWindowManager(),在createLocalWindowManager()实际是执行了一个new WindowManagerImpl()方法来创建。

关于这部分代码本人存在一个很大的疑惑点,Activity获取WMS服务的对外接口为啥Android当初要把这个地方设计的这么复杂呢,其实传递过来的wm已经是一个WindowManagerImpl类型的实例了,这里createLocalWindowManager()重新创建一个实例,不过是使用了前面传递过来的wm的成员变量mContext,然后加上此时PhoneWindow自己的this指针而已!

直接使用传递过来wm,然后添加一个方法将PhoneWindow传递过去就可以这样不香吗,反而多此一举再new一个呢!

//[WindowManagerImpl.java]
    private WindowManagerImpl(Context context, Window parentWindow) {
    
    
        mContext = context;
        mParentWindow = parentWindow;
    }

    public WindowManagerImpl createLocalWindowManager(Window parentWindow) {
    
    
    	//注意这里的parentWindow是我们前面创建的PhoeWindow
        return new WindowManagerImpl(mContext, parentWindow);
    }

注意此处的parentWindow指向了Activity对应的PhoneWindw窗口,这个地方需要注意,后续我们会用到!


1.2 Android应用层通过对外接口类WindowManager使用WMS服务

  通过前面的一顿操作,我们获取到了WMS服务对外的接口类WindowManager真正的实现类WindowManagerImpl,在正式开始分析Android应用程序是怎么通过它使用WMS服务之前,我们先来看看AMS的整体类图框架图,如下:
在这里插入图片描述

好了对外接口我们也获取到了,此时的读者肯定在想对外接口也获取到了和WMS服务的通信那肯定就简单了,我们随意来看看它对外提供接口中的一个,我们这里以addView()为例说明:

//[WindowManagerImpl.java]
	private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
    @Override
    public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
    
    
        android.util.SeempLog.record_vg_layout(383,params);
        applyDefaultToken(params);
        //关键在此
        mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
    }

此时的估计大部分在想,此时的mGlobal是不是WMS的Binder代理端,NO,NO,NO。WMS服务完全是个大骗子,根本没有提供对应的Binder代理端出来给Android应用程序端使用,而是饶了一大圈,又使用了另外一个Binder匿名端来实现了Android应用程序和WMS的交互。关于mGlobal.addView()这部分逻辑就这里不展开了,详见Android应用程序窗口设计之窗口的添加的章节2.1,总之最后会通过ViewRootImpl对象来实现的,我们这里来看看ViewRootImpl类:

//[viewRootImpl.java]
public ViewRootImpl(Context context, Display display) {
    
    
	//在WMS服务中创建Session Binder对象,同时返回Binder代理对象给当前应用程序进程,并保存在ViewRootImpl的成员变量	mWindowSession中,注意此处
	mWindowSession = WindowManagerGlobal.getWindowSession();
	...
	//为每一个ViewRootImpl对象创建W Binder对象,用于WMS服务访问对应的Activity
	mWindow = new W(this);

		/*
			构造一个AttachInfo对象
			注意传入的一个参数为Handler类型ViewRootHandler实例mHandler,
			用于处理Android应用程序的窗口信息
		*/
		//此处的mAttachInfo会在对UI进行布局的时候,在其performTraversals()方法中通过host.dispatchAttachedToWindow(mAttachInfo, 0)传递到View中去
       mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this);
	...	
}

此时的读者估计要骂娘了,尼玛整这么复杂!为啥不直接使用WMS的Binder服务代理端直接就好了呢,Android的开发世界就是这么朴实无华而又繁琐!


1.3 通过IWindowSession匿名Binder建立应用程序到WMS服务之间的连接

通过前面的分析可知要使应用程序进程可以请求WMS服务,必须在WMS服务这边创建一个类型为Session的匿名Binder本地对象,同时应用程序这边获取匿名Binder服务Session的代理对象,通过该代理对象,应用程序进程就可以请求WMS服务了。

如果对Binder特别是匿名/实名Binder还没有了解的读者强烈建议先阅读一下该篇以及一系列的博客Android Binder框架实现之何为匿名/实名Binder。通过前面的博客我们应该知道匿名Binder有两个比较重要的用途:
一个是拿到Binder代理端后可跨Binder调用实体端的方法接口(我们这里主要利用了这个功能 )
另一个作用便是在多个进程中标识同一个对象

在开始分析getWindowSession()的源码前,我们先来认识一下IWindowSession,正所谓知己知彼方能百战百胜吗!它是一个aidl接口类(肯定和Binder通信有关了),Android源码对它的定义如下:

//[IWindowSession.aidl]
/**
	中文翻译一下就是用于每个应用程序和WMS进行通信的接口
 * System private per-application interface to the window manager.
 *
 * {@hide}
 */
interface IWindowSession {
    
    
}

在这里插入图片描述
好了,是时候开始我们真正的表演了,不真真的分析了!

//[WindowManagerGlobal.java]
    public static IWindowSession getWindowSession() {
    
    
        synchronized (WindowManagerGlobal.class) {
    
    
            if (sWindowSession == null) {
    
    
                try {
    
    
					//获取输入法管理器服务Binder代理端
                    InputMethodManager imm = InputMethodManager.getInstance();
					//获取窗口管理器服务Binder代理端
                    IWindowManager windowManager = getWindowManagerService();
					//得到IWindowSession代理对象
					//这里需要注意的是传递的两个参数都是匿名Binder,所以匿名Binder读者最好能掌握掌握
                    sWindowSession = windowManager.openSession(
                            new IWindowSessionCallback.Stub() {
    
    
                                @Override
                                public void onAnimatorScaleChanged(float scale) {
    
    
                                    ValueAnimator.setDurationScale(scale);
                                }
                            },
                            imm.getClient(), imm.getInputContext());
                } catch (RemoteException e) {
    
    
                    throw e.rethrowFromSystemServer();
                }
            }
            return sWindowSession;
        }
    }

这里需要注意地是注意这里的IWindowManager不要和WindowManager搞混淆了,IWindowManager也是一个aidl接口类,被WMS服务实现了。有了上述分析,此处的源码就水到渠成了,首先通过getWindowManagerService来获取WMS服务的代理对象,由于WMS服务是一个有名Binder对象,即已经注册到了ServiceManager进程中,因此可以通过查询服务的方式来获取WMS的代理对象。

//[WindowManagerGlobal.java]
    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) {
    
    
                    throw e.rethrowFromSystemServer();
                }
            }
            return sWindowManagerService;
        }
    }

然后WMS服务的代理端Binder跨进程调用到的WMS服务的openSession()方法创建应用程序与WMS之间的连接通道,即获取IWindowSession代理对象,并将该代理对象保存到WindowManagerGlobal的静态成员变量sWindowSession中,因此在整个应用程序进程中IWindowSession代理对象是唯一的!

这里我们简单来看看WMS服务是怎么处理openSession()请求的,如下:

//[windowManagerService.java]
    @Override
    public IWindowSession openSession(IWindowSessionCallback callback, IInputMethodClient client,
            IInputContext inputContext) {
    
    
        //异常检测
        if (client == null) throw new IllegalArgumentException("null client");
        if (inputContext == null) throw new IllegalArgumentException("null inputContext");
        //直接明了,构建一个Session实例
        Session session = new Session(this, callback, client, inputContext);
        return session;
    }

WMS端对openSession()的处理简单明了,直接通过传递过来的参数构建了Session的Binder实体端,然后跨Binder返回回去。至此构建了如下的一条Binder通信通道:

在这里插入图片描述


1.4 Android应用程序建立和WMS的Binder通信过程小结

Android应用程序建立和WMS的Binder通信分析到此结束了,我们可以发现Android应用程序建立和WMS之间的Binder通信,并没有走一条传统的流程即WMS对应的Binder代理端和WMS之间直接通信的方式,如下:
在这里插入图片描述

而是借助了WMS的代理端传递了一个在WMS端实现的匿名Binder Session给Android应用程序段,从而来实现Android应用程序建立和WMS的Binder通信(有点曲线救国的意思啊)。

这牵涉到了匿名BInder的传递,如果对于它还有不太熟悉的读者详见博客Android Binder框架实现之何为匿名/实名Binder
这里我有一个疑问想和读者朋友一起探讨一下,就是Android为什么对于WMS要采用这种方式呢,我个人认为有两个原因:
1.安全考虑,不想对外直接提供WMS的代理接口给第三方使用
2.使用WMS的代理Binder模式不太好实施对各个窗口的管理,而是采取了另外开辟一个Session的BInder通道从而能够更好的管控窗口
如果读者有更加深层次的认识,也可以@我,一起沟通和探讨!

在这里插入图片描述



二.WMS服务建立和Android应用程序之间的Binder通信连接

  通过前面的一顿操作,读者此时应该已将掌握了Android应用程序进程和WMS服务进程之间的跨进程Binder通信连接过程了,但是由于Binder通信的特点(或者说缺点,通常只至此Binder代理端请求Binder实体端),所以此时虽然应用程序进程中的ViewRootImpl对象已经获取WMS服务中的Session的代理对象,也就是说应用程序进程可以请求WMS服务了,但是WMS服务还是不能请求应用程序进程,怎么在应用程序进程和WMS服务之间建立双向连接呢?在解开这个谜题之前我们先来介绍一下IWindow类!

2.1 IWindow的Binder实体端类W的构造过程

我们可以看到在前面章节介绍ViewRootImpl的构造函数中,创建了一个类型为W的对象,W实现了IWindow接口,WMS服务正是通过W的代理对象来请求应用程序进程的。在开始分析W的构造之间,我们先来认识一下IWindow,正所谓知己知彼方能百战百胜吗!它是一个aidl接口类(肯定和Binder通信有关了),Android源码对它的定义如下:

//[IWindow.aidl]
/**
	这里我用我蹩脚的英文简单翻译一下就是,WMS服务用于通知客户端窗口一些事情的通道
 * API back to a client window that the Window Manager uses to inform it of
 * interesting things happening.
 *
 * {@hide}
 */
oneway interface IWindow {
    
    
		 void resized(in Rect frame, in Rect overscanInsets, in Rect contentInsets,
		            in Rect visibleInsets, in Rect stableInsets, in Rect outsets, boolean reportDraw,
		            in Configuration newConfig, in Rect backDropFrame, boolean forceLayout,
		            boolean alwaysConsumeNavBar);
		 ...
          
}

好了有了上面Android的官方解释我们就很清楚了,此处的W本地对象用于WMS通知应用程序进程,有点像ActiviytThread中的IApplicationThread实例对象用于AMS和APP进程通信的方法,都是使用匿名Binder!

关于Activity和WMS的交互完全可以参考Activity启动过程中和AMS的交互但是ActivityThread中的IApplicationThread是单一实例,而这里的W却是每一个ViewRootImpl都拥有一个

在这里插入图片描述


2.2 WMS服务使用IWindow的Binder代理端来实现和App进程通信

好了那WMS服务是如何获取W的代理对象的呢?怎么获取的呢,如果读者对博客Android应用程序建立与AMS服务之间的通信过程的章节二AMS建立和Android应用程序以及关联的四大组件之间的Binder通信还有印象的话,肯定难不倒你了。是的可以通过已经建立的Binder通道,跨进程传递Binder到其它进程端!

通过前面章节我们知道ViewRootImpl对象的成员变量mWindowSession保存了Session的代理对象,然后在窗口添加过程中会调用setView()方法,如下:

//[ViewRootImpl.java]
  public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
    
    
  	...
		/*
		将窗口添加到WMS服务中,mWindow为W本地Binder对象,
		通过Binder传输到WMS服务端后,变为IWindow代理对象
		*/
        res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                getHostVisibility(), mDisplay.getDisplayId(),
                mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                mAttachInfo.mOutsets, mInputChannel);

		//建立窗口消息通道
		if (view instanceof RootViewSurfaceTaker) {
    
    
             mInputQueueCallback =
                 ((RootViewSurfaceTaker)view).willYouTakeTheInputQueue();
         }

		//建立输入事件通道
          if (mInputChannel != null) {
    
    
              if (mInputQueueCallback != null) {
    
    
                  mInputQueue = new InputQueue();
                  mInputQueueCallback.onInputQueueCreated(mInputQueue);
              }
              mInputEventReceiver = new WindowInputEventReceiver(mInputChannel,
                      Looper.myLooper());
          }
  	...
  }

可以看到,其中有一个核心调用就是通过前面的建立的App应用程端到WMS服务之间的Binder通道mWindowSession的addToDisplay方法传递的第一个参数为成员变量mWindow,而mWindow中保存了W类型的Binder本地对象,因此这里通过函数addToDisplay就可以将W的代理对象传递给WMS服务。

分析到这里,我想到了昨天有一个读者询问我,说感觉学习了Binder相关的知识好像对于自己的开发并没有什么实质性帮助!
如果读者能看到这里他就不会这么问了,Android源码的实现中都是各种Android Binder的利用,如果没有搞清楚Android Binder的基本知识,对于上述通过匿名Binder传递匿名Binder理解起来怕是更有点难度了!

让我们接着来看看mWindowSession的实体端是怎么处理addToDisplay方法的,如下:

//[Sesstion.java'
    @Override
    public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs,
            int viewVisibility, int displayId, Rect outContentInsets, Rect outStableInsets,
            Rect outOutsets, InputChannel outInputChannel) {
    
    
        //注意这里的mService是在前面WMS调用openSession方法构造Session时传递过来的引用
        return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId,
                outContentInsets, outStableInsets, outOutsets, outInputChannel);//详见3.2
    }

好吗,其实Session就是一个中间人而已(掮客,不需要留下买路钱的那种),APP应用程序段通过它的跨进程Binder能力发送来的请求,它想都不想直接传给WMS服务进行处理了。我是干饭人!

上述的源码更加说明了一个道理,Android设计出一个Session就是不希望App应用进程端直接使用WMS的Binder代理端和直接通信,而是硬要绕一道通过Session来间接使用和WMS的通信。

既然Session这么不负责任,那我们只能接着看WMS中的addWindow方法是怎么处理传递过来的W对应的Binder引用了(通过跨进程传递,此处传递过来的window已经是W对应的代理端了),如下:

//[WindowManagerService.java]
    public int addWindow(Session session, IWindow client, int seq,
            WindowManager.LayoutParams attrs, int viewVisibility, int displayId,
            Rect outContentInsets, Rect outStableInsets, Rect outOutsets,
            InputChannel outInputChannel) {
    
    

			//这里的client为IWindow的代理对象,用于WMS和Activity进行通信
			//session为前面创建的用于APP端和WMS通信的匿名Binder

			...
		     boolean addToken = false;
			//根据attrs.token从mWindowMap中取出应用程序窗口在WMS服务中的描述符WindowToken
            WindowToken token = mTokenMap.get(attrs.token);
            AppWindowToken atoken = null;
            if (token == null) {
    
    //第一次add的情况下token怎么会有值
            	...
            	token = new WindowToken(this, attrs.token, -1, false);
                addToken = true;
            }//应用程序窗口
			else if (type >= FIRST_APPLICATION_WINDOW && type <= LAST_APPLICATION_WINDOW) {
    
    
				...
			}//输入法窗口
			else if (type == TYPE_INPUT_METHOD) {
    
    
				...
			}else if (token.appWindowToken != null) {
    
    
                Slog.w(TAG_WM, "Non-null appWindowToken for system window of type=" + type);
                // It is not valid to use an app token with other system types; we will
                // instead make a new token for it (as if null had been passed in for the token).
                attrs.token = null;
                token = new WindowToken(this, null, -1, false);
                addToken = true;
            }
            ...
			//在WMS服务中为窗口创建WindowState对象
            WindowState win = new WindowState(this, session, client, token,
                    attachedWindow, appOp[0], seq, attrs, viewVisibility, displayContent);


			//以键值对<IWindow.Proxy/Token,WindowToken>形式保存到mTokenMap表中
            if (addToken) {
    
    
                mTokenMap.put(attrs.token, token);
            }
            win.attach();
			//以键值对<IWindow的代理对象,WindowState>形式保存到mWindowMap表中
            mWindowMap.put(client.asBinder(), win);
            ...
}

此方法中的逻辑很多,这个我们不关心其它的无关紧要的逻辑,只关心对传递过来的W的Binder代理的处理,在该方法中为为应用程序进程新增的窗口在WMS服务中创建对应的WindowState对象,并且将WMS接收应用程序进程的Session的Binder实体,应用程序进程中的W代理对象保存到WindowState中。我们来看下WindowState的构造方法如下:

//[WindowState.java]
    WindowState(WindowManagerService service, Session s, IWindow c, WindowToken token,
           WindowState attachedWindow, int appOp, int seq, WindowManager.LayoutParams a,
           int viewVisibility, final DisplayContent displayContent) {
    
    
        mService = service;//WMS的引用
        mSession = s;//和APP端对应的Session端
        mClient = c;//APP端W对应的Binder代理端
        mAppOp = appOp;
        mToken = token;//此处的Token为AppWindowToken,Token从AMS传递到WMS过程中创建的

		...
		WindowState appWin = this;
        while (appWin.isChildWindow()) {
    
    
            appWin = appWin.mAttachedWindow;
        }
        WindowToken appToken = appWin.mToken;
        mRootToken = appToken;
        mAppToken = appToken.appWindowToken;
        ...
   }

这里我们可以看到在该构造WindowState对象过程中基本都是常规操作,但是在该构造方法中又创建了一个IWindowId本地对象(它的作用是什么呢,主要用于标识指定窗口的,因此应用程序进程必定会获取该对象的代理对象)。关于IWindowId代理对象的获取过程在接下来分析。在WMS服务中为应用程序进程中的指定窗口创建了对应的WindowState对象后,接着调用该对象的attach()函数:

//[WindowState.java]
    void attach() {
    
    
        if (WindowManagerService.localLOGV) Slog.v(
            TAG, "Attaching " + this + " token=" + mToken
            + ", list=" + mToken.windows);
        mSession.windowAddedLocked();
    }

通过前面的分析我们可以看到在构造WindowState对象时,会将用于接收应用程序进程请求的本地Session对象保存在WindowState的成员变量mSession中,这里调用Session的windowAddedLocked()函数来创建请求SurfaceFlinger的SurfaceSession对象,同时将接收应用程序进程请求的Session保存到WMS服务的mSessions数组中。

//[WindowState.java]
    void windowAddedLocked() {
    
    
        if (mSurfaceSession == null) {
    
    
            if (WindowManagerService.localLOGV) Slog.v(
                TAG_WM, "First window added to " + this + ", creating SurfaceSession");
            mSurfaceSession = new SurfaceSession();
            if (SHOW_TRANSACTIONS) Slog.i(
                    TAG_WM, "  NEW SURFACE SESSION " + mSurfaceSession);
            mService.mSessions.add(this);
            if (mLastReportedAnimatorScale != mService.getCurrentAnimatorScale()) {
    
    
                mService.dispatchNewAnimatorScaleLocked(this);
            }
        }
        mNumWindow++;//记录对应的某个应用程序添加的窗口数量
    }

2.3 WMS服务建立和Android应用程序之间的Binder通信连接小结

至此WMS服务建立和Android应用程序之间的Binder通信连接就到此告一段落了,Android应用程序端窗口对应的W的代理端就传递到了WMS服务端,从而WMS就可以通过W的Binder代理端对Android应用程序的窗口进行相关的控制了,譬如修改窗口的大小等。这里我们看下WMS对Android应用窗口端的跨进程Binder调用,如下:

//[WindowState.java]
    private void dispatchResized(Rect frame, Rect overscanInsets, Rect contentInsets,
            Rect visibleInsets, Rect stableInsets, Rect outsets, boolean reportDraw,
            Configuration newConfig) throws RemoteException {
    
    
        final boolean forceRelayout = isDragResizeChanged() || mResizedWhileNotDragResizing;
		//此时的mClinet是W的Binder代理端
        mClient.resized(frame, overscanInsets, contentInsets, visibleInsets, stableInsets, outsets,
                reportDraw, newConfig, getBackdropFrame(frame),
                forceRelayout, mPolicy.isNavBarForcedShownLw(this));
        mDragResizingChangeReported = true;
    }

并且我们这里需要注意的是Android应用程序中的每个ViewRootImpl都拥有一个W对象,这个是和IWindowSession有所不同的,从而我们可以得到如下的对应关系:

在这里插入图片描述

其核心的建立通信的流程图如下:
在这里插入图片描述


三.IWindowId代理对象获取过程(选学)

  这里买一送一,还记得之前说过在构造WindowState对象过程中,创建了一个IWindowId本地对象,并保存在WindowState的成员变量mWindowId中,而对于此IWindowId我一直没有找到是用来干什么的,后面我全局搜索才发现了,在动画Transition的实现中会通过该IWindowId来标识具体的窗口(这个),如下:

XXX/ap/frameworks$ grep -nr "getWindowId()"  ./base/core/java/android/transition/Transition.java
776:                                sceneRoot.getWindowId(), infoValues);
1679:                WindowId windowId = sceneRoot.getWindowId();
1712:                WindowId windowId = sceneRoot.getWindowId();
1745:        WindowId windowId = sceneRoot.getWindowId();
1952:            WindowId windowId = sceneRoot.getWindowId();

这里我们简单看下,应用程序端是怎么获取标识每一个窗口对象的IWindowId的代理对象的,还记得我们前面章节ViewRootImpl的构造方法中会创建一个此处的mAttachInfo方法吗,它会在对UI进行布局的时候,在其performTraversals()方法中通过host.dispatchAttachedToWindow(mAttachInfo, 0)将mAttachInfo传递到View中去,此时mAttachInfo中就包含了和WMS进行通信的IWindowSession代理端,这就很显然的View可以通过它获取到,如下:

//[View.java]
    public WindowId getWindowId() {
    
    
        if (mAttachInfo == null) {
    
    
            return null;
        }
        if (mAttachInfo.mWindowId == null) {
    
    
            try {
    
    
	            /**获取WMS服务端的IWindowId代理对象,mAttachInfo在前面创建ViewRootImpl对象时创建
				 * mWindow = new W(this);
				 * mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this);
				 * 在AttachInfo的构造函数中:
				 * mWindow = window;
	                         * mWindowToken = window.asBinder();
				 * 因此AttachInfo的成员变量mWindow和mWindowToken引用了同一对象,该对象就是类型为W的本地binder对象
				 * 下面通过W本地binder对象,来获取AMS服务端的IWindowId的代理对象
				 */
                mAttachInfo.mIWindowId = mAttachInfo.mSession.getWindowId(
                        mAttachInfo.mWindowToken);
                //WindowId类的定义主要是为了方便在进程间传输IWindowId Binder对象
                mAttachInfo.mWindowId = new WindowId(
                        mAttachInfo.mIWindowId);
            } catch (RemoteException e) {
    
    
            }
        }
        return mAttachInfo.mWindowId;
    }

正如我们预想的那样,App应用程序进程还是通过Session的代理对象来获取IWindowId的代理对象(参数window在应用程序进程端为W本地binder对象,经过Binder传输到达WMS服务进程后,就转换为W的binder代理对象了)。我们看看WMS端是怎么处理的,如下:

//[Session.java]
    public IWindowId getWindowId(IBinder window) {
    
    
        return mService.getWindowId(window);
    }

真懒啥也不干,直接给WMS服务来处理,我们继续看下:

//[WindowManagerService.java]
    public IWindowId getWindowId(IBinder token) {
    
    
        synchronized (mWindowMap) {
    
    
        	通过W的binder代理对象从mWindowMap哈希表中查找对应的WindowState对象。
            WindowState window = mWindowMap.get(token);
            return window != null ? window.mWindowId : null;
        }
    }

最好在WMS服务中根据W的binder代理对象token在WMS中查找窗口对应的WindowState对象,再将该窗口在WindowState对象中创建的IWindowId Binder本地对象返回,这样,客户端进程就可以得到该Binder的代理对象了。

关于此处不是本文的重点,刚兴趣的就可以看下,不感兴趣可以直接过!

其获取过程可以通过下面的图简单来表示一下:

在这里插入图片描述




写在最后

  Android应用程序窗口设计之建立与WMS服务之间的通信过程到这里就要完结了,通过前面的介绍我们知道了Android应用程序和WMS之间的交互主要是通过IWindowManager与IWindow以及IWindowSession这三个接口来完成的,IWindowManager和IWindowSession实现了Android应用程序到WMS之间通信,而IWindow实现了WMS到Android应用程序之间的通信。但是我们这里发现了无论是借助于那个接口,最终都脱离不了Binder通信,所以所以Android源码的源码学习一定是绕不开Binder的,读者一定要有所掌握不然真的很难深入理解Android源码的一些设计理念(打个广告,可以看看我的系列博客Android Binder框架学习系列博客)。好了,青山不改绿水长流先到这里了。如果本博客对你有所帮助,麻烦关注或者点个赞,如果觉得很烂也可以踩一脚!谢谢各位了!!

在这里插入图片描述

参考博客:
Android 应用程序建立与WMS服务之间的通信过程

猜你喜欢

转载自blog.csdn.net/tkwxty/article/details/111152011
今日推荐