天天看點

Android源碼剖析之Framework層基礎版(視窗、linux、token、Binder)

  本文來自 http://blog.csdn.net/liuxian13183/

 ,引用必須注明出處!

關于Framework,就是應用層底下的控制層,離應用層最近,總想找個機會,寫寫WindowMangerService和ActivityManagerService(注意非控件,而是指一類服務)以及其他一些東西,對底層做一個更為全面的認識。而很早以前,寫過一篇文章,來簡述Android系統-" 

Android進階之系統介紹

",同樣今天我們在講Framework層時也會再對系統做一個回顧;下圖是我對本節内容的一個基本介紹。

PS:W類是ViewRoot的一個内部類,ViewRoot最大作用就是把IPC調用轉為本地調用。

HistoryRecord-每個Acitivty都會有一個,用來管理和記錄Activity,是一個Binder對象

ViewRoot-實作View和WindowManger之間的協定,是View Hierarchy的最頂層

PhoneWindow-其中有autoManger和keyguardManager的實作對象

一、視窗

就着上圖,我們會對每一條做進一步的解說(注意上圖大多是包含關系,少數是關聯關系,請差別對待),上圖對Framework簡單做了描述;同時科普一下什麼叫視窗,視窗非指window類,而是指所有使用windowmanger将

其展示在使用者面前的控件,如toast、activity、menu等,而這些界面通過設定window的callback來監聽到wms傳給view對象的資訊,如手勢操作。而視窗類型基本可以分為3種:

1、系統視窗,不需要父視窗-可以指定2000-2999層 

2、子視窗,依賴父視窗-1000-1999層 如Toast

3、應用視窗,對應activity-小于99層 如Activity

視窗可以說是View,而wms不直接跟view溝通,而是通過實作IWindow的ViewRoot.W類,然後再傳給view。

關于Context,上下文引用,項目中使用的還比較多,是一個場景,用來配合上下文操作;一個應用中context的數量=service個數+activity個數+application個數,原因它們都繼承自ContextWraper,而它繼承自context。

二、linux檔案系統

由于android系統基于linux,就先講一下linux基礎,檔案系統通常有3個特點:

1、由檔案和目錄組成,占據一定存儲空間

2、具有讀、寫、執行權限,可以複制和移動

3、多個檔案系統可以并列,且類型可以不同,如FAT16和NTFS

主要的檔案目錄有以下幾種-與android系統類比:

1、bin,存放使用者級二進制工具-相當于android系統的acct目錄

2、boot,核心鏡像檔案,由bootloader裝載-firmware

3、dev,各種檔案系統如列印機等-相當于android系統的dev目錄+storage+mnt/sdcard

4、etc,配置檔案區-相當于android系統的config目錄

5、home,使用者工作目錄-data/user

6、lib,系統運作時庫的存放地-data/app-lib

7、opt,存放系統程式-data/app-private

8、proc,系統級如核心和程序所在檔案-proc目錄

9、root,管理者工作目錄-root目錄

10、sbin,管理者的二進制工具-sbin目錄

11、sys,驅動對應的系統檔案如固件、核心、電量等-sys目錄+system目錄

12、usr,應用程式安裝區-data/app

13、var,調試資訊等-data/anr等

是以從上面來以看出,其實作業系統都是由檔案組成,外加一些硬體感應裝置。但上面介紹的依然不全面,因為android是一層套一層,資源是總體一緻,大體分散的結構。同時上面會涉及到程序pid,值為100以内是系統程序,1000以内是root程序,1000以上是使用者程序。講完目錄,咱們再講講指令:

1、man,查詢某指令的意思

2、ls,列出目前目錄下所有檔案及檔案夾資訊

3、find,用名字查找檔案資訊

4、grep,查詢檔案中的字元串資訊

5、cat,打開檔案

6、chmod,指定權限,ugo指user(自身)、group(組)、other(其他),權限有r(讀1)w(寫2)x(執行4),指定權限有兩種方式如chmod ug+x(給予目前使用者和某群組執行的權限),chmod 777(給予三者所有權限,原因請看上一行)

7、ps和kill,ps列出目前所有程序,kill殺死某程序

8、export,用于設定變量作用于全局

9、mount和unmount,加載和解除安裝檔案系統

好在用過linux作業系統工作過一段時間,對後來做Android開發,起到很大的幫助,上面介紹的是一些常用指令,有興趣的可以安裝一個linux系統來用,之前有一個同僚使用ubuntu來編譯so,而我當年用的是小紅帽rethat。

三、linux啟動過程

下面簡單講一個linux啟動過程,其實Dalvik虛拟機也是類似

上圖少說一點,在CPU運作之前,要把磁盤和其他記憶體啟動,這樣才能保證系統正式開始,因為所有系統均為檔案,加載檔案的硬碟不啟,系統如何能被啟動?

四、異步資訊系統

在Android系統中,用的最多的設計模式就是handler+looper+messageQueue,無論在系統層還是在應用層,

之後我們會再講到都被用爛了,異步線程表現為:開啟後無限循環,周遊資料,如果為空則等待,直到下次資料不

為空時,使用場景有二

1、任務要常駐

2、任務需要根據消息來做不同操作

使用方法如上,解釋幾點,上面的資料指messegeQueue屬于隊列LILO,讀/寫資料時會加鎖;如何應用呢,

Handler handler =new Handler(); 首先建立handler時,一般需要在UI線程中(一般你也沒必要在子線程重寫

handler),獲得一個UI線程的looper對象;looper對象通過prepare方法(僅一次)準備一個MessageQueue

(系統唯一),調用loop方法一直分發消息;MessageQueue可以定義消息處理時刻,否則先進先出,

通過next和enquenceMessage方法來取和加入消息,處于wait狀态則喚醒,若無消息則挂起。

五、Binder

再講一個知識點Binder,然後結束本篇。

Binder工作在linux層面,是一小段記憶體,屬于驅動,主要用來解決IPC調用,應用架構包括3部分

1、服務端接口-Binder對象,收到消息即啟動隐藏線程,執行onTransact函數初始化;向用戶端發送消息時,

挂起目前線程。

2、Binder驅動-創造mRemote的Binder對象,重載以下方法

1)以線程間消息通信模式,向服務端發送參數

2)挂起用戶端線程,等待服務端傳回處理結果,并通知用戶端

3)接收服務端通知,繼續執行用戶端線程

3、用戶端接口-向用戶端發送消息,挂起目前線程。

Binder服務的設計原則是,開放ServiceManger接口給外界,關于具體的操作由底層去統一執行,不暴露出來,

這也是保證架構穩定、邏輯正确的重要方式,使不同管理類與底層實作可以分離解耦。

另一篇關于Binder介紹,最下面:

http://blog.csdn.net/reboot123/article/details/52370416 下面這篇文章底層就是使用binder進行通信  Android進階第十講之AIDL與JNI

六、token

token原意指密碼,在這裡指“身份證”,代表唯一性和代理性;一般token為IBind的實作類,即使View.Attach

Info中也是用IBind實作類來進行IPC調用。應用視窗一般都有token,而window可能沒有,為什麼?window可以不

對應此視窗存在。

應用視窗的token:一般最初跟window一樣最終指向DecorView的W類,初始值是window的mAppToken,指應用

HistoryRecord。Activity的token起始是HistoryRecord,最終指向此W類即mAppToken,與window相同。

子視窗的token:是父視窗view的W類,即mPanelParentWindowToken

系統視窗無token

是以view中會有window的token,也會有父視窗的token,而每個應用視窗随activity誕生,均會有一個Activity

Thread陪伴。

PopupWindow上套PopupWindow,報錯:unable to add window ,is your activity running ?

經查在windowManager執行addView操作時出錯

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

WindowManagerGlobal

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

        // 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.
            synchronized (mLock) {
                final int index = findViewLocked(view, false);
                if (index >= 0) {
                    removeViewLocked(index, true);
                }
            }
            throw e;
        }
    }      

ViewRootImpl出錯出現ADD_BAD_APP_TOKEN或ADD_BAD_SUBWINDOW_TOKEN,要查WindowSession.addToDisplay

/**
     * We have one child
     */
    public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
        synchronized (this) {
            if (mView == null) {
                mView = view;

                int res; /* = WindowManagerImpl.ADD_OKAY; */

                // Schedule the first layout -before- adding to the window
                // manager, to make sure we do the relayout before receiving
                // any other events from the system.
                requestLayout();
                if ((mWindowAttributes.inputFeatures
                        & WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
                    mInputChannel = new InputChannel();
                }
                mForceDecorViewVisibility = (mWindowAttributes.privateFlags
                        & PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY) != 0;
                try {
                    mOrigWindowType = mWindowAttributes.type;
                    mAttachInfo.mRecomputeGlobalAttributes = true;
                    collectViewAttributes();
                    res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                            getHostVisibility(), mDisplay.getDisplayId(),
                            mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                            mAttachInfo.mOutsets, mInputChannel);
                } catch (RemoteException e) {
                    mAdded = false;
                    mView = null;
                    mAttachInfo.mRootView = null;
                    mInputChannel = null;
                    mFallbackEventHandler.setView(null);
                    unscheduleTraversals();
                    setAccessibilityFocus(null, null);
                    throw new RuntimeException("Adding window failed", e);
                } finally {
                    if (restore) {
                        attrs.restore();
                    }
                }

                if (mTranslator != null) {
                    mTranslator.translateRectInScreenToAppWindow(mAttachInfo.mContentInsets);
                }
                mPendingOverscanInsets.set(0, 0, 0, 0);
                mPendingContentInsets.set(mAttachInfo.mContentInsets);
                mPendingStableInsets.set(mAttachInfo.mStableInsets);
                mPendingVisibleInsets.set(0, 0, 0, 0);
                mAttachInfo.mAlwaysConsumeNavBar =
                        (res & WindowManagerGlobal.ADD_FLAG_ALWAYS_CONSUME_NAV_BAR) != 0;
                mPendingAlwaysConsumeNavBar = mAttachInfo.mAlwaysConsumeNavBar;
                if (DEBUG_LAYOUT) Log.v(mTag, "Added window " + mWindow);
                if (res < WindowManagerGlobal.ADD_OKAY) {
                    mAttachInfo.mRootView = null;
                    mAdded = false;
                    mFallbackEventHandler.setView(null);
                    unscheduleTraversals();
                    setAccessibilityFocus(null, null);
                    switch (res) {
                        case WindowManagerGlobal.ADD_BAD_APP_TOKEN:
                        case WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN:
                            throw new WindowManager.BadTokenException(
                                    "Unable to add window -- token " + attrs.token
                                    + " is not valid; is your activity running?");
                        case WindowManagerGlobal.ADD_NOT_APP_TOKEN:
                            throw new WindowManager.BadTokenException(
                                    "Unable to add window -- token " + attrs.token
                                    + " is not for an application");
                        case WindowManagerGlobal.ADD_APP_EXITING:
                            throw new WindowManager.BadTokenException(
                                    "Unable to add window -- app for token " + attrs.token
                                    + " is exiting");
                        case WindowManagerGlobal.ADD_DUPLICATE_ADD:
                            throw new WindowManager.BadTokenException(
                                    "Unable to add window -- window " + mWindow
                                    + " has already been added");
                        case WindowManagerGlobal.ADD_STARTING_NOT_NEEDED:
                            // Silently ignore -- we would have just removed it
                            // right away, anyway.
                            return;
                        case WindowManagerGlobal.ADD_MULTIPLE_SINGLETON:
                            throw new WindowManager.BadTokenException("Unable to add window "
                                    + mWindow + " -- another window of type "
                                    + mWindowAttributes.type + " already exists");
                        case WindowManagerGlobal.ADD_PERMISSION_DENIED:
                            throw new WindowManager.BadTokenException("Unable to add window "
                                    + mWindow + " -- permission denied for window type "
                                    + mWindowAttributes.type);
                        case WindowManagerGlobal.ADD_INVALID_DISPLAY:
                            throw new WindowManager.InvalidDisplayException("Unable to add window "
                                    + mWindow + " -- the specified display can not be found");
                        case WindowManagerGlobal.ADD_INVALID_TYPE:
                            throw new WindowManager.InvalidDisplayException("Unable to add window "
                                    + mWindow + " -- the specified window type "
                                    + mWindowAttributes.type + " is not valid");
                    }
                    throw new RuntimeException(
                            "Unable to add window -- unknown error code " + res);
                }
        }
    }      

WindowSession

@Override
    public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs,
            int viewVisibility, int displayId, Rect outContentInsets, Rect outStableInsets,
            InputChannel outInputChannel) {
        return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId,
                outContentInsets, outStableInsets, outInputChannel);
    }      

WindowManagerService

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

        boolean reportNewConfig = false;
        WindowState attachedWindow = null;
        long origId;
        final int type = attrs.type;

        synchronized(mWindowMap) {
            if (!mDisplayReady) {
                throw new IllegalStateException("Display has not been initialialized");
            }

            final DisplayContent displayContent = getDisplayContentLocked(displayId);
            if (displayContent == null) {
                Slog.w(TAG_WM, "Attempted to add window to a display that does not exist: "
                        + displayId + ".  Aborting.");
                return WindowManagerGlobal.ADD_INVALID_DISPLAY;
            }
            if (!displayContent.hasAccess(session.mUid)) {
                Slog.w(TAG_WM, "Attempted to add window to a display for which the application "
                        + "does not have access: " + displayId + ".  Aborting.");
                return WindowManagerGlobal.ADD_INVALID_DISPLAY;
            }

            if (mWindowMap.containsKey(client.asBinder())) {
                Slog.w(TAG_WM, "Window " + client + " is already added");
                return WindowManagerGlobal.ADD_DUPLICATE_ADD;
            }

            if (type >= FIRST_SUB_WINDOW && type <= LAST_SUB_WINDOW) {
                attachedWindow = windowForClientLocked(null, attrs.token, false);
                if (attachedWindow == null) {
                    Slog.w(TAG_WM, "Attempted to add window with token that is not a window: "
                          + attrs.token + ".  Aborting.");
                    return WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN;
                }
                if (attachedWindow.mAttrs.type >= FIRST_SUB_WINDOW
                        && attachedWindow.mAttrs.type <= LAST_SUB_WINDOW) {
                    Slog.w(TAG_WM, "Attempted to add window with token that is a sub-window: "
                            + attrs.token + ".  Aborting.");
                    return WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN;
                }
            }

            if (type == TYPE_PRIVATE_PRESENTATION && !displayContent.isPrivate()) {
                Slog.w(TAG_WM, "Attempted to add private presentation window to a non-private display.  Aborting.");
                return WindowManagerGlobal.ADD_PERMISSION_DENIED;
            }

            boolean addToken = false;
            WindowToken token = mTokenMap.get(attrs.token);
            AppWindowToken atoken = null;
            if (token == null) {
                if (type >= FIRST_APPLICATION_WINDOW && type <= LAST_APPLICATION_WINDOW) {
                    Slog.w(TAG_WM, "Attempted to add application window with unknown token "
                          + attrs.token + ".  Aborting.");
                    return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                }
                if (type == TYPE_INPUT_METHOD) {
                    Slog.w(TAG_WM, "Attempted to add input method window with unknown token "
                          + attrs.token + ".  Aborting.");
                    return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                }
                if (type == TYPE_VOICE_INTERACTION) {
                    Slog.w(TAG_WM, "Attempted to add voice interaction window with unknown token "
                          + attrs.token + ".  Aborting.");
                    return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                }
                if (type == TYPE_WALLPAPER) {
                    Slog.w(TAG_WM, "Attempted to add wallpaper window with unknown token "
                          + attrs.token + ".  Aborting.");
                    return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                }
                if (type == TYPE_DREAM) {
                    Slog.w(TAG_WM, "Attempted to add Dream window with unknown token "
                          + attrs.token + ".  Aborting.");
                    return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                }
                if (type == TYPE_QS_DIALOG) {
                    Slog.w(TAG_WM, "Attempted to add QS dialog window with unknown token "
                          + attrs.token + ".  Aborting.");
                    return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                }
                if (type == TYPE_ACCESSIBILITY_OVERLAY) {
                    Slog.w(TAG_WM, "Attempted to add Accessibility overlay window with unknown token "
                            + attrs.token + ".  Aborting.");
                    return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                }
                token = new WindowToken(this, attrs.token, -1, false);
                addToken = true;
            } else if (type >= FIRST_APPLICATION_WINDOW && type <= LAST_APPLICATION_WINDOW) {
                atoken = token.appWindowToken;
                if (atoken == null) {
                    Slog.w(TAG_WM, "Attempted to add window with non-application token "
                          + token + ".  Aborting.");
                    return WindowManagerGlobal.ADD_NOT_APP_TOKEN;
                } else if (atoken.removed) {
                    Slog.w(TAG_WM, "Attempted to add window with exiting application token "
                          + token + ".  Aborting.");
                    return WindowManagerGlobal.ADD_APP_EXITING;
                }
                if (type == TYPE_APPLICATION_STARTING && atoken.firstWindowDrawn) {
                    // No need for this guy!
                    if (DEBUG_STARTING_WINDOW || localLOGV) Slog.v(
                            TAG_WM, "**** NO NEED TO START: " + attrs.getTitle());
                    return WindowManagerGlobal.ADD_STARTING_NOT_NEEDED;
                }
            } else if (type == TYPE_INPUT_METHOD) {
                if (token.windowType != TYPE_INPUT_METHOD) {
                    Slog.w(TAG_WM, "Attempted to add input method window with bad token "
                            + attrs.token + ".  Aborting.");
                      return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                }
            } else if (type == TYPE_VOICE_INTERACTION) {
                if (token.windowType != TYPE_VOICE_INTERACTION) {
                    Slog.w(TAG_WM, "Attempted to add voice interaction window with bad token "
                            + attrs.token + ".  Aborting.");
                      return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                }
            } else if (type == TYPE_WALLPAPER) {
                if (token.windowType != TYPE_WALLPAPER) {
                    Slog.w(TAG_WM, "Attempted to add wallpaper window with bad token "
                            + attrs.token + ".  Aborting.");
                      return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                }
            } else if (type == TYPE_DREAM) {
                if (token.windowType != TYPE_DREAM) {
                    Slog.w(TAG_WM, "Attempted to add Dream window with bad token "
                            + attrs.token + ".  Aborting.");
                      return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                }
            } else if (type == TYPE_ACCESSIBILITY_OVERLAY) {
                if (token.windowType != TYPE_ACCESSIBILITY_OVERLAY) {
                    Slog.w(TAG_WM, "Attempted to add Accessibility overlay window with bad token "
                            + attrs.token + ".  Aborting.");
                    return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                }
            } else if (type == TYPE_QS_DIALOG) {
                if (token.windowType != TYPE_QS_DIALOG) {
                    Slog.w(TAG_WM, "Attempted to add QS dialog window with bad token "
                            + attrs.token + ".  Aborting.");
                    return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                }
            } else if (token.appWindowToken != null) {
                Slog.w(TAG_WM, "Non-null appWindowToken for system window of type=" + type);
                // It is not valid to use an app token with other system types; we will
                // instead make a new token for it (as if null had been passed in for the token).
                attrs.token = null;
                token = new WindowToken(this, null, -1, false);
                addToken = true;
            }      
if (addToken) {
                mTokenMap.put(attrs.token, token);
            }
            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);
                }
            }      
Binder.restoreCallingIdentity(origId);

        return res;
    }      

如果加入成功(mWindowMap.put(attr.token,token)),則子View與父View建立連接配接關系。

查到mTokenMap.get(attr.token)為null,同時又是子視窗,是以報錯。

至于為什麼是null,繼續

回到PopupWindow

public void showAsDropDown(View anchor, int xoff, int yoff, int gravity) {
        if (isShowing() || mContentView == null) {
            return;
        }

        TransitionManager.endTransitions(mDecorView);

        attachToAnchor(anchor, xoff, yoff, gravity);

        mIsShowing = true;
        mIsDropdown = true;

        final WindowManager.LayoutParams p = createPopupLayoutParams(anchor.getWindowToken());
        preparePopup(p);

        final boolean aboveAnchor = findDropDownPosition(anchor, p, xoff, yoff,
                p.width, p.height, gravity);
        updateAboveAnchor(aboveAnchor);
        p.accessibilityIdOfAnchor = (anchor != null) ? anchor.getAccessibilityViewId() : -1;

        invokePopup(p);
    }      

可以看到,二次添加的PopupWidow,使用一次添加的PopupWindow對象的token,而此token為

private int mWindowLayoutType = WindowManager.LayoutParams.TYPE_APPLICATION_PANEL;      
private WindowManager.LayoutParams createPopupLayoutParams(IBinder token) {
        final WindowManager.LayoutParams p = new WindowManager.LayoutParams();

        // These gravity settings put the view at the top left corner of the
        // screen. The view is then positioned to the appropriate location by
        // setting the x and y offsets to match the anchor's bottom-left
        // corner.
        p.gravity = computeGravity();
        p.flags = computeFlags(p.flags);
        p.type = mWindowLayoutType;
        p.token = token;
        p.softInputMode = mSoftInputMode;
        p.windowAnimations = computeAnimationResource();
        return p;
    }
      

但在WindowManagerService裡,PopupWindow依賴show的控件是傳過來的,而一次PopupWindow的type剛好等于

/**
         * Window type: a panel on top of an application window.  These windows
         * appear on top of their attached window.
         */
        public static final int TYPE_APPLICATION_PANEL = FIRST_SUB_WINDOW;      

所在WindowManagerService不允許在子視窗上建立子視窗。

if (type >= FIRST_SUB_WINDOW && type <= LAST_SUB_WINDOW) {
                attachedWindow = windowForClientLocked(null, attrs.token, false);
                if (attachedWindow == null) {
                    Slog.w(TAG_WM, "Attempted to add window with token that is not a window: "
                          + attrs.token + ".  Aborting.");
                    return WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN;
                }
                if (attachedWindow.mAttrs.type >= FIRST_SUB_WINDOW
                        && attachedWindow.mAttrs.type <= LAST_SUB_WINDOW) {
                    Slog.w(TAG_WM, "Attempted to add window with token that is a sub-window: "
                            + attrs.token + ".  Aborting.");
                    return WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN;
                }
            }      

完。

補充:Dialog則無此限制

public void show() {
        if (mShowing) {
            if (mDecor != null) {
                if (mWindow.hasFeature(Window.FEATURE_ACTION_BAR)) {
                    mWindow.invalidatePanelMenu(Window.FEATURE_ACTION_BAR);
                }
                mDecor.setVisibility(View.VISIBLE);
            }
            return;
        }

        WindowManager.LayoutParams l = mWindow.getAttributes();
        if ((l.softInputMode
                & WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) == 0) {
            WindowManager.LayoutParams nl = new WindowManager.LayoutParams();
            nl.copyFrom(l);
            nl.softInputMode |=
                    WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION;
            l = nl;
        }

        mWindowManager.addView(mDecor, l);
        mShowing = true;

        sendShowMessage();
    }      
Dialog(@NonNull Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) {

        final Window w = new PhoneWindow(mContext);
        mWindow = w;

    }
      

PhoneWindow

public final WindowManager.LayoutParams getAttributes() {
        return mWindowAttributes;
    }
      
private final WindowManager.LayoutParams mWindowAttributes =
        new WindowManager.LayoutParams();      
public LayoutParams() {
            super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
            type = TYPE_APPLICATION;
            format = PixelFormat.OPAQUE;
        }      
public static final int TYPE_APPLICATION        = 2;      

WindowManagerService要求大于等1000,小于等于1999才報ADD_BAD_SUB_WINDOW。

是以Dialog無此限制。

Android的安裝過程:

http://www.2cto.com/kf/201402/280725.html

Binder能支援跨程序通信的原因,是它實作IBinder接口,系統定義實作此接口即賦予程序通信的功能。

優勢:做資料拷貝隻用一次,則Pipe、Socket都需要兩次;其次安全性高,不會像Socket會暴露位址,被人替換;

通信機制:Service向ServiceManager注冊,得到虛拟的Uid和Pid,使用Binder通信;Client向ServiceManager請求,得到虛拟的Uid和Pid,以及目标對象的Proxy,底層通過硬體協定傳輸,使用Binder通訊。

通信時Client手持Proxy,ServiceManger進行轉換,調用到Service。三者運作在三個獨立程序中。Client/Sever全雙工,互為Sever/Client

實際案例:

Vivo7.0以上手機,無法彈出自定義Toast(WindowManager通過addView方式,再配合動畫),由于Android手機系統權限逐漸收緊,導緻無法成功申請的“SYSTEM_ALERT_WINDOW”權限。

解決思路:先看權限是否被收回,其次看使用系統Toast是否可行,最後嘗試降級使用WindowManager。

解決辦法,降低WindowManager.LayoutParam的flag等級,使用2000以下的等級,比如LAST_SUB_WINDOW(次級window1999)

繼續閱讀