为了实现这个功能,我尝试了使用jar包的方式来共享资源,过程中遇到了一些问题,现在把这些问题归纳成四点,记录在这里,希望能帮到跟我有同样需求的人。这四点分别是:
一. 以lib工程方式静态共享资源;
二. Android不支持jar包中的资源的访问;
三. 第三方发布的开发包带有资源时的处理方式;
四. 为什么Android系统资源包Android.jar中的资源可以被访问。
本文的Demo代码位于http://download.csdn.net/detail/romantic_energy/8598171, 有需要的朋友自己去下载。
一. 以lib工程方式静态共享资源
把应用中的所有资源都放到了一个Android lib工程中(project->property->Android选项中把 Is Labrary勾选中),假设这个工程名为ResLib,它包含2个图片drawable:ok_n.png、ok_d.png , 一个xml drawable:selector_ok.xml ,内容为:
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/ok_p" android:state_pressed="true" />
<item android:drawable="@drawable/ok_n" />
</selector>
再建一个apk工程,名为app1,它包含一个图片drawable ic_launcher.png,一个layout activity_main.xml, 在app1工程的属性中选择Android,点击Library选项框的add...按钮, 选中ResLib作为app1的依赖工程。在activity_main.xml中增加一个按钮:
<Button
android:id="@+id/button1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignLeft="@+id/textView1"
android:layout_below="@+id/textView1"
android:layout_marginLeft="41dp"
android:layout_marginTop="75dp"
android:text="Button"
android:background="@drawable/selector_ok"/>
按钮中用到了ResLib中的drawable。
编译ResLib,然后再编译app1, 发现没问题, app1正确引用到了ResLib中的drawableselector_ok。
分别观察ResLib、app1中自动生成的R.java文件,发现总共有3个R.java文件,分别是:
1. 位于ResLib中的com.example.reslib.R.java;
2. 位于app1中的com.example.reslib.R.java;
3. 位于app1中的com.example.app1.R.java;
查看R.java 中的drawable类,发现
1. 位于ResLib中的com.example.reslib.R.java中的是:
public static final class drawable {
public static int ok_n=0x7f020000;
public static int ok_p=0x7f020001;
public static int selector_ok=0x7f020002;
}
2. 位于app1中的com.example.reslib.R.java是:
public static final class drawable {
public static final int ok_n = 0x7f020001;
public static final int ok_p = 0x7f020002;
public static final int selector_ok = 0x7f020003;
}
3. 位于app1中的com.example.app1.R.java是:
public static final class drawable {
public static final int ic_launcher=0x7f020000;
public static final int ok_n=0x7f020001;
public static final int ok_p=0x7f020002;
public static final int selector_ok=0x7f020003;
}
这里有2个需要注意的地方:
A. 变量的类型: 位于ResLib中的R.java中的drawable类型是public static int, 没有final,说明它不是常量, 可以在运行时变化,从设计上说,不能用这个值来索引资源,因为它可能在运行时被修改; 位于app1中dR.java中的drawable类型是public static final int,是常量,可以用来索引资源。
B. 变量的值:以标注为红色的selector_ok为例,位于ResLib中的R.java中的selector_ok值为0x7f020002,位于app1中dR.java中的selector_ok值为0x7f020003; 如果在app1中直接使用位于ResLib中的R.java中的selector_ok来获取资源,其实获取的是ok_p, 因为ok_p的值是0x7f020002;
再来把app1工程生成的apk包解压缩,看看里面都有些什么drawable,发现里面包含了3个图片drawable:ic_launcher.png、ok_n.png、ok_d.png , 一个xml drawable:selector_ok.xml, 也就是说app1的apk包把所有在ResLib中存在的drawable资源都拷到了apk包中。
最后我们通过aapt命令来查看一下app1.apk中的资源索引表,从命令行进入到app1.apk所在的目录,输入以下命令:aapt d resources app1.apk > out.txt, 查看out.txt, 可以看到以下内容:
Package Groups (1)
Package Group 0 id=127 packageCount=1 name=com.example.app1
Package 0 id=127 name=com.example.app1 typeCount=8
type 0 configCount=0 entryCount=0
type 1 configCount=1 entryCount=4
spec resource 0x7f020000 com.example.app1:drawable/ic_launcher: flags=0x00000000
spec resource 0x7f020001 com.example.app1:drawable/ok_n: flags=0x00000000
spec resource 0x7f020002 com.example.app1:drawable/ok_p: flags=0x00000000
spec resource 0x7f020003 com.example.app1:drawable/selector_ok: flags=0x00000000
我们看到app1中的资源索引表中是含有ok_n、ok_p、selector_ok资源的索引的,并且索引值跟app1中的R.java中的值一样。
由上可知:app1不会直接引用ResLib中的资源,不会使用位于ResLib中的R.java文件,而是把属于ResLib中的资源拷贝到了自己的apk中,把这些资源当作自己的资源,再重新生成资源ID和R.java文件,然后像访问自己的资源一样去访问原本属于ResLib中的资源。
二. Android不支持jar包中的资源的访问;
区别于第一点,这里的jar包是指不带源码工程的jar包, lib工程默认生成的jar包中是只包含.class文件,不带资源文件的, 如果你想你的jar包中包含资源文件,需要使用Eclipse的导出(export)功能,将资源文件一起导出到jar包中,但是请注意:以这种方式导出的资源文件不仅引用jar包的apk工程无法使用,连jar包中的代码也无法使用。
下面来看看,为什么Android中jar包中的资源文件无法访问。
先来做个实验,看看使用使用Eclipse的导出(export)功能导出的ResLib中的资源id,与直接工程依赖的ResLib中的id,是否一样。
我们在之前的ResLib工程中增加一个ResLibTest.java文件, 包名com\example\reslib,内容为:
package com.example.reslib;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.util.Log;
public class ResLibTest {
public ResLibTest() {
Log.i("ResLibTest", "id: ok_n = 0x" + Integer.toHexString(R.drawable.ok_n));
Log.i("ResLibTest", "id: ok_p = 0x" + Integer.toHexString(R.drawable.ok_p));
Log.i("ResLibTest", "id: selector_ok = 0x" + Integer.toHexString(R.drawable.selector_ok));
}
public Drawable get_ok_n(Context context) {
return context.getResources().getDrawable(R.drawable.ok_n);
}
}
使用Eclipse中的导出功能,把ResLib的代码和资源导出到名为ResLib.jar的jar包中,导出方式是: 右击ResLib工程->Export->Java->JAR file 点击Next,将ResLib的以下内容导出:
注意不要把manifest.xml导出,否则引用时会报错:有2个manifest.xml。
任然在app1中已代码工程方式引用ResLib,并在app1的MainActivity类中的onCreate函数中增加以下代码:
ResLibTest test = new ResLibTest();
TextView view1 = (TextView)findViewById(R.id.textView1);
view1.setBackground(test.get_ok_n(this));
以此查看资源id和test.get_ok_n()函数返回的drawable。
再建一个app2的apk工程,只包含ic_launcher.png一个drawable,把之前导出的ResLib.jar包拷贝到工程的libs目录下,同样在app2的MainActivity类中的onCreate函数中增加以下代码:
ResLibTest test = new ResLibTest();
TextView view1 = (TextView)findViewById(R.id.textView1);
view1.setBackground(test.get_ok_n(this));
以此查看资源id和test.get_ok_n()函数返回的drawable。
运行app1,得到以下打印信息:
id: ok_n = 0x7f020001
id: ok_p = 0x7f020002
id: selector_ok = 0x7f020003
并且textView1显示的背景是ok_n.png。
运行app2,得到以下打印信息:
id: ok_n = 0x7f020000
id: ok_p = 0x7f020001
id: selector_ok = 0x7f020002
并且textView1显示的背景是ic_launcher.png。
由上可知,使用导出包ResLib.jar时,得到的资源id与直接工程依赖时得到的id并不同,并且使用导出包ResLib.jar时,访问到了错误的资源。
Android中一般资源的访问方式是: context.getResources().getDrawable(R.drawable.ok_n);
说明资源是和Context、ResourceManager类关联在一起的,而导出的jar包无法构造出类似的关联类,Android本身也没有提供类似的访问机制,所以我们无法以正常的方式来访问jar包中的资源。
stackoverflow上有2个问题是关于这个的, 分别是:
另外,Android文档中也有相关的介绍:http://tools.android.com/recent/buildchangesinrevision14
但其实jar包中的资源文件还是可以被访问到的,只不过是被当作一般的文件io流,需要自己去解析,这并不是一个完整的方案,会引出许多其它的问题,所以实际意义不大。这个只给出图片文件的访问方式,在ResLibTest类中加入以下函数:
public Drawable get_ok_n_2(Context context) {
Bitmap bitmap = null;
BitmapDrawable drawable;
InputStream iStream = getClass().getClassLoader()
.getResourceAsStream("res/drawable/ok_n.png");
Log.i("ResLibTest", "iStream = " + iStream);
if(iStream != null) {
bitmap = BitmapFactory.decodeStream(iStream);
drawable = new BitmapDrawable(context.getResources(),bitmap);
return drawable;
}
return null;
}
导出ResLib后,使用get_ok_n_2能得到正确的drawable。这里有3点值得注意:
A. getClass().getClassLoader().getResourceAsStream 和 getClass().getResourceAsStream都能获取到io流,ClassLoader版本的getResourceAsStream不能访问"/res/drawable/ok_n.png", 注意前面的斜杠,Class版本的getResourceAsStream可以。并且Class版本的函数调用的也是ClassLoader中的函数。
B. 在app2中可以以以下方式访问图片:
ResLibTest test = new ResLibTest();
InputStream iStream = test.getClass().getResourceAsStream("res/drawable/ok_n.png");
C. iStream的打印结果是:
iStream = libcore.net.url.JarURLConnectionImpl$JarURLConnectionInputStream@41ade240
当以getResourceAsStream方式来获取xml文件时,xml需要全部自己解析之后再根据解析结果去加载相应的资源文件,实际应用中不具备实际意义,这里就不深究了。
getResourceAsStream方式加载的文件属于java类加载器提供的功能,Andriod并没有为获取jar包中的资源提供任何便利的方法,所以得出的结论是:Android不支持jar包中的资源的访问。
三. 第三方发布的开发包带有资源时的处理方式;
一般是类文件导出成jar包,和资源文件分开,一起提供给客户。客户端的apk把资源打包到自己的apk中,此时jar包中的类不能再以资源id来访问资源,而是使用由apk层传过来的Context对象加上资源路径来访问。如下:
public int getDrawableID(Context context, String strPath) {
return context.getResources().getIdentifier(strPath,
"drawable", context.getPackageName());
}
网上关于这种方式的论述有很多,这里不再赘述。
四. 为什么Android系统资源包Android.jar中的资源可以被访问;
这个是最困扰我的一个问题。这要从android资源编译打包、系统资源引用方式方面说起。以下2篇博文对这个问题有些论述: