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

架構(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)了。