之前介绍了修改sdk后的代码大体架构,其实修改代码倒不是什么麻烦的事,无非实现一种新功能,下面说说真正让人抓狂的事——封装和集成sdk。
sdk打包
首先要打包,如果是纯代码的sdk导出成jar包就行了;像我这样sdk里还包含图片资源要打包aar。编译成功用gradle直接打包成release包:
模块名称为downloadsdk,生成的aar就在download/build/output/aar路径下。
sdk集成
取决与工程的复杂程度,如果只是其中一个模块需要用到sdk就很简单,把android studio左边的工程视图切换成Project Files,在那个模块下新建libs文件夹,把aar复制进去,然后在那个模块的build.gradle中添加仓库路径如下:
repositories {
flatDir {
dirs 'libs'
}
}
repositories 要包在android里面,还要在dependencies增加下面一行:
compile(name:'downloadsdk',ext:'aar')
ext是扩展名的意思,表明需要依赖downloadsdk.aar文件。在代码里直接调用aar里的类和方法,android studio会自动import aar包里对应的类和方法。
如果项目由多个模块构成,模块相互之间有依赖关系,并且不止一个模块需要使用sdk就很麻烦。比如我们项目,一共有6个模块,moduleA和moduleB都依赖moduleC,moduleC又依赖moduleD,moduleD又依赖moduleE和moduleF,而moduleC和moduleA都要使用sdk,一开始的想法是,既然A依赖于C,是不是只在C把上面导入aar的工作做一遍就行了,试了下,不行,moduleA报错,找不到sdk,然后又在moduleA把相同的事做了一遍,编译,又报错,moduleB找不到sdk,奇怪了,moduleB只是依赖moduleC并没有使用sdk也会报错,最后把每个module都加上aar,修改gradle,仍然报错,报的什么错已经记不得了,反正是让人一头雾水又很抓狂。
还有个问题,aar中有个类“UpdateInfo”,本地工程也有一个一样名字的“UpdateInfo”,这个对象是由moduleC通过AIDL接口传给moduleA和moduleB,集成sdk后moduleC得到的是sdk中的“UpdateInfo”,对象类型不同编译的时候会报错,这两个问题搞得人很烦,一下不知道该怎么解决。
后来基本上是把用到sdk的代码屏蔽了,一切从头开始,很仔细的修改每个模块的build.gradle,遇到问题逐个解决,到处查资料,发现了这篇文章,里面介绍多个module相互依赖如何在gradle中设置依赖aar,比如我这个工程,因为moduleA和moduleC都是要用到sdk的,需要按照正常流程导入aar;而moduleB中没有用到sdk只是依赖moduleC,不需要引入aar,但是要在gradle中配置aar在其它模块的路径:
repositories {
flatDir {
dirs '../moduleC/libs'
}
}
这样就可以了,其它有依赖关系的module做相同配置就行了。还有一点,当提示找不到sdk路径时尝试关闭android studio重新打开,如果没有其它问题是可以发现sdk的,自动导入的类、方法也没有问题,这也是一个坑,多次clean、rebuild都没用,关了重开才能刷新IDE旧的配置。
然后是第二个问题,对象类型不同编译报错,首先我忽略了一个问题,使用AIDL接口传递数据时,非基本类型的数据都要实现序列化接口,然后重新修改sdk中的“UpdateInfo”,把代码贴出来以加深印象:
ublic class UpdateInfo implements Parcelable{
private String versionName;//apk版本名称
private String serverVersion;//code
private String upgradeDetailInfo;//升级信息
private String serverSupportMinVer;//升级的支持的最小版本
private String apk_download_url;//apk的下载路径
private String apk_size;// apk的大小
private String apk_name;//apk的名称
private String apk_icon_url;//apk的图标的下载路径
private String apk_hashCode;//apk的hashcode
public UpdateInfo() {
}
public String getVersionName() {
return versionName;
}
public void setVersionName(String versionName) {
this.versionName = versionName;
}
public String getServerVersion() {
return serverVersion;
}
public void setServerVersion(String serverVersion) {
this.serverVersion = serverVersion;
}
public String getUpgradeDetailInfo() {
return upgradeDetailInfo;
}
public void setUpgradeDetailInfo(String upgradeDetailInfo) {
this.upgradeDetailInfo = upgradeDetailInfo;
}
public String getServerSupportMinVer() {
return serverSupportMinVer;
}
public void setServerSupportMinVer(String serverSupportMinVer) {
this.serverSupportMinVer = serverSupportMinVer;
}
public String getApk_download_url() {
return apk_download_url;
}
public void setApk_download_url(String apk_download_url) {
this.apk_download_url = apk_download_url;
}
public String getApk_size() {
return apk_size;
}
public void setApk_size(String apk_size) {
this.apk_size = apk_size;
}
public String getApk_name() {
return apk_name;
}
public void setApk_name(String apk_name) {
this.apk_name = apk_name;
}
public String getApk_icon_url() {
return apk_icon_url;
}
public void setApk_icon_url(String apk_icon_url) {
this.apk_icon_url = apk_icon_url;
}
public String getApk_hashCode() {
return apk_hashCode;
}
public void setApk_hashCode(String apk_hashCode) {
this.apk_hashCode = apk_hashCode;
}
@Override
public String toString() {
return "UpdateInfo{" +
"versionName='" + versionName + '\'' +
", serverVersion='" + serverVersion + '\'' +
", upgradeDetailInfo='" + upgradeDetailInfo + '\'' +
", serverSupportMinVer='" + serverSupportMinVer + '\'' +
", apk_download_url='" + apk_download_url + '\'' +
", apk_size='" + apk_size + '\'' +
", apk_name='" + apk_name + '\'' +
", apk_icon_url='" + apk_icon_url + '\'' +
", apk_hashCode='" + apk_hashCode + '\'' +
'}';
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(versionName);
dest.writeString(serverVersion);
dest.writeString(upgradeDetailInfo);
dest.writeString(serverSupportMinVer);
dest.writeString(apk_download_url);
dest.writeString(apk_size);
dest.writeString(apk_name);
dest.writeString(apk_icon_url);
dest.writeString(apk_hashCode);
}
protected UpdateInfo(Parcel in){
readFromParcel(in);
}
public void readFromParcel(Parcel in){
versionName = in.readString();
serverVersion = in.readString();
upgradeDetailInfo = in.readString();
serverSupportMinVer = in.readString();
apk_download_url = in.readString();
apk_size = in.readString();
apk_name = in.readString();
apk_icon_url = in.readString();
apk_hashCode = in.readString();
}
public static final Creator<UpdateInfo> CREATOR = new Creator<UpdateInfo>() {
public UpdateInfo createFromParcel(Parcel source) {
return new UpdateInfo(source);
}
public UpdateInfo[] newArray(int size) {
return new UpdateInfo[size];
}
};
}
就是后面5个方法实现了序列化接口。
实现序列化之后才是对象类型不兼容的问题,为了不影响以前的代码,写了个转换方法,从sdk中得到UpdateInfo后转变成旧的UpdateInfo:
public static UpdateInfo conver2UpdateInfo(Context context, acxingyun.cetcs.com.downloadsdk.bean.UpdateInfo updateInfoSdk){
UpdateInfo updateInfo = new UpdateInfo();
......
return updateInfo;
}
这样就解决了。
后来集成好了,自己测试的时候,又发现一个问题,moduleC是一个service,如果把app设置为记住密码自动登录,进去后会检查版本更新,这时是没有app的activity的,但升级sdk的界面是一个dialogfragment要依赖activity,无法直接弹出升级提示,没办法,只有耐着性子改成activity。
整个过程,从修改到集成,花了我半个月时间,作为程序员真是能遇到各种各样的问题,尤其是没那么多资料又无人可问的时候更是考验一个人的耐心和智慧的时候,我也低估了工作量,如果知道有这么麻烦,说不定就不改别人的代码直接合了。趁现在不忙,多多努力提高自己吧。