一個(gè)關(guān)于RadioGroup的坑

RadioGroup應(yīng)該算是一個(gè)很常用的控件了,用于作為RadioButton的父控件,可以實(shí)現(xiàn)單選框。然而最近用了類似flux的單向數(shù)據(jù)流架構(gòu)后,再使用RadioGroup立馬遇到了一個(gè)大坑。

類flux架構(gòu).png

架構(gòu)如上圖,View的狀態(tài)由ViewHolder中的變量決定,而ViewHolder中值的修改由用戶輸入觸發(fā)控件的各種OnChangeListener改變。這里用到的是RadioGroup.OnChangeListener。

void onCheckedChanged (RadioGroup group, int checkedId)

onCheckChanged中int checkedId官方給的解釋是:the unique identifier of the newly checked radio button。也就是說,在RadioGroup中一旦有RadioButton.checked改變了,就可以通過這個(gè)Listener獲取到通知。

    public void check(@IdRes int id) {
        // don't even bother
        if (id != -1 && (id == mCheckedId)) {
            return;
        }

        if (mCheckedId != -1) {
            setCheckedStateForView(mCheckedId, false);
        }

        if (id != -1) {
            setCheckedStateForView(id, true);
        }

        setCheckedId(id);
    }

RadioGroup.check函數(shù)可以修改子控件的check值,并且在進(jìn)入函數(shù)的第一行就做了去重處理,防止Listener觸發(fā)check函數(shù),check函數(shù)又觸發(fā)Lisnter導(dǎo)致無限調(diào)用地獄。這一切看著是如此完美,然后這就是坑的開始。一旦按照這個(gè)架構(gòu)搭建好之后,如果只是用戶觸發(fā)該RadioGroup中的RadioButton觸發(fā)值的刷新,這個(gè)流程完全沒有任何問題。但是使用該架構(gòu),為的就是使UI能實(shí)時(shí)響應(yīng)ViewHolder中的值更改,隨時(shí)刷新頁面。例如在一個(gè)其它的頁面也能設(shè)置一個(gè)這個(gè)選項(xiàng)值,導(dǎo)致了Model的更新,Model的更新觸發(fā)了ViewHolder的更新,ViewHolder的更新觸發(fā)了RadioGroup.check函數(shù)。這時(shí)就會(huì)出現(xiàn)ANR,按前面的分析,一切都是那么的完美,不科學(xué)啊!而且通過單步調(diào)試,發(fā)現(xiàn)onCheckedChanged中收到的checkedId居然是錯(cuò)誤的,百思不得其解啊!
這時(shí),就需要看看RadioGroup的工作原理了。在RadioGroup.check函數(shù)中,setCheckedStateForView(mCheckedId, false);這行調(diào)用觸發(fā)了上一個(gè)被checked的RadioButton.check函數(shù)。下面是RadioButton.check函數(shù)的源碼:

    public void setChecked(boolean checked) {
        if (mChecked != checked) {
            mChecked = checked;
            refreshDrawableState();
            notifyViewAccessibilityStateChangedIfNeeded(
                    AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);

            // Avoid infinite recursions if setChecked() is called from a listener
            if (mBroadcasting) {
                return;
            }

            mBroadcasting = true;
            if (mOnCheckedChangeListener != null) {
                mOnCheckedChangeListener.onCheckedChanged(this, mChecked);
            }
            if (mOnCheckedChangeWidgetListener != null) {
                mOnCheckedChangeWidgetListener.onCheckedChanged(this, mChecked);
            }

            mBroadcasting = false;            
        }
    }

其中可以看到 mOnCheckedChangeWidgetListener.onCheckedChanged(this, mChecked);這行調(diào)用通知了RadioGroup,說我的checked值改變了。我們來看看通知RadioGroup之后,它做了什么。

    private class CheckedStateTracker implements CompoundButton.OnCheckedChangeListener {
        public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
            // prevents from infinite recursion
            if (mProtectFromCheckedChange) {
                return;
            }

            mProtectFromCheckedChange = true;
            if (mCheckedId != -1) {
                setCheckedStateForView(mCheckedId, false);
            }
            mProtectFromCheckedChange = false;

            int id = buttonView.getId();
            setCheckedId(id);
        }
    }

可以看到,里面居然沒有使用傳遞進(jìn)來的isChecked值!也就是說RadioGroup根本沒有檢查回調(diào)的RadioButton是不是被checked的。緊接著就調(diào)用setCheckedId函數(shù),把觸發(fā)的控件當(dāng)作了是被checked的對(duì)待:

    private void setCheckedId(@IdRes int id) {
        mCheckedId = id;
        if (mOnCheckedChangeListener != null) {
            mOnCheckedChangeListener.onCheckedChanged(this, mCheckedId);
        }
    }

在里面,再次觸發(fā)了RadioGroup.OnCheckedChangeListener,并且把不是被checked的id當(dāng)作參數(shù)傳遞了進(jìn)去。于是乎,這就改變了ViewHolder中的值,接著觸發(fā)了RadioGroup.check函數(shù),接下來就是一系列的無窮調(diào)用...為什么在不使用響應(yīng)式架構(gòu)的時(shí)候不會(huì)出現(xiàn)bug呢,因?yàn)橐郧癓istener中的值不會(huì)觸發(fā)RadioGroup.check函數(shù),而在check函數(shù)會(huì)觸發(fā)兩次Listener,在最后一次會(huì)將正確的id值傳入Listener中。
知道了坑在哪之后,解決的方法就是不要直接使用Listener傳遞的checkedId,還要檢查該RadioButton.checked屬性是不是true,只有為true時(shí)才將值設(shè)置給ViewHolder,這樣地獄調(diào)用也就不會(huì)出現(xiàn)了。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 先對(duì)曾經(jīng)點(diǎn)喜歡或者收藏這篇文章的朋友說聲抱歉,因部分原因個(gè)人決定在簡書停更并轉(zhuǎn)移駐扎到其他平臺(tái)。本想刪除賬號(hào),可不...
    OCNYang閱讀 6,530評(píng)論 10 84
  • 1 基本UI Zealer、CSDN、github StormZhang、 張弘揚(yáng)(Hyman):http:/...
    征程_Journey閱讀 12,447評(píng)論 0 4
  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 179,008評(píng)論 25 709
  • 一向孤陋寡聞的我,很多花草擺在面前只能是它認(rèn)識(shí)我,我不認(rèn)識(shí)它。有些花草見旁人養(yǎng)得頻率多了,耳濡目染后便也知曉了名稱...
    補(bǔ)拙莫如勤LV閱讀 882評(píng)論 2 6
  • 昨日收到博紅媽媽關(guān)于生物學(xué)習(xí)的建議,及時(shí)對(duì)兒子說了,兒子當(dāng)時(shí)很排斥,說自己沒時(shí)間看視頻。我且暫時(shí)放下。等到...
    大愛無疆楊青閱讀 324評(píng)論 0 3

友情鏈接更多精彩內(nèi)容