目录:
移动大脑-SpringMVc搭建RestFul后台服务(一)-环境搭建
移动大脑-SpringMVc搭建RestFul后台服务(二)-配置mysql数据库
移动大脑-SpringMVc搭建RestFul后台服务(三)-RestFul接口编写(模拟用户注册登录)
移动大脑-SpringMVc搭建RestFul后台服务(四)-添加Token过滤器
移动大脑-SpringMVc搭建RestFul后台服务(五)-支付宝支付
移动大脑-SpringMVc搭建RestFul后台服务(六)-微信支付(Android)移动大脑-SpringMVc搭建RestFul后台服务(七)-增量更新
前段时间抽空把增量更新给写好了,增量更新博客见《Android增量更新(一)-差分文件(Windows-part1)》。今天这篇博客就是增量更新的实际应用。先看看效果,由于增量更新是第一次访问时才根据客户端版本生成的差分文件并保存的,所以第一次访问会稍微慢点,后面访问就很快了:
首次访问:
以后访问:
看这效果还是不错的~。
一、首先讲讲大体业务逻辑:
1、当客户端(android)检查更新时会把当前apk的md5值,版本号,渠道号等信息发送给服务器;
2、服务器收到客户端的版本信息,首先根据md5值、版本号和渠道号在数据库中查找是否生成过相应的差分包,有记录就直接返回差分包下载地址;没有记录就循环对比和客户端apk的md5值相等的apk包,这个包就是旧apk,然后和渠道一致的新版本的apk包生成差分包,相应的查分表保存到差分文件夹下并存储差分信息到数据库并返回给客户端。如果没有想要渠道的新包,就表示没有更新。
二、创建更新实体类和配置数据表
2.1、更新实体类UpdateBean.java(get和set方法自行添加)
[java] view plain copy print?
- package com.ywl5320.appservice.bean;
- /**
- * Created by ywl5320 on 2017/10/26.
- */
- public class UpdateBean extends BaseBean{
- private Integer id;
- /**
- * old apk md5 值
- */
- private String md5value;
- /**
- * 旧版本号(code)
- */
- private Integer versioncode;
- /**
- * 旧版本号(name)
- */
- private String versionName;
- /**
- * 新版本号(code)
- */
- private Integer newversioncode;
- /**
- * 新版本号(name)
- */
- private String newversionName;
- /**
- * 文件总大小
- */
- private Long filesize;
- /**
- * patch包大小
- */
- private Long patchsize;
- /**
- * 下载地址
- */
- private String downloadpath;
- /**
- * 差分包下载地址
- */
- private String patchdownloadpath;
- /**
- * 渠道标识
- */
- private String channelid;
- }
package com.ywl5320.appservice.bean;
/**
* Created by ywl5320 on 2017/10/26.
*/
public class UpdateBean extends BaseBean{
private Integer id;
/**
* old apk md5 值
*/
private String md5value;
/**
* 旧版本号(code)
*/
private Integer versioncode;
/**
* 旧版本号(name)
*/
private String versionName;
/**
* 新版本号(code)
*/
private Integer newversioncode;
/**
* 新版本号(name)
*/
private String newversionName;
/**
* 文件总大小
*/
private Long filesize;
/**
* patch包大小
*/
private Long patchsize;
/**
* 下载地址
*/
private String downloadpath;
/**
* 差分包下载地址
*/
private String patchdownloadpath;
/**
* 渠道标识
*/
private String channelid;
}
- <class name="com.ywl5320.appservice.bean.UpdateBean" table="t_update">
- <id name="id" column="id">
- <generator class= "native"/>
- </id>
- <property name="md5value" column="md5value"></property>
- <property name="versioncode" column="versioncode"></property>
- <property name="versionName" column="versionName"></property>
- <property name="newversioncode" column="newversioncode"></property>
- <property name="newversionName" column="newversionName"></property>
- <property name="filesize" column="filesize"></property>
- <property name="patchsize" column="patchsize"></property>
- <property name="downloadpath" column="downloadpath"></property>
- <property name="patchdownloadpath" column="patchdownloadpath"></property>
- <property name="channelid" column="channelid"></property>
- </class>
<class name="com.ywl5320.appservice.bean.UpdateBean" table="t_update">
<id name="id" column="id">
<generator class="native"/>
</id>
<property name="md5value" column="md5value"></property>
<property name="versioncode" column="versioncode"></property>
<property name="versionName" column="versionName"></property>
<property name="newversioncode" column="newversioncode"></property>
<property name="newversionName" column="newversionName"></property>
<property name="filesize" column="filesize"></property>
<property name="patchsize" column="patchsize"></property>
<property name="downloadpath" column="downloadpath"></property>
<property name="patchdownloadpath" column="patchdownloadpath"></property>
<property name="channelid" column="channelid"></property>
</class>
3.1、UpdateDao.java接口提供更新、存储和删除更新记录的功能
[java] view plain copy print?
- package com.ywl5320.appservice.dao;
- import com.ywl5320.appservice.bean.UpdateBean;
- /**
- * Created by ywl5320 on 2017/10/27.
- */
- public interface UpdateDao {
- /**
- * 获取更新信息
- * @param md5value
- * @param versioncode
- * @return
- */
- UpdateBean getUpdateInfo(String md5value, int versioncode, String channelid);
- /**
- * 存储更新信息
- * @param updateBean
- */
- void saveUpdateInfo(UpdateBean updateBean);
- /**
- * 删除更新信息
- * @param updateBean
- */
- void deleteUpdateInfo(UpdateBean updateBean);
- }
package com.ywl5320.appservice.dao;
import com.ywl5320.appservice.bean.UpdateBean;
/**
* Created by ywl5320 on 2017/10/27.
*/
public interface UpdateDao {
/**
* 获取更新信息
* @param md5value
* @param versioncode
* @return
*/
UpdateBean getUpdateInfo(String md5value, int versioncode, String channelid);
/**
* 存储更新信息
* @param updateBean
*/
void saveUpdateInfo(UpdateBean updateBean);
/**
* 删除更新信息
* @param updateBean
*/
void deleteUpdateInfo(UpdateBean updateBean);
}
[java] view plain copy print?
- package com.ywl5320.appservice.dao;
- import com.ywl5320.appservice.bean.UpdateBean;
- import org.springframework.orm.hibernate5.support.HibernateDaoSupport;
- import java.util.List;
- /**
- * Created by hlwky001 on 2017/10/27.
- */
- public class UpdateDaoImpl extends HibernateDaoSupport implements UpdateDao {
- public UpdateBean getUpdateInfo(String md5value, int versioncode, String channelid) {
- List<UpdateBean> updates = (List<UpdateBean>) this.getHibernateTemplate().find("from UpdateBean where md5value=? and versioncode=? and channelid=?", md5value, versioncode, channelid);
- if(updates != null && updates.size() > 0)
- {
- return updates.get(0);
- }
- return null;
- }
- public void saveUpdateInfo(UpdateBean updateBean) {
- this.getHibernateTemplate().save(updateBean);
- }
- public void deleteUpdateInfo(UpdateBean updateBean) {
- this.getHibernateTemplate().delete(updateBean);
- }
- }
package com.ywl5320.appservice.dao;
import com.ywl5320.appservice.bean.UpdateBean;
import org.springframework.orm.hibernate5.support.HibernateDaoSupport;
import java.util.List;
/**
* Created by hlwky001 on 2017/10/27.
*/
public class UpdateDaoImpl extends HibernateDaoSupport implements UpdateDao {
public UpdateBean getUpdateInfo(String md5value, int versioncode, String channelid) {
List<UpdateBean> updates = (List<UpdateBean>) this.getHibernateTemplate().find("from UpdateBean where md5value=? and versioncode=? and channelid=?", md5value, versioncode, channelid);
if(updates != null && updates.size() > 0)
{
return updates.get(0);
}
return null;
}
public void saveUpdateInfo(UpdateBean updateBean) {
this.getHibernateTemplate().save(updateBean);
}
public void deleteUpdateInfo(UpdateBean updateBean) {
this.getHibernateTemplate().delete(updateBean);
}
}
四、创建service层,这也是重点
4.1、更新逻辑处理
1、定义apk文件命名规则:apkname_versioncode_versionname_channelid.apk。如:app_1_v1.0.0_xiaomi.apk。这样就能根据名字取出apk的版本信息和渠道信息。
2、创建文件夹oldversion存储旧版本的apk;创建文件夹newversion存储新版本的apk;创建文件夹patch存储旧apk和新apk生成的差分包。
3、当获取到客户端的apk信息,然后遍历oldversion文件夹更加md5值找出和客户端一致的apk;然后根据客户端apk的渠道id和版本号在newversion文件夹中找出是否有相应的更新包:如果有就生成差分包,返回差分包的下载地址并存储差分包对应的版本信息;没有就返回“已是最新版本”给客户端。
4.2、首先添加增量更新库,在博客《Android增量更新(二)-差分文件(Windows-part2)-dll动态库和jar包》和《Android增量更新(三)-差分文件(Linux)-生成jar和.so库》中生成的dll或so库和jar包,如:

相应的动态库需要配置到环境变量里面: Windows: dll库位置:D:\DevelopSofts\dll\BsDiffYwl5320.dll 然后把路径添加到系统环境变量D:\DevelopSofts\dll\ 如图:

Linux:
so库位置:/usr/dev/mylib/BsDiffYwl5320.so
添加到环境变量:
[plain] view plain copy print?
- vim ~/.bashrc
- export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/dev/mylib
- source ~/.bashrc
vim ~/.bashrc
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/dev/mylib
source ~/.bashrc
这样就jar包就可以调用dll和so库了。
4.2、生成patch包(ps:这是我的逻辑,还可以有更好的)
UpdateService.java
[java] view plain copy print?
- package com.ywl5320.appservice.service;
- import com.ywl5320.appservice.bean.RestFulBean;
- import com.ywl5320.appservice.bean.UpdateBean;
- import com.ywl5320.appservice.dao.UpdateDao;
- import com.ywl5320.appservice.util.CommonUtils;
- import com.ywl5320.appservice.util.RestFulUtil;
- import com.ywl5320.bsdiff.BsDiffYwl5320Util;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.transaction.annotation.Transactional;
- import java.io.File;
- /**
- * Created by ywl5320 on 2017/10/26.
- */
- @Transactional
- public class UpdateService {
- @Autowired
- private UpdateDao updateDao;
- static String oldApksPath = "E:/source/oldversion";
- static String newApkPath = "E:/source/newversion";
- static String patchPath = "E:/source/patch";
- static
- {
- if(CommonUtils.getOsName().contains("linux"))//linux操作系统
- {
- oldApksPath = "/usr/dev/sources/oldversion";
- newApkPath = "/usr/dev/sources/newversion";
- patchPath = "/usr/dev/sources/patch";
- }
- else if(CommonUtils.getOsName().contains("windows")) //window操作系统
- {
- oldApksPath = "E:/source/oldversion";
- newApkPath = "E:/source/newversion";
- patchPath = "E:/source/patch";
- }
- }
- public RestFulBean<UpdateBean> checkUpdate(String md5value, int versioncode, String channelid){
- UpdateBean updateBean = updateDao.getUpdateInfo(md5value, versioncode, channelid);
- if(updateBean != null)
- {
- File file = new File(patchPath + "/" + updateBean.getPatchdownloadpath());
- if(file.exists()) {
- return RestFulUtil.getInstance().getResuFulBean(updateBean, 0, "有新版本");
- }
- //todo 不存在可以再生产增量包或删除此条记录
- updateDao.deleteUpdateInfo(updateBean);
- return RestFulUtil.getInstance().getResuFulBean(null, 1, "没有新版本");
- }
- else
- {
- updateBean = createPatch(md5value, versioncode, channelid);
- if(updateBean != null)
- {
- updateDao.saveUpdateInfo(updateBean);
- UpdateBean updateBean1 = updateDao.getUpdateInfo(md5value, versioncode, channelid);
- if(updateBean1 != null) {
- File file = new File(patchPath + "/" + updateBean1.getPatchdownloadpath());
- if(file.exists()) {
- return RestFulUtil.getInstance().getResuFulBean(updateBean1, 0, "有新版本");
- }
- updateDao.deleteUpdateInfo(updateBean1);
- return RestFulUtil.getInstance().getResuFulBean( null, 1, "没有新版本");
- }
- }
- return RestFulUtil.getInstance().getResuFulBean(null, 1, "没有新版本");
- }
- }
- private UpdateBean createPatch(String md5value, int versioncode, String channelid)
- {
- File md5File = null;
- File newFile = null;
- String patchName = "";
- File patchFile = null;
- String versionname = "";
- UpdateBean updateBean = null;
- int newVersionCode = 1;
- String newVersionName = "";
- File file = new File(oldApksPath);
- if(!file.exists())
- return null;
- File[] files = file.listFiles();
- if(files == null || files.length == 0)
- return null;
- /**
- * 根据MD5值找到和客户端相同的版本
- */
- for(File f : files)
- {
- String fmd5 = CommonUtils.getFileMd5(f);
- if(fmd5.equals(md5value))
- {
- System.out.print(f.getName() + " md5: " + fmd5);
- String[] flag = f.getName().replace(".apk", "").split("_");
- if(flag != null && flag.length == 4 && flag[3].equals(channelid))
- {
- versionname = flag[2];
- md5File = f;
- break;
- }
- }
- }
- if(md5File == null)
- return null;
- /**
- * 根据渠道获取当前最新版本
- */
- File nfile = new File(newApkPath);
- if(!nfile.exists())
- return null;
- File[] nfiles = nfile.listFiles();
- if(nfiles == null || nfiles.length == 0)
- return null;
- for(File nf : nfiles)
- {
- String[] flag = nf.getName().replace(".apk", "").split( "_");
- if(flag != null && flag.length == 4 && flag[3].equals(channelid))
- {
- System.out.println("渠道:" + channelid + " 的当前最新版本" + nf.getName());
- newFile = nf;
- newVersionCode = Integer.parseInt(flag[1]);
- newVersionName = flag[2];
- patchName = patchPath + "/" + nf.getName().replace( ".apk", "") + "_patch_" + versioncode + ".patch";
- break;
- }
- }
- if(newfile == null)
- return null;
- System.out.println("oldfile:" + md5File.getAbsolutePath());
- System.out.println("newfile:" + newFile.getAbsolutePath());
- System.out.println("patchfile:" + patchName);
- int result = BsDiffYwl5320Util.getInstance().bsDiffFile(md5File.getAbsolutePath(), newFile.getAbsolutePath(), patchName);
- if(result != 0)
- return null;
- patchFile = new File(patchName);
- if(!patchFile.exists())
- return null;
- updateBean = new UpdateBean();
- updateBean.setMd5value(md5value);
- updateBean.setVersioncode(versioncode);
- updateBean.setVersionName(versionname);
- updateBean.setNewversioncode(newVersionCode);
- updateBean.setNewversionName(newVersionName);
- updateBean.setFilesize(md5File.length());
- updateBean.setPatchsize(patchFile.length());
- updateBean.setDownloadpath(newFile.getName());
- updateBean.setPatchdownloadpath(patchFile.getName());
- updateBean.setChannelid(channelid);
- return updateBean;
- }
- }
package com.ywl5320.appservice.service;
import com.ywl5320.appservice.bean.RestFulBean;
import com.ywl5320.appservice.bean.UpdateBean;
import com.ywl5320.appservice.dao.UpdateDao;
import com.ywl5320.appservice.util.CommonUtils;
import com.ywl5320.appservice.util.RestFulUtil;
import com.ywl5320.bsdiff.BsDiffYwl5320Util;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import java.io.File;
/**
* Created by ywl5320 on 2017/10/26.
*/
@Transactional
public class UpdateService {
@Autowired
private UpdateDao updateDao;
static String oldApksPath = "E:/source/oldversion";
static String newApkPath = "E:/source/newversion";
static String patchPath = "E:/source/patch";
static
{
if(CommonUtils.getOsName().contains("linux"))//linux操作系统
{
oldApksPath = "/usr/dev/sources/oldversion";
newApkPath = "/usr/dev/sources/newversion";
patchPath = "/usr/dev/sources/patch";
}
else if(CommonUtils.getOsName().contains("windows"))//window操作系统
{
oldApksPath = "E:/source/oldversion";
newApkPath = "E:/source/newversion";
patchPath = "E:/source/patch";
}
}
public RestFulBean<UpdateBean> checkUpdate(String md5value, int versioncode, String channelid){
UpdateBean updateBean = updateDao.getUpdateInfo(md5value, versioncode, channelid);
if(updateBean != null)
{
File file = new File(patchPath + "/" + updateBean.getPatchdownloadpath());
if(file.exists()) {
return RestFulUtil.getInstance().getResuFulBean(updateBean, 0, "有新版本");
}
//todo 不存在可以再生产增量包或删除此条记录
updateDao.deleteUpdateInfo(updateBean);
return RestFulUtil.getInstance().getResuFulBean(null, 1, "没有新版本");
}
else
{
updateBean = createPatch(md5value, versioncode, channelid);
if(updateBean != null)
{
updateDao.saveUpdateInfo(updateBean);
UpdateBean updateBean1 = updateDao.getUpdateInfo(md5value, versioncode, channelid);
if(updateBean1 != null) {
File file = new File(patchPath + "/" + updateBean1.getPatchdownloadpath());
if(file.exists()) {
return RestFulUtil.getInstance().getResuFulBean(updateBean1, 0, "有新版本");
}
updateDao.deleteUpdateInfo(updateBean1);
return RestFulUtil.getInstance().getResuFulBean(null, 1, "没有新版本");
}
}
return RestFulUtil.getInstance().getResuFulBean(null, 1, "没有新版本");
}
}
private UpdateBean createPatch(String md5value, int versioncode, String channelid)
{
File md5File = null;
File newFile = null;
String patchName = "";
File patchFile = null;
String versionname = "";
UpdateBean updateBean = null;
int newVersionCode = 1;
String newVersionName = "";
File file = new File(oldApksPath);
if(!file.exists())
return null;
File[] files = file.listFiles();
if(files == null || files.length == 0)
return null;
/**
* 根据MD5值找到和客户端相同的版本
*/
for(File f : files)
{
String fmd5 = CommonUtils.getFileMd5(f);
if(fmd5.equals(md5value))
{
System.out.print(f.getName() + " md5: " + fmd5);
String[] flag = f.getName().replace(".apk", "").split("_");
if(flag != null && flag.length == 4 && flag[3].equals(channelid))
{
versionname = flag[2];
md5File = f;
break;
}
}
}
if(md5File == null)
return null;
/**
* 根据渠道获取当前最新版本
*/
File nfile = new File(newApkPath);
if(!nfile.exists())
return null;
File[] nfiles = nfile.listFiles();
if(nfiles == null || nfiles.length == 0)
return null;
for(File nf : nfiles)
{
String[] flag = nf.getName().replace(".apk", "").split("_");
if(flag != null && flag.length == 4 && flag[3].equals(channelid))
{
System.out.println("渠道:" + channelid + " 的当前最新版本" + nf.getName());
newFile = nf;
newVersionCode = Integer.parseInt(flag[1]);
newVersionName = flag[2];
patchName = patchPath + "/" + nf.getName().replace(".apk", "") + "_patch_" + versioncode + ".patch";
break;
}
}
if(newfile == null)
return null;
System.out.println("oldfile:" + md5File.getAbsolutePath());
System.out.println("newfile:" + newFile.getAbsolutePath());
System.out.println("patchfile:" + patchName);
int result = BsDiffYwl5320Util.getInstance().bsDiffFile(md5File.getAbsolutePath(), newFile.getAbsolutePath(), patchName);
if(result != 0)
return null;
patchFile = new File(patchName);
if(!patchFile.exists())
return null;
updateBean = new UpdateBean();
updateBean.setMd5value(md5value);
updateBean.setVersioncode(versioncode);
updateBean.setVersionName(versionname);
updateBean.setNewversioncode(newVersionCode);
updateBean.setNewversionName(newVersionName);
updateBean.setFilesize(md5File.length());
updateBean.setPatchsize(patchFile.length());
updateBean.setDownloadpath(newFile.getName());
updateBean.setPatchdownloadpath(patchFile.getName());
updateBean.setChannelid(channelid);
return updateBean;
}
}
[java] view plain copy print?
- package com.ywl5320.appservice.action;
- import com.ywl5320.appservice.bean.RestFulBean;
- import com.ywl5320.appservice.bean.UpdateBean;
- import com.ywl5320.appservice.bean.UserBean;
- import com.ywl5320.appservice.service.UpdateService;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.stereotype.Controller;
- import org.springframework.web.bind.annotation.RequestMapping;
- import org.springframework.web.bind.annotation.RequestMethod;
- import org.springframework.web.bind.annotation.ResponseBody;
- /**
- * Created by ywl5320 on 2017/10/26.
- */
- @Controller
- @RequestMapping("/update")
- public class UpdateAction {
- @Autowired
- private UpdateService updateService;
- @ResponseBody
- @RequestMapping(value="/checkupdate.do", method= RequestMethod.GET)
- public RestFulBean<UpdateBean> loginByPwd(String md5value, int versioncode, String channelid)
- {
- System.out.println("md5value:" + md5value);
- return updateService.checkUpdate(md5value, versioncode, channelid);
- }
- }
package com.ywl5320.appservice.action;
import com.ywl5320.appservice.bean.RestFulBean;
import com.ywl5320.appservice.bean.UpdateBean;
import com.ywl5320.appservice.bean.UserBean;
import com.ywl5320.appservice.service.UpdateService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
/**
* Created by ywl5320 on 2017/10/26.
*/
@Controller
@RequestMapping("/update")
public class UpdateAction {
@Autowired
private UpdateService updateService;
@ResponseBody
@RequestMapping(value="/checkupdate.do", method= RequestMethod.GET)
public RestFulBean<UpdateBean> loginByPwd(String md5value, int versioncode, String channelid)
{
System.out.println("md5value:" + md5value);
return updateService.checkUpdate(md5value, versioncode, channelid);
}
}
六、测试
6.1、创建新版本apk放到newversion文件夹下:
6.2、把旧版本apk放到oldversion文件夹下:
6.3、访问接口:
服务端生成差分包就完成了。
七、tomact配置虚拟路径:
进入tomact根目录下的conf文件夹,编辑server.xml,
在<Host></Host>之间添加:
[plain] view plain copy print?
- <Context docBase="E:\source\patch" reloadable="true" debug="0" path="apk/update/patch"/>
<Context docBase="E:\source\patch" reloadable="true" debug="0" path="apk/update/patch"/>

八、发布
上一步配置了虚拟路径后,还需要发布重启tomact后才能访问
8.1、生成war包:
8.2、发布到tomact
复制生成的AppService.war到tomact的webapps文件夹下:

8.3、启动tomact
在tomact的bin目录下收入命令:startup.bat
8.4、测试下载
下载地址:
[plain] view plain copy print?
- http://192.168.1.138:8080/apk/update/patch/app_2_v1.0.1_xiaomi_patch_1.patch
http://192.168.1.138:8080/apk/update/patch/app_2_v1.0.1_xiaomi_patch_1.patch

OK,这样就实现了增量更新,这里是以Windows举的例子,Linux原理也是一样的,配置好tomact和mysql,添加.so环境变量和下载虚拟地址就可以了。
客户端下载用的OKhttp3,这里就不接受了,不然又是一大堆截图和代码,客户端完整代码也在GitHub上,可以下载来看看。
源码下载 GitHub AppServiceRestFul 喜欢就请start一下吧~,