主頁 > 移動端開發 > Android應用程式視窗設計之建立與WMS服務之間的通信程序

Android應用程式視窗設計之建立與WMS服務之間的通信程序

2020-12-16 13:27:31 移動端開發

???Android應用程式視窗設計之建立與WMS服務之間的通信程序


Android應用程式視窗設計系列博客:

Android應用程式視窗設計之Window及WindowManager的創建
Android應用程式視窗設計之setContentView布局加載的實作
普法Android的Token前世今生以及在APP,AMS,WMS之間傳遞
Android應用程式視窗設計之視窗的添加
Android應用程式視窗設計之建立與WMS服務之間的通信程序


本篇博客撰寫思路總結和關鍵點說明:

在這里插入圖片描述
為了更加方便的讀者閱讀博客,通過導讀思維圖的形式將本博客的關鍵點列舉出來,從而方便讀者取舍和閱讀!



引言

??對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服務之間的通信程序了,

在這里插入圖片描述



一.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服務之間的通信程序

轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/235522.html

標籤:其他

上一篇:Cordova打包流程(其中一種簡單的方法)

下一篇:自定義View從實作到原理(二)- 原始碼決議Activity的構成

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【從零開始擼一個App】Dagger2

    Dagger2是一個IOC框架,一般用于Android平臺,第一次接觸的朋友,一定會被搞得暈頭轉向。它延續了Java平臺Spring框架代碼碎片化,注解滿天飛的傳統。嘗試將各處代碼片段串聯起來,理清思緒,真不是件容易的事。更不用說還有各版本細微的差別。 與Spring不同的是,Spring是通過反射 ......

    uj5u.com 2020-09-10 06:57:59 more
  • Flutter Weekly Issue 66

    新聞 Flutter 季度調研結果分享 教程 Flutter+FaaS一體化任務編排的思考與設計 詳解Dart中如何通過注解生成代碼 GitHub 用對了嗎?Flutter 團隊分享如何管理大型開源專案 插件 flutter-bubble-tab-indicator A Flutter librar ......

    uj5u.com 2020-09-10 06:58:52 more
  • Proguard 常用規則

    介紹 Proguard 入口,如何查看輸出,如何使用 keep 設定入口以及使用實體,如何配置壓縮,混淆,校驗等規則。

    ......

    uj5u.com 2020-09-10 06:59:00 more
  • Android 開發技術周報 Issue#292

    新聞 Android即將獲得類AirDrop功能:可向附近設備快速分享檔案 谷歌為安卓檔案管理應用引入可安全隱藏資料的Safe Folder功能 Android TV新主界面將顯示電影、電視節目和應用推薦內容 泄露的Android檔案暗示了傳說中的谷歌Pixel 5a與折疊屏新機 谷歌發布Andro ......

    uj5u.com 2020-09-10 07:00:37 more
  • AutoFitTextureView Error inflating class

    報錯: Binary XML file line #0: Binary XML file line #0: Error inflating class xxx.AutoFitTextureView 解決: <com.example.testy2.AutoFitTextureView android: ......

    uj5u.com 2020-09-10 07:00:41 more
  • 根據Uri,Cursor沒有獲取到對應的屬性

    Android: 背景:呼叫攝像頭,拍攝視頻,指定保存的地址,但是回傳的Cursor檔案,只有名稱和大小的屬性,沒有其他諸如時長,連ID屬性都沒有 使用 cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DURATIO ......

    uj5u.com 2020-09-10 07:00:44 more
  • Android連載29-持久化技術

    一、持久化技術 我們平時所使用的APP產生的資料,在記憶體中都是瞬時的,會隨著斷電、關機等丟失資料,因此android系統采用了持久化技術,用于存盤這些“瞬時”資料 持久化技術包括:檔案存盤、SharedPreference存盤以及資料庫存盤,還有更復雜的SD卡記憶體儲。 二、檔案存盤 最基本存盤方式, ......

    uj5u.com 2020-09-10 07:00:47 more
  • Android Camera2Video整合到自己專案里

    背景: Android專案里呼叫攝像頭拍攝視頻,原本使用的 MediaStore.ACTION_VIDEO_CAPTURE, 后來因專案需要,改成了camera2 1.Camera2Video 官方demo有點問題,下載后,不能直接整合到專案 問題1.多次拍攝視頻崩潰 問題2.雙擊record按鈕, ......

    uj5u.com 2020-09-10 07:00:50 more
  • Android 開發技術周報 Issue#293

    新聞 谷歌為Android TV開發者提供多種新功能 Android 11將自動填表功能整合到鍵盤輸入建議中 谷歌宣布Android Auto即將支持更多的導航和數字停車應用 谷歌Pixel 5只有XL版本 搭載驍龍765G且將比Pixel 4更便宜 [圖]Wear OS將迎來重磅更新:應用啟動時間 ......

    uj5u.com 2020-09-10 07:01:38 more
  • 海豚星空掃碼投屏 Android 接收端 SDK 集成 六步驟

    掃碼投屏,開放網路,獨占設備,不需要額外下載軟體,微信掃碼,發現設備。支持標準DLNA協議,支持倍速播放。視頻,音頻,圖片投屏。好點意思。還支持自定義基于 DLNA 擴展的操作動作。好像要收費,沒體驗。 這里簡單記錄一下集成程序。 一 跟目錄的build.gradle添加私有mevan倉庫 mave ......

    uj5u.com 2020-09-10 07:01:43 more
最新发布
  • 歡迎頁輪播影片

    如圖,引導開始,球從上落下,同時淡入文字,然后文字開始輪播,最后一頁時停止,點擊進入首頁。 在來看看效果圖。 重力球先不講,主要歡迎輪播簡單實作 首先新建一個類 TextTranslationXGuideView,用于影片展示 文本是類似的,最后會有個圖片箭頭影片,布局很簡單,就是一個 TextVi ......

    uj5u.com 2023-04-20 08:40:31 more
  • 【FAQ】關于華為推送服務因營銷訊息頻次管控導致服務通訊類訊息

    一. 問題描述 使用華為推送服務下發IM訊息時,下發訊息請求成功且code碼為80000000,但是手機總是收不到訊息; 在華為推送自助分析(Beta)平臺查看發現,訊息發送觸發了頻控。 二. 問題原因及背景 2023年1月05日起,華為推送服務對咨詢營銷類訊息做了單個設備每日推送數量上限管理,具體 ......

    uj5u.com 2023-04-20 08:40:11 more
  • 歡迎頁輪播影片

    如圖,引導開始,球從上落下,同時淡入文字,然后文字開始輪播,最后一頁時停止,點擊進入首頁。 在來看看效果圖。 重力球先不講,主要歡迎輪播簡單實作 首先新建一個類 TextTranslationXGuideView,用于影片展示 文本是類似的,最后會有個圖片箭頭影片,布局很簡單,就是一個 TextVi ......

    uj5u.com 2023-04-20 08:39:36 more
  • 【FAQ】關于華為推送服務因營銷訊息頻次管控導致服務通訊類訊息

    一. 問題描述 使用華為推送服務下發IM訊息時,下發訊息請求成功且code碼為80000000,但是手機總是收不到訊息; 在華為推送自助分析(Beta)平臺查看發現,訊息發送觸發了頻控。 二. 問題原因及背景 2023年1月05日起,華為推送服務對咨詢營銷類訊息做了單個設備每日推送數量上限管理,具體 ......

    uj5u.com 2023-04-20 08:39:13 more
  • iOS從UI記憶體地址到讀取成員變數(oc/swift)

    開發除錯時,我們發現bug時常首先是從UI顯示發現例外,下一步才會去定位UI相關連的資料的。XCode有給我們提供一系列debug工具,但是很多人可能還沒有形成一套穩定的除錯流程,因此本文嘗試解決這個問題,順便提出一個暴論:UI顯示例外問題只需要兩個步驟就能完成定位作業的80%: 定位例外 UI 組 ......

    uj5u.com 2023-04-19 09:16:23 more
  • FIDE重磅更新!性能飛躍!體驗有禮!

    FIDE 開發者工具重構升級啦!實作500%性能提升,誠邀體驗! 一直以來不少開發者朋友在社區反饋,在使用 FIDE 工具的程序中,時常會遇到諸如加載不及時、代碼預覽/渲染性能不如意的情況,十分影響開發體驗。 作為技術團隊,我們深知一件趁手的開發工具對開發者的重要性,因此,在2023年開年,FinC ......

    uj5u.com 2023-04-19 09:16:15 more
  • 游戲內嵌社區服務開放,助力開發者提升玩家互動與留存

    華為 HMS Core 游戲內嵌社區服務提供快速訪問華為游戲中心論壇能力,支持玩家直接在游戲內瀏覽帖子和交流互動,助力開發者擴展內容生產和觸達的場景。 一、為什么要游戲內嵌社區? 二、游戲內嵌社區的典型使用場景 1、游戲內打開論壇 您可以在游戲內繪制論壇入口,為玩家提供沉浸式發帖、瀏覽、點贊、回帖、 ......

    uj5u.com 2023-04-19 09:15:46 more
  • iOS從UI記憶體地址到讀取成員變數(oc/swift)

    開發除錯時,我們發現bug時常首先是從UI顯示發現例外,下一步才會去定位UI相關連的資料的。XCode有給我們提供一系列debug工具,但是很多人可能還沒有形成一套穩定的除錯流程,因此本文嘗試解決這個問題,順便提出一個暴論:UI顯示例外問題只需要兩個步驟就能完成定位作業的80%: 定位例外 UI 組 ......

    uj5u.com 2023-04-19 09:14:53 more
  • FIDE重磅更新!性能飛躍!體驗有禮!

    FIDE 開發者工具重構升級啦!實作500%性能提升,誠邀體驗! 一直以來不少開發者朋友在社區反饋,在使用 FIDE 工具的程序中,時常會遇到諸如加載不及時、代碼預覽/渲染性能不如意的情況,十分影響開發體驗。 作為技術團隊,我們深知一件趁手的開發工具對開發者的重要性,因此,在2023年開年,FinC ......

    uj5u.com 2023-04-19 09:14:08 more
  • 游戲內嵌社區服務開放,助力開發者提升玩家互動與留存

    華為 HMS Core 游戲內嵌社區服務提供快速訪問華為游戲中心論壇能力,支持玩家直接在游戲內瀏覽帖子和交流互動,助力開發者擴展內容生產和觸達的場景。 一、為什么要游戲內嵌社區? 二、游戲內嵌社區的典型使用場景 1、游戲內打開論壇 您可以在游戲內繪制論壇入口,為玩家提供沉浸式發帖、瀏覽、點贊、回帖、 ......

    uj5u.com 2023-04-19 09:08:34 more