一、发现问题
最近一个项目临到收尾,就差最后一步打包了~~
但却遇到了一些小问题。这个项目里面用到Notification,用过notification的人都知道,notification必须设置小图标setSmallIcon(int icon),参数icon就是资源文件的Id。于是很简单,我把需要的资源文件一起打包成jar包就可以了;可是老大说,我们是产出SDK给第三方用的,而第三方可以把notification资源文件换成第三方自己的应用图标,意思就是我不能把资源文件一起打包;还是很简单,我只打包jar文件,不包含资源文件,到时候第三方在自己项目中添加对应名称的资源文件就OK啦 ~~
可是,测试运行jar包,发现找不能资源文件。为什么??
二、分析问题
原来,问题出现在下面这句简单的代码:
builder.setSmallIcon(R.id.small_icon);这个代码我们常写,会有什么问题呢? 问题就出现在R这个类。熟悉Eclipse项目结构的人都知道,会在 " /gen/包名/" 下面自动生成一个R.java类,例如:/gen/com.mobisummer.ads.banner/R.java 。而这个R类有点像数据库的索引表,把资源文件和ID对应起来,开发者可以通过id直接找到资源文件。 所以,上面的代码是省略的,实际可以看成下面这样:
builder.setSmallIcon(com.mobisummer.ads.banner.R.id.small_icon);
所以, 当在第三方项目中引用jar包,把资源文件直接放在第三方项目中,那么上面的代码当然找不到对应的资源了,因为jar包的包名和第三方项目的包名不一样,即是索引ID的包名和资源实际的包名不一致。
那么,就没有办法了吗? 有,当然有。。。
三、方法一
这种方法的原理也很简单:上面说到出错原因是ID和资源的包名不一致,所以我们就婉转一点,不引用绝对的ID地址,而把ID改成相对的,即 “getPackageName().R.id.small_icon” 的形式,这样,不管第三方的包名是什么,都能索引到正确的包下面的资源了。
当然,上面的形式是不符合代码格式的,所以android提供了专门的接口:
builder.setSmallIcon(getResources().getIdentifier("small_icon", "drawable", getPackageName()));
其中,第一个参数是资源的名字,第二个参数是资源的类型,例如drawable/ layout/ string等,第三个参数是目标项目的包名。这个实际就是,利用反射根据资源名字获取资源ID。
当然,为了项目的模块化和结构优化,我们可以搞一个专门的类实现该功能,代码很简单,应该都能读懂,不多做分析,至于参数,代码里面有说明:
package com.mobisummer.ads.banner.util;
import android.content.Context;
import android.util.Log;
public class MResource {
/**
* 根据资源的名字获取其ID值
*
* @param context
* 目标应用程序,一般通过getApplication()取得
* @param className
* 资源类型,如layout/id/drawable等
* @param name
* 资源的名称
* @return
*/
public static int getIdByName(Context context, String className, String name) {
String packageName = context.getPackageName();
Class r = null;
int id = 0;
try {
r = Class.forName(packageName + ".R");
Class[] classes = r.getClasses();
Class desireClass = null;
for (int i = 0; i < classes.length; ++i) {
if (classes[i].getName().split("\\$")[1].equals(className)) {
desireClass = classes[i];
break;
}
}
if (desireClass != null)
id = desireClass.getField(name).getInt(desireClass);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (SecurityException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
return id;
}
}
调用代码如下:
builder.setSmallIcon(MResource.getIdByName(
mContext.getApplicationContext(), "drawable", "small_icon"));
很简单吧~~~
四、方法二
这个方法的原理也很简单: 当自己的项目是为自己所用,或者代码可以公开给第三方时,将项目设置为引用库,在其他项目中添加这个库的引用即可。
具体的操作就不自己写了, 直接从网上摘抄过来的,摘抄链接:
http://blog.csdn.net/jia635/article/details/44004079
根据Android的官方文档,将其中一个项目设置为引用库,在另一个项目中添加这个库的引用。
简单的做法是
在被引用项目TestJar中的project.properties中添加一行
android.library=true
在引用的项目TestMain的project.properties中添加
android.library=false
android.library.reference.1=../TestJar
其中1表示引用包的序号,“../TestJar”表示引用项目的路径
在Eclipse中操作具体做法如下:
-
把普通的android project设置成库项目
库项目也是一个标准的Android项目,因此你先创建一个普通的Android项目,这个项目可以起任何的名称,任何的包名,设置其他需要设置的字段等。
接着把项目设置成库项目,步骤如下:
1)在Package Explorer中,鼠标右键项目文件夹(TestJar),点击Properties。
2)在Properties窗口选择”Android“,Library属性显示在右下边。
3)把”is Library“单选框选上,在点击Apply。
4)点击OK关闭Properties。
这时,这个项目就变成库项目了,当然纯的java项目也可以变成库项目,非常简单,执行上面四步就OK了。其他程序项目就可以引用这个库项目了。
引用库项目
如果你开发的应用程序想要调用库项目中的代码和资源,也easy哦,引用步骤如下:
1)在Package Explorer中,鼠标右键项目文件夹(TestMain),点击Properties。
2)在Properties窗口选择”Android“,Library属性显示在右下边。
3)点击Add,打开了Project Selection对话框。
4)从可用的库项目列表中选择要添加的库项目,点击OK。
5)对话框关闭之后点击Apply,(在Properties窗口)。
6)点击OK,关闭Properties窗口。
完成以上六步,Eclipse会重建项目,把库项目中的内同包含进去。
- 如果你想增加多个库项目的引用,使用up和down可以设置他们的相对的优先级和合并顺序。工具在合并引用的库的时候顺序是从低优先级(列表的下面)到高优先级(列表的上面)。 如果不只一个库定义了相同的资源ID,这个工具选择资源时会选择高优先级的资源。应用程序自身拥有最高的优先级。