onCreate中獲取View的寬高為0抒钱?

開發(fā)的過(guò)程中,我們有時(shí)候需要在Activity啟動(dòng)以后第一時(shí)間獲取某個(gè)View的寬高颜凯,并作相應(yīng)處理继效,當(dāng)我們?cè)?code>onCreate中通過(guò)View.getWidth(或getMeasuredWidth)和View.getHeight(或getMeasuredHeight)方法獲取的時(shí)候,你會(huì)發(fā)現(xiàn)它們都返回0装获。我們猜測(cè)是因?yàn)槿鹦牛@個(gè)時(shí)候View還沒有布局完成。解決的辦法有很多種穴豫,比如在ViewOnGlobalLayoutListener中獲取凡简,在onLayout中獲取等等,具體請(qǐng)參考StackOverflow:
https://stackoverflow.com/questions/18861585/get-content-view-size-in-oncreate

有另外一個(gè)方法精肃,有些人應(yīng)該也知道秤涩,在onCreate中通過(guò)View.post,在其中的Runnable中就能獲取到正確的寬高司抱。那么筐眷,這里的原理是什么,很多人認(rèn)為這里就是一個(gè)時(shí)間差习柠,等Viewmeasurelayout完了就有寬和高了匀谣,有些人甚至使用postDelay延遲幾秒來(lái)確保萬(wàn)無(wú)一失。那么這里面到底會(huì)不會(huì)有不穩(wěn)定的因素呢资溃?這篇文章告訴你答案武翎。

本篇文章分析的是Android 6.0系統(tǒng)的源碼。閱讀之前溶锭,請(qǐng)先了解Android中Handler宝恶,LooperMessageQueue等概念,推薦文章:http://blog.csdn.net/lmj623565791/article/details/38377229/

詳細(xì)分析:

請(qǐng)看View.post源碼,

public boolean post(Runnable action) {
    final AttachInfo attachInfo = mAttachInfo;
    if (attachInfo != null) {
        return attachInfo.mHandler.post(action);
    }
    // Assume that post will succeed later
    ViewRootImpl.getRunQueue().post(action);
    return true;
}

onCreate的方法中執(zhí)行的時(shí)候垫毙,attachInfo==null(為什么霹疫?請(qǐng)自行驗(yàn)證),所以post方法執(zhí)行到了

ViewRootImpl.getRunQueue().post(action)综芥。

這個(gè)方法是將action先保存到ViewRootImpl中的一個(gè)靜態(tài)隊(duì)列中更米,保存起來(lái)什么時(shí)候用呢?

ViewRootImpl.perfromTravesals方法中可以看到這個(gè)隊(duì)列調(diào)用的代碼毫痕,

// Execute enqueued actions on every traversal in case a detached view enqueued an action
getRunQueue().executeActions(mAttachInfo.mHandler);
...
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
...
performLayout(lp, mWidth, mHeight);
...
performDraw();

大家都知道performTraversals是布局遍歷的方法征峦,所以我們知道了,post的Runnable是在布局遍歷的時(shí)候執(zhí)行的消请。然而栏笆,它是在performMeasure、performLayout之前執(zhí)行的臊泰。也就是說(shuō)蛉加,這個(gè)Runnable是在View的測(cè)量和布局之前執(zhí)行。View測(cè)量和布局完成之前是獲取不到寬高的缸逃,那我們的Runnable是怎么獲取到寬高的呢针饥。

事實(shí)上,getRunQueue().executeActions(mAttachInfo.mHandler) 也不是直接執(zhí)行這些Runnable需频,而是往主線程消息隊(duì)列添加對(duì)應(yīng)的消息進(jìn)去丁眼,那么我們的這個(gè)消息執(zhí)行的時(shí)機(jī)是怎么保證在View布局完成之后呢?研究過(guò)performTraversals源碼的話昭殉,應(yīng)該知道苞七,performTraversals這個(gè)方法也是被附到一個(gè)消息上添加到消息隊(duì)列等待執(zhí)行的。下面看分析挪丢。

首先從View布局的終極方法performTraversals開始分析蹂风,這個(gè)方法到底是怎么執(zhí)行的,在哪執(zhí)行的乾蓬。一路追過(guò)去惠啄,

void doTraversal() {
    if (mTraversalScheduled) {
        mTraversalScheduled = false;
        mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);

        if (mProfile) {
            Debug.startMethodTracing("ViewAncestor");
        }

        performTraversals();

        if (mProfile) {
            Debug.stopMethodTracing();
            mProfile = false;
        }
    }
}

perfromTraversalsdoTraversal調(diào)用,

final class TraversalRunnable implements Runnable {
    @Override
    public void run() {
        doTraversal();
    }
}

doTraversal方法在TraversalRunnable調(diào)用任内,那么這個(gè)Runnable的對(duì)象在哪執(zhí)行的呢撵渡?

final TraversalRunnable mTraversalRunnable = new TraversalRunnable();

可以看到,mTraversalRunnable出現(xiàn)在這個(gè)方法scheduleTraversals里:

void scheduleTraversals() {
    if (!mTraversalScheduled) {
        mTraversalScheduled = true;
        mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
        mChoreographer.postCallback(
                Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
        if (!mUnbufferedInputDispatch) {
            scheduleConsumeBatchedInput();
        }
        notifyRendererOfFramePending();
        pokeDrawLockIfNeeded();
    }

這里面mTraversalRunnable也是被post了族奢,這個(gè)post方法叫做postCallback姥闭,猜測(cè)是不是也是加到消息隊(duì)列去了丹鸿?

繼續(xù)看mChoreographer.postCallback方法越走,這個(gè)方法最終會(huì)到下面這個(gè)方法中:

private void postCallbackDelayedInternal(int callbackType,
        Object action, Object token, long delayMillis) {
    if (DEBUG_FRAMES) {
        Log.d(TAG, "PostCallback: type=" + callbackType
                + ", action=" + action + ", token=" + token
                + ", delayMillis=" + delayMillis);
    }

    synchronized (mLock) {
        final long now = SystemClock.uptimeMillis();
        final long dueTime = now + delayMillis;
        mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);

        if (dueTime <= now) {
            scheduleFrameLocked(now);
        } else {
            Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
            msg.arg1 = callbackType;
            msg.setAsynchronous(true);
            mHandler.sendMessageAtTime(msg, dueTime);
        }
    }
}

注意到熟悉的一句代碼:mHandler.sendMessageAtTime,這個(gè)方法就是將消息加入到消息隊(duì)列,而這個(gè)Handler對(duì)應(yīng)的消息隊(duì)列是什么廊敌?
我們從 mHandler這個(gè)對(duì)象著手铜跑,看看它是怎么來(lái)的。

private Choreographer(Looper looper) {
    mLooper = looper;
    mHandler = new FrameHandler(looper);
    mDisplayEventReceiver = USE_VSYNC ? new FrameDisplayEventReceiver(looper) : null;
    mLastFrameTimeNanos = Long.MIN_VALUE;

    mFrameIntervalNanos = (long)(1000000000 / getRefreshRate());

    mCallbackQueues = new CallbackQueue[CALLBACK_LAST + 1];
    for (int i = 0; i <= CALLBACK_LAST; i++) {
        mCallbackQueues[i] = new CallbackQueue();
    }
}

在這個(gè)構(gòu)造函數(shù)中骡澈,可以看到mHandler構(gòu)造的時(shí)候傳入一個(gè)Looper對(duì)象锅纺,那就要看下這個(gè)Looper是從哪里來(lái)的。

// Thread local storage for the choreographer.
private static final ThreadLocal<Choreographer> sThreadInstance =
        new ThreadLocal<Choreographer>() {
    @Override
    protected Choreographer initialValue() {
        Looper looper = Looper.myLooper();
        if (looper == null) {
            throw new IllegalStateException("The current thread must have a looper!");
        }
        return new Choreographer(looper);
    }
};

這里又看到熟悉的代碼了Looper looper = Looper.myLooper(); 獲取了當(dāng)前線程(主線程)的Looper對(duì)象肋殴。

所以囤锉,performTraversals方法實(shí)際上也是被加入到消息隊(duì)列中去執(zhí)行的,而我們最初post的Runnable护锤,是在performTraversals方法中將其附加到一個(gè)消息上并加入到消息隊(duì)列里官地。所以,我們post的Runnable的消息烙懦,肯定是在performTraversals的消息之后執(zhí)行的驱入,也就是我們post的Runnable肯定是在performTraversals之后執(zhí)行,而performTraversals之后氯析,View的寬和高便計(jì)算出來(lái)了亏较。

需要注意的是, 在onCreate(以及onStart, onResume)方法中必須保證是在主線程調(diào)用View.post方法掩缓,因?yàn)?code>ViewRootImpl.getRunQueue()在不同線程獲取到的是不同的對(duì)象(為什么雪情?請(qǐng)研究getRunQueue()的類型),所以在onCreate方法內(nèi)新建線程并在里面post的時(shí)候你辣,這個(gè)post是成功不了的旺罢。在Android 7.0中修復(fù)了這個(gè)問(wèn)題,也就說(shuō)7.0系統(tǒng)中绢记,在onCreate方法中新建線程并在里面post扁达,這個(gè)Runnable是可以被執(zhí)行到的。感興趣的看源碼蠢熄。

一句話總結(jié):

onCreate中調(diào)用View.post的時(shí)候跪解,post的Runnable被保存起來(lái),在下一次遍歷布局的時(shí)候重新加到消息隊(duì)列里(Android 7.0是在attach window的時(shí)候重新加到消息隊(duì)列里)签孔,而且布局遍歷本身也是加到消息隊(duì)列執(zhí)行的叉讥,所以我們post的Runnable所在的消息肯定是在布局遍歷之后,所以在Runnable里必定可以獲取到正確的寬高饥追。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末图仓,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子但绕,更是在濱河造成了極大的恐慌救崔,老刑警劉巖惶看,帶你破解...
    沈念sama閱讀 212,816評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異六孵,居然都是意外死亡纬黎,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,729評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門劫窒,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)本今,“玉大人,你說(shuō)我怎么就攤上這事主巍」谙ⅲ” “怎么了?”我有些...
    開封第一講書人閱讀 158,300評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵孕索,是天一觀的道長(zhǎng)铐达。 經(jīng)常有香客問(wèn)我,道長(zhǎng)檬果,這世上最難降的妖魔是什么瓮孙? 我笑而不...
    開封第一講書人閱讀 56,780評(píng)論 1 285
  • 正文 為了忘掉前任,我火速辦了婚禮选脊,結(jié)果婚禮上杭抠,老公的妹妹穿的比我還像新娘。我一直安慰自己恳啥,他們只是感情好偏灿,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,890評(píng)論 6 385
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著钝的,像睡著了一般翁垂。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上硝桩,一...
    開封第一講書人閱讀 50,084評(píng)論 1 291
  • 那天沿猜,我揣著相機(jī)與錄音,去河邊找鬼碗脊。 笑死啼肩,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的衙伶。 我是一名探鬼主播祈坠,決...
    沈念sama閱讀 39,151評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼矢劲!你這毒婦竟也來(lái)了赦拘?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,912評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤芬沉,失蹤者是張志新(化名)和其女友劉穎躺同,沒想到半個(gè)月后阁猜,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,355評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡笋籽,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,666評(píng)論 2 327
  • 正文 我和宋清朗相戀三年蹦漠,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了椭员。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片车海。...
    茶點(diǎn)故事閱讀 38,809評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖隘击,靈堂內(nèi)的尸體忽然破棺而出侍芝,到底是詐尸還是另有隱情,我是刑警寧澤埋同,帶...
    沈念sama閱讀 34,504評(píng)論 4 334
  • 正文 年R本政府宣布州叠,位于F島的核電站,受9級(jí)特大地震影響凶赁,放射性物質(zhì)發(fā)生泄漏咧栗。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,150評(píng)論 3 317
  • 文/蒙蒙 一虱肄、第九天 我趴在偏房一處隱蔽的房頂上張望致板。 院中可真熱鬧,春花似錦咏窿、人聲如沸斟或。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,882評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)萝挤。三九已至,卻和暖如春根欧,著一層夾襖步出監(jiān)牢的瞬間怜珍,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,121評(píng)論 1 267
  • 我被黑心中介騙來(lái)泰國(guó)打工凤粗, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留绘面,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,628評(píng)論 2 362
  • 正文 我出身青樓侈沪,卻偏偏與公主長(zhǎng)得像揭璃,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子亭罪,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,724評(píng)論 2 351

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