移動大腦-SpringMVc搭建RestFul後臺服務(七)-增量更新

 

我的視頻課程(基礎):《(NDK)FFmpeg打造Android萬能音頻播放器》

我的視頻課程(進階):《(NDK)FFmpeg打造Android視頻播放器》

我的視頻課程(編碼直播推流):《Android視頻編碼和直播推流》

 

目錄:

 

        移動大腦-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方法自行添加)

 

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;

    
}

 

 

2.2、在entitis.hbm.xml中配置更新數據表t_update

<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>

 

三、創建更新dao層

3.1、UpdateDao.java接口提供更新、存儲和刪除更新記錄的功能

 

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);

}

 

 

3.1、實現dao接口

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

添加到環境變量:

 

vim ~/.bashrc  
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/dev/mylib  
source ~/.bashrc  

這樣就jar包就可以調用dll和so庫了。

 

 

 

4.2、生成patch包(ps:這是我的邏輯,還可以有更好的)

UpdateService.java

 

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;
    }

}

 

 

五、action層UpdateAction.java

 

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>之間添加:

 

<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、測試下載

下載地址:

 

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一下吧~,

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章