Android Window 9問9答

希爾瓦娜斯女神發表於2016-02-14

之前剛分享過一篇Android View繪製13問13答,這篇文章是關於Android Window的9個問題及其答案。

1.簡述一下window是什麼?在android體系裡 扮演什麼角色?

答:window就是一個抽象類,他的實現類是phoneWindow。我們一般通過windowManager 來訪問window。就是windowmanager 和windowmanagerservice的互動。

此外 android中 你所有能看到的檢視,activity,dialog,toast等 都是附加在window上的。window就是view的直接管理者。

2.如何使用windowmanager新增一個view?

答:

Button bt = new Button(this);
        bt.setText("button here");
        WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams(WindowManager.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.WRAP_CONTENT,
                0, 0, PixelFormat.TRANSPARENT);
        layoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
        layoutParams.x = 300;
        layoutParams.y = 300;
        layoutParams.gravity = Gravity.RIGHT | Gravity.TOP;
        getWindowManager().addView(bt, layoutParams);

3.總共有幾種window型別?

答:三種。應用window,就是activity這種型別。子window,就是dialog這種,系統類,toast,狀態列就是系統型別window。

每種對對應著層級範圍,應用1-99 子1000-1999 系統 2000-2999.層級最大的,就是顯示在最頂層的window了。

4.使用系統window需要注意什麼?

答:注意system_alert_window這個許可權。否則要出錯

5.嘗試簡單分析window的新增過程?

答:即window.addView()函式的執行過程:

//首先我們要知道 windwmanger本身就是一個介面,他的實現是交給WindowManagerImpl 來做的。
public final class WindowManagerImpl implements WindowManager {


//他的view方法 一看,發現也是基本沒做實際的addview操作 是交給mGlobal來做的
 @Override
    public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
        applyDefaultToken(params);
        mGlobal.addView(view, params, mDisplay, mParentWindow);
    }


//發現這是一個工廠嗎,到這裡一看就明白了,WindowManagerImpl的實際操作 都橋接給了WindowManagerGlobal來處理
private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();

//先看一下WindowManagerGlobal的 重要變數,注意上面已經分析過了,WindowManagerGlobal本身自己是一個單例,全域性唯一,
//所以下面這些引數list ,全域性也是唯一的,mViews 就是所有window對應的view,mRoots就是所有viewRootImpl,mParams就是這些
//view的引數,dyingviews 就是正在刪除的物件,就是那種你呼叫了remove操作 但是remove還沒有操作完畢的那些view
 private final ArrayList<View> mViews = new ArrayList<View>();
    private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>();
    private final ArrayList<WindowManager.LayoutParams> mParams =
            new ArrayList<WindowManager.LayoutParams>();
    private final ArraySet<View> mDyingViews = new ArraySet<View>();




//所以 我們就看看WindowManagerGlobal原始碼裡的addView是如何實現的
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");
        }

        final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params;
        //如果是子window 就調整一下引數
        if (parentWindow != null) {
            parentWindow.adjustLayoutParamsForSubWindow(wparams);
        } else {
            // If there's no parent and we're running on L or above (or in the
            // system context), assume we want hardware acceleration.
            final Context context = view.getContext();
            if (context != null
                    && context.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.LOLLIPOP) {
                wparams.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
            }
        }

        ViewRootImpl root;
        View panelParentView = null;

        synchronized (mLock) {
            // Start watching for system property changes.
            if (mSystemPropertyUpdater == null) {
                mSystemPropertyUpdater = new Runnable() {
                    @Override public void run() {
                        synchronized (mLock) {
                            for (int i = mRoots.size() - 1; i >= 0; --i) {
                                mRoots.get(i).loadSystemProperties();
                            }
                        }
                    }
                };
                SystemProperties.addChangeCallback(mSystemPropertyUpdater);
            }

            int index = findViewLocked(view, false);
            if (index >= 0) {
                if (mDyingViews.contains(view)) {
                    // Don't wait for MSG_DIE to make it's way through root's queue.
                    mRoots.get(index).doDie();
                } else {
                    throw new IllegalStateException("View " + view
                            + " has already been added to the window manager.");
                }
                // The previous removeView() had not completed executing. Now it has.
            }

            // If this is a panel window, then find the window it is being
            // attached to for future reference.
            if (wparams.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW &&
                    wparams.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {
                final int count = mViews.size();
                for (int i = 0; i < count; i++) {
                    if (mRoots.get(i).mWindow.asBinder() == wparams.token) {
                        panelParentView = mViews.get(i);
                    }
                }
            }

            //這個程式碼充分說明了每一個window都對應著一個view 和一個viewrootIMPL,window本身自己不存在,
            //他的意義就在於管理view,而管理view 就要通過windowmanager 最終走到windwmanagerglobal這裡來完成管理
            root = new ViewRootImpl(view.getContext(), display);

            view.setLayoutParams(wparams);

            mViews.add(view);
            mRoots.add(root);
            mParams.add(wparams);
        }

        // do this last because it fires off messages to start doing things
        try {
            //view的最終繪製 是在viewrootimpl裡完成的,所以這裡view的繪製也是在這個裡面完成的
            //我們在viewrootimpl裡能找到setview的原始碼 他在這個函式裡呼叫了requetlayout
            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;
        }
    }


    //而requestLayout裡有scheduleTraversals方法 這個就是view繪製的入口處
    public void requestLayout() {
        if (!mHandlingLayoutInLayoutRequest) {
           checkThread();
            mLayoutRequested = true;
            scheduleTraversals();
        }
    }

    //回到前面提到的setView那個函式 
    //我們可以看到requestLayout 結束以後 mWindowSession.addToDisplay 就有了這個方法的呼叫
    //實際上這個方法 完成的就是一個window的新增。
     requestLayout();
    if ((mWindowAttributes.inputFeatures
                         & WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
                    mInputChannel = new InputChannel();
    }
    try {
                    mOrigWindowType = mWindowAttributes.type;
                    mAttachInfo.mRecomputeGlobalAttributes = true;
                   collectViewAttributes();
                    res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                            getHostVisibility(), mDisplay.getDisplayId(),
                            mAttachInfo.mContentInsets, mAttachInfo.mStableInsets, mInputChannel);

    //然後我們很容易就發現這是一個介面 並且程式碼一看就知道 還是一個binder
    //所以實際上新增window的功能 就是通過BInder 是呼叫windwmangerservice的方法 來完成的                
public interface IWindowSession extends android.os.IInterface
{
/** Local-side IPC implementation stub class. */
public static abstract class Stub extends android.os.Binder implements android.view.IWindowSession
{
private static final java.lang.String DESCRIPTOR = "android.view.IWindowSession";
/** Construct the stub at attach it to the interface. */
public Stub()
{
this.attachInterface(this, DESCRIPTOR);
}

6.activity的window是如何建立的?

答:應用類的window建立過程:

//activity的window建立 由activityThread的performLaunchActivity 方法開始
 private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
        ......
            if (activity != null) {
                Context appContext = createBaseContextForActivity(r, activity);
                CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager());
                Configuration config = new Configuration(mCompatConfiguration);
                if (DEBUG_CONFIGURATION) Slog.v(TAG, "Launching activity "
                        + r.activityInfo.name + " with config " + config);
                //其中最主要的就是attach方法 注意是呼叫的activity的attach方法 不是activitytherad的
                activity.attach(appContext, this, getInstrumentation(), r.token,
                        r.ident, app, r.intent, r.activityInfo, title, r.parent,
                        r.embeddedID, r.lastNonConfigurationInstances, config,
                        r.referrer, r.voiceInteractor);

            ......

        return activity;
    }


final void attach(Context context, ActivityThread aThread,
            Instrumentation instr, IBinder token, int ident,
            Application application, Intent intent, ActivityInfo info,
            CharSequence title, Activity parent, String id,
            NonConfigurationInstances lastNonConfigurationInstances,
            Configuration config, String referrer, IVoiceInteractor voiceInteractor) {
        attachBaseContext(context);

        mFragments.attachActivity(this, mContainer, null);
        //這裡一下就能看出來 Acitity的window物件 是由PolicyManager的makeNewWindow方法構造出來
        //有興趣的還可以看一下 這裡set了很多介面 都是我們熟悉的那些方法
        mWindow = PolicyManager.makeNewWindow(this);
        mWindow.setCallback(this);
        mWindow.setOnWindowDismissedCallback(this);
        mWindow.getLayoutInflater().setPrivateFactory(this);
        if (info.softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) {
            mWindow.setSoftInputMode(info.softInputMode);
        }
        if (info.uiOptions != 0) {
            mWindow.setUiOptions(info.uiOptions);
        }
        mUiThread = Thread.currentThread();

        mMainThread = aThread;
        mInstrumentation = instr;
        mToken = token;
        mIdent = ident;
        mApplication = application;
        mIntent = intent;
        mReferrer = referrer;
        mComponent = intent.getComponent();
        mActivityInfo = info;
        mTitle = title;
        mParent = parent;
        mEmbeddedID = id;
        mLastNonConfigurationInstances = lastNonConfigurationInstances;
        if (voiceInteractor != null) {
            if (lastNonConfigurationInstances != null) {
                mVoiceInteractor = lastNonConfigurationInstances.voiceInteractor;
            } else {
                mVoiceInteractor = new VoiceInteractor(voiceInteractor, this, this,
                        Looper.myLooper());
            }
        }

        mWindow.setWindowManager(
                (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
                mToken, mComponent.flattenToString(),
                (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
        if (mParent != null) {
            mWindow.setContainer(mParent.getWindow());
        }
        mWindowManager = mWindow.getWindowManager();
        mCurrentConfig = config;
    }



//makenewWindow就是在這裡被呼叫的,可以看出來 makenewWindow返回的 正是phoneWindow物件
//到這裡我們的window物件就生成了,
    public class Policy implements IPolicy {
    private static final String TAG = "PhonePolicy";

    private static final String[] preload_classes = {
        "com.android.internal.policy.impl.PhoneLayoutInflater",
        "com.android.internal.policy.impl.PhoneWindow",
        "com.android.internal.policy.impl.PhoneWindow$1",
        "com.android.internal.policy.impl.PhoneWindow$DialogMenuCallback",
        "com.android.internal.policy.impl.PhoneWindow$DecorView",
        "com.android.internal.policy.impl.PhoneWindow$PanelFeatureState",
        "com.android.internal.policy.impl.PhoneWindow$PanelFeatureState$SavedState",
    };

    static {
        // For performance reasons, preload some policy specific classes when
        // the policy gets loaded.
        for (String s : preload_classes) {
            try {
                Class.forName(s);
            } catch (ClassNotFoundException ex) {
                Log.e(TAG, "Could not preload class for phone policy: " + s);
            }
        }
    }

    public Window makeNewWindow(Context context) {
        return new PhoneWindow(context);
    }

    public LayoutInflater makeNewLayoutInflater(Context context) {
        return new PhoneLayoutInflater(context);
    }

    public WindowManagerPolicy makeNewWindowManager() {
        return new PhoneWindowManager();
    }

    public FallbackEventHandler makeNewFallbackEventHandler(Context context) {
        return new PhoneFallbackEventHandler(context);
    }
}


//再看activity的方法 就是在這裡把我們的佈局檔案和window給關聯了起來
//我們上面已經知道window物件就是phonewindow 所以這裡就要看看phonewindow的setContentView方法
public void setContentView(@LayoutRes int layoutResID) {
        getWindow().setContentView(layoutResID);
        initWindowDecorActionBar();
    }


//phoneWindow的setContentView方法

//要注意的是 這個方法執行完畢 我們也只是 通過decorView建立好了 我們自己的view物件而已。
//但是這個物件還沒有被顯示出來,只是存在於記憶體之中。decorview真正被顯示 要在makevisible方法裡了    
     @Override
    public void setContentView(int layoutResID) {
        // Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
        // decor, when theme attributes and the like are crystalized. Do not check the feature
        // before this happens.
        if (mContentParent == null) {
            //這個就是建立decorview的 decorView就是那個framelayout我們的根佈局 有一個標題欄和內容欄
            //其中內容蘭 就是android.R.id.content
            installDecor();
        } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            mContentParent.removeAllViews();
        }

        if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
                    getContext());
            transitionTo(newScene);
        } else {
            //這裡就是我們自己寫的佈局 layout 給關聯到deorview的content佈局裡面
            mLayoutInflater.inflate(layoutResID, mContentParent);
        }
        final Callback cb = getCallback();
        if (cb != null && !isDestroyed()) {
            //新增完畢以後呼叫回撥
            cb.onContentChanged();
        }
    }

7.dialog的window建立過程?

答:子window的建立過程如下:其實和activity的過程差不多 無非是acitivity對於decorView的顯示是自動控制,交給actitytherad 按照流程來走 最後makevISIABLE函式來完成最終顯示的,而dialog就是需要你手動來完成這個過程也就是show函式

//看Dialog的建構函式 ,和acitivity差不多 也是PhoneWindow 物件。
Dialog(@NonNull Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) {
        if (createContextThemeWrapper) {
            if (themeResId == 0) {
                final TypedValue outValue = new TypedValue();
                context.getTheme().resolveAttribute(R.attr.dialogTheme, outValue, true);
                themeResId = outValue.resourceId;
            }
            mContext = new ContextThemeWrapper(context, themeResId);
        } else {
            mContext = context;
        }

        mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);

        final Window w = new PhoneWindow(mContext);
        mWindow = w;
        w.setCallback(this);
        w.setOnWindowDismissedCallback(this);
        w.setWindowManager(mWindowManager, null, null);
        w.setGravity(Gravity.CENTER);

        mListenersHandler = new ListenersHandler(this);
    }

//Dialog的setContentView方法 也是呼叫的phonewindow的方法 和acitivity流程也是一樣的
     public void setContentView(@LayoutRes int layoutResID) {
        mWindow.setContentView(layoutResID);
    }


//我們都知道dialog必須要show才能顯示出來,

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

        mCanceled = false;
        
        if (!mCreated) {
            dispatchOnCreate(null);
        }

        onStart();
        mDecor = mWindow.getDecorView();

        if (mActionBar == null && mWindow.hasFeature(Window.FEATURE_ACTION_BAR)) {
            final ApplicationInfo info = mContext.getApplicationInfo();
            mWindow.setDefaultIcon(info.icon);
            mWindow.setDefaultLogo(info.logo);
            mActionBar = new WindowDecorActionBar(this);
        }

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

        try {
            //在這裡 把decorview給add到這個window中了 與activity流程也是一樣的
            mWindowManager.addView(mDecor, l);
            mShowing = true;
    
            sendShowMessage();
        } finally {
        }
    }

8.Dialog的建立是不是必須要有activity的引用?

答:不需要,只要你更改為系統window就可以了。系統window是不需要activity作為引用的。注意別遺漏了許可權

Dialog dialog=new Dialog(MainActivity.this.getApplicationContext());
                dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ERROR);
                TextView textView=new TextView(MainActivity.this);
                textView.setText("this is dialog not use activity this");
                dialog.setContentView(textView);
                dialog.show();

9.toast的window建立過程?

答:這屬於系統級別的window建立了,和前面的兩種window建立過程稍微不一樣。其實主要就是notificationmanagerservice和toast本身之間兩者的相互呼叫而已。

就是簡單的ipc過程。前面binder的教程有講到,如何利用binder來進行雙向通訊。toast的原始碼 就是利用了binder的雙向通訊來完成toast的功能。

原始碼就不分析了,ipc的東西講過太多了,有興趣的可以自己看。

相關文章