preface

From the perspective of Android App, Window is an abstract concept. It is the bearer of View. The Windows Manager, as the name suggests, is the manager of the Window. The addView method adds the View to the Window and finally displays it on the screen.

Series of articles:

How does Android Window determine the size /onMeasure() reason for executing multiple times

Through this article, you will learn:

1, the Window/WindowManager create, properties, and its use. 2, WindowManager LayoutParams flag properties of 3 key/touch events, how the View associated with the Window 4, WindowManager common scene

Window/WindowManager creation and use

Let’s take a look at the simple process of adding a View to a Window

Private void showView() {// Get WindowManager instance, Application WindowManager wm = (WindowManager) app.getApplication ().getSystemService(context.window_service); / / set properties LayoutParams WindowManager. LayoutParams LayoutParams = new WindowManager. LayoutParams (); layoutParams.height = 400; layoutParams.width = 400; layoutParams.format = PixelFormat.RGBA_8888; / / window tag attributes layoutParams. Flags = WindowManager. LayoutParams. FLAG_NOT_TOUCH_MODAL | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; //Window type if (build.version.sdk_int >= build.version_codes.o) {layoutparams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; } else { layoutParams.type = WindowManager.LayoutParams.TYPE_PHONE; TextView = new TextView(this); textView.setBackground(new ColorDrawable(Color.WHITE)); textView.setText("hello windowManager"); // Add textView to WindowManager wm.addView(textView, layoutParams); }Copy the code

The effect is as follows:


1. Get the WindowManager object. 2. Set the LayoutParams property

I gets the WindowManager object

App is inherited from Application, app.getApplication () gets the Application instance of the current Application, which is itself a Context. About Context please move: Android various Context past life

public interface WindowManager extends ViewManager
Copy the code

WindowManager is an interface that inherits ViewManager, which is also an interface. Let’s look at its contents:

Public interface ViewManager {// Add View, View represents the content itself, Public void addView(view view, viewgroup. LayoutParams params); View public void updateViewLayout(View View, viewGroup.layoutParams params); Public void removeView(View View); }Copy the code

Since WindowManager is an interface, there must be a class that implements it. The answer is in getSystemService(Context.window_service).

ContextImpl.java
    @Override
    public Object getSystemService(String name) {
        return SystemServiceRegistry.getSystemService(this, name);
    }
Copy the code
SystemServiceRegistry.java public static Object getSystemService(ContextImpl ctx, String name) { ServiceFetcher<? > fetcher = SYSTEM_SERVICE_FETCHERS.get(name); return fetcher ! = null ? fetcher.getService(ctx) : null; } registerService(Context.WINDOW_SERVICE, WindowManager .class, new CachedServiceFetcher<WindowManager>() { @Override public WindowManager createService (ContextImpl ctx){ return new WindowManagerImpl(ctx); }});Copy the code
  • The WindowManager implementation class is WindowManagerImpl

WindowManagerImpl doesn’t have much content

Public final class WindowManagerImpl implements WindowManager {//WindowManagerImpl implements WindowManager global singleton private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance(); Private final Context mContext; Private final Window mParentWindow; // Private IBinder mDefaultToken is assigned when an Activity is associated; public WindowManagerImpl(Context context) { this(context, null); } private WindowManagerImpl(Context context, Window parentWindow) { mContext = context; mParentWindow = parentWindow; } public WindowManagerImpl createLocalWindowManager(Window parentWindow) { return new WindowManagerImpl(mContext, parentWindow); } // omit}Copy the code
  • Its implementation of the add/update/remove methods is ultimately implemented by Windows ManagerGlobal.
  • WindowManagerGlobal records information about all Windows displayed in the App

II Set the LayoutParams property

WindowManager. LayoutParams inherited from the ViewGroup. LayoutParams, take a look at some of our focus on the properties of the:

Width: specifies the width of the Window. Height: specifies the height of the Window. X: specifies the offset of the Window on the X-axis of the screen. The offset of the Window on the Y axis of the screen (the offset starts at the gravity set position) flags: controls the behavior of the Window, such as whether the lower Window can get the click event, and whether the Window can be displayed outside the screen. FIRST_APPLICATION_WINDOW ~ LAST_APPLICATION_WINDOW (1~99) Application window FIRST_SUB_WINDOW ~ LAST_SUB_WINDOW (1000 ~ 1999) child window FIRST_SYSTEM_WINDOW ~ LAST_SYSTEM_WINDOW (2000 ~ 2999) The higher the value of the system window is, the higher the hierarchy is. Gravity: indicates the location of Windows. The value is set from gravity windowAnimations: indicates Window animations

In this example, the type we set belongs to the system window, which requires the user to open the permissions, and corresponds to the Settings: “Display on top of other applications” Check and obtain applications in the Activity as follows:

    public void onClick(View view) {
        if (checkPermission(this)) {
            showView();
        } else {
            Intent intent = getPermissionIntent(this);
            if (intent != null) {
                try {
                    startActivityForResult(intent, 100);
                } catch (Exception e) {
                    Log.d("hello", "error");
                }
            } else {
            }
        }
    }

    public static boolean checkPermission(@NonNull Context context) {

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            return Settings.canDrawOverlays(context);
        } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            int op = 24;
            AppOpsManager manager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
            try {
                Class clazz = AppOpsManager.class;
                Method method = clazz.getDeclaredMethod("checkOp", int.class, int.class, String.class);
                return AppOpsManager.MODE_ALLOWED == (int) method.invoke(manager, op, Binder.getCallingUid(), context.getPackageName());
            } catch (Exception e) {
                return false;
            }
        } else {
            return true;
        }
    }

    public static Intent getPermissionIntent(@NonNull Context context) {

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            return new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:" + context.getPackageName()));
        } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            String brand = Build.BRAND;
            if (TextUtils.isEmpty(brand)) {
                return null;
            }
            return null;
        } else {
            return null;
        }
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (requestCode == 100) {
            if (checkPermission(this)) {
                showView();
            }
        }
    }
Copy the code

You also need to declare permissions in androidmanifest.xml

<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
Copy the code

III Add View to Window

wm.addView(textView, layoutParams)

WindowManagerGlobal.java public void addView(View view, ViewGroup.LayoutParams params, Display display, Window parentWindow) { final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params; if (parentWindow ! . = null) {/ / adjust LayoutParams parentWindow adjustLayoutParamsForSubWindow (wparams); } else {// omit} ViewRootImpl root; View panelParentView = null; Synchronized (mLock) {// Construct ViewRootImpl root = new ViewRootImpl(view.getContext(), display); view.setLayoutParams(wparams); //mViews //mRoots //mViews //mParams // mviews.add (view); mRoots.add(root); mParams.add(wparams); Try {// Call ViewRootImpl setView root.setView(view, wparams, panelParentView); } catch (RuntimeException e) {}} // omit}Copy the code

The actual window addition is achieved through the ViewRootImpl setView(xx) method

ViewRootImpl.java public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) { synchronized (this) { if (mView == null) { mView = view; // omit int res; RequestLayout (); // Submit the View to display the request (measure, layout, draw), just submit it to the queue Try {// add to window // interprocess communication, Tell WindowManagerService open a Window for us space res = mWindowSession. AddToDisplay (mWindow mSeq, mWindowAttributes, getHostVisibility(), mDisplay.getDisplayId(), mTmpFrame, mAttachInfo.mContentInsets, mAttachInfo.mStableInsets, mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel, mTempInsets); } catch (RemoteException e) {} finally {} if (res < WindowManagerGlobal.ADD_OKAY) { // The mParent of the Window root View is the ViewRootImpl and the mParent of other views is its parent. // Input event-related touch and key events to receive CharSequence counterSuffix = attrs.getTitle(); mSyntheticInputStage = new SyntheticInputStage(); InputStage viewPostImeStage = new ViewPostImeInputStage(mSyntheticInputStage); InputStage nativePostImeStage = new NativePostImeInputStage(viewPostImeStage, "aq:native-post-ime:" + counterSuffix); InputStage earlyPostImeStage = new EarlyPostImeInputStage(nativePostImeStage); InputStage imeStage = new ImeInputStage(earlyPostImeStage, "aq:ime:" + counterSuffix); InputStage viewPreImeStage = new ViewPreImeInputStage(imeStage); InputStage nativePreImeStage = new NativePreImeInputStage(viewPreImeStage, "aq:native-pre-ime:" + counterSuffix); }}}Copy the code

For requestLayout(), go: Android Activity creates a display to View

Using Binder, ViewRootImpl communicate with WindowManagerService establish Session mWindowSession. AddToDisplay simple look at subsequent calls (interested can deep source and see).

Session.java
    public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs,
            int viewVisibility, int displayId, Rect outFrame, Rect outContentInsets,
            Rect outStableInsets, Rect outOutsets,
            DisplayCutout.ParcelableWrapper outDisplayCutout, InputChannel outInputChannel,
            InsetsState outInsetsState) {
        return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId, outFrame,
                outContentInsets, outStableInsets, outOutsets, outDisplayCutout, outInputChannel,
                outInsetsState);
    }
Copy the code
WindowManagerService.java public int addWindow(Session session, IWindow client, int seq, LayoutParams attrs, int viewVisibility, int displayId, Rect outFrame, Rect outContentInsets, Rect outStableInsets, Rect outOutsets, DisplayCutout. ParcelableWrapper outDisplayCutout, InputChannel outInputChannel, InsetsState outInsetsState) {/ / omitted}Copy the code

WindowManager. LayoutParams flag attribute key/touch events

We know that an Activity is actually presented as a Window, and now that another Window is added to the Activity, how does the key/touch event decide which Window to distribute to?

    public static final int FLAG_NOT_TOUCHABLE      = 0x00000010;
    public static final int FLAG_NOT_FOCUSABLE      = 0x00000008;
    public static final int FLAG_NOT_TOUCH_MODAL    = 0x00000020;
    public static final int FLAG_WATCH_OUTSIDE_TOUCH = 0x00040000;
    public static final int FLAG_ALT_FOCUSABLE_IM = 0x00020000;
Copy the code

Flag defaults to 0, and without flag, Window2 accepts all touch/key events by default, even if the clicked area is outside Window2’s scope.

FLAG_NOT_TOUCHABLE

Indicates that Window does not receive all touch events. At this point, whether you click on the Window2 region or a region outside of Window2, the Touch event is distributed to the next layer of Window1. Key events are not affected.

FLAG_NOT_FOCUSABLE

The Window does not receive input focus and does not interact with the keyboard. For example, when you use editText in a Window, you can’t pop the keyboard. Another function is: When clicking on a region outside of Window2, touch events are distributed to Window1, whereas clicking on a Window2 region is distributed to Window2 itself, and key events are not distributed to Window2, but to Window1 (this is equivalent to FLAG_NOT_TOUCH_MODAL).

FLAG_NOT_TOUCH_MODAL

Indicates that when a region outside of Window2 is clicked, the touch event is dispatched to Window1, but the key event is not affected. Of course, Windows 2 can get focus and interact with the keyboard.

FLAG_WATCH_OUTSIDE_TOUCH

This value works with FLAG_NOT_TOUCH_MODAL. So what that means is when YOU set FLAG_NOT_TOUCH_MODAL, when you click on the outside of Windows 2 you don’t get a touch event, but at this point Windows 2 wants to get an outside hit event without affecting the event being sent to Windows 1, The FLAG_WATCH_OUTSIDE_TOUCH flag comes into play. This Window2 receives events of type ACTION_OUTSIDE, and the touch events (down/move/ Up) are dispatched to Window1. Key events are not affected.

FLAG_ALT_FOCUSABLE_IM

It has to do with keyboards. When FLAG_NOT_FOCUSABLE is not set and FLAG_ALT_FOCUSABLE_IM is set, no keyboard interaction is required. When FLAG_NOT_FOCUSABLE/FLAG_ALT_FOCUSABLE_IM is set at the same time, interaction with the input method is required. FLAG_ALT_FOCUSABLE_IM does not affect touch/key events when set separately.

How does a View associate with a Window

In the previous analysis, there is no direct correlation between View and Window. How can View contents be displayed on Window?

Surface with Canvas

We usually rewrite View onDraw(Canvas Canvas) to draw the desired effect on Canvas. Let’s see how Canvas works:

ViewRootImpl.java
public final Surface mSurface = new Surface();
final Canvas canvas = mSurface.lockCanvas(dirty);
Copy the code

It can be seen that Canvas is obtained from Surface, so it naturally comes to mind whether Surface is related to Window, and how is it related?

ViewRootImpl.java private int relayoutWindow(WindowManager.LayoutParams params, int viewVisibility, Boolean insetsPending) throws RemoteException {// omit // Pass SurfaceControl, Int relayoutResult = mWindowSession. Relayout (mWindow, mSeq, params, (int) (mView.getMeasuredWidth() * appScale + 0.5F), (int) (mView.getMeasuredHeight() * appScale + 0.5F), viewVisibility, insetsPending ? WindowManagerGlobal.RELAYOUT_INSETS_PENDING : 0, frameNumber, mTmpFrame, mPendingOverscanInsets, mPendingContentInsets, mPendingVisibleInsets, mPendingStableInsets, mPendingOutsets, mPendingBackDropFrame, mPendingDisplayCutout, mPendingMergedConfiguration, mSurfaceControl, mTempInsets); If (mSurfacecontrol.isValid ()) {// Return App layer Surface mSurface.copyFrom(mSurfaceControl); } else { destroySurface(); } // omit return relayoutResult; }Copy the code

When ViewTree is enabled, performTraversals->relayoutWindow associates the Window with the SurfaceControl, and then with the Surface. In this way, Window->Surface->Canvas is associated, drawing the View on the Surface through Canvas, and finally displaying it. For hardware acceleration: Every View has a RenderNode

RenderNode.java public @NonNull RecordingCanvas beginRecording(int width, int height) { if (mCurrentRecordingCanvas ! = null) { throw new IllegalStateException( "Recording currently in progress - missing #endRecording() call?" ); } mCurrentRecordingCanvas = RecordingCanvas.obtain(this, width, height); return mCurrentRecordingCanvas; }Copy the code

Drawing the Canvas of the View is captured by beginRecording, and the operations of drawing the Canvas are encapsulated in DisplayList. In ViewRootImpl – > performTraversals

hwInitialized = mAttachInfo.mThreadedRenderer.initialize(
                    mSurface);
Copy the code

Create a ThreadedRenderer association with the Surface, and ThreadedRenderer holds:

protected RenderNode mRootNode;
Copy the code

The mRootNode is the root of the entire ViewTree. In this way, Surface is associated with Canvas. Use the graph to represent the relationship between View, Window and Surface:

Window content is displayed through the Surface, while SurfaceFlinger combines multiple surfaces to display on the screen.

ViewManager other methods

The addView(xx) method that adds a View to the Window, and the updateViewLayout(xx) and removeView(xx) methods updateViewLayout(xx)

WindowManagerGlobal.java public void updateViewLayout(View view, ViewGroup.LayoutParams params) { final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params; SetLayoutParams (wparams); // Set params view.setLayoutParams(wparams); Int index = findViewLocked(View, true); synchronized (mLock) {int index = findViewLocked(View, true); ViewRootImpl root = mRoots.get(index); // remove old params mparams.remove (index); // add new params mparams. add(index, wparams); Root.setlayoutparams (wparams, false); }}Copy the code

removeView(xx)

public void removeView(View view, boolean immediate)
Copy the code

Immediate Indicates whether to remove the View immediately. If the value is false, a Message is sent through Handler and the View is executed after the next Looper poll.

The specific work is doDie() in ViewRootImpl.

Void doDie () {synchronized (this) {/ / notify the View has removed the if (mAdded) {dispatchDetachedFromWindow (); } if (mAdded && ! mFirst) { destroyHardwareRenderer(); if (mView ! If ((relayoutWindow(mWindowAttributes, viewVisibility,)) {null) {try {// Notify WindowManagerService to rearrange if (relayoutWindow(mWindowAttributes, viewVisibility, false) & WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME) ! = 0) { mWindowSession.finishDrawing(mWindow); }} catch (RemoteException e) {}} // Remove surface destroySurface(); } } mAdded = false; } / / removing WindowManagerGlobal record of information, such as ViewRootImpl, View array WindowManagerGlobal. GetInstance () doRemoveView (this); }Copy the code

Common Scenarios of WindowManager

Android interface display is through WindowManager.addView(xx), that is to say, we see the interface is a Window. It’s just that the Window is a bit more abstract, and we’re dealing more with the View.

Activity

The Activity actually displays the interface through the Window, but the system encapsulates the addView process. We just need setContentView(resId) and pass in our layout. DecorView (resId) : Android DecorView

Dialog

The inside of the Dialog is also displayed through addView(xx)

PopupWindow

Similar to Dialog, but without PhoneWindow

Toast

Toast and other system cartridges etc… AddView (xx) is used for any interface display

Dialog/PopupWindow/Toast Dialog PopupWindow Toast

Create suspension window source code