利用github release实现客户端程序的版本控制和自动更新

您所在的位置:网站首页 git获取最新版本 利用github release实现客户端程序的版本控制和自动更新

利用github release实现客户端程序的版本控制和自动更新

2023-07-03 05:50| 来源: 网络整理| 查看: 265

  最近思考了下客户端的自动更新问题,写了个demo。   主要思路是客户端通过api获取最新版本并下载文件,之后调用脚本替换文件,关闭客户端,再重启,实现自动更新的效果。

github release

  为了实现客户端更新,那么就需要有服务端存放最新的文件和版本信息,github release提供了存放文件以及其对应版本的功能,并且可以提供对应的api来获取下载链接和版本信息。

  Github release的api url格式为https://api.github.com/repos///releases/latest,有name和代码库名即可。   返回数据为JSON格式,示例如下: 在这里插入图片描述 在这里插入图片描述   其中tag_name就是当前release中最新的文件版本,assets即为release中的文件,并且每个文件都提供了browser_download_url,直接调用url就可实现下载。 在这里插入图片描述

版本获取实现

  利用http client实现get请求发送,获取api信息。   Maven依赖

org.apache.httpcomponents httpclient 4.5.12

  Get请求

// 利用http client发起get请求获取信息 public static String sendGetRequest(String url){ CloseableHttpClient httpClient = HttpClientBuilder.create().build(); HttpGet httpGet = new HttpGet(url); CloseableHttpResponse response = null; String result = null; int status = 0; try { response = httpClient.execute(httpGet); // 从响应模型中获取响应实体 HttpEntity responseEntity = response.getEntity(); // 响应状态 status = response.getStatusLine().getStatusCode(); // 响应结果 result = EntityUtils.toString(responseEntity); System.out.println(status); System.out.println(result); if(status != 200) result = null; return result; }catch(SocketException e){ e.printStackTrace(); } catch (ClientProtocolException e) { e.printStackTrace(); } catch (ParseException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { try { // 释放资源 if (httpClient != null) { httpClient.close(); } if (response != null) { response.close(); } } catch (IOException e) { e.printStackTrace(); } } return result; }

  利用httpGet发送请求,获取responseEntity即可。

  JSON数据处理,获取当前version信息。

com.alibaba fastjson 1.2.58 public class VersionInfo { private static String VERSION = "0.2";// 当前版本 private static String VERSIONURL = "https://api.github.com/repos/huiluczP/autoupdate/releases/latest";// 获取当前最新版本的地址 private Map map; public String getVersionInfo(){ String result = HttpRequest.sendGetRequest(VERSIONURL); if(result!=null){ Map map = (Map) JSON.parse(result); this.map = map; return "Version info get success"; }else{ return "Version info get failed"; } } public String getLatestVersion(){ if(this.map!=null){ return map.get("tag_name").toString(); }else{ return null; } }

  VersionInfo类中利用一个常量来存储当前版本和api url。同时,对JSON处理使用了fastjson,支持直接将JSON字符串反序列化为map对象,还是挺方便的。

文件下载链接获取与文件下载

  文件下载提供了download url,也使用http client进行下载处理。   要注意的是,github的https是TSL1.2协议的,而jdk1.7以下不支持,使用时可能出现connect reset错误。

  为了文件下载链接对应,设计一个bean类,包含文件名和对应的url。

public class DownloadInfo { private String name; private String url; public DownloadInfo(String name,String url){ this.name = name; this.url = url; } // setter&getter }

  利用api获取的map获取下载连接和文件信息

public ArrayList getDownLoadUrl(){ // 返回当前文件的下载列表 ArrayList downloadInfos = new ArrayList(); if(this.map!=null){ List l =JSON.parseArray(map.get("assets").toString()); for (Object s:l){ Map simpleMap = (Map) JSON.parse(s.toString()); downloadInfos.add(new DownloadInfo(simpleMap.get("name").toString(), simpleMap.get("browser_download_url").toString())); } return downloadInfos; }else{ return downloadInfos; } }

  利用http client实现下载

public class HttpDownload { private static final int cache = 10 * 1024; private static final String splash; private static final String root; static { splash = "/"; root = "download"; } // 根据url下载文件,保存到filepath中 public static boolean download(String url, String filename, JProgressBar bar) { System.out.println("start downloading"); try { // cookie时间可能会出错,设置下 CloseableHttpClient client= HttpClients.custom() .setDefaultRequestConfig(RequestConfig.custom() .setCookieSpec(CookieSpecs.STANDARD).build()) .build(); HttpGet httpget = new HttpGet(url); HttpResponse response = client.execute(httpget); HttpEntity entity = response.getEntity(); InputStream is = entity.getContent(); String filepath = getFilePath(filename); File file = new File(filepath); boolean makeDir = file.getParentFile().mkdir(); System.out.println(file.getAbsolutePath()); FileOutputStream fileOut = new FileOutputStream(file); // 根据实际运行效果 设置缓冲区大小 byte[] buffer = new byte[cache]; int ch = -1; while ((ch = is.read(buffer)) != -1) { // 假进度条 int valueNow = bar.getValue(); if(valueNow bar.setValue(valueNow + 2); } System.out.println("cache " + filename); fileOut.write(buffer, 0, ch); } bar.setValue(100); is.close(); fileOut.flush(); fileOut.close(); System.out.println(filename + " download success"); } catch (Exception e) { e.printStackTrace(); return false; } return true; } // 获取下载路径 private static String getFilePath(String fileName) { String filepath = root + splash; filepath += fileName; return filepath; }

  download方法中实现下载,并保证文件的父文件夹存在。需要注意的是,进行下载的get请求可能会出现cookie中时间格式不匹配的错误,使用setCookieSpec方法将cookie的解析方式改为标准。方法中的bar参数是客户端的进度条组件,这边耦合度比较高,有空重新设计一下。

客户端界面

  客户端简单使用Swing实现,考虑到后台操作可能导致主界面假死的问题,使用SwingWorker实现AWT线程和后台线程的分离处理。   Swing界面:

public class MainScreen extends JFrame { private VersionInfo versionInfo; private String latestVersion = null; private String currentVersion = null; private JFrame frame = this; private JPanel mainPanel; private JPanel versionPanel; private JPanel processPanel; private JPanel buttonPanel; private JLabel versionLabel; private JLabel processLabel; private JProgressBar uploadProcess; private JButton checkButton; private JButton updateButton; public MainScreen(){ versionInfo = new VersionInfo(); initComponent(); } private void initComponent(){ mainPanel = new JPanel(); mainPanel.setLayout(new MigLayout("", "10px[grow]10px", "5px[grow]5px[grow]5px[grow]5px")); this.add(mainPanel, BorderLayout.CENTER); versionPanel = new JPanel(); versionLabel = new JLabel(); currentVersion = versionInfo.getCurrentVersion(); versionLabel.setText("current version:" + currentVersion); versionPanel.add(versionLabel, BorderLayout.CENTER); mainPanel.add(versionPanel, "cell 0 0"); processPanel = new JPanel(); processPanel.setLayout(new MigLayout("", "10px[grow]10px", "[grow]5px[grow]")); uploadProcess = new JProgressBar(); uploadProcess.setStringPainted(true); uploadProcess.setValue(0); processLabel = new JLabel(""); processPanel.add(processLabel, "cell 0 0"); processPanel.add(uploadProcess, "cell 0 1"); mainPanel.add(processPanel, "cell 0 1"); buttonPanel = new JPanel(); buttonPanel.setLayout(new MigLayout("", "10px[grow]10px[grow]10px", "[grow]5px")); checkButton = new JButton("check version"); updateButton = new JButton("download update"); checkButton.addActionListener(new VersionGetListener()); updateButton.addActionListener(new UploadListener()); buttonPanel.add(checkButton, "cell 0 0"); buttonPanel.add(updateButton, "cell 1 0"); mainPanel.add(buttonPanel, "cell 0 2"); this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); this.setLocationRelativeTo(null); this.setPreferredSize(new Dimension(300,400)); this.pack(); this.setVisible(true); }

  这边是导入了MigLayout的依赖进行的页面设计,MigLayout能将Swing组件分割成cell块,排版比较方便。页面中主要组件为显示版本信息的label,进度条和两个按钮。按钮分别对应最新版本显示和文件下载。

客户端显示最新版本

  为了将网络操作和界面更新线程分离,利用SwingWorker创建新线程进行处理,并利用ActionListener接口实现按钮的监听。SwingWorker中doInBackground方法中代码在新线程中执行,done为阻塞方法,当background执行完毕return时执行,同时该类实现get方法来获取后台返回内容。

// 获取最新version private class VersionGetSwingWorker extends SwingWorker{ @Override protected String doInBackground() throws Exception { versionInfo.getVersionInfo(); return versionInfo.getLatestVersion(); } @Override protected void done() { try { latestVersion = get(); if(latestVersion!=null){ versionLabel.setText(" current version:" + currentVersion + "" + "latest version:" + latestVersion + ""); }else{ versionLabel.setText(" current version:" + currentVersion + "" + "can't check latest version"); } } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } } } // 设置版本查询按钮监听方法 private class VersionGetListener implements ActionListener{ @Override public void actionPerformed(ActionEvent e) { versionLabel.setText("current version:" + currentVersion + "" + "checking latest version......"); new VersionGetSwingWorker().execute(); } }

  获取不到最新version时显示提示。Swing的label控件换行需要html标签和,比较奇怪。

客户端实现文件下载和自动更新

  调用上述文件下载方法,同时将JProcessBar进度条控件对象作为参数输入。

// 下载最新版本对应文件 private class updateSwingWorker extends SwingWorker{ @Override protected String doInBackground() throws Exception { String result = null; if(latestVersion == null){ processLabel.setText("please check latest version first"); }else{ processLabel.setText("Downloading..."); ArrayList infos = versionInfo.getDownLoadUrl(); result = Update.download(infos, uploadProcess); } return result; } @Override protected void done() { String result = null; try { result = get(); if(result!=null && !result.equals("")){ processLabel.setText("Download Success:" + result); Runtime.getRuntime().exec("cmd /k start .\\update.bat"); close(); }else if(result!=null){ processLabel.setText("Download failed"); } } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } } // 设置下载更新按钮监听器 private class UploadListener implements ActionListener{ @Override public void actionPerformed(ActionEvent e) { new updateSwingWorker().execute(); } }

  要注意的是,为了实现自动更新,在done中执行 Runtime.getRuntime().exec("cmd /k start .\\update.bat");该行执行一个bat脚本文件,用来实现文件的替换。要注意的是,该行代码执行并不阻塞,所以该行执行后立即调用close方法实现客户端关闭。   Close方法

private void close(){ frame.dispose(); }

  这边实现比较简单,真实需求可能会保存些当前客户端数据等。

启动与文件替换bat脚本

  Update.bat

@ping 127.0.0.1 -n 1 & move .\download\autoupdate.jar .\ & .\start.bat

  这边ping一下本地是为了控制下时间,免得close没执行完文件就被换了,之后执行move命令进行更换,最后执行start脚本

  Start.bat

@echo off java -jar autoupdate.jar

  start简单执行jar中的启动类,没什么好说的。

jar打包

  因为使用idea进行开发,直接使用它提供的build->build artifact->build即可。要注意的是在这之前需要使用project structure在项目中创建MF文件并指定启动类。

在这里插入图片描述

实现效果

在这里插入图片描述 在这里插入图片描述

总结

  项目实现了简单的客户端自动更新功能,利用github release做为版本控制媒介来实现。整个实现还是比较粗糙,当前客户端版本直接写在代码里,同时文件更新脚本也是写死的。后续优化可以利用xml文件进行各个组件的版本控制,同时对照多个文件的版本,仅下载对应更新文件即可。总的来说,简单实现下验证下思路,可以看一看。

项目代码已上传至github https://github.com/huiluczP/autoupdate



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3