Android Studio 打包Jar

最近公司一个新需求,需要将项目中某一个模块打包成SDK提供给第三方公司使用。

打包SDK?我只会用啊!!没办法,硬着头皮也得上!

选择打包方式

说干就干,撸起袖子就Google。。。

通过Google可以确定常见的几种打包SDK的方式

直接以Library Module的方式引入

  • 优点

    简单方便,直接把模块抠出来放进一个Library中扔给第三方公司用就行了

  • 缺点

    只能适合于Android Studio开发的项目,假如第三方公司用的是Eclipse,那不就懵逼了.

    不安全,相当于把源码完全暴露给第三方。泄露资料事小,代码写的差被嘲笑事大啊!!

以aar包的方式引入

  • 优点

    生成简单,Android Studio的项目在编译完成后,Library Module 的build目录中会自动生成 aar包,不用做额外操作

  • 缺点

    还是比较适合Android Studio开发的项目,虽然Eclipse 也能引入aar包,但是比较复杂,我可不想写一堆接入文档,还不一定说的清。

以jar包的方式引入

  • 优点

    接入方便,是个Android开发应该都会引入jar包吧。。

  • 缺点

    打包比较麻烦,而且Jar包中关于一些资源文件的引用比较麻烦



经过一番比较,最终我选择以Jar包的形式提供一个对外的SDK。毕竟是给别人使用的东西,用户的使用体验最重要,通过jar包方式可以以最简单的方式集成SDK,减少接入SDK的成本以及时间。

打包Jar

相关知识

Android Studio 生成Jar包,还是需要借助 Library Module来操作。创建一个Library Module ,然后在主项目中依赖这个Module,当项目经过编译后,我们会发现在Library Module 的 build/intermediates/bundles/default/目录下会有一个class.jar文件

我们生成Jar文件就是需要将这个class.jar文件进行打包处理

操作

  • 将项目中需要生成SDK的模块分离出来,单独放入一个Module中,并将这个Module切换为Library,使主项目依赖这个Library。
  • 在Library Module 的build.gradle 文件的最后添加如下代码
1
2
3
4
5
6
7
8
9
10
11
12
//打jar包
def SDK_BASENAME = "*****"; //打包后的名称
def sdkJarPath = "build"; //打包输出目录
def zipFile = file('build/intermediates/bundles/default/classes.jar') //将编译生成的classes.jar文件打包

task makeJar(type: Jar) {
from zipTree(zipFile)
from fileTree(dir: 'src/main', includes: ['assets/**']) //需要保留的资源文件
baseName = SDK_BASENAME
destinationDir = file(sdkJarPath)
}
makeJar.dependsOn(build)
  • rebuild 项目,确保build/intermediates/bundles/default/目录下存在class.jar文件。
  • 打开Terminal命令行,或者直接用系统命令行进入项目目录,输入gradlew makeJar 回车,开始进行打包。第一次进行打包,可能会需要下载一些文件,需要一些时间,请耐心等待。
  • 当出现BUILD SUCCESSFUL时代表打包完成,打开输出目录就会有打包生成的jar包。

一些问题及注意事项

  1. 如果Jar包中包含Activity,在项目中引入这个Jar后,还需要在项目的AndroidManifest.xml中声明这个Activity,以及添加一些必要的权限声明。

  2. 如果module中已经引用了第三方Jar包,例如Gson.jar,直接打包后,也会将第三方Jar打包到你的JAR文件中。如果不想让第三方jar被打包进去,可以在module的build.gradle中引用第三方jar包时 使用 provided 替换 compile。例如 compile files('libs/tbs_sdk_thirdapp.jar') 替换成 provided files('libs/tbs_sdk_thirdapp.jar') 。这样在打包Jar包时就不会包含第三方的内容,在使用的时候只要将第三方jar包与你的Jar包同时引入就可。

  3. 打包时可以选择保留项目中的资源文件,但仅仅只有assets下的资源文件在jar包中可以正常使用。其他的资源文件,如drawable、layout等,都不可以用常见的方法引用。我们在正常使用某一个资源文件时,是通过R.**.**方法引用,而打包Jar时并不会保留资源文件id对应的映射R文件,所以这些资源文件就不能正常调用了。

  4. 如果Jar包中需要使用一些资源文件,可以将需要的资源文件,例如图片、布局等单独拿出来,项目中引入Jar时,同时添加这些必需的资源文件(类似友盟、腾讯等第三方jar的引入),这样Jar包中可以通过反射读取项目中资源文件。反射读取资源文件的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
/**
* ****************************************************************
* Author: LCM
* Date: 2017/6/2 上午11:09
* Desc: 通过反射获取资源文件
* *****************************************************************
*/

public class MResource {
/**
*
* @param context 上下文
* @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;
}

/**
* 从Assets中读取图片
*/
public static Bitmap getImageFromAssetsFile(Context context, String fileName) {
Bitmap image = null;
AssetManager am = context.getResources().getAssets();
try {
InputStream is = am.open(fileName);
image = BitmapFactory.decodeStream(is);
is.close();
} catch (IOException e) {
e.printStackTrace();
}
return image;

}
}
1
2
3
4
//代码中调用
setContentView(MResource.getIdByName(getApplicationContext(), "layout", "layout_demo"));

tvTitle = (TextView) findViewById(MResource.getIdByName(getApplicationContext(), "id", "demo_tv_title"));

额外发现

生成一个Jar,还需要另外提供一些资源文件给别人,对我这种有代码洁癖的人来说是不能忍的。我的理想状态是,就一个jar包,你想用的都在里面,干净简洁。

本着不作死就不会死的精神,竟然真的被我发现了一种从assets中加载布局文件的方式!!!!!!

我们知道生成布局文件一般有两种方式

  • findViewById
  • 直接new一个布局文件,代码中添加属性

其实还有一种容易被我们忽略的方法 LayoutInflater,我们常在ListView或者RecyclerView 的Adapter中通过Layoutinflater加载Item项的布局,那么能不能通过它来加载一个Activity的布局呢?

我们通常使用的是这两个
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot)
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root)
方法来加载布局

但其实LayoutInflater还有两个重载的方法
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot)
public View inflate(XmlPullParser parser, @Nullable ViewGroup root)

看见XmlPullParser是不是感觉到希望了呢?

没错!XmlPullParser可以用来解析Xml文件,这样我们就不用通过R文件来映射资源文件,而是直接通过解析Xml文件来加载布局。

解析资源布局文件需要用XmlResourceParser

1
2
3
4
5
6
7
8
9
10
11
public static XmlResourceParser getIsFromAssetsFile(Context context, String fileName) {
AssetManager am = context.getResources().getAssets();

XmlResourceParser xmlResourceParser = null;
try {
xmlResourceParser = am.openXmlResourceParser("assets/" + fileName);
} catch (IOException e) {
e.printStackTrace();
}
return xmlResourceParser;
}
1
2
LinearLayout layout = (LinearLayout) LayoutInflater.from(this).inflate(MResource.getIsFromAssetsFile(this, "layout_demo.xml"), null,false);
setContentView(layout);

把布局文件扔进assets目录,来来来!接下来就是见证奇迹的时刻!


f**k ………

淡定淡定。。发现问题还是要解决问题嘛。。。

经过一番艰苦卓越的google ,发现这里XmlResourceParser解析的布局必需是编译过后的布局文件。

那么怎么获取编译后的布局文件呢,很简单也很无脑,把你需要的布局文件放到任意一个正常项目的layout目录下,编译项目,在build/output/apk文件夹中会有一个 apk包,解压就可在layout目录下获得编译后的布局文件

重新将编译后的布局文件扔到assets目录中,运行


布局文件能找到,那么布局中的控件呢,肯定不能findViewById了,别担心,android中还有另一个findview的方法 findViewByTag,你可以在布局中给每一个控件添加一个android:tag=""属性,然后你就可以通过findViewByTag方法来找到指定的控件了。

终于我成功的将SDK封装的只剩一个Jar包!!

然鹅!!我最终还是放弃了这种方式!!!因为这种方式打包的jar包在Android Studio中可以正常使用,但是在Eclipse中不能使用!!!解析不了这个布局!!!!网上关于这方面的资料又比较少,我只能猜测这是因为Eclipse 与Android Studio 的加载机制不同导致的。。

坚持原创技术分享,您的支持将鼓励我继续创作!