美文网首页
android悬浮窗源码分析

android悬浮窗源码分析

作者: 小歪_de4c | 来源:发表于2018-09-17 18:31 被阅读0次

    代码实现添加悬浮窗

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

    相关文章

      网友评论

          本文标题:android悬浮窗源码分析

          本文链接:https://www.haomeiwen.com/subject/uhfegftx.html