java typedarray_TypedArray流程分析

9984402c79a5

obtainStyledAttributes_flow.png

Context#obtainStyledAttributes

// 调用Resources.Theme的obtainStyledAttributes方法

return getTheme().obtainStyledAttributes(attrs);

在Context中的getTheme方法是抽象方法,那我们得看他的子类的具体实现,我们一般会在自定义View的时候调用此方法,而自定义View中的Context是传进来的,一般指的是它显示的Activity,我们在Activity中搜索getTheme方法,会搜索到它的父类ContextThemeWrapper中,那我们来看看ContextThemeWrapper中getTheme怎么实现的:

ContextThemeWrapper#getTheme

根据版本选择默认主题并保存在mThemeResource中

mThemeResource = Resources.selectDefaultTheme(mThemeResource,

getApplicationInfo().targetSdkVersion);

初始化主题

initializeTheme();

在initializeTheme方法内部的实现原理:最终调用了AssetManager的native方法applyThemeStyle

Context#obtainStyledAttributes

Context类中有4个obtainStyledAttributes, 最终调用的都是4个参数的obtainStyledAttributes方法,而最终调用的是ResourcesImpl.ThemeImpl的obtainStyledAttributes方法。让我们看看Context的obtainStyledAttributes方法的4个参数分别代表着什么:

AttributeSet set :AttributeSet是在布局中定义的一系列属性的集合,包括系统定义的属性。在下列例子中,如layout_width,还有自定义的属性,如MyProgress。

@StyleableRes int[] attrs :自定义属性集合,在下列例子中,如R.styleable.MyView。

@AttrRes int defStyleAttr :在当前主题中有一個引用指向样式文件,這個样式文件将 TypedArray 设置默认值。如果此参数为0即表示不进行默认值设置。在下列例子中,如R.attr.DefaultViewStyleAttr。

@StyleRes int defStyleRes :默认的样式资源文件,只有当 defStyleAttr 为 0 或者无法在对应的主题下找到资源文件时才起作用。如果此参数为0即表示不进行默认值设置。在下列例子中,如R.style.DefaultViewStyleRes。

以自定义View为例,现在创建一个MyView:

public MyView(Context context, @Nullable AttributeSet attrs) {

Logger logger = Logger.getLogger("MyView");

for (int i = 0; i < attrs.getAttributeCount(); i++) {

logger.info(attrs.getAttributeName(i) + " : " + attrs.getAttributeValue(i));

}

TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.MyView, R.attr.DefaultViewStyleAttr, R.style.DefaultViewStyleRes);

logger.info("-----------------------------------------");

logger.info("MyText1的最终值" + " : " + a.getString(R.styleable.MyView_MyText1));

logger.info("MyText2的最终值" + " : " + a.getString(R.styleable.MyView_MyText2));

logger.info("MyText3的最终值" + " : " + a.getString(R.styleable.MyView_MyText3));

logger.info("MyText4的最终值" + " : " + a.getString(R.styleable.MyView_MyText4));

logger.info("MyText5的最终值" + " : " + a.getString(R.styleable.MyView_MyText5));

}

在attr.xml中自定义属性:

在styles.xml中自定义style

@color/colorPrimary

@color/colorPrimaryDark

@color/colorAccent

@style/MyViewStyleAttr

"defStyleAttr中提供的默认的属性值,在主题中直接定义"

"defStyleAttr中提供的默认的属性值,在主题中直接定义"

"defStyleAttr中提供的默认的属性值,在主题中直接定义"

"defStyleAttr中提供的默认的属性值,在主题中直接定义"

"XML中在style里定义的属性值"

"XML中在style里定义的属性值"

"defStyleRes中提供的默认的属性值"

"defStyleRes中提供的默认的属性值"

"defStyleRes中提供的默认的属性值"

"defStyleRes中提供的默认的属性值"

"defStyleRes中提供的默认的属性值"

"defStyleAttr中提供的默认的属性值,在主题中的style里定义"

"defStyleAttr中提供的默认的属性值,在主题中的style里定义"

"defStyleAttr中提供的默认的属性值,在主题中的style里定义"

在布局文件中引用这个MyView:

android:layout_width="match_parent"

android:layout_height="wrap_content"

style="@style/MyViewStyle"

app:MyText1="XML中直接定义的属性值" />

运行之后输出的结果是:

I/MyView: layout_width : -1

I/MyView: layout_height : -2

I/MyView: MyText1 : XML中直接定义的属性值

I/MyView: style : @style/MyViewStyle

I/MyView: -----------------------------------------

I/MyView: MyText1的最终值 : XML中直接定义的属性值

I/MyView: MyText2的最终值 : XML中在style里定义的属性值

I/MyView: MyText3的最终值 : defStyleAttr中提供的默认的属性值,在主题中的style里定义

I/MyView: MyText4的最终值 : defStyleAttr中提供的默认的属性值,在主题中直接定义

I/MyView: MyText5的最终值 : null

从上面的结果来看,xml attributes > xml style > theme style defStyleAttr > theme defStyleAttr > defStyleRes

**上面例子的代码不变,我们将defStyleAttr设为0,如: **

TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.MyView, 0, R.style.DefaultViewStyleRes);

运行之后输出的结果是:

I/MyView: layout_width : -1

I/MyView: layout_height : -2

I/MyView: MyText1 : XML中直接定义的属性值

I/MyView: style : @style/MyViewStyle

I/MyView: -----------------------------------------

I/MyView: MyText1的最终值 : XML中直接定义的属性值

I/MyView: MyText2的最终值 : XML中在style里定义的属性值

I/MyView: MyText3的最终值 : defStyleRes中提供的默认的属性值

I/MyView: MyText4的最终值 : defStyleRes中提供的默认的属性值

I/MyView: MyText5的最终值 : defStyleRes中提供的默认的属性值

此时,MyText3、MyText4、MyText5的最终值都变成了在DefaultViewStyleRes中定义的属性的值了。可以得知在defStyleAttr中检索不到值,才会去取defStyleRes中设置的值。

一般设置属性的默认值,都会使用defStyleRes来设置。

Context#obtainStyledAttributes

return getTheme().obtainStyledAttributes(

set, attrs, defStyleAttr, defStyleRes);

ResourcesImpl.ThemeImpl#obtainStyledAttributes

调用TypedArray的obtain方法

final TypedArray array = TypedArray.obtain(Resources.this, len);

调用本地方法给array(TypedArray)的mData、mIndices赋值

AssetManager.applyStyle(mTheme, 0, 0, 0, attrs, array.mData, array.mIndices);

查看本地方法applyStyle的具体实现,是在mData数组中存储了六种类型的数据,分别为:

STYLE_TYPE

STYLE_DATA

STYLE_ASSET_COOKIE

STYLE_RESOURCE_ID

STYLE_CHANGING_CONFIGURATIONS

STYLE_DENSITY

STYLE_NUM_ENTRIES

在base/core/jni/android_util_AssetManager.cpp查看android_content_AssetManager_applyStyle

// Write the final value back to Java.

dest[STYLE_TYPE] = value.dataType;

dest[STYLE_DATA] = value.data;

dest[STYLE_ASSET_COOKIE] = block != kXmlBlock ?

static_cast(res.getTableCookie(block)) : -1;

dest[STYLE_RESOURCE_ID] = resid;

dest[STYLE_CHANGING_CONFIGURATIONS] = typeSetFlags;

dest[STYLE_DENSITY] = config.density;

TypedArray#obtain

从Resource.mTypedArrayPool(SynchronizedPool)池中取TypedArray对象

final TypedArray attrs = res.mTypedArrayPool.acquire();

...

没取到,则调用TypedArray的构造方法

return new TypedArray(res,

new int[len*AssetManager.STYLE_NUM_ENTRIES],

new int[1+len], len);

在TypedArray中我们会看到很多getxxx()方法,我们点进去看会发现基本上都会有这么一行代码:

if (mRecycled) {

throw new RuntimeException("Cannot make calls to a recycled instance!");

}

猜测:这段代码会不会和每次在自定义View中取完自定义属性之后调用的typedArray.recycle();有关?

if (mRecycled) {

throw new RuntimeException(toString() + " recycled twice!");

}

mRecycled = true;

// These may have been set by the client.

...

mResources.mTypedArrayPool.release(this);

查看recycle()方法,可以知道Android要求我们在每次不再使用TypedArray时,必须手动调用该方法以复用TypedArray。

注意:

不能重复调用该方法,否则会抛出以下异常:

Caused by: java.lang.RuntimeException: [0, 0, 1, 0, ...] recycled twice!

不能在调用该方法后,还调用getxxx等TypedArray的方法,否则回抛出以下异常:

Caused by: java.lang.RuntimeException: Cannot make calls to a recycled instance!

TypedArray#getInt

根据下标index获取mData数组存储的Type类型的值,判断Type是否为TypedValue.TYPE_NULL,true则,返回默认值defValue

...

final int type = data[index+AssetManager.STYLE_TYPE];

if (type == TypedValue.TYPE_NULL) {

return defValue;

}

根据下标index获取data、assetCookie、resourceId、changingConfigurations、density等类型的值,并存储在TypedValue中

getValueAt(index, v)

通过XmlUtils.convertValueToInt方法将诸如"-12,0xa1,014,#fff"这类字符串转化为真正的数值

return XmlUtils.convertValueToInt(v.coerceToString(), defValue);

TypedArray$getValueAt

将mData数组数组中的数据存储在TypedValue中:

...

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;

XmlUtils$convertValueToInt

转换负数

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;

}

将String转换成数值

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

总结

TypedArray是用来检索项目中各种资源的

只有当 defStyleAttr 为 0 或者无法在对应的主题下找到资源文件时才起作用,defStyleRes中定义的默认样式才起作用

TypedArray检索完资源,必须调用recycle方法来循环使用

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值