目前提供第三方jar包支持,我已知的解决方案包括:
1. 直接提供library工程。这种形式主要用于内部或者公共项目。
2. 将代码打包成jar,提供尽包含资源的library工程。
3. 将所有非图片资源转化成代码,提供图片和jar包。
直接提供library工程和提供资源文件的方式最方便,且集成方可自主修改待集成界面,但有些时候出于公司的考虑需要禁止用户修改界面(至少比较难)。
目前公司使用的是通过代码创建布局,将图片资源打包到assets目录,并打包到jar目录中,android编译的时候会将所有jar包后打开合并成目录的形式。
关于图片资源
目前:放到assets目录,客户还是可以通过解压jar包取出里面的资源文件,并在assets目录中保留一份同名的文件即可替换。
解决方案:使用BINCompiler将所有图片文件压缩成一个单独的bin文件,然后通过偏移位置和文件长度从bin文件中读取对应的内容。
/** * @Param ctx * @Param binFileName bin文件 * @Param position 文件偏移量 */ public static Bitmap createBitmapToAssets(Context ctx, String binFileName, int position) { byte[] buffer = null; // 文件内容 int len = -1; // 文件长度 try { // 通过AssetManager读取文件内容 InputStream in = ctx.getResources().getAssets().open(binFileName, AssetManager.ACCESS_RANDOM); try { // 跳过 in.skip(position); // 读取文件长度 len = (in.read() & 0xFF) << 24; len |= (in.read() & 0xFF) << 16; len |= (in.read() & 0xFF) << 8; len |= (in.read() & 0xFF); // 读取内容 buffer = new byte[len]; in.read(buffer); } finally { in.close(); } if (null == buffer || buffer.length <= 0 || len <= 0) { return null; } // 转化成Bitmap Bitmap bitmap = BitmapFactory.decodeByteArray(buffer, 0, buffer.length); return bitmap; } catch (IOException e) { e.printStackTrace(); } return null; }
关于代码
使用ProGuard混淆
关于布局和其他xml资源
使用java代码实现
这种方式如果界面较少的时候挺方便的,但是如果提供的界面较多,需要通过代码构建的界面越复杂,后续维护起来就比较麻烦,有没有更好的方式呢?
能不能将xml资源也打包到assets目录?
关于显示类资源,在Drawable类中提供了一个方法,可以通过传入一个XmlPullParser对象并转换成对应的Drawable实例
/** * Create a drawable from an XML document. For more information on how to * create resources in XML, see * <a href="{@docRoot}guide/topics/resources/drawable-resource.html">Drawable Resources</a>. */ public static Drawable createFromXml(Resources r, XmlPullParser parser) throws XmlPullParserException, IOException { AttributeSet attrs = Xml.asAttributeSet(parser); int type; while ((type=parser.next()) != XmlPullParser.START_TAG && type != XmlPullParser.END_DOCUMENT) { // Empty loop } if (type != XmlPullParser.START_TAG) { throw new XmlPullParserException("No start tag found"); } Drawable drawable = createFromXmlInner(r, parser, attrs); if (drawable == null) { throw new RuntimeException("Unknown initial tag: " + parser.getName()); } return drawable; }
我们随便在drawable中定义一个a_shape.xml
<?xml version="1.0" encoding="utf-8"?> <shape xmlns:android="http://schemas.android.com/apk/res/android" > <solid android:color="#00FF00" /> <corners android:radius="5dp" /> <padding android:left="5dp" android:top="5dp" android:right="5dp" android:bottom="5dp" /> <gradient android:startColor="#000000" android:endColor="#000000" android:centerColor="#FFFFFF" android:angle="90" /> <stroke android:width="5dp" android:color="#FFFFFF" android:dashWidth="5dp" android:dashGap="5dp" /> </shape>
然后将a_shape.xml拷贝到assets目录中,通过Drawable.createFromXml()方法获取一个Drawable对象实例,如下:
try { String filename = "a_shape.xml"; Drawable shape = Drawable.createFromXml(getResources(), getAssets().openXmlResourceParser(filename)); view1.setBackgroundDrawable(shape); } catch (Exception e) { e.printStackTrace(); }
如果运行,你会收到错误信息:
05-12 19:39:04.200: W/ResourceType(18251): Bad XML block: header size 28024 or total size 1702240364 is larger than data size 534
查看AssetManager源码,在注释中发现
/** * Retrieve a parser for a compiled XML file. * * @param fileName The name of the file to retrieve. */ public final XmlResourceParser openXmlResourceParser(String fileName) throws IOException { return openXmlResourceParser(0, fileName); }
需要提供的fileName是一个已经编译过的xml文件
如何获得一个已经编译过的xml呢?
使用aapt,写了一个脚本来生成编译后的文件
#! /bin/bash # 工程目录 dir=/Users/yanjun/workspaces/android/yiji01/TestLibrary/ sdk_dir=/Users/yanjun/dev/android/sdk/platforms/android-21/ # 编译后生成的zip文件 out_filename=./aaa.zip # 解压后的文件夹 out_zipdir=./aaa # 编译 aapt p -f -M $dir/AndroidManifest.xml -F $out_filename -S $dir/res -I $sdk_dir/android.jar # 解压 unzip -o $out_filename -d $out_zipdir # 删除zip文件 rm $out_filename
这时候再拷贝编译过后的xml到assets目录后使用上面的方法,即可成功获取资源。
能不能也将xml压缩到bin文件呢?
/** * 获取bin文件中的xml文件 * @param ctx 上下文 * @param binFileName bin文件名 * @param position 文件偏移量 * @return */ public static XmlPullParser createXmlPullParserToAssets(Context ctx, String binFileName, int position) { byte[] buffer = null; // 文件内容 int len = -1; // 文件长度 try { InputStream in = ctx.getResources().getAssets().open(binFileName, AssetManager.ACCESS_RANDOM); try { in.skip(position); // 文件长度 len = (in.read() & 0xFF) << 24; len |= (in.read() & 0xFF) << 16; len |= (in.read() & 0xFF) << 8; len |= (in.read() & 0xFF); // 读取文件内容 buffer = new byte[len]; in.read(buffer); } finally { in.close(); } if (null == buffer || buffer.length <= 0 || len <= 0) { return null; } // 创建XmlPullParser XmlPullParserFactory factory = XmlPullParserFactory.newInstance(); XmlPullParser parser = factory.newPullParser(); parser.setInput(new InputStreamReader(new ByteArrayInputStream(buffer))); return parser; } catch (Exception e) { e.printStackTrace(); } return null; }
如果使用上面的代码运行,会得到如下错误:
05-12 19:51:54.895: W/System.err(19054): org.xmlpull.v1.XmlPullParserException: name expected (position:START_TAG <null>@1:6 in java.io.InputStreamReader@422459b0) 05-12 19:51:54.905: W/System.err(19054): at org.kxml2.io.KXmlParser.checkRelaxed(KXmlParser.java:302) 05-12 19:51:54.905: W/System.err(19054): at org.kxml2.io.KXmlParser.readName(KXmlParser.java:1538) 05-12 19:51:54.905: W/System.err(19054): at org.kxml2.io.KXmlParser.parseStartTag(KXmlParser.java:1052) 05-12 19:51:54.905: W/System.err(19054): at org.kxml2.io.KXmlParser.next(KXmlParser.java:369) 05-12 19:51:54.905: W/System.err(19054): at org.kxml2.io.KXmlParser.next(KXmlParser.java:310) 05-12 19:51:54.910: W/System.err(19054): at android.graphics.drawable.Drawable.createFromXml(Drawable.java:869)
看上面这个问题说的是xml格式有问题,难道不能使用编译后的xml?使用一个未编译的xml测试上面代码
Drawable background = null; try { XmlPullParserFactory factory = XmlPullParserFactory.newInstance(); XmlPullParser parser = factory.newPullParser(); parser.setInput(new InputStreamReader(getAssets().open("bg.xml"))); background = Drawable.createFromXml(getResources(), parser); } catch (Exception e) { e.printStackTrace(); }
发现如下错误:
05-12 20:02:44.690: W/System.err(19982): java.lang.ClassCastException: android.util.XmlPullAttributes cannot be cast to android.content.res.XmlBlock$Parser 05-12 20:02:44.695: W/System.err(19982): at android.content.res.Resources.obtainAttributes(Resources.java:1573) 05-12 20:02:44.695: W/System.err(19982): at android.graphics.drawable.Drawable.inflate(Drawable.java:969) 05-12 20:02:44.695: W/System.err(19982): at android.graphics.drawable.ColorDrawable.inflate(ColorDrawable.java:161) 05-12 20:02:44.695: W/System.err(19982): at android.graphics.drawable.Drawable.createFromXmlInner(Drawable.java:937) 05-12 20:02:44.695: W/System.err(19982): at android.graphics.drawable.Drawable.createFromXml(Drawable.java:877)
XmlBlock类是未公开的类,查看其源码,
final class More XmlBlock { ... public More XmlBlock(byte[] data) { mAssets = null; mNative = nativeCreate(data, 0, data.length); mStrings = new StringBlock(nativeGetStringBlock(mNative), false); } ... public XmlResourceParser More newParser() { synchronized (this) { if (mNative != 0) { return new Parser(nativeCreateParseState(mNative), this); } return null; } } ... }
以上两个关键方法,修改我们的工具方法
/** * 获取bin文件中的xml文件 * @param ctx 上下文 * @param binFileName bin文件名 * @param position 文件偏移量 * @return */ public static XmlPullParser createXmlPullParserToAssets(Context ctx, String binFileName, int position) { byte[] buffer = null; // 文件内容 int len = -1; // 文件长度 try { InputStream in = ctx.getResources().getAssets().open(binFileName, AssetManager.ACCESS_RANDOM); try { in.skip(position); // 文件长度 len = (in.read() & 0xFF) << 24; len |= (in.read() & 0xFF) << 16; len |= (in.read() & 0xFF) << 8; len |= (in.read() & 0xFF); // 读取文件内容 buffer = new byte[len]; in.read(buffer); } finally { in.close(); } if (null == buffer || buffer.length <= 0 || len <= 0) { return null; } try { Class clazz = Class.forName("android.content.res.XmlBlock"); Constructor constructor = clazz.getDeclaredConstructor(byte[].class); constructor.setAccessible(true); Object xmlBlock = constructor.newInstance(buffer); Method method = clazz.getDeclaredMethod("newParser"); method.setAccessible(true); return (XmlResourceParser)method.invoke(xmlBlock); } catch (Exception e) { e.printStackTrace(); } } catch (Exception e) { e.printStackTrace(); } return null; }
这样就能将xml资源也打包到bin文件中了
能否将布局也打包到bin文件中呢?
LayoutInflater也支持从XmlPullParser
View android.view.LayoutInflater.inflate(XmlPullParser parser, ViewGroup root)
问题1:对于layout中使用的id
解决:可以在打包jar的时候包含R文件
问题2:对于layout中引用到的资源
尝试了使用public.xml定义所有引用资源的id值,比如默认的id值为0x7f020000,发觉可以修改0x7fX20000,X为可修改的位,修改后立马可以在R文件中看到效果。但是使用aapt编译layout文件的时候会出现找不到资源的情况,提示的资源id还是为原来的0x7f020000。
是否可修改加载布局的方法?