目前提供第三方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混淆
使用java代码实现
这种方式如果界面较少的时候挺方便的,但是如果提供的界面较多,需要通过代码构建的界面越复杂,后续维护起来就比较麻烦,有没有更好的方式呢?
/**
* Create a drawable from an XML document. For more information on how to
* create resources in XML, see
* Drawable Resources.
*/
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
<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文件
使用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目录后使用上面的方法,即可成功获取资源。
/**
* 获取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 @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文件中了
LayoutInflater也支持从XmlPullParser
View android.view.LayoutInflater.inflate(XmlPullParser parser, ViewGroup root)
解决:可以在打包jar的时候包含R文件