晨鸣的博客–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
文件
![](http://om0qizim4.bkt.clouddn.com/Library%20Module%20%E4%B8%AD%E7%9A%84class.jar%E6%96%87%E4%BB%B6.png)
我们生成Jar文件就是需要将这个class.jar
文件进行打包处理
操作
- 将项目中需要生成SDK的模块分离出来,单独放入一个Module中,并将这个Module切换为Library,使主项目依赖这个Library。
- 在Library Module 的build.gradle 文件的最后添加如下代码
//打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包。
一些问题及注意事项
如果Jar包中包含Activity,在项目中引入这个Jar后,还需要在项目的
AndroidManifest.xml
中声明这个Activity,以及添加一些必要的权限声明。打包时可以选择保留项目中的资源文件,但仅仅只有assets下的资源文件在jar包中可以正常使用。其他的资源文件,如drawable、layout等,都不可以用常见的方法引用。我们在正常使用某一个资源文件时,是通过
R.**.**
方法引用,而打包Jar时并不会保留资源文件id对应的映射R文件,所以这些资源文件就不能正常调用了。如果Jar包中需要使用一些资源文件,可以将需要的资源文件,例如图片、布局等单独拿出来,项目中引入Jar时,同时添加这些必需的资源文件(类似友盟、腾讯等第三方jar的引入),这样Jar包中可以通过反射读取项目中资源文件。反射读取资源文件的代码如下:
/**
* ****************************************************************
* 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;
}
}
//代码中调用
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
类
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;
}
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目录中,运行
![](http://om0qizim4.bkt.clouddn.com/%E5%8A%A0%E8%BD%BDjar%E5%8C%85%E4%B8%AD%E5%B8%83%E5%B1%80%E6%88%90%E5%8A%9F.jpeg?imageView2/0/w/400)
布局文件能找到,那么布局中的控件呢,肯定不能findViewById
了,别担心,android中还有另一个findview的方法 findViewByTag
,你可以在布局中给每一个控件添加一个android:tag=""
属性,然后你就可以通过findViewByTag
方法来找到指定的控件了。
终于我成功的将SDK封装的只剩一个Jar包!!
然鹅!!我最终还是放弃了这种方式!!!因为这种方式打包的jar包在Android Studio中可以正常使用,但是在Eclipse中不能使用!!!解析不了这个布局!!!!网上关于这方面的资料又比较少,我只能猜测这是因为Eclipse 与Android Studio 的加载机制不同导致的。。