Android 源碼解析之WindowManager添加窗口

一,寫在前面       

 這篇文章先介紹如何使用WindowManager向設備窗口裏添加View,並顯示View,然後從源碼角度分析這一過程。

二,WindowManager的使用

WindowManager的使用,先來看看效果,如下所示:


代碼如下:

public class MainActivity extends Activity {

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		WindowManager wm = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
		
		//創建WindowManager的佈局參數對象
		WindowManager.LayoutParams params = new WindowManager.LayoutParams(
		LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT, 0, 0, PixelFormat.TRANSPARENT);
		
		params.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
	      //params.type = WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY;
		params.type = WindowManager.LayoutParams.TYPE_APPLICATION;
		
		params.gravity = Gravity.TOP | Gravity.LEFT;
		params.x = 50;
		params.y = 400;
		
		//創建一個View
		final TextView view = new TextView(this);
		view.setText("window_demo");
		view.setTextSize(24);
		view.setTextColor(Color.RED);
		view.setOnTouchListener(new OnTouchListener() {
			
			@Override
			public boolean onTouch(View v, MotionEvent event) {
				view.setTextColor(Color.BLUE);
				return false;
			}
		});

		//在窗口添加View
		wm.addView(view, params);		
	}
}

分析:

       1,LayoutParams是WindowManager中的一個內部類,它繼承至ViewGroup.LayoutParams,並實現了Parcelable接口,public static class LayoutParams extends ViewGroup.LayoutParams implements Parcelable{...}。需要重點關注LayoutParams中的兩個字段:type,flag。

       type:決定了窗口的類型,Android給窗口設置了三個層級,抽象來說分爲:應用程序窗口,子窗口,系統窗口。具體來說對應int值的範圍,分別爲1~99,1000~1999,2000~2999。上面代碼中TYPE_SYSTEM_OVERLAY是系統窗口類型的一種,TYPE_APPLICATION是應用程序窗口的一種。窗口層級越大,該窗口會在比它層級小的窗口上面顯示,也就是層級大的窗口會覆蓋層級較小的窗口。因此,若想讓窗口處在所有窗口上面顯示,可以將type設置爲系統窗口。一般可以設置系統窗口的type值有:TYPE_SYSTEM_OVERLAY,TYPE_SYSTEM_ERROR。


      特別需要注意的是:若將type設置爲系統窗口層級時,需要在AndroidManifest.xml文件中配置權限“android.permission.SYSTEM_ALERT_WINDOW”,否則會報異常信息。

如下所示:


        

        flag:決定窗口的顯示特性,上面代碼中的FLAG_NOT_TOUCH_MODAL指:可以處理窗口區域裏的事件,區域外的事件傳給底層的Window。flag有很多值可以選擇,並使窗口顯示不同的特性,有興趣哥們可以自己去研究。

        另外,LayoutParams還提供gravity,x,y三個屬性,gravity:決定窗口的重心位置,如果gravity不設置,窗口默認在屏幕中間。值得一提的是,x,y均是相對於設置好gravity後的偏移量。


        2,上例中給TextView設置了觸摸的監聽,點擊TextView後修改其顏色爲藍色。若設置窗口類型爲TYPE_SYSTEM_OVERLAY,無法響應觸摸動作,顏色不會改變。這是爲什麼呢?因爲系統窗口不接收焦點,否則會干擾鍵盤窗口的輸入。翻看Android給TYPE_SYSTEM_OVERLAY的註釋便知,如下:

/**
         * Window type: system overlay windows, which need to be displayed
         * on top of everything else.  These windows must not take input
         * focus, or they will interfere with the keyguard.
         * In multiuser systems shows only on the owning user's window.
         */
        public static final int TYPE_SYSTEM_OVERLAY     = FIRST_SYSTEM_WINDOW+6;
       值得一提的是,即使設備當前顯示界面不是這個主界面,而是顯示桌面,系統窗口仍然顯示(有些手機需要手動設置懸浮框開啓,纔可以看到效果)。

效果如下:


     上面介紹了WindowManager的一個簡單使用,將TextView添加到窗口中並顯示,下面分析WindowManager源碼角度的分析。


三,WindowManager源碼分析

     上面調用wm.addView(view,params)方法添加窗口,並沒有涉及到Window,有木有覺得很奇怪,下面來解惑。
      1,首先,瞭解Window,public abstract class Window。Window是一個抽象類,提取了窗口需要的一些方法,並不是一個真實意義上的窗口。Window有且只有一個子類PhoneWindow,public class PhoneWindow extends Window implements MenuBuilder.Callback,Window的實例化的體現由PhoneWindow來完成。任何一個View都對應一個Window,也就是說View不能孤立存在的顯示,需要依靠一個Window。
 
    ,2,然後瞭解接口ViewManager,接口WindowManager,類WindowManagerImpl之間的關係:ViewManager是爺爺,WindowManager是爸爸,WindowManagerImpl是孩子。
     ViewManager如下:
public interface ViewManager
{
    /**
     * Assign the passed LayoutParams to the passed View and add the view to the window.
     * <p>Throws {@link android.view.WindowManager.BadTokenException} for certain programming
     * errors, such as adding a second view to a window without removing the first view.
     * <p>Throws {@link android.view.WindowManager.InvalidDisplayException} if the window is on a
     * secondary {@link Display} and the specified display can't be found
     * (see {@link android.app.Presentation}).
     * @param view The view to be added to this window.
     * @param params The LayoutParams to assign to view.
     */
    public void addView(View view, ViewGroup.LayoutParams params);
    public void updateViewLayout(View view, ViewGroup.LayoutParams params);
    public void removeView(View view);
}
      添加,更新,刪除Window的三個方法由ViewManager抽取定義,具體的實現在WindowManagerImpl。
      
      WindowManage如下:
public interface WindowManager extends ViewManager {

	//...

	public static class LayoutParams extends ViewGroup.LayoutParams implements Parcelable {
		//...
		
		int type;
		int flag;
		
		//...
	}
	//...

}

WidowManagerImpl中操作窗口的三個方法,具體實現如下:
public final class WindowManagerImpl implements WindowManager {

 //...code

 @Override
    public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
        applyDefaultToken(params);
        mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
    }

    @Override
    public void updateViewLayout(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
        applyDefaultToken(params);
        mGlobal.updateViewLayout(view, params);
    }

    @Override
    public void removeView(View view) {
        mGlobal.removeView(view, false);
    }
	
    //...code

}
      上面簡單介紹了抽象類Window,類WindowPhone,接口ViewManager,接口WindowManager,類WindowManagerImpl。

3.1,進入主題

      下面正式開始,從源碼角度分析WindowManager添加window的過程,實際上添加window具體由添加View來體現。上面的demo中,在onCreate方法中做了一些這樣的操作:獲取了WindowManager對象,並調用wm.addView方法添加窗口。

      需要注意的是,在啓動Activity的時候,在onCreate方法調用之前,會先調用attach方法。至於啓動Activity的具體流程這裏就不做介紹了,後面會碼一篇blog出來介紹。好了,直接看attach方法。
     Activity$attach如下:
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) {
        attachBaseContext(context);

        mFragments.attachHost(null /*parent*/);

        mWindow = new PhoneWindow(this, window);
        mWindow.setWindowControllerCallback(this);
        mWindow.setCallback(this);
        mWindow.setOnWindowDismissedCallback(this);
        mWindow.getLayoutInflater().setPrivateFactory(this);
        if (info.softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) {
            mWindow.setSoftInputMode(info.softInputMode);
        }
        if (info.uiOptions != 0) {
            mWindow.setUiOptions(info.uiOptions);
        }
        mUiThread = Thread.currentThread();

        mMainThread = aThread;
        mInstrumentation = instr;
        mToken = token;
        mIdent = ident;
        mApplication = application;
        mIntent = intent;
        mReferrer = referrer;
        mComponent = intent.getComponent();
        mActivityInfo = info;
        mTitle = title;
        mParent = parent;
        mEmbeddedID = id;
        mLastNonConfigurationInstances = lastNonConfigurationInstances;
        if (voiceInteractor != null) {
            if (lastNonConfigurationInstances != null) {
                mVoiceInteractor = lastNonConfigurationInstances.voiceInteractor;
            } else {
                mVoiceInteractor = new VoiceInteractor(voiceInteractor, this, this,
                        Looper.myLooper());
            }
        }

        mWindow.setWindowManager(
                (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
                mToken, mComponent.flattenToString(),
                (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
        if (mParent != null) {
            mWindow.setContainer(mParent.getWindow());
        }
        mWindowManager = mWindow.getWindowManager();
        mCurrentConfig = config;
    }
        這個方法有很多重要的代碼,這裏我們只分析和WindowManager相關的代碼。12行,創建了PhoneWindow的實例;47行,創建WindowManagerImpl的實例,並賦值給字段Window$mWindowManager;54行 ,返回字段Window$mWindowManager的值,並賦值給字段Activity$mWindowManager。
       Window$setWindowManager方法中創建了WindowManagerImpl的實例,源碼如下:
public void setWindowManager(WindowManager wm, IBinder appToken, String appName,
            boolean hardwareAccelerated) {
        mAppToken = appToken;
        mAppName = appName;
        mHardwareAccelerated = hardwareAccelerated
                || SystemProperties.getBoolean(PROPERTY_HARDWARE_UI, false);
        if (wm == null) {
            wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
        }
        mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
    }
     繼續看WindowManagerImpl$createLocalWindowManager方法,源碼如下:
public WindowManagerImpl createLocalWindowManager(Window parentWindow) {
        return new WindowManagerImpl(mContext, parentWindow);
    }
       小結:在attach方法中,創建了Window的實例,並通過Window獲取到了WindowManagerImpl的實例,並賦值給Activity中的字段mWindowManager。Window與WindowManager的關係一目瞭然。
        
        在前面demo的onCreate方法中,調用了WindowManager wm = (WindowManager) getSystemService(Context.WINDOW_SERVICE)。
        查看Activity$getSystemService源碼,如下:
@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)) {
            return mWindowManager;
        } else if (SEARCH_SERVICE.equals(name)) {
            ensureSearchManager();
            return mSearchManager;
        }
        return super.getSystemService(name);
    }
       當name爲WINDOW_SERVICE時,返回字段mWindowManager。這個mWindowManager在前面的attach方法中已經賦值了,它是一個WindowManagerImpl對象。也就是說,demo中的wm就是WindowManagerImpl對象。那麼,wm.addView方法調用的是WindowManagerImpl的addView(...)。
      查看WindowManagerImpl$addView源碼:
    @Override
    public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
        applyDefaultToken(params);
        mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
    }
     WindowManagerImpl重寫了ViewManager中的addView方法,前面已經介紹過了三者之間的關係了。mGlobal變量是WindowManagerGlobal對象,在類WindowManagerImpl中創建了WindowManagerGlobal對象:private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance(),mGlobal是一個單例對象。

     查看WindowManagerGlobal$addView源碼:
    public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow) {
        if (view == null) {
            throw new IllegalArgumentException("view must not be null");
        }
        if (display == null) {
            throw new IllegalArgumentException("display must not be null");
        }
        if (!(params instanceof WindowManager.LayoutParams)) {
            throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
        }

        final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
        if (parentWindow != null) {
            parentWindow.adjustLayoutParamsForSubWindow(wparams);
        } else {
            // If there's no parent, then hardware acceleration for this view is
            // set from the application's hardware acceleration setting.
            final Context context = view.getContext();
            if (context != null
                    && (context.getApplicationInfo().flags
                            & ApplicationInfo.FLAG_HARDWARE_ACCELERATED) != 0) {
                wparams.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
            }
        }

        ViewRootImpl root;
        View panelParentView = null;

        synchronized (mLock) {
            // Start watching for system property changes.
            if (mSystemPropertyUpdater == null) {
                mSystemPropertyUpdater = new Runnable() {
                    @Override public void run() {
                        synchronized (mLock) {
                            for (int i = mRoots.size() - 1; i >= 0; --i) {
                                mRoots.get(i).loadSystemProperties();
                            }
                        }
                    }
                };
                SystemProperties.addChangeCallback(mSystemPropertyUpdater);
            }

            int index = findViewLocked(view, false);
            if (index >= 0) {
                if (mDyingViews.contains(view)) {
                    // Don't wait for MSG_DIE to make it's way through root's queue.
                    mRoots.get(index).doDie();
                } else {
                    throw new IllegalStateException("View " + view
                            + " has already been added to the window manager.");
                }
                // The previous removeView() had not completed executing. Now it has.
            }

            // If this is a panel window, then find the window it is being
            // attached to for future reference.
            if (wparams.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW &&
                    wparams.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {
                final int count = mViews.size();
                for (int i = 0; i < count; i++) {
                    if (mRoots.get(i).mWindow.asBinder() == wparams.token) {
                        panelParentView = mViews.get(i);
                    }
                }
            }

            root = new ViewRootImpl(view.getContext(), display);

            view.setLayoutParams(wparams);

            mViews.add(view);
            mRoots.add(root);
            mParams.add(wparams);

            // do this last because it fires off messages to start doing things
            try {
                root.setView(view, wparams, panelParentView);
            } catch (RuntimeException e) {
                // BadTokenException or InvalidDisplayException, clean up.
                if (index >= 0) {
                    removeViewLocked(index, true);
                }
                throw e;
            }
        }
    }
      第3行,第6行,第9行分別對參數view,display,params進行檢查;
      第14行,若該窗口類型爲子窗口,調用adjustLayoutParamsForSubWindow方法對佈局參數進行處理;
      第69行創建了ViewRootImpl對象,也就是說每次調用addView方法,都會創建一個ViewRootImpl對象。
      第71行給窗口中的View設置佈局參數LayoutParams;
      第73,74,75行分別將View對象,ViewRootImpl對象,LayoutParams對象存入三個集合中,再刪除更新窗口時也是從該集合裏取出來;
      第79行,調用ViewRootImpl的setView方法完成window的添加。
      
      查看ViewRootImpl$setView源碼:
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {

	//...code
	
	requestLayout();
	
	//...code

	res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                            getHostVisibility(), mDisplay.getDisplayId(),
                            mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                            mAttachInfo.mOutsets, mInputChannel);
	
	//...code

}
      setView方法代碼比較多,這裏只展示比較重要的兩個點。requestLayout()方法是View繪製流程的入口,mWindowSession.addToDisplay是真正實現window添加的地方,mWindowSession到底是什麼呢,繼續往下分析。
      先查看ViewRootImpl的構造函數:
 public ViewRootImpl(Context context, Display display) {
        mContext = context;
	//通過Binder機制實現,獲取mWindowSession,即Session
        mWindowSession = WindowManagerGlobal.getWindowSession();

	//...code
}
      繼續查看WindowManagerGlobal相關方法的源碼:
public static IWindowSession getWindowSession() {
        synchronized (WindowManagerGlobal.class) {
            if (sWindowSession == null) {
                try {
                    InputMethodManager imm = InputMethodManager.getInstance();

                    IWindowManager windowManager = getWindowManagerService();
                    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;
        }
    }

//繼續看getWindowManagerService方法

public static IWindowManager getWindowManagerService() {
        synchronized (WindowManagerGlobal.class) {
            if (sWindowManagerService == null) {
                sWindowManagerService = IWindowManager.Stub.asInterface(
                        ServiceManager.getService("window"));
                try {
                    if (sWindowManagerService != null) {
                        ValueAnimator.setDurationScale(
                                sWindowManagerService.getCurrentAnimatorScale());
                    }
                } catch (RemoteException e) {
                    throw e.rethrowFromSystemServer();
                }
            }
            return sWindowManagerService;
        }
}
       第29行獲取代理對象sWindowManagerService,那麼遠程進程的系統服務必然extends IWindowManager.Stub,搜索可知爲WindowManagerService。第8行,代理對象windowManager調用openSession方法,裏面會調用transact方法。通過Binder機制,onTransact方法會被回調,裏面會調用遠程服務WindowManagerService的openSession方法。
      
      查看WindowManagerService$openSession源碼:
@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 = new Session(this, callback, client, inputContext);
        return session;
    }
       裏面創建了一個Session對象,也就是說前面mWindowSession就是Session對象。
       
       小結:在本地進程中創建ViewRootImpl對象,調用其構造方法時,通過Binder機制,調用遠程服務WindowManagerService的openSession方法,創建了一個Session對象。Session對象的addToDisplay方法,查看其源碼如下:
    @Override
    public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs,
            int viewVisibility, int displayId, Rect outContentInsets, Rect outStableInsets,
            Rect outOutsets, InputChannel outInputChannel) {
        return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId,
                outContentInsets, outStableInsets, outOutsets, outInputChannel);
    }
       這裏的mService就是遠程的系統服務WindowManagerService,也就說添加window的操作,最終交給了系統服務WindowManagerService來處理。

四,最後

      使用WindowManager添加window的過程:其實是交由ViewRootImpl的setView方法來完成,該方法裏通過Binder機制獲取到Session對象,Session內部添加window的操作是交給WindowManagerService來完成。

五,另外

WindowManagerService具體是如何一步步完成添加window的操作,本篇文章不做分析,只帶着大家分析到這裏,有興趣的哥們可以查看老羅的博客。

    

                     














     






發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章