Android OnWindowVisibilityChanged浅碾、OnVisibilityChanged和OnViewAttachedToWindow詳解

在Android開發(fā)過程中鸟廓,經(jīng)常需要獲取Window或某個View的可見性變化時機从祝,以便在View的Visibility變化時進行相應(yīng)的處理。目前引谜,比較常用的判斷View可見性時機的回調(diào)有

  • onWindowVisibilityChanged

  • onVisibilityChanged

  • OnAttachStateChangeListener#onViewAttachedToWindow

一牍陌、onWindowVisibilityChanged

/**
 * Called when the window containing has change its visibility
 * (between {@link #GONE}, {@link #INVISIBLE}, and {@link #VISIBLE}).  Note
 * that this tells you whether or not your window is being made visible
 * to the window manager; this does <em>not</em> tell you whether or not
 * your window is obscured by other windows on the screen, even if it
 * is itself visible.
 *
 * @param visibility The new visibility of the window.
 */
 protected void onWindowVisibilityChanged(@Visibility int visibility) {
   if (visibility == VISIBLE) {
     initialAwakenScrollBars();
   }
 }

由方法注釋可知,它是在窗口可見性改變時調(diào)用员咽,而且注意這只是在WindowWindowManager可見時調(diào)用毒涧,并不是告知你當前可見的Window是否被遮擋。

查看代碼贝室,發(fā)現(xiàn)其調(diào)用位置有3處契讲,添加時在performTraversals方法中(代碼有省略)仿吞,Activity onStop生命周期會removeDecorView和對應(yīng)的Window,在removeView方法中會調(diào)用dispatchDetachedFromWindow方法怀泊,該方法內(nèi)又會調(diào)用onWindowVisibilityChanged

private void performTraversals() {
 // cache mView since it is used so much below...
 final View host = mView;
 ......

 final int viewVisibility = getHostVisibility();
 final boolean viewVisibilityChanged = !mFirst && (mViewVisibility != viewVisibility|| mNewSurfaceNeeded
 // Also check for possible double visibility update, which will make current
 // viewVisibility value equal to mViewVisibility and we may miss it.
 || mAppVisibilityChanged);
 mAppVisibilityChanged = false;
 final boolean viewUserVisibilityChanged = !mFirst &&
 ((mViewVisibility == View.VISIBLE) != (viewVisibility == View.VISIBLE));

 ......
 if (mFirst) {
 ......

 // We used to use the following condition to choose 32 bits drawing caches:
 // PixelFormat.hasAlpha(lp.format) || lp.format == PixelFormat.RGBX_8888
 // However, windows are now always 32 bits by default, so choose 32 bits
 mAttachInfo.mUse32BitDrawingCache = true;
 mAttachInfo.mWindowVisibility = viewVisibility;
 mAttachInfo.mRecomputeGlobalAttributes = false;
 mLastConfigurationFromResources.setTo(config);
 mLastSystemUiVisibility = mAttachInfo.mSystemUiVisibility;
 // Set the layout direction if it has not been set before (inherit is the default)
 if (mViewLayoutDirectionInitial == View.LAYOUT_DIRECTION_INHERIT) {
   host.setLayoutDirection(config.getLayoutDirection());
 }
 /**
 *  ①
 */
 host.dispatchAttachedToWindow(mAttachInfo, 0);
 mAttachInfo.mTreeObserver.dispatchOnWindowAttachedChange(true);
 dispatchApplyInsets(host);
 } else {
   ......
   }
 }

 if (viewVisibilityChanged) {
   mAttachInfo.mWindowVisibility = viewVisibility;
   /**
   *  ②
   */
   host.dispatchWindowVisibilityChanged(viewVisibility);
   if (viewUserVisibilityChanged) {
     host.dispatchVisibilityAggregated(viewVisibility == View.VISIBLE);
   }
   if (viewVisibility != View.VISIBLE || mNewSurfaceNeeded) {
     endDragResizing();
     destroyHardwareResources();
   }
   if (viewVisibility == View.GONE) {
     // After making a window gone, we will count it as being
     // shown for the first time the next time it gets focus.
     mHasHadWindowFocus = false;
    }
  }
}
void dispatchDetachedFromWindow() {
 AttachInfo info = mAttachInfo;
 if (info != null) {
   int vis = info.mWindowVisibility;
   if (vis != GONE) {
     onWindowVisibilityChanged(GONE);// ③
   if (isShown()) {
     // Invoking onVisibilityAggregated directly here since the subtree
     // will also receive detached from window
     onVisibilityAggregated(false);
   }
 }
}

 onDetachedFromWindow();
 onDetachedFromWindowInternal();

 ......
 ListenerInfo li = mListenerInfo;
 final CopyOnWriteArrayList<OnAttachStateChangeListener> listeners =
 li != null ? li.mOnAttachStateChangeListeners : null;
 if (listeners != null && listeners.size() > 0) {
 // NOTE: because of the use of CopyOnWriteArrayList, we *must* use an iterator to
 // perform the dispatching. The iterator is a safe guard against listeners that
 // could mutate the list by calling the various add/remove methods. This prevents
 // the array from being modified while we iterate it.
 for (OnAttachStateChangeListener listener : listeners) {
   listener.onViewDetachedFromWindow(this);
  }
 }

 ......
 }

調(diào)用位置已注釋茫藏,首先看第一處①,應(yīng)用啟動時霹琼,host就是DecorView對象务傲,它是一個ViewGroup,然后在ViewGroup類中查看枣申。

/**
 * ViewGroup.class
 */
 @Override
 @UnsupportedAppUsage
 void dispatchAttachedToWindow(AttachInfo info, int visibility) {
   mGroupFlags |= FLAG_PREVENT_DISPATCH_ATTACHED_TO_WINDOW;
   // [1]
   super.dispatchAttachedToWindow(info, visibility);
   mGroupFlags &= ~FLAG_PREVENT_DISPATCH_ATTACHED_TO_WINDOW;

   final int count = mChildrenCount;
   final View[] children = mChildren;
   for (int i = 0; i < count; i++) {
     final View child = children[i];
     // [2]
     child.dispatchAttachedToWindow(info,
     combineVisibility(visibility, child.getVisibility()));
   }
   final int transientCount = mTransientIndices == null ? 0 : mTransientIndices.size();
   for (int i = 0; i < transientCount; ++i) {
     View view = mTransientViews.get(i);
     view.dispatchAttachedToWindow(info,
     combineVisibility(visibility, view.getVisibility()));
   }
 }

可以看到售葡,ViewGroup#dispatchAttachedToWindow方法主要作用就是

[1] 調(diào)用自身的dispatchAttachedToWindow方法,處理自己attachWindow

[2] 向子View分發(fā)事件忠藤,讓每個子View處理attachWindow的事件

由此可知挟伙,最終都會調(diào)用到View#dispatchAttachedToWindow方法:

void dispatchAttachedToWindow(AttachInfo info, int visibility) {
 mAttachInfo = info;
 ......
 // Transfer all pending runnables.
 if (mRunQueue != null) {
 mRunQueue.executeActions(info.mHandler);
 mRunQueue = null;
 }
 performCollectViewAttributes(mAttachInfo, visibility);
 onAttachedToWindow();// [1]

 ListenerInfo li = mListenerInfo;
 final CopyOnWriteArrayList<OnAttachStateChangeListener> listeners =
 li != null ? li.mOnAttachStateChangeListeners : null;
 if (listeners != null && listeners.size() > 0) {
 // NOTE: because of the use of CopyOnWriteArrayList, we *must* use an iterator to
 // perform the dispatching. The iterator is a safe guard against listeners that
 // could mutate the list by calling the various add/remove methods. This prevents
 // the array from being modified while we iterate it.
 for (OnAttachStateChangeListener listener : listeners) {
     listener.onViewAttachedToWindow(this);// [2]
   }
 }

 int vis = info.mWindowVisibility;
 if (vis != GONE) {
     onWindowVisibilityChanged(vis);// [3]
     if (isShown()) {
     // Calling onVisibilityAggregated directly here since the subtree will also
     // receive dispatchAttachedToWindow and this same call
     onVisibilityAggregated(vis == VISIBLE);
   }
 }

 // Send onVisibilityChanged directly instead of dispatchVisibilityChanged.
 // As all views in the subtree will already receive dispatchAttachedToWindow
 // traversing the subtree again here is not desired.
 onVisibilityChanged(this, visibility);

 ......
 }

View#dispatchAttachedToWindow方法集中處理了onAttachedToWindowonViewAttachedToWindow模孩、onWindowVisibilityChangedonVisibilityChanged這4種可見性變化相關(guān)的回調(diào)函數(shù)尖阔。

對于onWindowVisibilityChanged方法來說,首先會通過AttachInfo對象獲取現(xiàn)在窗口(mWindowVisibility)可見性榨咐。mWindowVisibility變量的賦值也在performTraversals方法中介却。

......
final int viewVisibility = getHostVisibility();
// mViewVisibility在創(chuàng)建ViewRootImpl對象時,初始化值是GONE块茁;在完成測量布局后賦值為viewVisibility
final boolean viewVisibilityChanged = !mFirst
 && (mViewVisibility != viewVisibility || mNewSurfaceNeeded
 // Also check for possible double visibility update, which will make current
 // viewVisibility value equal to mViewVisibility and we may miss it.
 || mAppVisibilityChanged);
......
mAttachInfo.mWindowVisibility = viewVisibility;
......

Window被添加到屏幕上后(mWindowSession.addToDisplay)齿坷,getHostVisibility()就返回Visible。所以只要mWindowVisibility不為GONE就會調(diào)用onWindowVisibilityChanged方法数焊。這就是它的第一種調(diào)用場景永淌。
查看getHostVisibility()方法

// #ViewRootImpl.java
int getHostVisibility() {
        return (mAppVisible || mForceDecorViewVisibility) ? mView.getVisibility() : View.GONE;
    }

可見,最終取得的是mView對象的可見性佩耳,而mView對象就是DecorView對象(在ViewRootImpl#setView方法中設(shè)置的)遂蛀,所以:

mWindowVisibility方法只會在頁面(ActivityDialog)打開和關(guān)閉(具體說是ActivityDialogWindow可見性改變時)各調(diào)用一次
第二處調(diào)用位置在處,主要代碼是

 if (viewVisibilityChanged) {
 mAttachInfo.mWindowVisibility = viewVisibility;
 /**
 *  ②
 */
 host.dispatchWindowVisibilityChanged(viewVisibility);
 ......
 }

DecorView加載時干厚,如果mFirst==false(非首次加載)李滴,很可能進行該條件體進行調(diào)用。

第三種情況③萍诱,例如打開一個新頁面悬嗓,老頁面走到onStop聲明周期方法,如③處裕坊,只要不是GONE就會調(diào)用包竹。

綜上,onWindowVisibilityChanged的調(diào)用:

  • 每當一個頁面打開或移除時(具體點說就是ViewRootImpl將DecovView添加到屏幕上展示),如果關(guān)聯(lián)的Window可見(不等于GONE)周瞎,則會調(diào)用

  • 打開時苗缩,是在View#dispatchAttachedToWindow中進行調(diào)用,分離時在View#dispatchDetachFromWindow時調(diào)用声诸,并傳入默認參數(shù)GONE

二酱讶、onVisibilityChanged

onVisibilityChanged調(diào)用時機和onWindowVisibilityChanged非常類似,對于APP啟動打開頁面時彼乌,會處理重寫該方法的View attach到Window的事件泻肯,此時默認傳入的參數(shù)是Visible(值為0)。

 // ViewRootImpl.class
 private void performTraversals() {
 ......
 host.dispatchAttachedToWindow(mAttachInfo, 0);
 ......
 }

 // View.class
 void dispatchAttachedToWindow(AttachInfo info, int visibility) {
 ......
 // Send onVisibilityChanged directly instead of dispatchVisibilityChanged.
 // As all views in the subtree will already receive dispatchAttachedToWindow
 // traversing the subtree again here is not desired.
 onVisibilityChanged(this, visibility);
 ......
 }

然后慰照,每次調(diào)用setVisibility方法來控制視圖的可見性時都會回調(diào)該方法灶挟。

    // View.class
    public void setVisibility(@Visibility int visibility) {
        setFlags(visibility, VISIBILITY_MASK);
    }

    void setFlags(int flags, int mask) {
        ......

        if ((changed & VISIBILITY_MASK) != 0) {
            ......
            if (mAttachInfo != null) {
                dispatchVisibilityChanged(this, newVisibility);
                ......
            }
        }        
        .......
    }

    protected void dispatchVisibilityChanged(@NonNull View changedView,
            @Visibility int visibility) {
        onVisibilityChanged(changedView, visibility);
    }

同樣,在頁面關(guān)閉時(或打開新頁面覆蓋舊頁面)毒租,會執(zhí)行onStop生命周期方法稚铣,其實是調(diào)用ActivityThread#handleStopActivity方法,然后會調(diào)用到updateVisibility方法

// ActivityThread.class
private void updateVisibility(ActivityClientRecord r, boolean show) {
        View v = r.activity.mDecor;
        if (v != null) {
            if (show) {
                if (!r.activity.mVisibleFromServer) {
                    r.activity.mVisibleFromServer = true;
                    mNumVisibleActivities++;
                    if (r.activity.mVisibleFromClient) {
                        r.activity.makeVisible();
                    }
                }
                ......
            } else {
                if (r.activity.mVisibleFromServer) {
                    r.activity.mVisibleFromServer = false;
                    mNumVisibleActivities--;
                    v.setVisibility(View.INVISIBLE);
                }
            }
        }
    }

mVisibleFromServer默認是false墅垮,在onResume后賦值為true惕医,此時mVisibleFromServertrue,進入條件體算色,首先將mVisibleFromServer設(shè)置為false抬伺,然后通過DecorView調(diào)用setVisibility方法來控制視圖顯示,并默認傳輸View.INVISIBLE剃允。我們知道調(diào)用setVisibility就可能觸發(fā)onVisibilityChanged的執(zhí)行沛简。

總結(jié)

  • 頁面加載時齐鲤,會在View attachWindow時(dispatchAttachedToWindow方法)調(diào)用onVisibilityChanged

  • 通過setVisibility來改變View的可見性時會調(diào)用onVisibilityChanged

  • 關(guān)閉頁面時斥废,在handleStopActivity方法中會調(diào)用updateVisibility方法,內(nèi)部也會調(diào)用setVisibility给郊,并傳入默認參數(shù)INVISIBLE牡肉。

三、OnAttachStateChangeListener#onViewAttachedToWindow

OnAttachStateChangeListener定義了2個接口方法淆九,分別在View Attach/Detach to Window時候調(diào)用统锤。要使用該接口首先需要注冊這個監(jiān)聽器。

public void addOnAttachStateChangeListener(OnAttachStateChangeListener listener) {
        ListenerInfo li = getListenerInfo();
        if (li.mOnAttachStateChangeListeners == null) {
            li.mOnAttachStateChangeListeners
                    = new CopyOnWriteArrayList<OnAttachStateChangeListener>();
        }
        li.mOnAttachStateChangeListeners.add(listener);
    }

調(diào)用位置很單純炭庙,就在View#dispatchAttachedToWindow方法里饲窿,如果有注冊過該監(jiān)聽器,就會調(diào)用

void dispatchAttachedToWindow(AttachInfo info, int visibility) {
        ......
        onAttachedToWindow();

        ListenerInfo li = mListenerInfo;
        final CopyOnWriteArrayList<OnAttachStateChangeListener> listeners =
                li != null ? li.mOnAttachStateChangeListeners : null;
        if (listeners != null && listeners.size() > 0) {
            // NOTE: because of the use of CopyOnWriteArrayList, we *must* use an iterator to
            // perform the dispatching. The iterator is a safe guard against listeners that
            // could mutate the list by calling the various add/remove methods. This prevents
            // the array from being modified while we iterate it.
            for (OnAttachStateChangeListener listener : listeners) {
                listener.onViewAttachedToWindow(this);
            }
        }
        ......
    }
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末焕蹄,一起剝皮案震驚了整個濱河市逾雄,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖鸦泳,帶你破解...
    沈念sama閱讀 222,464評論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件银锻,死亡現(xiàn)場離奇詭異,居然都是意外死亡做鹰,警方通過查閱死者的電腦和手機击纬,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,033評論 3 399
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來钾麸,“玉大人更振,你說我怎么就攤上這事》钩ⅲ” “怎么了殃饿?”我有些...
    開封第一講書人閱讀 169,078評論 0 362
  • 文/不壞的土叔 我叫張陵,是天一觀的道長芋肠。 經(jīng)常有香客問我乎芳,道長,這世上最難降的妖魔是什么帖池? 我笑而不...
    開封第一講書人閱讀 59,979評論 1 299
  • 正文 為了忘掉前任奈惑,我火速辦了婚禮,結(jié)果婚禮上睡汹,老公的妹妹穿的比我還像新娘肴甸。我一直安慰自己,他們只是感情好囚巴,可當我...
    茶點故事閱讀 69,001評論 6 398
  • 文/花漫 我一把揭開白布原在。 她就那樣靜靜地躺著,像睡著了一般彤叉。 火紅的嫁衣襯著肌膚如雪庶柿。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,584評論 1 312
  • 那天秽浇,我揣著相機與錄音浮庐,去河邊找鬼。 笑死柬焕,一個胖子當著我的面吹牛审残,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播斑举,決...
    沈念sama閱讀 41,085評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼搅轿,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了富玷?” 一聲冷哼從身側(cè)響起璧坟,我...
    開封第一講書人閱讀 40,023評論 0 277
  • 序言:老撾萬榮一對情侶失蹤没宾,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后沸柔,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體循衰,經(jīng)...
    沈念sama閱讀 46,555評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,626評論 3 342
  • 正文 我和宋清朗相戀三年褐澎,在試婚紗的時候發(fā)現(xiàn)自己被綠了会钝。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,769評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡工三,死狀恐怖迁酸,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情俭正,我是刑警寧澤奸鬓,帶...
    沈念sama閱讀 36,439評論 5 351
  • 正文 年R本政府宣布,位于F島的核電站掸读,受9級特大地震影響串远,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜儿惫,卻給世界環(huán)境...
    茶點故事閱讀 42,115評論 3 335
  • 文/蒙蒙 一澡罚、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧肾请,春花似錦留搔、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,601評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至饵逐,卻和暖如春梢灭,著一層夾襖步出監(jiān)牢的瞬間棍苹,已是汗流浹背淮蜈。 一陣腳步聲響...
    開封第一講書人閱讀 33,702評論 1 274
  • 我被黑心中介騙來泰國打工般婆, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留捐下,地道東北人账锹。 一個月前我還...
    沈念sama閱讀 49,191評論 3 378
  • 正文 我出身青樓,卻偏偏與公主長得像坷襟,于是被迫代替她去往敵國和親奸柬。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,781評論 2 361

推薦閱讀更多精彩內(nèi)容