android懸浮窗源碼分析

代碼實(shí)現(xiàn)添加懸浮窗

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

關(guān)鍵代碼: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);
    }

關(guān)鍵代碼:mGlobal.addView()
源碼位置:android.view.WindowManagerGlobal#addView

  public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow) {
        // 參數(shù)效驗(yàn)
        ...
        ViewRootImpl root;
        synchronized (mLock) {
            // 查找緩存,類型效驗(yàn)
            ...
            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?
        }
    }

關(guān)鍵代碼:root.setView(...)
源碼位置:android.view.ViewRootImpl#setView

    public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
        synchronized (this) {
            // 各種屬性讀取,賦值及效驗(yàn)
            ...
            mView = view;
            mWindowAttributes.copyFrom(attrs);
            ...
            mAttachInfo.mRootView = view;

                try {
                       ...
//1-進(jìn)行View繪制三大流程;
                       requestLayout();
                       ...
                    int res; /* = WindowManagerImpl.ADD_OKAY; */
//2-會(huì)通過WindowSession完成Window的添加過程(一次IPC調(diào)用)
                    res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                            getHostVisibility(), mDisplay.getDisplayId(),
                            mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                            mAttachInfo.mOutsets, mInputChannel);
                            
                } catch (RemoteException e) {
                   ...
                }
                
}

關(guān)鍵代碼 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;//默認(rèn)值為-1

            return changes;
}

關(guān)鍵代碼:mWindowSession.addToDisplay(...)
1.獲取Session對(duì)象
2.調(diào)用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;
        }
    }

關(guān)鍵代碼: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對(duì)象完成

2.調(diào)用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);
    }

關(guān)鍵代碼: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;
        }
             ...
    }
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容