序言
这是一篇半技术类文章。众所周知现在Google主推Android Studio开发工具,而Eclipse已经被闲置一阵子了,但是Eclipse项目却还有很多没有迁移到AS中;而现在一些新的库都是采用AS Gradle打包并发布到公共仓库中,而这些库Eclipse 并不能直接使用,在本篇文章中将讲解如何导入一个或者多个库到Eclipse中使用。
目的
Eclipse基本算是荒废的状态了,这并不是说Eclipse就不好,毕竟都是Eclipse过来的;只能说一个个是新欢一个旧情人了。旧情人肯定是有她特殊的韵味的,所以现在并不是所有的都迁移到AS了,还有很大一部分处于Eclipse上的项目。
这里就有问题了,许多的开发者在AS上的库一般来说都不是打包为Jar包,而是直接生成AAR库,随后发布到公共仓库中,AS的使用者呢就使用一句简单的代码就能使用这个库了,其下载-加载-编译-运行都用Gradle搞定了,而Eclipse就不乐意了~~
Eclipse 就面临着尴尬的局面想要用新的库,但是却没法用~~
在本篇文章中,将会讲解如何在Eclipse中使用一个公共仓库中的库。
Gradle 发布流程
仓库简介
在AS中发布库都是采用Gradle发布到对应的公共仓库中进行使用,仓库有很多,你也可以自己使用服务器搭建仓库;而公共仓库中有两个特别OK的:
在最初google框架中默认使用的仓库是:mavencentral ,但是因为mavencentral 仓库的发布流程较为严格并且操作上不够人性化所以后来改为了:jcenter。
所以推荐使用jcenter,当然如果你在mavencentral中发布了你的库,那么这个库在发布成功后的24小时内会被同步到jcenter中,而jcenter中发布的库也可以通过一键式操作发布到mavencentral仓库中。
因为有上面的仓库区别,所以如果你使用的是AS将会在配置中看见诸如这样的配置:
repositories {
jcenter()
}
repositories {
jcenter {
url "http://jcenter.bintray.com/"
}
}
repositories {
mavenLocal()
}
这就是在告诉你的项目中使用的库的来源,AS将会更加库的标识去寻找需要的文件,当然两个仓库可以同时写入到项目配置中。
发布到仓库
在我的文章中有一篇关于发布到mavencentral仓库的文章:[Publish AAR To Maven] 使用 Gradle 发布 AAR 到 Maven 仓库
上面所介绍的是发布到mavencentral仓库的流程,而jcenter的流程也基本类似,只不过把文件加密的过程放到了服务器上去完成。
整体来说:
- 首先我们需要有一份库的代码
- 编译源码得到Jar, R, JNI, xml等源码文件。
- 打包所有文件为一个aar的文件
- 签名
- 发布到仓库
- 校验并发布
如何使用
而使用这个库,则也很简单下载对应版本的AAR文件,加入到项目中,并编译进项目。
从这里就可以看出一点,实际的android使用中就是使用的aar文件,只不过此时的aar文件会被解包编译。
在AS的项目配置中我们加上这样一句:
dependencies {
compile ‘net.qiujuer.genius:ui:1.3.0‘
}
在编译后,我们查看一下项目的Build文件夹:
在这里我们可以看见实际的使用中的确如我们所想的一般。
当然这里之所以会出现“res“包,是因为“genius-ui“包中默认引用了“genius-res“包,所以当我们使用“UI“包的时候就会同步下载“res“包。详细可以见开源项目:Genius-Android
OK, 一个库的简单流程我们说好了;那么来看看如何在Eclipse中使用。
问题
从上面的库的流程中可以看出一个AAR库中可以包括:jar, aidl, assets, res, R 文件等数据;而Eclipse中默认引用库的时候只能引用jar文件。
如果我们只是把jar文件放到eclipse中的话将会无法正常使用对应的资源文件。那么是不是只要同时把资源文件放到eclipse中也就解决了这个问题?并不是的!
众所周知,一个资源文件在编译的时候会在R文件中生成一个int 值与之相对应,而实际代码中全部使用这个int值代替;那么上面添加的资源文件全部会生成一份新的值与之对应,那么在库的jar中依然无法找到与之对应的资源,最终将会导致APP崩溃。
此时有一个办法可以解决,那就是固化资源对应的INT值,让改资源在编译的时候每次都生成我们指定的INT值。在这里我们使用一个android中特有的文件:“public.xml“
public.xml: 用于固化资源所对应的INT值。
实施
下载
首先我们需要准备一份对应的aar文件,在这里我使用 genius-android 库的 UI 包来完成该操作。
下载点有:
我们进入mavencentral,并搜索 genius ,找到 ui 包:
因为UI包中引用了res包,所以我们同时下载ui和res包。
解包
更改后缀为zip,并解压:
- AndroidManifest.xml :含有权限-服务声明-广播注册-activity声明等信息,直接将其中的数据拷贝到自己的项目中就OK。
- aidl:这个中一般都是独立服务的东西,在项目中按照对应的地方建立好文件就好。
- assets:这个中一般是字体,html等数据
- classes.jar 这个就是我们需要直接使用的jar包了
- R.txt 这个就是资源对应的int值了,我们需要做的主要工作就是把其转换为 public.xml 文件
- Res 文件夹中的内容可以原封不动的拷贝到项目的res文件夹中。
创建项目
为了避免该项目与自己的项目相冲突,我们首先需要建立项目:
在这里起名为:GeniusUI,并指定包名为:”net.qiujuer.genius.ui”
为什么呢?因为上面解压后的AndroidManifest.xml中有指定包名:
如果这里未指定包名,那么就算后面我们写好了 public.xml文件,其虽然能固化INT值,但是却没法对应到我们的包名上,换句话说我们的 jar 文件中依然无法找到对应的资源文件。
进入项目属性(Properties)-Android-勾选上(Is Library) 使其成为库项目。
请记住在这里我们并不为RES包单独创建项目;因为UI库中引用了RES包中的资源,那么在UI库生成的时候将会重新计算一份新的Res库的资源INT值。
在这里之所以要下载Res包,仅仅是只需要其中的资源文件而已。
同时打开 Res包中的 R文件(左边)与UI包中的R文件(右边):
可以看出UI包中同样含有颜色对应的INT值,但是其值与原Res包中并不相同。
拷贝文件
我们按照图片所示把现有的文件全部拷贝进项目中:
此时编译一次项目,一般来说只要文件全部拷贝正确了,那么是不应该报错了,并且此时应该生成了一份R文件,只不过此时的R文件与原来库中的值并不完全相同(有可能有部分相同,这也就导致有部分资源可以正常使用)。
对比
此时我们对比项目生成的R文件与库中的R文件:
可以发现不尽相同,当然你的也或许完全相同,比如:
这意味着什么?意味着这个项目中的资源可以完全使用,但是什么情况下会不同?
我们在 Res 项目中的 values.xml 文件中加上一个颜色:
<color name="a">#ffecb3</color>
这句话将会带来什么?带来后续的ID全部对应失败的问题:
这也就是我说的资源无法正常对应的问题。
这个相同和不相同都是随机的,也许原来的库刚刚巧合了那么也就相同了。
Public.xml 固化
此时我们在 项目的 values 文件夹中创建 public.xml 文件,并加上:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<public id="0x7f020000" name="amber_100" type="color" />
<public id="0x7f020001" name="amber_200" type="color" />
<public id="0x7f020002" name="amber_300" type="color" />
<public id="0x7f020003" name="amber_400" type="color" />
</resources>
编译项目,此时我们再次查看 R 文件:
在这里可以看出我们写了的部分已经OK了,从第5个开始又出现问题了,所以啊,我们需要把全部的R对应的INT值都固化到Public中。
好了,现在知道Public文件的魅力了,但是想要固化一整个文件并不是那么的如意;我一打开UI库的R文件的时候是崩溃的。
我擦,500多汗~~
坑爹呢不是!!
而且大概看了一下有颜色-数字-style-TM 还有 styleable 这是什么> 鬼?如果你写过自定义控件那么应该知道这个是:自定义属性。
但是,但是这个怎么固化?500多行,等弄好菜都凉了。
代码化
我们是程序员,我们都很懒,所以我们用程序来解决。
在这里我们写一个从 R.txt 转换到 public.xml 的小程序就好。
格式
首先来看看R.txt 文件中转换到 XML 时对应的格式。
- 颜色、数字、数组、ID、String、drawable可以直接转换
int color black 0x7f04000e
int dimen font_10 0x7f050000
int array loading 0x7f030000
int id all 0x7f080009
int string g_font_file 0x7f060000
int drawable background 0x7f020000
<public type="color" name="black" id="0x7f04000e" />
<public type="dimen" name="font_10" id="0x7f050000" />
<public type="array" name="loading" id="0x7f030000" />
<public type="id" name="all" id="0x7f080009" />
<public type="string" name="g_font_file" id="0x7f060000" />
<public type="drawable" name="background" id="0x7f020000" />
- style 需要替换“_“为“.“
int style Genius_Widget_BalloonMarker 0x7f070000
<public type="style" name="Genius.Widget.BalloonMarker" id="0x7f070000" />
- attr属性需要抛弃styleable部分
int attr gAllowTrackClickToDrag 0x7f010022
...
int[] styleable AbsSeekBar { 0x7f01000c, 0x7f010019, 0x7f01001a, 0x7f01001b, 0x7f01001c, 0x7f01001d, 0x7f01001e, 0x7f01001f, 0x7f010020, 0x7f010021, 0x7f010022, 0x7f010023, 0x7f010024, 0x7f010025, 0x7f010026, 0x7f010027, 0x7f010028, 0x7f010029, 0x7f01002a, 0x7f01002b, 0x7f01002c }
int styleable AbsSeekBar_gAllowTrackClickToDrag 10
要得到 gAllowTrackClickToDrag
所代表的INT值,两种办法,直接从第一句中转换得到。
第二个办法,读取到 styleable AbsSeekBar
时存储后面的数组,然后读取到 int styleable AbsSeekBar_gAllowTrackClickToDrag 10
时,按照 10 这个下标去取得值 0x7f010022
。
<public type="attr" name="gAllowTrackClickToDrag" id="0x7f010022" />
代码
实体类
static class PublicLine implements Comparable<PublicLine> {
public String type;
public String name;
public String id;
public PublicLine() {
}
public PublicLine(String type, String name, String id) {
this.type = type.trim();
this.name = name.trim();
this.id = id.trim();
}
public String getKey() {
return type + "_" + name;
}
@Override
public String toString() {
return "<public type=\"" + type + "\" name=\"" + name + "\" id=\"" + id + "\" />";
}
@Override
public int compareTo(PublicLine o) {
int i = this.type.compareTo(o.type);
if (i == 0)
return this.name.compareTo(o.name);
else
return i;
}
}
- 实现 compareTo 是用于后面排序,避免输出的内容乱糟糟的。
- “getKey()“ 方法是为了存储到Map中的主键值而准备,避免重复。
- “toString()“这是为了输出日志而准备。
整体流程
public static void main(String[] args) throws IOException {
File in = new File("R.txt");
File out = new File("public.xml");
if (!in.exists()) {
throw new NullPointerException("R.txt is not null.");
}
try {
out.createNewFile();
} catch (IOException e) {
e.printStackTrace();
return;
}
System.out.println(in.getAbsolutePath());
System.out.println(out.getAbsolutePath());
InputStreamReader read = new InputStreamReader(new FileInputStream(in));
BufferedReader bufferedReader = new BufferedReader(read);
OutputStreamWriter writer = new OutputStreamWriter(new FileOutputStream(out));
BufferedWriter bufferedWriter = new BufferedWriter(writer);
Map<String, PublicLine> xml = new HashMap<>();
buildXml(bufferedReader, xml);
List<PublicLine> lines = new ArrayList<>();
lines.addAll(xml.values());
Collections.sort(lines);
saveFile(lines, bufferedWriter);
close(bufferedReader);
close(bufferedWriter);
System.out.println("End.");
}
- 整个流程非常简单,直接使用当前目录生成两个文件,R.txt 为输入源,public.xml 则是输出文件,该文件每次运行都重新生成新文件。
- 然后输出一次两个文件的目录信息到控制台。
- 然后初始化Buffer,在这里经过2层的分装,最终得到的是:BufferedReader 与 BufferedWriter,才有这两个的目的主要是为了一行行的读取,最终也一行行的输出。
- 然后声明一个 Map 变量,用于存储读取并转换为实体的集合。之所以采用Map是为了实现避免重复内容的出现。
- 再后面我们又声明了一个 List 变量,并把Map的值存储到List中,这一步主要是为了排序整个集合;Map本身是无序的,而且如果直接对Map排序就相当复杂,所以这里我们填充到 List 后再进行排序。
- 然后则是输出到Public文件中,然后关闭流的操作。
读取集合
public static void buildXml(BufferedReader reader, Map<String, PublicLine> xml) {
while (true) {
String line;
try {
line = reader.readLine();
if (line == null || line.trim().length() == 0)
return;
} catch (IOException e) {
e.printStackTrace();
continue;
}
if (line.contains("styleable")) {
// skip styleable array
continue;
} else {
// convert other xml
String[] split = line.split(" ");
if (split.length == 0)
continue;
String type = split[1];
String name = split[2];
String id = split[3];
if (type.contains("style"))
name = name.replace("_", ".");
saveToMap(xml, new PublicLine(type, name, id));
}
}
}
- 循环读取每一行,当读取到某行空的情况下则退出循环
- 在这里我们对attr的转换采用过滤的方式,所以凡事具有“styleable“ 标示的行都可以直接抛弃掉。
- 正常情况下,我们读取一行,并按空格划分开;然后分别读取到 type 、name、id 的值。
- 如果当前类型为 style 类型,那么我们的名称需要把 “_” 替换为 “.” 。
- 把当前识别到的信息存储到 Map 中。
如果这里对 attr 的识别采取的是读取 styleable 数组中的信息,然后再按索引找寻对应INT值的话,代码就应该是这样:
public static void buildXml(BufferedReader reader, Map<String, PublicLine> xml) {
while (true) {
String line;
try {
line = reader.readLine();
if (line == null || line.trim().length() == 0)
return;
} catch (IOException e) {
e.printStackTrace();
continue;
}
String[] split = line.split(" ");
if (split.length == 0)
continue;
if (line.contains("int[]")) {
// convert attr xml
String name = split[2].trim();
line = line.substring(line.indexOf("{") + 1, line.lastIndexOf("}"));
System.out.println(line);
String[] ids = line.split(",");
if (ids.length > 0) {
readStyleableXml(reader, xml, ids, name);
}
} else {
// convert other xml
String type = split[1];
String name = split[2];
String id = split[3];
if (type.contains("style"))
name = name.replace("_", ".");
saveToMap(xml, new PublicLine(type, name, id));
}
}
}
@SuppressWarnings("unused")
public static void readStyleableXml(BufferedReader reader, Map<String, PublicLine> xml, String[] ids, String name) {
for (String id : ids) {
String line;
try {
line = reader.readLine();
if (line == null)
continue;
} catch (IOException e) {
e.printStackTrace();
continue;
}
String[] split = line.split(" ");
String lName = split[2].substring(split[2].indexOf(name) + name.length() + 1);
String lId = ids[Integer.parseInt(split[3].trim())];
saveToMap(xml, new PublicLine("attr", lName, lId));
}
}
在这里我们就需要多一个方法,在这个方法中我们按照当前控件的数组长度进行循环读取其后对应数组长度的行,然后再解析并存储。
存储
public static void saveToMap(Map<String, PublicLine> xml, PublicLine line) {
try {
xml.putIfAbsent(line.getKey(), line);
System.out.println(">>>: " + line.toString());
} catch (Exception e) {
e.printStackTrace();
}
}
存储方法相对简单,我们采用 Map 的 “putIfAbsent“ 方法,该方法有一个作用,就是会判断当前 KEY 是否存在,如果不存在则存储。
存储
public static void saveFile(List<PublicLine> lines, BufferedWriter writer) throws IOException {
// write head
writer.append("<?xml version=\"1.0\" encoding=\"utf-8\"?>");
writer.append("\n");
writer.append("<resources>");
writer.append("\n");
for (PublicLine line : lines) {
try {
writer.append(" ");
writer.append(line.toString());
writer.append("\n");
writer.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
// write footer
writer.append("</resources>");
writer.flush();
}
存储过程分为存储头部,存储集合数据,以及存储底部来完成。
关闭流
public static void close(Closeable closeable) {
try {
closeable.close();
} catch (IOException e) {
e.printStackTrace();
}
}
关闭流的方法就更加简单了,无非就是单独出来了而已。
效果
此时我们把R文件放到根目录,运行一次代码,控制台将会输出:
此时已经生成了一份 public 文件,与R文件对应看一下:
可以看出效果是相当不错,把public文件拷贝到库中直接投入使用。
预览
为了测试是否成功,我按照开源库 Genius-Android 中的 sample 项目代码在 Eclipse 中完成了一份界面布局。
所有控件与资源都能正常使用。
最后
在这里我把代码生成了一份 jar 文件,你可以下载该文件,并把 R.txt 文件放到同一目录,运行该 jar 文件既可得到 public 文件。
同时我把所有的源文件与代码都上传到了 GtiHub 的 BeFoot 项目中;以后的所有例子也都会更新到该项目中。
如果有哪里不对,或者说的不够清楚还请多多指教。
========================================================
作者:qiujuer
开源库:github.com/qiujuer/Genius-Android
转载请注明出处:http://blog.csdn.net/qiujuer/article/details/50084345
—— 学之开源,用于开源;初学者的心态,与君共勉!
========================================================