Android提供第三方jar包时资源打包

目前提供第三方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
  * 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文件

如何获得一个已经编译过的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 @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中引用到的资源
  1. 尝试了使用public.xml定义所有引用资源的id值,比如默认的id值为0x7f020000,发觉可以修改0x7fX20000,X为可修改的位,修改后立马可以在R文件中看到效果。但是使用aapt编译layout文件的时候会出现找不到资源的情况,提示的资源id还是为原来的0x7f020000。
  2. 是否可修改加载布局的方法?

你可能感兴趣的:(Android)