preference的解析与显示

一、preference的xml解析过程
解析触发流程:
在一个继承自PreferenceActivity类的子类中调用addPreferencesFromResource(R.xml.voicemail_settings);后,就开始了xml的解析过程。在方法PreferenceActivity.addPreferencesFromResource中,首先是调用PreferenceManager.inflateFromResource–PreferenceInflater.inflate得到PreferenceScreen实例引用,该PreferenceScreen中保存了从xml中解析到的所有preference元素的对象引用。

xml的解析工作主要由PreferenceInflater类完成,它继承自GenericInflater,
大致流程:
1.解析从PreferenceInflater.inflate方法开始;

2.首先是得到AttributeSet实例,大致过程:
XmlResourceParser parser = getContext().getResources().getXml(resource);
AttributeSet attrs = Xml.asAttributeSet(parser);

3.然后开始逐一解析xml文件的所有元素节点,将xml中配置的所有preference创建出对应的java实例:
3.1 首先得到根元素实例:inflate–createItemFromTag–onCreateItem
3.2 然后开始循环遍历创建每个非根元素实例:
1)inflate–rInflate–onCreateCustomFromTag——解析preference中配置的intent,并调用Preference.setIntent方法将intent保存在preference对象中。
2)inflate–rInflate–createItemFromTag–onCreateItem——这里类似创建根节点元素实例的逻辑,创建好preference对象后,调用PreferenceGroup.addItemFromInflater方法将preference引用传到PreferenceGroup(一般是PreferenceScreen)缓存,
3)rInflate–rInflate,递归调用自己,这个情况属于内嵌了PreferenceScreen或PreferenceCategory的情况,它将继续解析子PreferenceScreen或PreferenceCategory里面的preference元素。
4)最终得到下面的树形数据结构:
PreferenceScreen {
preference,preference,…,
PreferenceScreen {
preference,preference,…,
PreferenceCategory {preference,preference,… }
},
PreferenceCategory {preference,preference,…}
}

4.创建每个节点元素实例的流程:
主要逻辑在方法:
public final T createItem(String name, String prefix, AttributeSet attrs),
参数:
name:各preference类的name
prefix:程序员自定义的preference时,prefix值为null,android系统的preference,prefix值为android.preference.
attrs:这个是创建preference实例时,会传入的参数,即在preference构造方法中会使用到,用来获取xml元素节点中配置的preference的属性值。
主要逻辑代码如下,完整代码参考createItem方法:

Constructor constructor = (Constructor) sConstructorMap.get(name);

        try {
            if (null == constructor) {
                // Class not found in the cache, see if it's real,
                // and try to add it
                Class clazz = mContext.getClassLoader().loadClass(
                        prefix != null ? (prefix + name) : name);
                constructor = clazz.getConstructor(mConstructorSignature);
                constructor.setAccessible(true);
                sConstructorMap.put(name, constructor);
            }

            Object[] args = mConstructorArgs;
            args[1] = attrs;
            return (T) constructor.newInstance(args);

        } catch (NoSuchMethodException e) {}

使用反射机制去创建preference的实例,首先从sConstructorMap(sConstructorMap缓存了已经使用过的preference构造方法类型实例引用)获取构造方法引用,如果没获取到,则先加载preference对应的class,然后获取具有context和AttributeSet(在上面2中创建的)两个参数的构造方法引用,怎么知道是获取两个参数的构造方法,可看mConstructorSignature的定义:

private static final Class[] mConstructorSignature = new Class[] {
            Context.class, AttributeSet.class};

获取到构造方法类型应用后,再以mConstructorArgs为构造方法的参数值,最终调用constructor.newInstance(args)得到preference实例。mConstructorArgs的值是如何来的,可参考代码,这里就不介绍了。在一个preference的对象创建后,构造方法引用会被缓存在sConstructorMap变量,下次使用直接从缓存获取即可。
创建preference对象还会分程序员自定义的preference或是android原生的preference,逻辑主要在GenericInflater.createItemFromTag方法,判断节点标签的name是否包含.,包含则是用户自定义的preference,不包含,则是使用android系统的preference,代码如下:

if (-1 == name.indexOf('.')) {
                    item = onCreateItem(name, attrs);
                } else {
                    item = createItem(name, null, attrs);
                }

name不包含点符号时,则它不是一个java类全路径,这时需要有系统preference默认的包名,这是在PreferenceInflater的构造方法执行时会调用setDefaultPackage(“android.preference.”);传入了默认的package名,赋值给变量mDefaultPackage。

以上差不多就是将preference从xml解析出来的全过程了。

二、preference显示过程
回到最开始的PreferenceManager.inflateFromResource方法调用,得到了PreferenceScreen引用后,就开始进行显示流程的逻辑调用了。
调用PreferenceActivity.setPreferenceScreen–PreferenceManager.setPreferences,将得到的PreferenceScreen引用保存在PreferenceManager的变量mPreferenceScreen中。当mPreferenceScreen不为null,则会继续调用PreferenceActivity.postBindPreferences–使用handler发送MSG_BIND_PREFERENCES消息,handleMessage方法中进而调用bindPreferences–PreferenceScreen.bind,bind代码如下:

public void bind(ListView listView) {
        listView.setOnItemClickListener(this);
        listView.setAdapter(getRootAdapter());
        
        onAttachedToActivity();
    }

这个方法首先给listview设置了点击事件,然后是将解析xml得到的PreferenceScreen及其包含的各子preference最终显示到listview中,每个preference就会形成listview的一个item。

将解析到的preference变成listview的每个item的数据,这个工作就在getRootAdapter方法之后的逻辑中,调用关系如下:
PreferenceScreen.getRootAdapter–onCreateRootAdapter–new PreferenceGroupAdapter–syncMyPreferences–flattenPreferenceGroup,
在flattenPreferenceGroup方法中,会将根元素PreferenceScreen内包含的preference都add到mPreferenceList中,mPreferenceList用来缓存待显示的preference,对于PreferenceGroup类型的情况,会有如下情况:
1)如果preference是PreferenceGroup类型,但不是PreferenceScreen,那么就会将它内部包含的preference也会add到mPreferenceList中;
2)如果preference是内嵌的子PreferenceScreen类型,它里面包含的子preference则不会add到mPreferenceList,内嵌的PreferenceScreen会以一个独立的界面来显示。
这样的话,flattenPreferenceGroup方法得到了mPreferenceList内容,再回到方法syncMyPreferences,之后会调用notifyDataSetChanged,调用它后,会执行BaseAdapter.notifyDataSetChanged–AbsListView(ListView的父类).AdapterDataSetObserver.onChanged–AdapterView<ListAdapter>.AdapterDataSetObserver.onChanged–requestLayout(),这样就触发了ListView的显示流程,先会执行onMeasure,onLayout等方法,之后执行PreferenceGroupAdapter.getView方法,这个方法将会从mPreferenceList中获取到对应位置的preference,进而调用Preference.getView–onCreateView–onBindView等方法,Preference.getView调用完成后,每个item的内容显示就完成了,最终每个item的内容显示到view中都是在Preference自己的方法中完成的。

这大概就是preference的显示过程了。

三、内嵌子PreferenceScreen的点击事件
首先看下preference的点击事件。
首先上面已经说到每个preference最终是作为ListView的一个item显示的,看下每个item的点击事件监听器代码PreferenceScreen.bind方法:

public void bind(ListView listView) {
        listView.setOnItemClickListener(this);
        listView.setAdapter(getRootAdapter());
        
        onAttachedToActivity();
    }

可以看到是PreferenceScreen自身作为监听器,然后看下onItemClick方法:

public void onItemClick(AdapterView parent, View view, int position, long id) {
        // If the list has headers, subtract them from the index.
        if (parent instanceof ListView) {
            position -= ((ListView) parent).getHeaderViewsCount();
        }
        Object item = getRootAdapter().getItem(position);
        if (!(item instanceof Preference)) return;

        final Preference preference = (Preference) item; 
        preference.performClick(this);
    }

最终是调用了Preference.performClick方法,该方法代码如下:

public void performClick(PreferenceScreen preferenceScreen) {

        if (!isEnabled()) {
            return;
        }

        onClick();

        if (mOnClickListener != null && mOnClickListener.onPreferenceClick(this)) {
            return;
        }

        PreferenceManager preferenceManager = getPreferenceManager();
        if (preferenceManager != null) {
            PreferenceManager.OnPreferenceTreeClickListener listener = preferenceManager
                    .getOnPreferenceTreeClickListener();
            if (preferenceScreen != null && listener != null
                    && listener.onPreferenceTreeClick(preferenceScreen, this)) {
                return;
            }
        }

        if (mIntent != null) {
            Context context = getContext();
            context.startActivity(mIntent);
        }
    }

从这个方法可看出有5种处理情况,按照先后顺序总结如下:
1.isEnabled方法的值,为false时,则不处理点击,直接return,相反则继续执行后面的逻辑;这个是由setEnabled方法设置的值决定的,或者在xml配置了android:enabled="true/false"决定。
2.onClick()方法被执行;当1未return时,这个方法会无条件执行,稍后看这个方法,Preference.onClick是一个空方法,主要是子类可覆盖该方法,实现有关逻辑调用。
3.mOnClickListener不为null时,则执行mOnClickListener.onPreferenceClick,这个是调用Preference.setOnPreferenceClickListener设置了监听后会执行,如果执行了它,则后面的不执行了。
4.PreferenceManager.OnPreferenceTreeClickListener.onPreferenceTreeClick,当这个执行返回了true,表示消费了该事件,则直接return。为false则继续执行后面的逻辑;这个其实是由PreferenceActivity.onPreferenceTreeClick的返回值来决定的,实际操作中,我们可以通过重写该方法来处理点击事件。
5.当mIntent 不为null,则调用context.startActivity(mIntent),进入到intent找到的界面。这个情况可以是在在xml中定义preference时配置intent,也可以在代码中调用Preference.setIntent来设置intent。

再来看PreferenceScreen,它属于Preference的子类,它重写了onClick方法,代码如下:

@Override
    protected void onClick() {
        if (getIntent() != null || getFragment() != null || getPreferenceCount() == 0) {
            return;
        }
        
        showDialog(null);
    }

由上面代码可知,当一个内嵌的子PreferenceScreen未指定fragment,也未配置intent,并且还有子preference元素时,将调用showDialog(null);,这个方法会使PreferenceScreen里面的元素以一个Dialog来显示一个新的界面,只不过这个Dialog是全屏的。

showDialog方法比较长,关键的代码如下:
Dialog dialog = mDialog = new Dialog(context, context.getThemeResId());
dialog.setTitle(title);
dialog.setContentView(childPrefScreen);
dialog.setOnDismissListener(this);
// Add the screen to the list of preferences screens opened as dialogs
getPreferenceManager().addPreferencesScreen(dialog);
dialog.show();
可以看到完全就是一个Dialog显示逻辑,这个dialog显示不会有像AlertDialog一样的效果,它显示的效果和activity一样。

这就是内嵌的子PreferenceScreen的显示原理,它不会启动一个新的activity,而是以dialog来展示的。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值