Android TypedArray源码详解

尊重原创,转载请标明出处    http://blog.csdn.net/abcdef314159

在自定义控件的时候,如果我们想额外的添加一些属性,就会用到TypedArray这个类,那么这个类是怎么得到的,以及怎么使用的,这篇讲会详细讲解,下面是我以前自定义控件的一段代码

TypedArray typedArray = context.obtainStyledAttributes(attrs,R.styleable.myaccount_item_style);

我们看到TypedArray是通过Context的方法得到的,但要记住完成之后一定要调用recycle()方法进行回收,我们点进去找到最终实现

        public TypedArray obtainStyledAttributes(AttributeSet set,
                int[] attrs, int defStyleAttr, int defStyleRes) {
            final int len = attrs.length;
            final TypedArray array = TypedArray.obtain(Resources.this, len);

            // XXX note that for now we only work with compiled XML files.
            // To support generic XML files we will need to manually parse
            // out the attributes from the XML file (applying type information
            // contained in the resources and such).
            final XmlBlock.Parser parser = (XmlBlock.Parser)set;
            AssetManager.applyStyle(mTheme, defStyleAttr, defStyleRes,
                    parser != null ? parser.mParseState : 0, attrs, array.mData, array.mIndices);

            array.mTheme = this;
            array.mXml = parser;

			…………………………
            return array;
        }

我们先看下面AssetManager的applyStyle方法是native方法,也就是用C++实现的,他会提取自定义控件属性的的值保存TypedArray中的mData数组中,这个数组的大小是由你定义控件属性的个数决定的,是他的6倍,上面的attrs其实就是你自定义属性的个数,我们来看一下

    static TypedArray obtain(Resources res, int len) {
        final TypedArray attrs = res.mTypedArrayPool.acquire();
        if (attrs != null) {
            attrs.mLength = len;
            attrs.mRecycled = false;

            final int fullLen = len * AssetManager.STYLE_NUM_ENTRIES;
            if (attrs.mData.length >= fullLen) {
                return attrs;
            }

            attrs.mData = new int[fullLen];
            attrs.mIndices = new int[1 + len];
            return attrs;
        }

        return new TypedArray(res,
                new int[len*AssetManager.STYLE_NUM_ENTRIES],
                new int[1+len], len);
    }

他首先会从TypedArray池中获取,如果有就取出,mDate的大小不能小于属性个数的6倍,因为STYLE_NUM_ENTRIES的值为6,如果没有就new一个然后返回,把属性的值提取出来之后我们就可以来操作了,我们先来看一下View类初始化中的一段代码

        final int N = a.getIndexCount();
        for (int i = 0; i < N; i++) {
            int attr = a.getIndex(i);
            switch (attr) {
                case com.android.internal.R.styleable.View_background:
                    background = a.getDrawable(attr);
                    break;
					…………………………
            }
        }

他会把TypedArray中的数据提取出来对View的属性赋值,我们来看一下TypedArray类的构造方法

    /*package*/ TypedArray(Resources resources, int[] data, int[] indices, int len) {
        mResources = resources;
        mMetrics = mResources.mMetrics;
        mAssets = mResources.mAssets;
        mData = data;
        mIndices = indices;
        mLength = len;
    }

代码很简单,其中mData就是就是从xml文件中提取到的数据,mData的大小是自定义属性个数的6倍,所以这里是每6个作为一组,我们可以看一下上面的obtain方法中data数组的大小是乘以6(STYLE_NUM_ENTRIES)的,这6种类型如下,定义在AssetManager类中,下面的第一个表示每组6个

    /*package*/ static final int STYLE_NUM_ENTRIES = 6;
    /*package*/ static final int STYLE_TYPE = 0;
    /*package*/ static final int STYLE_DATA = 1;
    /*package*/ static final int STYLE_ASSET_COOKIE = 2;
    /*package*/ static final int STYLE_RESOURCE_ID = 3;
    /*package*/ static final int STYLE_CHANGING_CONFIGURATIONS = 4;
    /*package*/ static final int STYLE_DENSITY = 5;

对应着TypedValue类中的这7中类型,其中string是根据type得到的

    /** The type held by this value, as defined by the constants here.
     *  This tells you how to interpret the other fields in the object. */
    public int type;

    /** If the value holds a string, this is it. */
    public CharSequence string;

    /** Basic data in the value, interpreted according to {@link #type} */
    public int data;

    /** Additional information about where the value came from; only
     *  set for strings. */
    public int assetCookie;

    /** If Value came from a resource, this holds the corresponding resource id. */
    public int resourceId;

    /** If Value came from a resource, these are the configurations for which
     *  its contents can change. */
    public int changingConfigurations = -1;

    /**
     * If the Value came from a resource, this holds the corresponding pixel density.
     * */
    public int density;

如果我们认真看的时候就会发现obtain方法中对mIndices数组初始化的时候是加1的,因为mIndices数组的第一个保存的是我们所使用属性的个数,记住是使用不是定义,我们来看一下其中的一些代码

    /**
     * Return the number of values in this array.
     */
    public int length() {
        if (mRecycled) {
            throw new RuntimeException("Cannot make calls to a recycled instance!");
        }

        return mLength;
    }

    /**
     * Return the number of indices in the array that actually have data.
     */
    public int getIndexCount() {
        if (mRecycled) {
            throw new RuntimeException("Cannot make calls to a recycled instance!");
        }

        return mIndices[0];
    }

第一个length返回的是我们所定义属性的个数,因为这个参数是在构造函数中赋值的,传递的是int[] attrs的长度,而这个sttrs就是我们在attrs文件中自定义属性的时候在R文件中自动生成的一个数组。而下面的getIndexCount()方法返回的是我们所使用的属性个数,因为mIndices的数据是从xml文件中提取的,第一个位置保存的是我们使用属性的个数,后面的位置就是我们使用的自定义属性在R文件中生成的id,在看一个方法,也是自定义的时候常用到的

    public int getIndex(int at) {
        if (mRecycled) {
            throw new RuntimeException("Cannot make calls to a recycled instance!");
        }

        return mIndices[1+at];
    }

这个得到的就是自定义属性在R文件中生成的id,剩下的一些方法就是从TypedArray中提取值了,主要有以下几种类型

    <declare-styleable name="CustomTheme">
        <attr name="textView1" format="string" />
        <attr name="textView2" format="boolean" />
        <attr name="textView3" format="integer" />
        <attr name="textView4" format="float" />
        <attr name="textView5" format="color" />
        <attr name="textView6" format="dimension" />
        <attr name="textView7" format="fraction" />
        <attr name="textView8" format="reference" />
        <attr name="textView9" format="enum" />
        <attr name="textView10" format="flags" />
    </declare-styleable>

TypedArray方法比较多,这里就捡常用的几个来分析一下,在分析之前先看一下下面这个方法

    private CharSequence loadStringValueAt(int index) {
        final int[] data = mData;
        final int cookie = data[index+AssetManager.STYLE_ASSET_COOKIE];
        if (cookie < 0) {
            if (mXml != null) {
                return mXml.getPooledString(
                    data[index+AssetManager.STYLE_DATA]);
            }
            return null;
        }
        return mAssets.getPooledStringForCookie(cookie, data[index+AssetManager.STYLE_DATA]);
    }

上面所说的每6个一组,其中每组下标为STYLE_ASSET_COOKIE(2)的是用来标记缓存的,并且是只对String类型的,我们来看一下

    private boolean getValueAt(int index, TypedValue outValue) {
        final int[] data = mData;
        final int type = data[index+AssetManager.STYLE_TYPE];
        if (type == TypedValue.TYPE_NULL) {
            return false;
        }
        outValue.type = type;
        outValue.data = data[index+AssetManager.STYLE_DATA];
        outValue.assetCookie = data[index+AssetManager.STYLE_ASSET_COOKIE];
        outValue.resourceId = data[index+AssetManager.STYLE_RESOURCE_ID];
        outValue.changingConfigurations = data[index+AssetManager.STYLE_CHANGING_CONFIGURATIONS];
        outValue.density = data[index+AssetManager.STYLE_DENSITY];
        outValue.string = (type == TypedValue.TYPE_STRING) ? loadStringValueAt(index) : null;
        return true;
    }

上面这个方法是把mData指定范围的6个数据提取到outValue中,其中string值通过type类型得到的,我们再来看一下assetCookie的注释

    /** Additional information about where the value came from; only
     *  set for strings. */
    public int assetCookie;

所以他只针对String类型,我们再来看一下String类型的注释

    /** The <var>string</var> field holds string data.  In addition, if
     *  <var>data</var> is non-zero then it is the string block
     *  index of the string and <var>assetCookie</var> is the set of
     *  assets the string came from. */
    public static final int TYPE_STRING = 0x03;

所以他只针对string类型的数据进行提取,比如text,String,color等,color可以是string类型也可以是int类型,还看上面的loadStringValueAt方法,如果cookie小于0,说明没有缓存,就会从xml中解析,否则就从缓存中取

    /*package*/ final CharSequence getPooledStringForCookie(int cookie, int id) {
        // Cookies map to string blocks starting at 1.
        return mStringBlocks[cookie - 1].get(id);

我们来看一下是怎么从xml中解析的,看到上面的obtainStyledAttributes方法,会发现这样一段代码 array.mXml
= parser;其中parser就是View及其子类在初始化的时候传递的AttributeSet,我们在前面的《Android LayoutInflater源码分析及使用(二)》中讲到,View及其子类创建的时候是通过反射来初始化的,我们来回顾一下

   public final View createView(String name, String prefix, AttributeSet attrs)
            throws ClassNotFoundException, InflateException {
        Constructor<? extends View> constructor = sConstructorMap.get(name);
        Class<? extends View> clazz = null;

        try {
				…………………………
                constructor = clazz.getConstructor(mConstructorSignature);
				…………………………

            Object[] args = mConstructorArgs;
            args[1] = attrs;

            constructor.setAccessible(true);
            final View view = constructor.newInstance(args);
			…………………………
            return view;

        } catch (NoSuchMethodException e) {
           …………………………
        } finally {
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }
    }

其中attrs是通过Resource的loadXmlResourceParser方法加载的,我们看一下

    /*package*/ XmlResourceParser loadXmlResourceParser(int id, String type)
            throws NotFoundException {
        synchronized (mAccessLock) {
            TypedValue value = mTmpValue;
            if (value == null) {
                mTmpValue = value = new TypedValue();
            }
            getValue(id, value, true);
            if (value.type == TypedValue.TYPE_STRING) {
                return loadXmlResourceParser(value.string.toString(), id,
                        value.assetCookie, type);
            }
            throw new NotFoundException(
                    "Resource ID #0x" + Integer.toHexString(id) + " type #0x"
                    + Integer.toHexString(value.type) + " is not valid");
        }
    }

剩下的就是涉及到Xml的解析,这里就不在作深入探讨,言归正传,还回到刚才的loadStringValueAt方法,如果缓存中存在就从缓存中去,如果不存在就通过xml解析获取。下面在看一下一些常用的方法,其中getText(int
index)和getString(int index)差不多,我们就来看一下getString(int index)方法

    public String getString(int index) {
        if (mRecycled) {
            throw new RuntimeException("Cannot make calls to a recycled instance!");
        }

        index *= AssetManager.STYLE_NUM_ENTRIES;
        final int[] data = mData;
        final int type = data[index+AssetManager.STYLE_TYPE];
        if (type == TypedValue.TYPE_NULL) {
            return null;
        } else if (type == TypedValue.TYPE_STRING) {
            return loadStringValueAt(index).toString();
        }

        TypedValue v = mValue;
        if (getValueAt(index, v)) {
            Log.w(Resources.TAG, "Converting to string: " + v);
            CharSequence cs = v.coerceToString();
            return cs != null ? cs.toString() : null;
        }
        Log.w(Resources.TAG, "getString of bad type: 0x"
              + Integer.toHexString(type));
        return null;
    }

上面的index要乘以6(STYLE_NUM_ENTRIES),因为是每6个一组的,如果type为null就返回空,如果为String类型就会调用loadStringValueAt方法获取我们设置的值。有一点要注意,如果我们在attrs中设置的format类型和我们自定义设置的参数不符的话,当运行的时候是会报错的,必须要设置相符并clean才能解决。否则就执行下面的方法,强制转换为字符串,代码比较简单,这里就不再贴出。在来看下一个bool类型和int类型的,由于这两个差不多,就随便挑一个

    public int getInt(int index, int defValue) {
        index *= AssetManager.STYLE_NUM_ENTRIES;
        final int[] data = mData;
        final int type = data[index+AssetManager.STYLE_TYPE];
        if (type == TypedValue.TYPE_NULL) {
            return defValue;
        } else if (type >= TypedValue.TYPE_FIRST_INT
            && type <= TypedValue.TYPE_LAST_INT) {
            return data[index+AssetManager.STYLE_DATA];
        }

        TypedValue v = mValue;
        if (getValueAt(index, v)) {
            Log.w(Resources.TAG, "Converting to int: " + v);
            return XmlUtils.convertValueToInt(
                v.coerceToString(), defValue);
        }
        Log.w(Resources.TAG, "getInt of bad type: 0x"
              + Integer.toHexString(type));
        return defValue;
    }

上面的类型如果大于TYPE_FIRST_INT并且小于TYPE_LAST_INT的时候就从mDate中提取值,这个不知道为什么要这样写,不过从他的范围来看也就int,Boolean,color三种是这样取值的

    /** Identifies the start of integer values that were specified as
     *  color constants (starting with '#'). */
    public static final int TYPE_FIRST_COLOR_INT = 0x1c;

    /** The <var>data</var> field holds a color that was originally
     *  specified as #aarrggbb. */
    public static final int TYPE_INT_COLOR_ARGB8 = 0x1c;
    /** The <var>data</var> field holds a color that was originally
     *  specified as #rrggbb. */
    public static final int TYPE_INT_COLOR_RGB8 = 0x1d;
    /** The <var>data</var> field holds a color that was originally
     *  specified as #argb. */
    public static final int TYPE_INT_COLOR_ARGB4 = 0x1e;
    /** The <var>data</var> field holds a color that was originally
     *  specified as #rgb. */
    public static final int TYPE_INT_COLOR_RGB4 = 0x1f;

    /** Identifies the end of integer values that were specified as color
     *  constants. */
    public static final int TYPE_LAST_COLOR_INT = 0x1f;

    /** Identifies the end of plain integer values. */
    public static final int TYPE_LAST_INT = 0x1f;

如果范围不在TYPE_FIRST_INT和TYPE_LAST_INT之间,就会把mData指定位置上的值提取到TypedValue中,然后在强制转化,如果没有提取到就会返回一个默认值,因为如果在attrs中定义但没有用到,就会返回一个默认值。我们来看一下是怎么转化的

    public static final int
    convertValueToInt(CharSequence charSeq, int defaultValue)
    {
        if (null == charSeq)
            return defaultValue;

        String nm = charSeq.toString();

        // XXX This code is copied from Integer.decode() so we don't
        // have to instantiate an Integer!

        int value;
        int sign = 1;
        int index = 0;
        int len = nm.length();
        int base = 10;

        if ('-' == nm.charAt(0)) {
            sign = -1;
            index++;
        }

        if ('0' == nm.charAt(index)) {
            //  Quick check for a zero by itself
            if (index == (len - 1))
                return 0;

            char    c = nm.charAt(index + 1);

            if ('x' == c || 'X' == c) {
                index += 2;
                base = 16;
            } else {
                index++;
                base = 8;
            }
        }
        else if ('#' == nm.charAt(index))
        {
            index++;
            base = 16;
        }

        return Integer.parseInt(nm.substring(index), base) * sign;
    }

这个很好理解,转化为int类型有0开头的8进制,0x开头的16进制,还有#开头的color值,如果转化之前是负数,转化之后还要乘以-1(sign)。再来看一个

    public int getColor(int index, int defValue) {
        index *= AssetManager.STYLE_NUM_ENTRIES;
        final int[] data = mData;
        final int type = data[index+AssetManager.STYLE_TYPE];
        if (type == TypedValue.TYPE_NULL) {
            return defValue;
        } else if (type >= TypedValue.TYPE_FIRST_INT
            && type <= TypedValue.TYPE_LAST_INT) {
            return data[index+AssetManager.STYLE_DATA];
        } else if (type == TypedValue.TYPE_STRING) {
            final TypedValue value = mValue;
            if (getValueAt(index, value)) {
                ColorStateList csl = mResources.loadColorStateList(
                        value, value.resourceId);
                return csl.getDefaultColor();
            }
            return defValue;
        }

        throw new UnsupportedOperationException("Can't convert to color: type=0x"
                + Integer.toHexString(type));
    }

这个就不用多说了,因为color有String和int两种类型,如果是String类型就会返回ColorStateList的默认值,因为ColorStateList可能有好几种类型,但必须都是false的才是默认的,下面随便看一个,下面这些提取之后默认的就是green,因为只有他的所有状态都是false。

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">

    <item android:state_pressed="true" android:color="@color/blue"/>
    <item android:state_pressed="false" android:state_selected="true" android:color="@color/yellow"/>
    <item android:state_pressed="false" android:state_selected="false" android:color="@color/green"/>

</selector>

下面在看最后一个方法

    public int getLayoutDimension(int index, String name) {
        index *= AssetManager.STYLE_NUM_ENTRIES;
        final int[] data = mData;
        final int type = data[index+AssetManager.STYLE_TYPE];
        if (type >= TypedValue.TYPE_FIRST_INT
                && type <= TypedValue.TYPE_LAST_INT) {
            return data[index+AssetManager.STYLE_DATA];
        } else if (type == TypedValue.TYPE_DIMENSION) {
            return TypedValue.complexToDimensionPixelSize(
                data[index+AssetManager.STYLE_DATA], mResources.mMetrics);
        }

        throw new RuntimeException(getPositionDescription()
                + ": You must supply a " + name + " attribute.");
    }

看方法名大概就知道是获取layout的尺寸的,大致看一下,在ViewGroup中

        protected void setBaseAttributes(TypedArray a, int widthAttr, int heightAttr) {
            width = a.getLayoutDimension(widthAttr, "layout_width");
            height = a.getLayoutDimension(heightAttr, "layout_height");
        }

其中获取到的值有3种,一种是精确的我们给的大于0的,一种是-1(MATCH_PARENT),另一种是-2(WRAP_CONTENT),记得在讲到《Android
LayoutInflater源码分析及使用(二)
》的时候说到,xml的属性除了宽和高以外在初始化的时候基本上都能提取到,但宽和高是不行的,因为他是最终计算出来的,如果大家自定义View继承View的时候,要必须重写onMeasure方法,重新计算他的宽和高,如果我们不计算,当我们使用MATCH_PARENT或WRAP_CONTENT属性的时候,结果是完全一样的,尺寸都是填满剩下的屏幕,如果不重写onMeasure方法,在xml文件中把他的宽和高都写死也行,但这样不够灵活,我们来看一下为什么要重写

    public static int getDefaultSize(int size, int measureSpec) {
        int result = size;
        int specMode = MeasureSpec.getMode(measureSpec);
        int specSize = MeasureSpec.getSize(measureSpec);

        switch (specMode) {
        case MeasureSpec.UNSPECIFIED:
            result = size;
            break;
        case MeasureSpec.AT_MOST:
        case MeasureSpec.EXACTLY:
            result = specSize;
            break;
        }
        return result;
    }

我们看到View中的getDefaultSize方法,AT_MOST和EXACTLY返回的结果都是一样的,如果想看建议看一下ViewGroup的getChildMeasureSpec方法,这个就不在贴出,可以自己去看。OK,TypedArray中剩下的方法基本上也都非常相似,这里就不在一一讲述。

时间: 2024-10-18 04:36:53

Android TypedArray源码详解的相关文章

Android ArrayMap源码详解

尊重原创,转载请标明出处    http://blog.csdn.net/abcdef314159 分析源码之前先来介绍一下ArrayMap的存储结构,ArrayMap数据的存储不同于HashMap和SparseArray,在上一篇<Android SparseArray源码详解>中我们讲到SparseArray是以纯数组的形式存储的,一个数组存储的是key值一个数组存储的是value值,今天我们分析的ArrayMap和SparseArray有点类似,他也是以纯数组的形式存储,不过不同的是他的

Android Matrix源码详解

尊重原创,转载请标明出处   http://blog.csdn.net/abcdef314159 Matrix是一个3*3的矩阵,通过矩阵执行对图像的平移,旋转,缩放,斜切等操作.先看一段代码 public static final int MSCALE_X = 0; //!< use with getValues/setValues public static final int MSKEW_X = 1; //!< use with getValues/setValues public st

Android MediaScannerJNI源码详解

1.简介 MediaScannerJNI的在MediaScanner中的地位可参考 Android MediaScanner 总纲 MediaScanner JNI文件名: android_media_MediaScanner.cpp 位置: frameworks\base\media\jni\android_media_MediaScanner.cpp android_media_MediaScanner是JAVA层和server层沟通的一个桥梁 android_media_MediaScan

Android编程之Fragment动画加载方法源码详解

上次谈到了Fragment动画加载的异常问题,今天再聊聊它的动画加载loadAnimation的实现源代码: Animation loadAnimation(Fragment fragment, int transit, boolean enter, int transitionStyle) { 接下来具体看一下里面的源码部分,我将一部分一部分的讲解,首先是: Animation animObj = fragment.onCreateAnimation(transit, enter, fragm

Android View 事件分发机制源码详解(View篇)

前言 在Android View 事件分发机制源码详解(ViewGroup篇)一文中,主要对ViewGroup#dispatchTouchEvent的源码做了相应的解析,其中说到在ViewGroup把事件传递给子View的时候,会调用子View的dispatchTouchEvent,这时分两种情况,如果子View也是一个ViewGroup那么再执行同样的流程继续把事件分发下去,即调用ViewGroup#dispatchTouchEvent:如果子View只是单纯的一个View,那么调用的是Vie

IntentService源码详解

IntentService可以做什么: 如果你有一个任务,分成n个子任务,需要它们按照顺序完成.如果需要放到一个服务中完成,那么IntentService就会使最好的选择. IntentService是什么: IntentService是一个Service(看起来像废话,但是我第一眼看到这个名字,首先注意的是Intent啊.),所以如果自定义一个IntentService的话,一定要在AndroidManifest.xml里面声明. 从上面的"可以做什么"我们大概可以猜测一下Inten

butterknife源码详解

butterknife源码详解 作为Android开发者,大家肯定都知道大名鼎鼎的butterknife.它大大的提高了开发效率,虽然在很早之前就开始使用它了,但是只知道是通过注解的方式实现的,却一直没有仔细的学习下大牛的代码.最近在学习运行时注解,决定今天来系统的分析下butterknife的实现原理. 如果你之前不了解Annotation,那强烈建议你先看注解使用. 废多看图: 从图中可以很直观的看出它的module结构,以及使用示例代码. 它的目录和我们在注解使用这篇文章中介绍的一样,大体

Java concurrent AQS 源码详解

一.引言 AQS(同步阻塞队列)是concurrent包下锁机制实现的基础,相信大家在读完本篇博客后会对AQS框架有一个较为清晰的认识 这篇博客主要针对AbstractQueuedSynchronizer的源码进行分析,大致分为三个部分: 静态内部类Node的解析 重要常量以及字段的解析 重要方法的源码详解. 所有的分析仅基于个人的理解,若有不正之处,请谅解和批评指正,不胜感激!!! 二.Node解析 AQS在内部维护了一个同步阻塞队列,下面简称sync queue,该队列的元素即静态内部类No

深入Java基础(四)--哈希表(1)HashMap应用及源码详解

继续深入Java基础系列.今天是研究下哈希表,毕竟我们很多应用层的查找存储框架都是哈希作为它的根数据结构进行封装的嘛. 本系列: (1)深入Java基础(一)--基本数据类型及其包装类 (2)深入Java基础(二)--字符串家族 (3)深入Java基础(三)–集合(1)集合父类以及父接口源码及理解 (4)深入Java基础(三)–集合(2)ArrayList和其继承树源码解析以及其注意事项 文章结构:(1)哈希概述及HashMap应用:(2)HashMap源码分析:(3)再次总结关键点 一.哈希概