1. 程式人生 > >Android windowTranslucentStatus屬性原始碼分析

Android windowTranslucentStatus屬性原始碼分析

簡介

我們在設定系統樣式時,將windowTranslucentStatuswindowTranslucentNavigation屬性設定為true後,Activity就會顯示為如下效果:

這裡寫圖片描述

狀態列和導航欄都會顯示成半透明的狀態。並且佈局會拓展到系統欄的後面。本文就是要從原始碼分析windowTranslucentStatus的實現原理。因為windowTranslucentNavigation是一樣的原理所以就不再去分析,我們只要理解了windowTranslucentStatus實現流程,自然而然也就知道了windowTranslucentNavigation的實現原理。當然,這是Android 4.4後才有的屬性介面,需要注意!!!

res/values/styles.xml

<style name="FullscreenTheme" parent="AppTheme">
    <item name="android:windowTranslucentStatus">true</item>
    <item name="android:windowTranslucentNavigation">true</item>
</style>

當然也可以在程式碼中動態設定

MainActivity.java

    @Override
    protected
void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION); }

原始碼分析

因為講的東西比較多,跨度比較大,程式碼量也比較大,先來一張時序圖,有一個大概的印象。然後,再一一分解之。

這裡寫圖片描述

上圖是個人根據程式碼呼叫的流程畫的一張時序圖,可以不是很規範。大概還是能表現出整個SystemUI變化呼叫的流程的,先湊合著看。

第一步,我們在styles.xml中設定了windowTranslucentStatustrue。它真正的實現是在PhoneWindow.javagenerateLayout方法中。程式碼如下:

frameworks/base/policy/src/com/android/internal/policy/impl/PhoneWindow.java

protected ViewGroup generateLayout(DecorView decor) {
    // Apply data from current theme.

    TypedArray a = getWindowStyle();
    if (a.getBoolean(com.android.internal.R.styleable.Window_windowTranslucentStatus, false)) {
        setFlags(FLAG_TRANSLUCENT_STATUS, FLAG_TRANSLUCENT_STATUS
                & (~getForcedWindowFlags()));
    }
}

frameworks/base/core/java/android/view/Window.java

public void setFlags(int flags, int mask) {
    final WindowManager.LayoutParams attrs = getAttributes();
    attrs.flags = (attrs.flags&~mask) | (flags&mask);
    if ((mask&WindowManager.LayoutParams.FLAG_NEEDS_MENU_KEY) != 0) {
        attrs.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_SET_NEEDS_MENU_KEY;
    }
    mForcedWindowFlags |= mask;
    if (mCallback != null) {
        mCallback.onWindowAttributesChanged(attrs);
    }
}

上面程式碼可以看出我們在styles.xml中設定的屬性,會通過setFlags設定下去。而setFlags其實就是將屬性設定給了WindowManager.LayoutParams。也就是說,我們設定的windowTranslucentStatus屬性,最終變成WindowManager.LayoutParamsflags屬性中FLAG_TRANSLUCENT_STATUS。那麼這個flags屬性對於我們的狀態列有什麼實際影響呢,接著往下看。

第二步,更新狀態列的狀態變化。在PhoneWindowManager.javaupdateSystemUiVisibilityLw方法是處理系統狀態列變化的地方。在其內部它會呼叫updateSystemBarsLw方法,然後,根據其返回值設定給SystemUI。而updateSystemBarsLwmStatusBarController才是真正管理狀態列的例項類。在updateSystemBarsLw中,它會呼叫mStatusBarControllerapplyTranslucentFlagLwupdateVisibilityLw的方法。這兩個方法就會返回相應的顯示狀態。SystemUI就是根據這個顯示狀態值,做相應的變化。

frameworks/base/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java


private final BarController mStatusBarController = new BarController("StatusBar",
        View.STATUS_BAR_TRANSIENT,
        View.STATUS_BAR_UNHIDE,
        View.STATUS_BAR_TRANSLUCENT,
        StatusBarManager.WINDOW_STATUS_BAR,
        WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);

private int updateSystemUiVisibilityLw() {
...
    final int visibility = updateSystemBarsLw(win, mLastSystemUiFlags, tmpVisibility);
    mFocusedApp = win.getAppToken();
    mHandler.post(new Runnable() {
        @Override
        public void run() {
            try {
                IStatusBarService statusbar = getStatusBarService();
                if (statusbar != null) {
                    //改變SystemUI的地方
                    statusbar.setSystemUiVisibility(visibility, 0xffffffff);
                    statusbar.topAppWindowChanged(needsMenu);
                }
            } catch (RemoteException e) {
                // re-acquire status bar service next time it is needed.
                mStatusBarService = null;
            }
        }
    });
}

private int updateSystemBarsLw(WindowState win, int oldVis, int vis) {
    // apply translucent bar vis flags
    WindowState transWin = mKeyguard != null && mKeyguard.isVisibleLw() && !mHideLockScreen ? mKeyguard : mTopFullscreenOpaqueWindowState;
    vis = mStatusBarController.applyTranslucentFlagLw(transWin, vis, oldVis);
    ...
    vis = mStatusBarController.updateVisibilityLw(transientStatusBarAllowed, oldVis, vis);
    ...
    return vis;
}

上面的程式碼主要是通過mStatusBarController來處理狀態列的顯示狀態。現在我們來看看它對應的實現方法。在applyTranslucentFlagLw方法中,就真正的聯絡上了,我們之前設定的windowTranslucentStatus屬性。為什麼這麼說?因為applyTranslucentFlagLw方法體有一個if ((win.getAttrs().flags & mTranslucentWmFlag) != 0)判斷。這個判斷其實就是判斷我們有沒有把windowTranslucentStatus屬性設定為truewin.getAttrs().flags對應的我們設定的WindowManager.LayoutParams的flags,而mTranslucentWmFlag就等於WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS。所以,這裡就和第一步聯絡起來了。如果我們設定了windowTranslucentStatustrue。這個if判斷就為true,vis就增加一個mTranslucentFlag標誌,其值為View.STATUS_BAR_TRANSLUCENT。這個標誌很重要,我們現在先放到這,後面會用這個標誌。這個新vis就會返回回去。updateVisibilityLw方法沒有做什麼很重要的事情就忽略不分析了。

frameworks/base/policy/src/com/android/internal/policy/impl/BarController.java

public BarController(String tag, int transientFlag, int unhideFlag, int translucentFlag, int statusBarManagerId, int translucentWmFlag) {
    mTag = "BarController." + tag; //tag="StatusBar"
    mTransientFlag = transientFlag; //transientFlag=View.STATUS_BAR_TRANSIENT
    mUnhideFlag = unhideFlag;       //unhideFlag=View.STATUS_BAR_UNHIDE
    mTranslucentFlag = translucentFlag;//translucentFlag=View.STATUS_BAR_TRANSLUCENT,
    mStatusBarManagerId = statusBarManagerId; //statusBarManagerId=StatusBarManager.WINDOW_STATUS_BAR
    mTranslucentWmFlag = translucentWmFlag; //translucentWmFlag=WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS
    mHandler = new Handler();
}

public int applyTranslucentFlagLw(WindowState win, int vis, int oldVis) {
    if (mWin != null) {
        if (win != null && (win.getAttrs().privateFlags
                & WindowManager.LayoutParams.PRIVATE_FLAG_INHERIT_TRANSLUCENT_DECOR) == 0) {
            //mTranslucentWmFlag=WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS
            //如果我們設定了FLAG_TRANSLUCENT_STATUS屬性,vis就會新增一個mTranslucentFlag標記,即View.STATUS_BAR_TRANSLUCENT
            if ((win.getAttrs().flags & mTranslucentWmFlag) != 0) {
                vis |= mTranslucentFlag;
            } else {
                vis &= ~mTranslucentFlag;
            }
        } else {
            vis = (vis & ~mTranslucentFlag) | (oldVis & mTranslucentFlag);
        }
    }
    return vis;
}

public int updateVisibilityLw(boolean transientAllowed, int oldVis, int vis) {
    if (mWin == null) return vis;
    if (isTransientShowing() || isTransientShowRequested()) { // transient bar requested
        if (transientAllowed) {
            vis |= mTransientFlag;
            if ((oldVis & mTransientFlag) == 0) {
                vis |= mUnhideFlag;  // tell sysui we're ready to unhide
            }
            setTransientBarState(TRANSIENT_BAR_SHOWING);  // request accepted
        } else {
            setTransientBarState(TRANSIENT_BAR_NONE);  // request denied
        }
    }
    if (mTransientBarState != TRANSIENT_BAR_NONE) {
        vis |= mTransientFlag;  // ignore clear requests until transition completes
        vis &= ~View.SYSTEM_UI_FLAG_LOW_PROFILE;  // never show transient bars in low profile
    }
    if ((vis & mTranslucentFlag) != 0 || (oldVis & mTranslucentFlag) != 0) {
        mLastTranslucent = SystemClock.uptimeMillis();
    }
    return vis;
}

第三步,updateSystemBarsLw呼叫結束後,接下來就是呼叫statusbar.setSystemUiVisibility(visibility, 0xffffffff);,這才是真正改變SystemUI狀態的介面。visibility就是updateSystemBarsLw的返回值,其實也就是mStatusBarController.applyTranslucentFlagLwmStatusBarController.updateVisibilityLw的返回值。而statusbar呼叫的是StatusBarManagerService.java中的setSystemUiVisibility方法,它最終呼叫的是mBar.setSystemUiVisibility(vis, mask),那這個mBar又是誰傳過來的呢?從程式碼可以看出,這個mBar是通過registerStatusBar註冊而來的。哪誰又呼叫了這個registerStatusBar方法?

frameworks/base/services/java/com/android/server/StatusBarManagerService.java

public void registerStatusBar(IStatusBar bar, StatusBarIconList iconList, List<IBinder> notificationKeys, List<StatusBarNotification> notifications, int switches[], List<IBinder> binders) {
    enforceStatusBarService();

    Slog.i(TAG, "registerStatusBar bar=" + bar);
    mBar = bar;
}

public void setSystemUiVisibility(int vis, int mask) {
    // also allows calls from window manager which is in this process.
    enforceStatusBarService();

    if (SPEW) Slog.d(TAG, "setSystemUiVisibility(0x" + Integer.toHexString(vis) + ")");

    synchronized (mLock) {
        updateUiVisibilityLocked(vis, mask);
        disableLocked(mCurrentUserId, vis & StatusBarManager.DISABLE_MASK, mSysUiVisToken, "WindowManager.LayoutParams");
    }
}

private void updateUiVisibilityLocked(final int vis, final int mask) {
    if (mSystemUiVisibility != vis) {
        mSystemUiVisibility = vis;
        mHandler.post(new Runnable() {
                public void run() {
                    if (mBar != null) {
                        try {
                            mBar.setSystemUiVisibility(vis, mask);
                        } catch (RemoteException ex) {
                        }
                    }
                }
            });
    }
}

第四步,SystemUI閃亮登場,真正實現SystemUI變化的主角。它在BaseStatusBar.java中呼叫了StatusBarManagerService.javaregisterStatusBarCommandQueue註冊到StatusBarManagerService與其產生關聯。所以StatusBarManagerServicesetSystemUiVisibility最終呼叫的是CommandQueue.java中方法。而CommandQueue.javaBaseStatusBar.java是通過介面回撥實現對應關係的,PhoneStatusBar.java繼承自BaseStatusBar.java。所以CommandQueue呼叫setSystemUiVisibility方法,最終呼叫的是PhoneStatusBar中的setSystemUiVisibility方法。

frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java

public void start() {

...
    // Connect in to the status bar manager service
    StatusBarIconList iconList = new StatusBarIconList();
    ArrayList<IBinder> notificationKeys = new ArrayList<IBinder>();
    ArrayList<StatusBarNotification> notifications = new ArrayList<StatusBarNotification>();
    mCommandQueue = new CommandQueue(this, iconList);

    try {
        mBarService.registerStatusBar(mCommandQueue, iconList, notificationKeys, notifications, switches, binders);
    } catch (RemoteException ex) {
        // If the system process isn't there we're doomed anyway.
    }
...
}

frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java

public CommandQueue(Callbacks callbacks, StatusBarIconList list) {
    mCallbacks = callbacks;
    mList = list;
}

public void setSystemUiVisibility(int vis, int mask) {
    synchronized (mList) {
        mHandler.removeMessages(MSG_SET_SYSTEMUI_VISIBILITY);
        mHandler.obtainMessage(MSG_SET_SYSTEMUI_VISIBILITY, vis, mask, null).sendToTarget();
    }
}

private final class H extends Handler {
    public void handleMessage(Message msg) {
        final int what = msg.what & MSG_MASK;
        switch (what) {
            case MSG_SET_SYSTEMUI_VISIBILITY:
                mCallbacks.setSystemUiVisibility(msg.arg1, msg.arg2);
            break;
            ...
        }
    }
}

第五步,PhoneStatusBar.javasetSystemUiVisibility的實現。這個方法真正關鍵地方是在呼叫barMode這個方法中獲取最新的mode狀態。它會用vis比較各個flag,vis就是我們在PhoneWindowManager.java中傳入的引數。我們知道在第二步後面增加了View.STATUS_BAR_TRANSLUCENT這個標誌,說了很重要。在這裡才真正的體現出來了。barMode方法通過比較得到modeMODE_TRANSLUCENT。然後,通過checkBarModes應用相應的mode。

frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java

@Override // CommandQueue
public void setSystemUiVisibility(int vis, int mask) {
    final int oldVal = mSystemUiVisibility;
    final int newVal = (oldVal&~mask) | (vis&mask);
    final int diff = newVal ^ oldVal;

    ...
    // update status bar mode
    final int sbMode = computeBarMode(oldVal, newVal, mStatusBarView.getBarTransitions(), View.STATUS_BAR_TRANSIENT, View.STATUS_BAR_TRANSLUCENT);

    final boolean sbModeChanged = sbMode != -1;

    boolean checkBarModes = false;
    if (sbModeChanged && sbMode != mStatusBarMode) {
        mStatusBarMode = sbMode;
        checkBarModes = true;
    }
    if (checkBarModes) {
        checkBarModes();
    }
}


private int computeBarMode(int oldVis, int newVis, BarTransitions transitions, int transientFlag, int translucentFlag) {
    final int oldMode = barMode(oldVis, transientFlag, translucentFlag);
    final int newMode = barMode(newVis, transientFlag, translucentFlag);
    if (oldMode == newMode) {
        return -1; // no mode change
    }
    return newMode;
}

private int barMode(int vis, int transientFlag, int translucentFlag) {
    return (vis & transientFlag) != 0 ? MODE_SEMI_TRANSPARENT
            : (vis & translucentFlag) != 0 ? MODE_TRANSLUCENT
            : (vis & View.SYSTEM_UI_FLAG_LOW_PROFILE) != 0 ? MODE_LIGHTS_OUT
            : MODE_OPAQUE;
}

private void checkBarModes() {
    if (mDemoMode) return;
    int sbMode = mStatusBarMode;
    if (panelsEnabled() && (mInteractingWindows & StatusBarManager.WINDOW_STATUS_BAR) != 0) {
        // if panels are expandable, force the status bar opaque on any interaction
        sbMode = MODE_OPAQUE;
    }
    checkBarMode(sbMode, mStatusBarWindowState, mStatusBarView.getBarTransitions());
    if (mNavigationBarView != null) {
        checkBarMode(mNavigationBarMode,
                mNavigationBarWindowState, mNavigationBarView.getBarTransitions());
    }
}

private void checkBarMode(int mode, int windowState, BarTransitions transitions) {
    final boolean anim = (mScreenOn == null || mScreenOn) && windowState != WINDOW_STATE_HIDDEN;
    transitions.transitionTo(mode, anim);
}

checkBarModetransitions是從PhoneStatusBarView中建立的PhoneStatusBarTransitions

frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java

public PhoneStatusBarView(Context context, AttributeSet attrs) {
    super(context, attrs);

    mBarTransitions = new PhoneStatusBarTransitions(this);
}

public BarTransitions getBarTransitions() {
    return mBarTransitions;
}

frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarTransitions.java

public PhoneStatusBarTransitions(PhoneStatusBarView view) {
    super(view, R.drawable.status_background);
    mView = view;
}

PhoneStatusBarTransitions繼承自BarTransitions。通過PhoneStatusBarTransitions的建構函式,我們看到其呼叫了父類的構造方法super(view, R.drawable.status_background);。在其父類的構造方法中會將R.drawable.status_background這個資源圖片設定到view的背景中去。那R.drawable.status_background是個什麼樣的圖片呢?如下圖:

這裡寫圖片描述

是不是恍然大悟,原來我們看到的狀態漸變效果就是因為設定這張點9的背景圖片。那問題來,我們的狀態列不應該一直都是這種漸變效果嗎?哈哈,是不是被忽悠了,客官別急,還有最後一步。

frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/BarTransitions.java

public BarTransitions(View view, int gradientResourceId) {
    mTag = "BarTransitions." + view.getClass().getSimpleName();
    mView = view;
    mBarBackground = new BarBackgroundDrawable(mView.getContext(), radientResourceId);
    if (HIGH_END) {
        mView.setBackground(mBarBackground);
    }
}

public void transitionTo(int mode, boolean animate) {
    // low-end devices do not support translucent modes, fallback to opaque
    if (!HIGH_END && (mode == MODE_SEMI_TRANSPARENT || mode == MODE_TRANSLUCENT)) {
        mode = MODE_OPAQUE;
    }
    if (mMode == mode) return;
    int oldMode = mMode;
    mMode = mode;
    if (DEBUG) Log.d(mTag, String.format("%s -> %s animate=%s",
            modeToString(oldMode), modeToString(mode),  animate));
    onTransition(oldMode, mMode, animate);
}

protected void onTransition(int oldMode, int newMode, boolean animate) {
    if (HIGH_END) {
        applyModeBackground(oldMode, newMode, animate);
    }
}

protected void applyModeBackground(int oldMode, int newMode, boolean animate) {
    if (DEBUG) Log.d(mTag, String.format("applyModeBackground oldMode=%s newMode=%s animate=%s",
            modeToString(oldMode), modeToString(newMode), animate));
    mBarBackground.applyModeBackground(oldMode, newMode, animate);
}

private static class BarBackgroundDrawable extends Drawable {


    public BarBackgroundDrawable(Context context, int gradientResourceId) {
        final Resources res = context.getResources();
        mGradient = res.getDrawable(gradientResourceId);
        mInterpolator = new LinearInterpolator();
    }

     public void applyModeBackground(int oldMode, int newMode, boolean animate) {
            if (mMode == newMode) return;
            mMode = newMode;
            mAnimating = animate;
            if (animate) {
                long now = SystemClock.elapsedRealtime();
                mStartTime = now;
                mEndTime = now + BACKGROUND_DURATION;
                mGradientAlphaStart = mGradientAlpha;
                mColorStart = mColor;
            }
            invalidateSelf();
        }

        @Override
        public void draw(Canvas canvas) {
            int targetGradientAlpha = 0, targetColor = 0;
            if (mMode == MODE_TRANSLUCENT) {
                targetGradientAlpha = 0xff;
            } else if (mMode == MODE_SEMI_TRANSPARENT) {
                targetColor = mSemiTransparent;
            } else {
                targetColor = mOpaque;
            }
            if (!mAnimating) {
                mColor = targetColor;
                mGradientAlpha = targetGradientAlpha;
            } else {
                ...
            }
            if (mGradientAlpha > 0) {
                mGradient.setAlpha(mGradientAlpha);
                mGradient.draw(canvas);
            }
            if (Color.alpha(mColor) > 0) {
                canvas.drawColor(mColor);
            }
            if (mAnimating) {
                invalidateSelf();  // keep going
            }
        }
    }

最後,我們再來看下PhoneStatusBar.javacheckBarMode方法的transitions.transitionTo呼叫。這個transitionTo最終呼叫的是BarTransitions.java中的transitionTo方法,通過上面的原始碼可以知道它其實最後呼叫就是BarBackgroundDrawable中的applyModeBackground方法。會傳入mode,·並呼叫invalidateSelf方法。最終在draw中,根據mode顯示對應的背景效果。這裡因為我們的mode為MODE_TRANSLUCENT,所以,最終會呼叫mGradient.draw(canvas);。而mGradient就是這個R.drawable.status_background圖片的drawable,所以我們在mode為MODE_TRANSLUCENT就會顯示為一個漸變的效果。而在其它mode時候顯示一個rgb顏色背景。

額外補充

有沒有發現我們在設定windowTranslucentStatuswindowTranslucentNavigationture後,我們的佈局也會拓展到狀態列和導航欄後面去。原因在這:

frameworks/base/core/java/android/view/ViewRootImpl.java

private int getImpliedSystemUiVisibility(WindowManager.LayoutParams params) {
    int vis = 0;
    // Translucent decor window flags imply stable system ui visibility.
    if ((params.flags & WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS) != 0) {
        vis |= View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
    }
    if ((params.flags & WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION) != 0) {
        vis |= View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION;
    }
    return vis;
}

系統在我們新增windowTranslucentStatuswindowTranslucentNavigation屬性時候,會自動為我們增加View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION屬性。

那要怎麼解決我們的佈局會被拓展到系統欄後面的效果。在layout.xml增加android:fitsSystemWindows="true"即可。效果如下:

這裡寫圖片描述

總結

寫這篇文章中途有很多次試圖放棄了,因為要把這些知識用文字表達出來確實很為難自己啊。表達能力本來就不是特別好,再加上用文字的方法更難了。自己雖然已經明白了整個實現流程。但是變向讓自己去把他們寫出來還是難以下筆。最終還是咬咬牙寫下來了,算是對這個知識點的一個總結吧。