新年第一篇,最近在做Android开发,碰到一个很神奇的bug,目前还没有找到出错的原因, 但是通过一番搜索,还是找到一个同类型的问题,下面的解决方法也基本上适用。 鉴于我这个问题在网上还没有找到答案,在StackOverFlow上提问也没有收到很合适的回复, 我就把这个问题写出来吧。

在StackOverFlow上的链接,如果各位知道是怎么回事,可以去回答这个问题。

##问题 在app中设置使用了PreferenceActivity,其中放了几个SwitchPreference,使用默认的Layout, 一开始没有发现问题。

由于最终是在嵌入式设备上使用这个app,设备屏蔽了Android常规的返回键,HOME键等, 所以我在Activity的Layout最上面加了按钮,模拟返回的动作。这样的话就需要自定义 PreferenceActivity的Layout,当然这个很简单。但是问题随之就来了,并且很搞笑。

替换之后,Preference里面的所有的SwitchPreference好像都被绑定在了一起,点击任意一个其他的都会改变。 只有两个SwitchPreference的情况就是两个SwitchPreference一起变化,多了之后变化情况就更加莫名奇妙。

##源码 我重现了一下这个bug,源码如下:

在Java代码里面只是简单的设置了自定义Layout:

public class SettingsActivity extends PreferenceActivity {
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // Set custom layout to the setting.
        setContentView(R.layout.setting);
    }

    @Override
    protected void onPostCreate(Bundle savedInstanceState) {
        super.onPostCreate(savedInstanceState);

        setupSimplePreferencesScreen();
    }

    private void setupSimplePreferencesScreen() {
        addPreferencesFromResource(R.xml.pref_general);
    }
}

SharePreference定义如下:

<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" >
<SwitchPreference
    android:key="switch1"
    android:summary="This is switch 1"
    android:title="Switch 1" />
<SwitchPreference
    android:key="switch2"
    android:summary="This is switch 2"
    android:title="Switch 2" />
</PreferenceScreen>

自定义的layout如下:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content" >

        <Button
            android:id="@+id/button1"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="Button" />

        <Button
            android:id="@+id/button2"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="Button" />
    </LinearLayout>

    <ListView
        android:id="@android:id/list"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" >
    </ListView>

</LinearLayout>

我在我手机上和平板上都测试了,看截图,两个SwitchPreference是一起变化的。

截图

##解决方法 通过搜索只在Google Code的Android项目的issues里面找到一个类似的帖子,链接 https://code.google.com/p/android/issues/detail?id=26194。 链接里面的问题是在同时有SwitchPreference(SP_A)和SwitchPreference(SP_B)滚出滚入页面时, SP_B在onBindView里面会使用SP_A的View做参数,所以SP_A会根据SP_B的状态被修改掉。 我的问题跟这个很类似。

下面有人给出建议说如果Preference里面出现了多个布尔值设置,使用CheckBoxPreference。 这个方法我测试了一下,可以解决问题,但是对于开关类型的设置,我觉得使用SwitchPreference 更加直观一点。

同时还有人贴出了解决方案,在onBindView里面清除其他Switch的listener。这个方法我测试了, 基本解决了问题,代码如下:

public class CustomSwitchPreference extends SwitchPreference {

    /**
     * Construct a new SwitchPreference with the given style options.
     *
     * @param context The Context that will style this preference
     * @param attrs Style attributes that differ from the default
     * @param defStyle Theme attribute defining the default style options
     */
    public CustomSwitchPreference(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    /**
     * Construct a new SwitchPreference with the given style options.
     *
     * @param context The Context that will style this preference
     * @param attrs Style attributes that differ from the default
     */
    public CustomSwitchPreference(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    /**
     * Construct a new SwitchPreference with default style options.
     *
     * @param context The Context that will style this preference
     */
    public CustomSwitchPreference(Context context) {
        super(context, null);
    }

    @Override
    protected void onBindView(View view) {
        // Clean listener before invoke SwitchPreference.onBindView
        ViewGroup viewGroup= (ViewGroup)view;
        clearListenerInViewGroup(viewGroup);
        super.onBindView(view);
    }

    /**
     * Clear listener in Switch for specify ViewGroup.
     *
     * @param viewGroup The ViewGroup that will need to clear the listener.
     */
    private void clearListenerInViewGroup(ViewGroup viewGroup) {
        if (null == viewGroup) {
            return;
        }

        int count = viewGroup.getChildCount();
        for(int n = 0; n < count; ++n) {
            View childView = viewGroup.getChildAt(n);
            if(childView instanceof Switch) {
                final Switch switchView = (Switch) childView;
                switchView.setOnCheckedChangeListener(null);
                return;
            } else if (childView instanceof ViewGroup){
                ViewGroup childGroup = (ViewGroup)childView;
                clearListenerInViewGroup(childGroup);
            }
        }
    }

}

问题解决了,但是bug出在哪儿,还待进一步的研究。