鸿蒙hap下载安装
方式1:描述文件下载

常规方法,将json5安装描述文件放到服务器上,访问json5文件的地址必须是https开头。

方式2:接口下载

通过接口文件流的形式返回

最最最重要的一点是接口必须支持分片下载,通过nginx的请求可以看出,访问json文件时,会先发HEAD请求,再发GET请求。访问安装包下载接口时也是先发HEAD请求,会根据包大小,来分发几次GET请求。

  • 访问json5文件

 @RequestMapping(value = "install/{id}/harmony.json5", method = {RequestMethod.GET,RequestMethod.HEAD})
     public void downJson5File(HttpServletRequest request, HttpServletResponse response,
             @PathVariable("id") String id) {
         OutputStream os = null;
         InputStream in = null;
         String method = request.getMethod();
         logger.info("请求方式:{}",method);
         try {
             //id是数据库主键,查询数据库,获取从后管配置的安装信息
             Map installInfo  = "通过id查询具体的安装信息";
             //鸿蒙端下载信息
             String sysIcon = String.valueOf(installInfo.get("SYSICON"));// 系統图标
             String version = String.valueOf(installInfo.get("VERSION"));// 版本
             String bundleId = String.valueOf(installInfo.get("BUNDLEID"));// 客户端package名称
             String packageHash = String.valueOf(installInfo.get("PACKAGEHASH"));// 安装包hash值
             String versionCode = String.valueOf(installInfo.get("VERSIONCODE"));// 441
             // 组装
             Map<String, String> harmonyMap = new HashMap<String, String>();
             harmonyMap.put("bundleName", bundleId);
             harmonyMap.put("packageHash", packageHash);
             harmonyMap.put("versionName", version);
             harmonyMap.put("versionCode", versionCode);
             harmonyMap.put("sysIcon", sysIcon);
             //这里拼接的是下面下载hap的接口地址,https开头
             harmonyMap.put("packageUrl", "https://xxx/xxx/" + "install/"+ id + "/hapDownload.hap");
             // 指定是二进制流
             response.setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
             // 文件流
             String jsonStr = AppDownloadUtil.generateHarmonyContent(harmonyMap);
             in = new ByteArrayInputStream(jsonStr.getBytes());
             os = response.getOutputStream();
             int len = 0;
             byte[] buffer = new byte[4096];
             while ((len = in.read(buffer)) > 0) {
                 os.write(buffer, 0, len);
             }
             os.flush();
         } catch (FileNotFoundException e) {
             e.printStackTrace();
         } catch (IOException e) {
             e.printStackTrace();
         } catch(Exception e){
             e.printStackTrace();
         } finally {
             try {
                 if (in != null) {
                     in.close();
                 }
             } catch (IOException e) {
                 e.printStackTrace();
             }
             try {
                 if (os != null) {
                     os.close();
                 }
             } catch (IOException e) {
                 e.printStackTrace();
             }
         }
     }
 ​
 ​
 ​
     /**
      * 生成鸿蒙.json5文件
      * @param map
      * @return
      */
     public static String generateHarmonyContent(Map<String, String> map) {
         Map jsonMap = new LinkedHashMap<>();
         //包名
         String bundleName = map.get("bundleName");
         //安装地址
         String packageUrl = map.get("packageUrl");
         //安装包hash值
         String packageHash = map.get("packageHash");
         //安装包版本号
         String versionName = map.get("versionName");
         //鸿蒙code 421
         String versionCode = map.get("versionCode");
         //系统图标
         String sysIcon = map.get("sysIcon");
         
         //根节点
         Map appLinkMap = new LinkedHashMap<>();
         appLinkMap.put("bundleName", bundleName);
         appLinkMap.put("bundleType", "app");
         appLinkMap.put("versionCode", Integer.valueOf(versionCode));
         appLinkMap.put("versionName", versionName);
         appLinkMap.put("label", "测试");
         appLinkMap.put("deployDomain", domain);
         
         //icon节点
         Map iconLinkMap = new LinkedHashMap<>();
         iconLinkMap.put("normal", sysIcon );
         iconLinkMap.put("large", sysIcon);
         appLinkMap.put("icons", iconLinkMap);
         
         appLinkMap.put("minAPIVersion", "5.0.0(12)");
         appLinkMap.put("targetAPIVersion", "5.0.0(12)");
         
         //modules模块
         List modulesList = new ArrayList<>();
         Map modulesLinkMap = new LinkedHashMap<>();
         modulesLinkMap.put("name", "entry");
         modulesLinkMap.put("type", "entry");
         
         //modules下的deviceType模块
         List deviceTypesList = new ArrayList<>();
         deviceTypesList.add("tablet");
         deviceTypesList.add("phone");
         modulesLinkMap.put("deviceTypes", deviceTypesList);
         
         modulesLinkMap.put("packageUrl", packageUrl);
         modulesLinkMap.put("packageHash", packageHash);
         
         modulesList.add(modulesLinkMap);
         appLinkMap.put("modules", modulesList);
         jsonMap.put("app", appLinkMap);
         String jsonStr = JSONObject.toJSONString(jsonMap);
         logger.info("json5:{}",jsonStr);
         return jsonStr;
     }

  • 下载hap接口

//controller中同时支持HEAD、GET接口请求
 @RequestMapping(value = "insall/{id}/hapDownload.hap", method = {RequestMethod.GET,RequestMethod.HEAD})
     public void hapDownloadFile(HttpServletRequest request, HttpServletResponse response,@PathVariable("id") String id) {
         OutputStream os = null;
         InputStream is = null;
         String method = request.getMethod();
         logger.info("请求方式:{}",method);
         
         try {
             Map installInfo = "根据id查询安装信息"
             String sysCode = String.valueOf(installInfo.get("SYSCODE"));// 系統編碼
             String platform = String.valueOf(installInfo.get("PLATFORM"));// 下载的类型
             String version = String.valueOf(installInfo.get("VERSION"));// 版本
             name = String.valueOf(installInfo.get("NAME"));// 下载的包名称
             // 拼接路径,安装包在服务器上的全路径,/home/app/xxx.hap,根据自己服务器拼写
             String downUrl = "/home/app/xxx.hap";
             logger.info("下载路径:{}",downUrl);
             //获取文件
             File file = new File(downUrl);
             //文件大小
             int fSize = (int) file.length();
             logger.info("安装包大小:{}",fSize);
             // 指定回复的内容为attachment,下载并保存到设备上,并伴随对话框
             response.setHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + name + "\"");
             //指定单位
             response.setHeader(HttpHeaders.ACCEPT_RANGES,"bytes");
             response.setHeader("filename",name);
             
             //下面是分片的操作,设置变量,分别是开始位置,最后结束位置,总数
             long pos = 0,last = fSize-1,sum = 0;
             String range = request.getHeader("Range");
             if(null != range) { 
                 response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
                 //请求头里面的range 0-6578945
                 String numberRange = range.replaceAll("bytes=","");
                 String[] strRange = numberRange.split("-");
                 logger.info("请求range:{},截取之后的range:{}",range,numberRange);
                 if(strRange.length == 2) {
                     pos = Long.parseLong(strRange[0].trim());
                     last = Long.parseLong(strRange[1].trim());
                     if(last > fSize -1 ) {
                         last = fSize -1;
                     }
                 }else{
                     pos = Long.parseLong(numberRange.replaceAll("-","").trim());
                 }
             }
             //计算待下载文件
             long rangeLength = last - pos + 1;
             logger.info("本次文件大小:{}",rangeLength);
             String contentRange = "bytes " + pos + "-" + last + "/" + fSize;
             logger.info("下载的范围:{}",contentRange);
             response.setHeader(HttpHeaders.CONTENT_RANGE,contentRange);
             // 告知文件大小,影响浏览器下载进度条
             response.setHeader(HttpHeaders.CONTENT_LENGTH, String.valueOf(rangeLength));
             // 指定是二进制流
             response.setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_OCTET_STREAM_VALUE);
             // 文件流
             os = new BufferedOutputStream(response.getOutputStream());
             is = new BufferedInputStream(new FileInputStream(file));
             is.skip(pos);
             byte[] buffer = new byte[4096];
             int length = 0;
             while(sum < rangeLength) {
                 int readLength = (int)(rangeLength - sum );
                 length = is.read(buffer,0, readLength <= buffer.length ? readLength : buffer.length);
                 sum +=length;
                 os.write(buffer, 0, length);
             }
         } catch (FileNotFoundException e) {
             e.printStackTrace();
         } catch (IOException e) {
             e.printStackTrace();
         } catch (Exception e) {
             e.printStackTrace();
         } finally {
             try {
                 if (is != null) {
                     is.close();
                 }
             } catch (IOException e) {
                 e.printStackTrace();
             }
             try {
                 if (os != null) {
                     os.close();
                 }
             } catch (IOException e) {
                 e.printStackTrace();
             }
         }
     }
  • 总结

    不管是json5的接口还是下载hap的接口,都要用对应的名称结尾,负责华为那边可能都不会来访问你给他的地址,json5文件的相关信息一定要配置正确。

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐