android悬浮窗源码分析

2018-09-17  本文已影响0人  小歪_de4c

代码实现添加悬浮窗

mWindowManager = (WindowManager) getApplication().getSystemService(getApplication().WINDOW_SERVICE);
mWindowView = LayoutInflater.from(getApplication()).inflate(R.layout.layout_window, null);
mWindowManager.addView(mWindowView, wmParams);

分析的源码为:android 8.0 api26

关键代码:WindowManager.addView()
源码位置:android.view.WindowManagerImpl#addView

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

关键代码:mGlobal.addView()
源码位置:android.view.WindowManagerGlobal#addView

  public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow) {
        // 参数效验
        ...
        ViewRootImpl root;
        synchronized (mLock) {
            // 查找缓存,类型效验
            ...
            root = new ViewRootImpl(view.getContext(), display);
            view.setLayoutParams(wparams);
            mViews.add(view);
            mRoots.add(root);
            mParams.add(wparams);
        }
        try {
            root.setView(view, wparams, panelParentView);
        } catch (RuntimeException e) {
            // who care?
        }
    }

关键代码:root.setView(...)
源码位置:android.view.ViewRootImpl#setView

    public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
        synchronized (this) {
            // 各种属性读取,赋值及效验
            ...
            mView = view;
            mWindowAttributes.copyFrom(attrs);
            ...
            mAttachInfo.mRootView = view;

                try {
                       ...
//1-进行View绘制三大流程;
                       requestLayout();
                       ...
                    int res; /* = WindowManagerImpl.ADD_OKAY; */
//2-会通过WindowSession完成Window的添加过程(一次IPC调用)
                    res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                            getHostVisibility(), mDisplay.getDisplayId(),
                            mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                            mAttachInfo.mOutsets, mInputChannel);
                            
                } catch (RemoteException e) {
                   ...
                }
                
}

关键代码 mWindowAttributes#copyFrom()分析
源码位置:android.view.WindowManager.LayoutParams#copyFrom

public final int copyFrom(LayoutParams o) {
            int changes = 0;
            //各种属性赋值
            ...

            // This can't change, it's only set at window creation time.
            hideTimeoutMilliseconds = o.hideTimeoutMilliseconds;//默认值为-1

            return changes;
}

关键代码:mWindowSession.addToDisplay(...)
1.获取Session对象
2.调用Session的方法addToDisplay()

1.获取Session
mWindowSession = WindowManagerGlobal.getWindowSession();
源码位置:android.view.WindowManagerGlobal#getWindowSession

    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;
        }
    }

关键代码:windowManager.openSession(...)
源码位置:com.android.server.wm.WindowManagerService#openSession

    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对象完成

2.调用Session的方法addToDisplay()
源码位置:com.android.server.wm.Session#addToDisplay

    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.addWindow(...)
源码位置:com.android.server.wm.WindowManagerService#addWindow

  public int addWindow(Session session, IWindow client, int seq,
            WindowManager.LayoutParams attrs, int viewVisibility, int displayId,
            Rect outContentInsets, Rect outStableInsets, Rect outOutsets,
            InputChannel outInputChannel) {
        int[] appOp = new int[1];
        int res = mPolicy.checkAddPermission(attrs, appOp);
        if (res != WindowManagerGlobal.ADD_OKAY) {
            return res;
        }
        ...
        final int type = attrs.type;

        synchronized(mWindowMap) {
        //各种条件判断
        ...
        WindowToken token = displayContent.getWindowToken(
                    hasParent ? parentWindow.mAttrs.token : attrs.token);
            final int rootType = hasParent ? parentWindow.mAttrs.type : type;
            boolean addToastWindowRequiresToken = false;
            if (token == null) {
//rooType判断符合某些type, 则返回
                if (type == TYPE_TOAST) {
                    // android版本>=26(andorid 8.0)
                    if (doesAddToastWindowRequireToken(attrs.packageName, callingUid,
                            parentWindow)) {
                        return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                    }
                }
                final IBinder binder = attrs.token != null ? attrs.token : client.asBinder();
                token = new WindowToken(this, binder, type, false, displayContent,
                        session.mCanAddInternalSystemWindow);
            }
             ...
            else if (type == TYPE_TOAST) {
                // android版本>=26(andorid 8.0)
                addToastWindowRequiresToken = doesAddToastWindowRequireToken(attrs.packageName,
                        callingUid, parentWindow);
                if (addToastWindowRequiresToken && token.windowType != TYPE_TOAST) {
                    return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                }
            } 
            ...

            final WindowState win = new WindowState(this, session, client, token, parentWindow,
                    appOp[0], seq, attrs, viewVisibility, session.mUid,
                    session.mCanAddInternalSystemWindow);
            ...
            mPolicy.adjustWindowParamsLw(win.mAttrs);
            ...
  
            if (type == TYPE_TOAST) {
                if (!getDefaultDisplayContentLocked().canAddToastWindowForUid(callingUid)) {
                    Slog.w(TAG_WM, "Adding more than one toast window for UID at a time.");
                    return WindowManagerGlobal.ADD_DUPLICATE_ADD;
                }
                if (addToastWindowRequiresToken
                        || (attrs.flags & LayoutParams.FLAG_NOT_FOCUSABLE) == 0
                        || mCurrentFocus == null
                        || mCurrentFocus.mOwnerUid != callingUid) {
                    mH.sendMessageDelayed(
                            mH.obtainMessage(H.WINDOW_HIDE_TIMEOUT, win),
                            win.mAttrs.hideTimeoutMilliseconds);
                }
            }
            ...

            win.attach();
            mWindowMap.put(client.asBinder(), win);

            if (win.mAppOp != AppOpsManager.OP_NONE) {
                int startOpResult = mAppOps.startOpNoThrow(win.mAppOp, win.getOwningUid(),
                        win.getOwningPackage());
                if ((startOpResult != AppOpsManager.MODE_ALLOWED) &&
                        (startOpResult != AppOpsManager.MODE_DEFAULT)) {
                    win.setAppOpVisibilityLw(false);
                }
            }
           ...

        return res;
    }

源码位置:com.android.server.policy.PhoneWindowManager#checkAddPermission

 /** {@inheritDoc} */
    @Override
    public int checkAddPermission(WindowManager.LayoutParams attrs, int[] outAppOp) {
        int type = attrs.type;
        outAppOp[0] = AppOpsManager.OP_NONE;

        if (!isSystemAlertWindowType(type)) {
            switch (type) {
                case TYPE_TOAST:
                    // Only apps that target older than O SDK can add window without a token, after
                    // that we require a token so apps cannot add toasts directly as the token is
                    // added by the notification system.
                    // Window manager does the checking for this.
                    outAppOp[0] = OP_TOAST_WINDOW;
                    return ADD_OKAY;
                case TYPE_DREAM:
                case TYPE_INPUT_METHOD:
                case TYPE_WALLPAPER:
                case TYPE_PRESENTATION:
                case TYPE_PRIVATE_PRESENTATION:
                case TYPE_VOICE_INTERACTION:
                case TYPE_ACCESSIBILITY_OVERLAY:
                case TYPE_QS_DIALOG:
                    // The window manager will check these.
                    return ADD_OKAY;
            }
            return mContext.checkCallingOrSelfPermission(INTERNAL_SYSTEM_WINDOW)
                    == PERMISSION_GRANTED ? ADD_OKAY : ADD_PERMISSION_DENIED;
        }
    }

源码位置:com.android.server.policy.PhoneWindowManager#adjustWindowParamsLw

    public void adjustWindowParamsLw(WindowManager.LayoutParams attrs) {
        switch (attrs.type) {
           ...
           case TYPE_TOAST:
                // While apps should use the dedicated toast APIs to add such windows
                // it possible legacy apps to add the window directly. Therefore, we
                // make windows added directly by the app behave as a toast as much
                // as possible in terms of timeout and animation.
                if (attrs.hideTimeoutMilliseconds < 0
                        || attrs.hideTimeoutMilliseconds > TOAST_WINDOW_TIMEOUT) {
                    attrs.hideTimeoutMilliseconds = TOAST_WINDOW_TIMEOUT;
                }
                attrs.windowAnimations = com.android.internal.R.style.Animation_Toast;
                break;
        }
             ...
    }
上一篇下一篇

猜你喜欢

热点阅读