Android 源码解析之WindowManager添加窗口

一,写在前面       

 这篇文章先介绍如何使用WindowManager向设备窗口里添加View,并显示View,然后从源码角度分析这一过程。

二,WindowManager的使用

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

Android 源码解析之WindowManager添加窗口_第1张图片

代码如下:

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;
       值得一提的是,即使设备当前显示界面不是这个主界面,而是显示桌面,系统窗口仍然显示(有些手机需要手动设置悬浮框开启,才可以看到效果)。

效果如下:

Android 源码解析之WindowManager添加窗口_第2张图片

     上面介绍了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.
     * 

Throws {@link android.view.WindowManager.BadTokenException} for certain programming * errors, such as adding a second view to a window without removing the first view. *

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的操作,本篇文章不做分析,只带着大家分析到这里,有兴趣的哥们可以查看老罗的博客。

    

                     














     






你可能感兴趣的:(android)