标签归档:XML

【Android XML】Android XML 转 Java Code 系列之 style(3)

Published / by sickworm / Leave a Comment

最近一个月把代码重构了一遍, 感觉舒服多了, 但总体开发进度没有变化.. 今天聊聊把style属性转换成Java代码的办法

 

先说结论: 引用系统style是无法完美的实现的, 我们如果有写成Java代码的需求, 请尽量避免使用系统style. 自定义style没问题.

 

style是什么?

(参考链接) http://developer.android.com/guide/topics/resources/style-resource.html

” A style resource defines the format and look for a UI. A style can be applied to an individual View (from within a layout file) or to an entireActivity or application (from within the manifest file).”

更通俗的理解是, style其实是放置一组attribute的宏, 在控件中指定这个style, 将在xml解析时将style的一组属性应用到该控件中. 举个栗子:

这里有个style, 定义在res/value中:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <style name="CustomText" parent="@style/Text">
        <item name="android:textSize">20sp</item>
        <item name="android:textColor">#008</item>
    </style>
</resources>

然后我们在layout中的某个控件引用了这个style:

<?xml version="1.0" encoding="utf-8"?>
<EditText style="@style/CustomText" android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="Hello, World!" />

 

实际上, 他是等于:

<?xml version="1.0" encoding="utf-8"?>
<EditText android:textSize="20sp" android:textColor="#008" android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="Hello, World!" />

有点宏替换的味道, 如果接触过html的同学可以很容易想到CSS这种东西, 两个想要做的基本是一样的.

需要指明的是: style的属性的优先级是最低的, 会被控件的其他相同属性覆盖掉. 这是理所当然的, 不然控件里的attribute要不服了(你都把我写出来了, 居然和我说我没用, 要被style那货覆盖掉?!)

 

让我们再回到这个被我们定义的style上, 会发现他还有一个叫parent的属性. 这里我们用面向对象的父类来理解, 就是子style会继承父类的属性. 同样的, 父类的属性要比子类属性优先级要低. (你都把我写出来了, 居然和我说我没用, 要被老爸的style覆盖掉?!)

 

style不是什么?

style不属于android Namespace中的属性 (不以android:开头), 没有对应的setStyle方法可以使用, 我的理解是它属于xml预加载的一种”机制”.

 

自定义style的翻译:

那么对于自定义的style, 翻译就很简单了.

对于一个style, 将其替换成style中的attribute集合, 并向上 (父类) 继续增加父类集合, 并注意优先级 (子类可以覆盖父类attribute).

 

系统style的翻译:

系统style的翻译主要存在两个问题:

1. 系统style是内置在系统内部的, 不同的系统style并不一样, 特别是深度定制化的如MIUI, Flyme等操作系统. 如果只是使用一些简单的比较标准的style, 如progressbar等, 可以通过SDK Manager下载最新Android版本的API, 然后在路径platformsandroid-20dataresvalues中找到标准的系统style, 或直接下载ASOP(路径framework/base/core/res/res/values/styles.xml. (我在项目中用到的是4.4的style文件, 5.0的sytle文件分成了多个, 而且里面的命名有点不规范)

有些同学会发现, 如progressbar里面的style: android:attr/progressBarStyleSmall, 是用android:attr索引的. android:attr其实是在theme中定义的属性, apk的theme在AndroidManifest.xml中定义. 在解析xml过程中, 遇到android:attr的时候, 他就会在apk指定的theme中找到相应的item. 这个item对应的值就是style的值, 如:

style=?android:attr/progressBarStyleSmall 对应 最基本的Theme中的 <item name="progressBarStyleSmall">@android:style/Widget.ProgressBar.Small</item>

然后你再去styles.xml中寻找Widget.ProgressBar.Small. (这里还有个要注意的是, 就是系统的style不会声明parent属性, 但是以点”.”做层级, 比如这里的Widget.ProgressBar.Small父类是Widget.ProgressBar)

这样带来的另一个问题, 深度定制的操作系统各种主题也会有改动, 这样造成更大的偏差.

 

2. 系统style用到的资源, 有些是我们从外部无法获取的. 还是举Widget.ProgressBar.Small的例子:

    <style name="Widget.ProgressBar.Small">
        <item name="android:indeterminateDrawable">@android:drawable/progress_small_white</item>
        <item name="android:minWidth">16dip</item>
        <item name="android:maxWidth">16dip</item>
        <item name="android:minHeight">16dip</item>
        <item name="android:maxHeight">16dip</item>
    </style>

他里面用到了@android:drawable/progress_small_white这个系统资源, 但是这个资源不是public的, 我们实现的时候没办法把这个资源调用出来. 有些同学会想到反射, 但反射也是不行的. 因为所有的系统资源在ASOP编译的时候会生成类似于R.java这样的资源索引文件, 这些文件用于我们使用系统资源的id来调用系统资源. 而非public是不会声明出来的, 也就是说你连id都无法知道, 也就无法获取对应资源了. 我曾大致阅读了一下xml的解析实现, 发现到最后都进入了native层, 在native层用c/c++实现, 这让我望code兴叹..

不过ProgressBar的各种样式应该可以通过api来设置出来, 这个我还没证实. 可以参考: [Android实例] Android 在Java代码中设置style属性–使用代码创建ProgressBar对象

对这个非public系统资源获取有经验的同学欢迎交流.

 

项目代码:

我在项目中实现了构建style元素, 使用style元素替换为attritutes, 以及style元素之间的关系, 包括对theme的android:attr寻找:

https://github.com/SickWorm/AndroidXMLToJava/blob/master/src/com/excelsecu/androidx2j/AX2JStyle.java

 

版权所有,转载请注明出处:

http://sickworm.com/?p=36

【Android XML】Android XML 转 Java Code 系列之 Selector(2)

Published / by sickworm / Leave a Comment

今天我们要把drawable下的selector的XML文件转换成Java代码。(打包进jar,不依赖apk)

在转换工具中的代码为:

https://github.com/SickWorm/AndroidXMLToJava/blob/master/src/com/excelsecu/androidx2j/SelectorTranslator.java

 

Selector是什么?就是给Button等控件使用的一个根据状态改变控件颜色或背景的状态器,它一般放在drawable目录下。

Selector分两种,一种是指定color和alpha的状态器,XML形式如下:

 <selector xmlns:android="http://schemas.android.com/apk/res/android">
   <item android:state_focused="true" android:color="@color/testcolor1"/>
   <item android:state_pressed="true" android:state_enabled="false" android:color="@color/testcolor2" />
   <item android:state_enabled="false" android:color="@color/testcolor3" />
   <item android:color="@color/testcolor5" android:alpha="0.5" />
 </selector>

另一种是指定drawable的状态器,XML形式如下:

 <selector xmlns:android="http://schemas.android.com/apk/res/android">
   <item android:state_focused="true" android:drawable="@drawable/testdrawable1"/>
   <item android:state_pressed="true" android:state_enabled="false" android:drawable="@drawable/testdrawable2" />
   <item android:state_enabled="false" android:drawable="@drawable/testdrawable3" />
   <item android:drawable="@color/testdrawable5"/>
 </selector>

 

知道了XML格式,我们如何去找到对应实现的类呢?这时候就应该上万能的http://developer.android.com/(科学上网,我用红杏我自豪)寻找selector。点击第一个链接:

http://developer.android.com/guide/topics/resources/color-list-resource.html

在这个文档上我们可以明显看到第一个XML对应的类就是ColorStateList(里面介绍的第一个就是)。而第二个XML,我一开始找了很久都没找到,直到我点开文档最下方的State List Drawable,发现赫然写着:

StateListDrawable(原谅我激动的心情)

点进这个类看到对应的XML布局,确定这就是第二个XML布局啦。

 

找到了对应的类,接下来要开始正式的工作了。我们先从ColorStateList下手。

所谓转换,其实就是构造一个对应类的对象,然后把XML的属性对应的内容用Java代码的方式“填充”进这个对象中便完成了。在使用到这个XML的地方,也用对应的设置方法把该成员设置进去就可以了。

那首先找到ColorStateL的构造函数:ColorStateList(int[][] states, int[] colors);

一开始便难倒我了。。这诡异的两个int数组是shenmegui?看看人家控件家族(Button, TextView等等)只需要一个context就可以构造了。根据名字猜测,这两个数组估计是对应的各种状态和颜色。那我们接下去认证这个猜想。

说时迟那时快,我发现了另一个函数:createFromXml(Resources r, XmlPullParser parser);

该函数就是给我们用Java代码动态解析XML用的方法。里面肯定有对XML各参数解析的过程,赶紧看一下源代码(作为一个开源操作系统的程序猿,一定要下一份源码)。

    /** * Create a ColorStateList from an XML document, given a set of {@link Resources}. */
    public static ColorStateList createFromXml(Resources r, XmlPullParser parser) throws XmlPullParserException, IOException { final AttributeSet attrs = Xml.asAttributeSet(parser); int type; while ((type=parser.next()) != XmlPullParser.START_TAG && type != XmlPullParser.END_DOCUMENT) { } if (type != XmlPullParser.START_TAG) { throw new XmlPullParserException("No start tag found"); } return createFromXmlInner(r, parser, attrs); }

我们跟着这个XmlPullparser走,因为他里面藏着XML信息。结果发现他调用了createFromXmlInner。继续跟,发现调用了colorStateList.inflate。看inflate就知道是解析函数啦!那我们来详细分析这个inflate:

(下面是我分析inflate函数时的笔记,应该算比较详细了)

    /** * Fill in this object based on the contents of an XML "selector" element. */
    //解析XmlPullparser, (剧透)最终得到 mColors 和 mStateSpecs, 这两个参数用于构造ColorStateList
    private void inflate(Resources r, XmlPullParser parser, AttributeSet attrs) throws XmlPullParserException, IOException { int type; final int innerDepth = parser.getDepth()+1; int depth; int[][] stateSpecList = ArrayUtils.newUnpaddedArray(int[].class, 20); int[] colorList = new int[stateSpecList.length]; int listSize = 0; while ((type=parser.next()) != XmlPullParser.END_DOCUMENT && ((depth=parser.getDepth()) >= innerDepth || type != XmlPullParser.END_TAG)) { if (type != XmlPullParser.START_TAG) { continue; } //只解析item项
            if (depth > innerDepth || !parser.getName().equals("item")) { continue; } int alphaRes = 0; float alpha = 1.0f; int colorRes = 0; int color = 0xffff0000; boolean haveColor = false; int i; int j = 0; final int numAttrs = attrs.getAttributeCount(); int[] stateSpec = new int[numAttrs]; //对item项的各个属性做分析
            for (i = 0; i < numAttrs; i++) { //获取属性名对应的id, 如果没有则返回0, 其实就是把字符匹配的工作交出去, 提高代码灵活性
                final int stateResId = attrs.getAttributeNameResource(i); if (stateResId == 0) break; //如果是alpha属性则赋值alpha. alpha是一个透明度的值
                if (stateResId == com.android.internal.R.attr.alpha) { //获取属性值对应的资源id, 就是我们熟悉的R.java里面的东西啦, 比如R.drawable.button1 = 0x7f040001这样的值
                    alphaRes = attrs.getAttributeResourceValue(i, 0); //如果不是资源id, 是常数, 则直接翻译
                    if (alphaRes == 0) { alpha = attrs.getAttributeFloatValue(i, 1.0f); } //如果是color属性, 也做类似的处理. color就是对应指定状态下的背景或颜色
                } else if (stateResId == com.android.internal.R.attr.color) { colorRes = attrs.getAttributeResourceValue(i, 0); if (colorRes == 0) { color = attrs.getAttributeIntValue(i, color); haveColor = true; } //不是上面两个条件的话, 只能是设置状态的布尔值了, 如android:state_pressed="true"
                } else { //此时会把属性名对应的id存进数组, 并根据是否设置正负, 这样selector的各种状态条件就构建出来了 //具体的id值是多少, 是R.attr中的属性值吗(如state_pressed就是android.R.attr.state_pressed)?我们下面再仔细分析
                    stateSpec[j++] = attrs.getAttributeBooleanValue(i, false) ? stateResId : -stateResId; } } //这个trimStateSet的用处是重新指定此int数组的长度, 也就是j, stateSpec初始化的时候是使用了属性数量作为长度, 在这里会被重新设置, 以节省空间. //因为上一条语句会做j++, 所以永远空出一个位置给下一个属性添加 //数组很小, 所以不会很费时间, 牺牲一点性能换取简单的写法也是可取的. 如果是我我会在循环开始的时候先找出有多少个设置状态的属性.
            stateSpec = StateSet.trimStateSet(stateSpec, j); //这里对刚才获取的资源值做获取, 并做一些结构完整性检查, 如果一个item连color都没有, 那真是坑爹了
            if (colorRes != 0) { color = r.getColor(colorRes); } else if (!haveColor) { throw new XmlPullParserException( parser.getPositionDescription() + ": <item> tag requires a 'android:color' attribute."); } //alpha就可有可无了, 如果没有, 默认是1 (初始化的值float alpha = 1.0f;), 也就是100%不透明
            if (alphaRes != 0) { alpha = r.getFloat(alphaRes); } //稍微转换一下, 把color算出来 // Apply alpha modulation. //算出alpha. Color.alpha(color)看源码, 就是 color >>> 24. 这个运算是算出color的alpha值 (int color 长度为4个字节, 3个字节分别代表RGB, 还有一个最高位字节的alpha喔) //比如#AAFFFFFF的alpha值就是AA. 但要注意的是#FFFFFF的alpha值是FF, 因为Android的Color.parse会自动给没填写alpha的值的最高位填上FF(具体看源代码).        //如果你使用int形式, 如0xaaaaaa, alpha值就是0, 这样写是会全透明的. 用int形式一定要写上alpha值, 如全黑是0xff000000. //所以一个item真正的alpha值是由color自带的alpha和android:alpha的值共同决定的 //MathUtils.constrain这个函数不用看都能知道是百分比缩放的函数, 比如0.5, 0, 255, 的出来的就是(255 - 0) * 0.5
            final int alphaMod = MathUtils.constrain((int) (Color.alpha(color) * alpha), 0, 255); //再把这个0~255的值塞回color的最高字节里面去 (折腾啊)
            color = (color & 0xFFFFFF) | (alphaMod << 24); //如果只有这个android:color这个属性, 没有表示状态的属性, 就认为他是默认色
            if (listSize == 0 || stateSpec.length == 0) { mDefaultColor = color; } //把color 和 stateSpec塞进数组里面去. 比用ArrayList要节省点空间. 看来Android在节省内存方面上很卖力啊 //append能猜到又是重新定义数组长度这种事情, 不同的是会加一个数组进去
            colorList = GrowingArrayUtils.append(colorList, listSize, color); stateSpecList = GrowingArrayUtils.append(stateSpecList, listSize, stateSpec); listSize++; } //所有的item都跑完了, 可以把结果存给全局变量mColors 和 mStateSpecs了.
        mColors = new int[listSize]; mStateSpecs = new int[listSize][]; System.arraycopy(colorList, 0, mColors, 0, listSize); System.arraycopy(stateSpecList, 0, mStateSpecs, 0, listSize); }

我们现在来看下ColorStateList的构造函数:

    public ColorStateList(int[][] states, int[] colors) { mStateSpecs = states; mColors = colors; if (states.length > 0) { mDefaultColor = colors[0]; for (int i = 0; i < states.length; i++) { if (states[i].length == 0) { mDefaultColor = colors[i]; } } } }

inflate得到的两个最终产物 mStateSpecs 和 mColors 就是我们想要的构造函数!我们只要照葫芦画瓢,照inflate画数组,就可以把这两个数组写出来了。ColorStateList被降服了!

喔,忘了还有一个遗留问题:

穿山甲到底说了什么?

stateResId到底是什么?

再看分析:

//首先这个id是从AttributeSet中得到的,AttributeSet是一个interface,实现它的是XmlPullAttributes(为什么?因为搜遍了源码就他一个...),但可惜的是XmlPullAttributes没有实现getAttributeNameResource() //继续找儿子。BridgeXmlPullAttributes 继承了XmlPullAttributes,而且他实现了getAttributeNameResource()
    BridgeXmlPullAttributes extends XmlPullAttributes //This methods must return com.android.internal.R.attr.<name> matching the name of the attribute.
        BridgeXmlPullAttributes.getAttributeNameResource(int index) -> Bridge.getResourceId(ResourceType.ATTR, name); ->->        Integer v = Bridge.getResourceId(ResourceType.ATTR, name); ->->-> sRevRMap.get(type); if (v != null) { return v.intValue(); } //这里写的有点乱,“->”的意思是深入一层,同样的梯度表示在同一个函数中,也就是说getAttributeNameResource调用了Bridge.getResourceId调用了sRevRMap.get,最后Bridge.getResourceId返回了sRevRMap.get的int值。 //这个sRevRMap是在哪初始化的呢?搜源码发现在init中()
 init(...) { ... Class<?> r = com.android.internal.R.class; //这里这里!init函数把这个class里面的所有子类声明的值put进去sRevRMap了。 //这个com.android.internal.R.class是在Android ROM编译的时候生成的一个类,就类似我们写应用时自动生成的R.java,里面全部是声明的值。 //而我们getResourceId的时候传的type是ResourceType.ATTR。所以就是ATTR的id值。这个值在http://developer.android.com/reference/android/R.attr.html中有详细列表。 //实际上我们使用只需要给对应的属性名加上前缀android.R.attr即可。如android:state_pressed对应android.R.attr.state_pressed。 //至于为什么初始化的时候是com.android.internal.R,使用的时候是android.R,应该是涉及到Android系统编译的东西,我没有深入了解。

            for (Class<?> inner : r.getDeclaredClasses()) { String resTypeName = inner.getSimpleName(); ResourceType resType = ResourceType.getEnum(resTypeName); if (resType != null) { Map<String, Integer> fullMap = new HashMap<String, Integer>(); sRevRMap.put(resType, fullMap); for (Field f : inner.getDeclaredFields()) { // only process static final fields. Since the final attribute may have // been altered by layoutlib_create, we only check static
                        int modifiers = f.getModifiers(); if (Modifier.isStatic(modifiers)) { Class<?> type = f.getType(); if (type.isArray() && type.getComponentType() == int.class) { // if the object is an int[] we put it in sRArrayMap using an IntArray // wrapper that properly implements equals and hashcode for the array // objects, as required by the map contract.
                                sRArrayMap.put(new IntArray((int[]) f.get(null)), f.getName()); } else if (type == int.class) { Integer value = (Integer) f.get(null); sRMap.put(value, Pair.of(resType, f.getName())); fullMap.put(f.getName(), value); } else { assert false; } } } } } ... } 

这里“com.android.internal.R,使用的时候是android.R”,说服力可能不足,但这个表http://developer.android.com/reference/android/R.attr.html应该是可靠的,Google不会做两个表出来吧。。

是com.android.internal.R,使用的时候是android.R,

 

接下来还有一个

StateListDrawable(怎么还是这么大)

这个东西就简单的多了,构造器不需要参数,直接new StateListDrawable()即可。添加状态使用addState(int[] stateSet, Drawable drawable)。stateSet和ColorStateList的规则是一样的,drawable就是XML文件里面指定的drawable资源。这里就不多赘述了。

 

注意:

虽然ColorStateList对应的selector XML放在drawable文件夹里面,但他本身是一个自定义类!父类是Object!和

StateListDrawable(啊啊啊啊吓死我了)

不一样,这货父类是Drawable,可以用getDrawable()获取。如果你用getDrawable(R.drawable.colorStateList)获取对应资源的话是会崩溃的。为什么要提到这点呢?因为转换工具(详细介绍看第一篇文章)有一个仿照getResources()专门管理资源的类(叫AXMLResources),因为这个ColorStateList的特殊原因,就要另外用函数处理了。

 

版权所有,转载请注明出处:

http://sickworm.com/?p=34 

【Android XML】Android XML 转 Java Code 系列之 介绍(1)

Published / by sickworm / Leave a Comment

最近在公司做一个项目,需要把Android界面打包进jar包给客户使用。对绝大部分开发者来说,Android界面的布局以XML文件为主,并辅以少量Java代码进行动态调整。而打包进jar包的代码,意味着无法通过常规的getResources(),getString()等方法来快速的获取资源,因为这些资源都是在apk安装的时候初始化生成的。为了满足客户的需求,笔者开始在网上寻找各种解决方案。结果如下:

 

1.apk 主体包方案

实现方法:安装一个新的apk,新apk和主apk使用android:sharedUserId连接起来,这样主apk就可以获取新apk的所有res资源。需要注意的是,R.id.xxx的值为新apk中的R.java的值。因此需要获取到新apk的R.class,或保证新apk和主apk的R.java中的资源的值是完全一致的,否则会出现获取资源对不上号的情况。
结论:可以实现,但需要多安装一个apk。静默安装apk需要root权限,否则会弹出安装界面。还没有找到不需要安装apk就能获取资源的方法。
参考链接:http://blog.csdn.net/lg707415323/article/details/7709076

2.LayoutInflater 方案

实现方法:通过LayoutInflater类解析外部XML资源,并转换为所需布局。
分析:Android解释因为性能原因,LayoutInflater只支持解析已经预编译在apk包里的xml。所以没办法解析外部导入的xml布局资源。

3.Android XML 转 Java Code方案  (开源/自研)

在线的Android XmToJaval转换服务,免费,支持各种控件(http://www.xmltojava.com/page/controls/ ),支持还算丰富,但转换完还需要一些简单的手动编辑。不支持drawable和values目录下的资源转换。
结论:能减轻手动翻译的工作量,但不能完全解决需求。drawable和string需要自己手动翻译实现。
个人开源项目,第一个项目用php写的,代码量很少,只更新过一次,应该没法用。第二个项目是Androidy应用动态解析,刚刚建立起来的,支持的字段很少,应该也没法用。完成度都很低。在Google和Github上都没有找到成熟的方案。
结论:不可用

4.zip包方案

只能替换图片资源,没办法解析Xml布局。(桌面应用的换肤功能都基于zip或apk这两个方案)
结论:不可行

5.ClassLoader方案

把apk包用ClassLoader加载进来,并通过反射的方法获取Activity或其他组件的各种方法,从而获得资源。
分析:实践发现,反射调用Activity
总结论:方案1最方便,但客户不一定能接受;方案3比较稳妥,但是耗费人力大。

 

由于方案1需要安装apk,如果要静默安装还需要root权限。这种方案过于累赘,只能选择Android XML翻译原生代码的方法。于是就有了AndroidXMLToJava管理工具的诞生。

接下来几篇会分享该转换工具的一些解决过程。项目快完善的时候会放上博客,现在暂时不放出来。

 

 

版权所有,转载请注明出处:

http://sickworm.com/?p=32