这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助

我们知道,在打包Android App之前,我们需要先通过HX生成打包资源。如果是通过cli创建的项目,则通过以下命令生成打包资源:

yarn build:app-plus

生成打包资源后的目录长这样:

然后将整个目录中的所有文件拷贝到Android项目的 assets/apps/<appid>/www 中:

可以看出,所有生成的文件,其实只是一个资源目录。

热更新的原理就是:替换资源目录中的所有打包资源

热更新包分析

我们通过HX生成的热更新包:

生成的热更新包长这样:

可以看出,wgt其实就是一个压缩文件,将生成的资源文件全部打包。

知道原理后,我们就不一定需要通过HX创建wgt了,我们可以使用yarn build:app-plus命令先生成打包资源目录,再将其压缩为zip包,修改扩展名为wgt即可

注意:wgt包中,必须将manifest,json所在路径当做根节点进行打包。

打完包后,我们可以将其上传到OSS。

热更新方案

热更新方案:通过增加当前APP资源的版本号(versionCode),跟上一次打包时的APP资源版本号进行对比,如果比之前的资源版本号高,即进行热更新。

热更新原理:uniapp的热更新,其实是将build后的APP资源,打包为一个zip压缩包(扩展名改为wgt)。

涉及到的版本信息文件:

  • src/manifest.json
  • app.json (自己创建,用于版本对比)
  • platforms/android/app/build.gradle

注意事项:

保证以上文件的versionNameversionCode均保持一致。

热更新核心代码

以下为热更新的核心代码:

// #ifdef APP-PLUS
let downloadPath = "https://xxx.cn/apk/app.wgt"
uni.downloadFile({
url: downloadPath,
success: (downloadResult) => {
if (downloadResult.statusCode === 200) {
plus.runtime.install(downloadResult.tempFilePath, {
force: true // 强制更新
}, function() {
console.log('install success...');
plus.runtime.restart();
}, function(e) {
console.error(e);
console.error('install fail...');
});
}
}
})
// #endif

这里是下载wgt包,并进行安装的代码。以上代码无论如何都会下载wgt进行安装。

更新接口

实际上,在这之前,我们还需要判断是否需要更新,这就涉及到接口的部分。在此,只讲讲思路:

  1. 获取安装的版本名、版本号等信息,将其当做参数调用对应的更新接口;
  2. 接口取到这些信息,与最新版本进行对比,如果版本已经更新,返回需要更新的信息;
  3. 接口可以自行约定,怎么方便这么来。

我自己做的话,根本没写什么接口,只是创建了一个app.json文件,用于存放最新版本信息:

{
"versionCode": "100",
"versionName": "1.0.0"
}

将其上传到OSS,然后在下载wgt包之前进行版本检查即可:

// #ifdef APP-PLUS
plus.runtime.getProperty(plus.runtime.appid, function(widgetInfo) {
console.log(widgetInfo);
uni.request({
url: 'https://xxx.cn/apk/app.json',
success: (result) => {
let { versionCode, versionName } = result.data
console.log({ versionCode, versionName });
// 判断版本名是否一致
if (versionName === widgetInfo.version) {
// 如果安装的版本号小于最新发布的版本号,则进行更新
if (parseInt(widgetInfo.versionCode) < parseInt(versionCode)) {
// 下载wgt更新包
let downloadPath = "https://xxx.cn/apk/app.wgt"
uni.downloadFile({
url: downloadPath,
success: (downloadResult) => {
if (downloadResult.statusCode === 200) {
plus.runtime.install(downloadResult.tempFilePath, {
force: true // 强制更新
}, function() {
console.log('热更新成功');
plus.runtime.restart();
}, function(e) {
console.error('热更新失败,错误原因:' + e);
});
}
}
})
} else {
console.log('你的版本为最新,不需要热更新');
}
} else {
console.log('版本名不一致,请使用整包更新');
}
}
});
});
// #endif

OK,至此,热更新就完成了。

Android整包更新

看到上面更新逻辑,如果版本名不一致,则需要下载最新的apk进行安装,在下载之前,建议给用户一个更新提示:

console.log('版本名不一致,请使用整包更新');
let url = "https://xxx.cn/apk/app.apk"
uni.showModal({ //提醒用户更新
title: "更新提示",
content: "有新的更新可用,请升级",
success: (res) => {
if (res.confirm) {
plus.runtime.openURL(url);
}
}
})

以上代码是官方提供的,其实也可以下载apk成功后,直接调用install进行安装:

console.log('版本名不一致,请使用整包更新');
let downloadPath = "https://zys201811.boringkiller.cn/shianonline/apk/app.apk"
uni.showModal({ //提醒用户更新
title: "更新提示",
content: "有新的更新可用,请升级",
success: (res) => {
if (res.confirm) {
// plus.runtime.openURL(downloadPath);
uni.downloadFile({
url: downloadPath,
success: (downloadResult) => {
if (downloadResult.statusCode === 200) {
console.log('正在更新...');
plus.runtime.install(downloadResult.tempFilePath, {
force: true // 强制更新
}, function() {
console.log('整包更新成功');
plus.runtime.restart();
}, function(e) {
console.error('整包更新失败,错误原因:' + e);
});
}
}
})
}
}
})

热更新的自动化处理

知道原理后,就好办了,我们可以将其繁杂的工作自动化,以减少重复劳动。

修改package.json的相关打包脚本:

{
"name": "shianaonline",
"version": "0.1.224",
"private": true,
"scripts": {
"apk": "node deploy/scripts/build-apk.js",
"wgt": "node deploy/scripts/build-wgt.js",
"build:app-plus-android": "cross-env NODE_ENV=production UNI_PLATFORM=app-plus UNI_OUTPUT_DIR=./platforms/android/app/src/main/assets/apps/your appid/www vue-cli-service uni-build",
"build:app-plus-ios": "cross-env NODE_ENV=production UNI_PLATFORM=app-plus UNI_OUTPUT_DIR=./platforms/iOS/apps/your appid/www vue-cli-service uni-build",
}
}

其中,需要替换的地方是your appid,换为自己的uniapp appid

创建app.json,用于存储当前app的版本信息:

{
"versionName": "1.0.27",
"versionCode": 336,
"appPath": "https://xxx.oss.com/apk/app-release.apk",
"wgtPath": "https://xxx.oss.com/apk/www.wgt"
}

创建自动化打包脚本build-wgt.js

const fs = require('fs')
const { execSync } = require('child_process')
const join = require('path').join // 修改版本号
let app = require('../../app.json')
let manifest = require('../../src/manifest.json') if (app.versionName !== manifest.versionName) {
console.info('manifest.json和app.json的versionName不一致,请检查')
return
} if (app.versionCode !== manifest.versionCode) {
console.info('manifest.json和app.json的versionCode不一致,请检查')
return
} // 获取build.gradle的版本名
let gradleFilePath = '../../platforms/android/app/build.gradle'
let data = fs.readFileSync(__dirname + '/' + gradleFilePath, {
encoding: 'utf-8'
}) let reg = new RegExp(`versionCode ${app.versionCode}`, "gm") if (!reg.test(data)) {
console.log('platforms/android/app/build.gradle的versionCode不一致,请检查')
return
} app.versionCode += 1
manifest.versionCode += 1 console.log('====================');
console.log('newVersion:' + app.versionName + "." + app.versionCode);
console.log('===================='); let appJSON = JSON.stringify(app, null, 2)
let manifestJSON = JSON.stringify(manifest, null, 2) let replaceFiles = [{
path: '../../app.json',
name: 'app.json',
content: appJSON
}, {
path: '../../src/manifest.json',
name: 'manifest.json',
content: manifestJSON
}] replaceFiles.forEach(file => {
fs.writeFileSync(__dirname + '/' + file.path, file.content, {
encoding: 'utf-8'
})
console.log(file.name + ': 替换成功');
}) // 替换build.gradle的版本名
let result = data.replace(reg, `versionCode ${app.versionCode}`)
fs.writeFileSync(__dirname + '/' + gradleFilePath, result, {
encoding: 'utf-8'
})
console.log('platforms/android/build.gradle: 替换成功') console.log('===================='); // 编译
console.log(execSync('yarn build:app-plus-android', { encoding: 'utf-8'})) // 打包
const compressing = require('compressing'); const tarStream = new compressing.zip.Stream(); const targetPath = './platforms/android/app/src/main/assets/apps/your appid/www'
const targetFile = './www.wgt' let paths = fs.readdirSync(targetPath);
paths.forEach(function (item) {
let fPath = join(targetPath, item);
tarStream.addEntry(fPath);
}); tarStream
.pipe(fs.createWriteStream(targetFile))
.on('finish', upToOss) // 上传至OSS
let OSS = require('ali-oss'); function upToOss() {
let client = new OSS({
region: 'oss-cn-shenzhen',
accessKeyId: 'your accessKeyId',
accessKeySecret: 'your accessKeySecret'
}); client.useBucket('your bucketName'); let ossBasePath = `apk` put(`${ossBasePath}/www.wgt`, 'www.wgt')
put(`${ossBasePath}/wgts/${app.versionCode}/www.wgt`, 'www.wgt')
put(`webview/vod.html`, 'src/hybrid/html/vod.html')
put(`${ossBasePath}/app.json`, 'app.json') async function put (ossPath, localFile) {
try {
await client.put(ossPath, localFile);
console.log(`${localFile}上传成功:${ossPath}`);
} catch (err) {
console.log(err);
}
}
} console.log('====================');
console.log('更新完毕,newVersion:' + app.versionName + "." + app.versionCode);
console.log('====================');

以上打包脚本,做了以下工作:

  1. 验证版本号和版本名是否正确,如果不正确,终止脚本
  2. 修改当前APP版本号
  3. 生成APP打包资源
  4. 将打包资源做成zip包(扩展名改为wgt)
  5. 上传wgt资源包到OSS

一键式操作,打包为wgt只需要执行:

yarn wgt

Android整包更新的自动化处理

Android整包更新需要在AndroidManifest.xml中配置:

<uses-permission android:name="android.permission.INSTALL_PACKAGES"/>
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/>

Android整包更新的业务代码跟热更新一样,都可以调用plus.runtime.install来实现。

主要还是说一下打包apk的自动化脚本build-apk.js

const fs = require('fs')
const { execSync } = require('child_process') let app = require('../../app.json')
let manifest = require('../../src/manifest.json') if (app.versionName !== manifest.versionName) {
console.log('manifest.json和app.json的versionName不一致,请检查')
return
} if (app.versionCode !== manifest.versionCode) {
console.log('manifest.json和app.json的versionCode不一致,请检查')
return
} // 获取build.gradle的版本名
let gradleFilePath = '../../platforms/android/app/build.gradle'
let data = fs.readFileSync(__dirname + '/' + gradleFilePath, {
encoding: 'utf-8'
}) let reg = new RegExp(`versionName "${app.versionName}"`, "gm") if (!reg.test(data)) {
console.info('platforms/android/app/build.gradle的versionName不一致,请检查')
return
} let regCode = new RegExp(`versionCode ${app.versionCode}`, "gm")
if (!regCode.test(data)) {
console.info('platforms/android/app/build.gradle的versionCode不一致,请检查')
return
} // 修改版本名
let appVersionName = app.versionName.split('.')
let manifestVersionName = manifest.versionName.split('.') let appVersionLast = Number(appVersionName[2])
let manifestVersionLast = Number(manifestVersionName[2]) appVersionLast += 1
manifestVersionLast += 1 app.versionName = appVersionName[0] + '.' + appVersionName[1] + '.' + appVersionLast
manifest.versionName = manifestVersionName[0] + '.' + manifestVersionName[1] + '.' + manifestVersionLast console.log('====================');
console.log('newVersion:' + app.versionName + "." + app.versionCode);
console.log('===================='); let appJSON = JSON.stringify(app, null, 2)
let manifestJSON = JSON.stringify(manifest, null, 2) // 替换项目版本名
let replaceFiles = [{
path: '../../app.json',
name: 'app.json',
content: appJSON
}, {
path: '../../src/manifest.json',
name: 'manifest.json',
content: manifestJSON
}] replaceFiles.forEach(file => {
fs.writeFileSync(__dirname + '/' + file.path, file.content, {
encoding: 'utf-8'
})
console.log(file.name + ': 替换成功');
}) // 替换build.gradle的版本名
let result = data.replace(reg, `versionName "${app.versionName}"`)
fs.writeFileSync(__dirname + '/' + gradleFilePath, result, {
encoding: 'utf-8'
})
console.log('platforms/android/build.gradle: 替换成功') console.log('===================='); // 打包资源
console.log(execSync(`yarn build:app-plus-android`, { encoding: 'utf-8'})) // 打包apk
console.log(execSync(`cd platforms/android && gradle assembleRelease`, { encoding: 'utf-8'})) // 上传至OSS
let OSS = require('ali-oss'); function upToOss() {
let client = new OSS({
region: 'oss-cn-shenzhen',
accessKeyId: 'your accessKeyId',
accessKeySecret: 'your accessKeySecret'
}); client.useBucket('your bucketName'); let ossBasePath = `apk` put(`${ossBasePath}/app-release.apk`, 'platforms/android/app/build/outputs/apk/release/app-release.apk')
put(`${ossBasePath}/apks/${app.versionName}/app-release.apk`, 'platforms/android/app/build/outputs/apk/release/app-release.apk')
put(`${ossBasePath}/apks/${app.versionName}/output.json`, 'platforms/android/app/build/outputs/apk/release/output.json')
put(`webview/vod.html`, 'src/hybrid/html/vod.html')
put(`${ossBasePath}/app.json`, 'app.json') async function put (ossPath, localFile) {
try {
await client.put(ossPath, localFile);
console.log(`${localFile}上传成功:${ossPath}`);
} catch (err) {
console.log(err);
}
}
} upToOss() console.log('====================');
console.log('更新完毕,newVersion:' + app.versionName + "." + app.versionCode);
console.log('====================');

以上打包脚本,做了以下工作:

  1. 验证版本号和版本名是否正确,如果不正确,终止脚本
  2. 修改当前APP版本名
  3. 生成APP打包资源
  4. 打包Android APP(扩展名apk)
  5. 上传apk到OSS

一键式操作,打包为apk只需要执行:

yarn apk

安装更新

我们看看plus.runtime.install的官方文档:

void plus.runtime.install(filePath, options, installSuccessCB, installErrorCB);

支持以下类型安装包:

  1. 应用资源安装包(wgt),扩展名为'.wgt';
  2. 应用资源差量升级包(wgtu),扩展名为'.wgtu';
  3. 系统程序安装包(apk),要求使用当前平台支持的安装包格式。 注意:仅支持本地地址,调用此方法前需把安装包从网络地址或其他位置放置到运行时环境可以访问的本地目录。

知道了调用方式就好办了,我们封装一个检测更新的方法:

class Utils {
... // 获取APP版本信息
getVersion() {
let {versionName, versionCode} = manifest
return {
versionName,
versionCode,
version: `${versionName}.${versionCode}`
}
} // 检测更新
detectionUpdate(needRestartHotTip = false, needRestartFullTip = false) {
return new Promise(async (resolve, reject) => {
let appInfo = this.getVersion()
uni.request({
url: 'https://xxx.oss.com/apk/app.json',
success: async (result) => {
let { versionCode, versionName, appPath, wgtPath } = result.data
let versionInfo = {
appPath,
wgtPath,
newestVersion: `${versionName}.${versionCode}`,
newestVersionCode: versionCode,
newestVersionName: versionName,
currentVersion: appInfo.version,
currentVersionCode: appInfo.versionCode,
currentVersionName: appInfo.versionName
} // 判断版本名是否一致
try {
if (versionName === appInfo.versionName) {
// 如果安装的版本号小于最新发布的版本号,则进行更新
if (appInfo.versionCode < versionCode) {
// 下载wgt更新包
if (needRestartHotTip) {
uni.showModal({
title: '提示',
content: `检测到新版本 ${versionInfo.newestVersion} (当前版本:${versionInfo.currentVersion}),是否立即更新并重启应用,以使更新生效?`,
success: async (res) => {
if (res.confirm) {
await this.downloadAndInstallPackage(wgtPath)
plus.runtime.restart();
resolve({code: 1, data: versionInfo})
} else if (res.cancel) {
await this.downloadAndInstallPackage(wgtPath)
resolve({code: 1, data: versionInfo})
}
}
})
} else {
await this.downloadAndInstallPackage(wgtPath)
resolve({code: 1, data: versionInfo})
}
} else {
resolve({code: 0, data: versionInfo})
console.log('你的版本为最新,不需要热更新');
}
} else {
// 整包更新
console.log('版本名不一致,请使用整包更新');
if (needRestartFullTip) {
uni.showModal({
title: '提示',
content: `检测到新版本 ${versionInfo.newestVersion} (当前版本:${versionInfo.currentVersion}),是否立即更新应用?`,
success: async (res) => {
if (res.confirm) {
// await this.downloadAndInstallPackage(appPath)
plus.runtime.openURL(appPath)
resolve({code: 2, data: versionInfo})
} else if (res.cancel) {}
}
})
} else {
// await this.downloadAndInstallPackage(appPath)
plus.runtime.openURL(appPath)
resolve({code: 2, data: versionInfo})
}
}
} catch (e) {
reject(e)
}
}
});
})
} // 下载并安装更新包
downloadAndInstallPackage(url) {
console.log('开始下载更新包:' + url)
return new Promise((resolve, reject) => {
uni.downloadFile({
url: url,
success: (downloadResult) => {
if (downloadResult.statusCode === 200) {
console.log('正在更新...');
plus.runtime.install(downloadResult.tempFilePath, {
force: true // 强制更新
}, function() {
console.log('更新成功');
resolve()
}, function(e) {
console.error('更新失败,错误原因:' + JSON.stringify(e));
reject(e)
});
}
}
})
})
}
} ...

创建Utils的实例,并挂载到Vue的原型中,调用起来非常方便:

  ...

  let res = await this.$utils.detectionUpdate(false, true)
if (res.code === 1) {
uni.showModal({
title: '提示',
content: `发现新的热更新包,是否立即重启APP以使更新生效?`,
success: async (res) => {
if (res.confirm) {
plus.runtime.restart()
} else if (res.cancel) {}
}
})
}
  ...

  let res = await this.$utils.detectionUpdate(true, true)
if (res.code === 0) {
let {currentVersion} = res.data
uni.showModal({
title: '提示',
content: `你的APP为最新版本 ${currentVersion},不需要更新!`,
showCancel: false,
success: async (res) => {
if (res.confirm) {
} else if (res.cancel) {}
}
})
}

实战案例代码及过程

思路

服务器中存储着最新版本号,前端进行查询
可以在首次进入应用时进行请求版本号进行一个匹对
如果版本号一致则不提示,反之则提示进行更新执行更新操作

1.封装一个对比版本号的函数

/**
* 对比版本号,如需要,请自行修改判断规则
* 支持比对 ("3.0.0.0.0.1.0.1", "3.0.0.0.0.1") ("3.0.0.1", "3.0") ("3.1.1", "3.1.1.1") 之类的
* @param {Object} v1
* @param {Object} v2
* v1 > v2 return 1
* v1 < v2 return -1
* v1 == v2 return 0
*/
function compare(v1 = '0', v2 = '0') {
v1 = String(v1).split('.')
v2 = String(v2).split('.')
const minVersionLens = Math.min(v1.length, v2.length); let result = 0;
for (let i = 0; i < minVersionLens; i++) {
const curV1 = Number(v1[i])
const curV2 = Number(v2[i]) if (curV1 > curV2) {
result = 1
break;
} else if (curV1 < curV2) {
result = -1
break;
}
} if (result === 0 && (v1.length !== v2.length)) {
const v1BiggerThenv2 = v1.length > v2.length;
const maxLensVersion = v1BiggerThenv2 ? v1 : v2;
for (let i = minVersionLens; i < maxLensVersion.length; i++) {
const curVersion = Number(maxLensVersion[i])
if (curVersion > 0) {
v1BiggerThenv2 ? result = 1 : result = -1
break;
}
}
}
return result;
}

2.封装更新函数

var updateUseModal = (packageInfo) => {
const {
title, // 标题
contents, // 升级内容
is_mandatory, // 是否强制更新
url, // 安装包下载地址
platform, // 安装包平台
type // 安装包类型
} = packageInfo; let isWGT = type === 'wgt'
let isiOS = !isWGT ? platform.includes('iOS') : false;
let confirmText = isiOS ? '立即跳转更新' : '立即下载更新' return uni.showModal({
title,
content: contents,
showCancel: !is_mandatory,
confirmText,
success: res => {
if (res.cancel) return; // 安装包下载
if (isiOS) {
plus.runtime.openURL(url);
return;
}
let waiting = plus.nativeUI.showWaiting("正在下载 - 0%");
// uni.showLoading({
// title: '安装包下载中'
// });
// wgt 和 安卓下载更新
const downloadTask = uni.downloadFile({
url,
success: res => {
if (res.statusCode !== 200) {
console.error('下载安装包失败', err);
return;
}
// 下载好直接安装,下次启动生效
plus.runtime.install(res.tempFilePath, {
force: false
}, () => {
uni.hideLoading()
if (is_mandatory) {
//更新完重启app
plus.runtime.restart();
return;
}
uni.showModal({
title: '安装成功是否重启?',
success: res => {
if (res.confirm) {
//更新完重启app
plus.runtime.restart();
}
}
});
}, err => {
uni.hideLoading()
uni.showModal({
title: '更新失败',
content: err.message,
showCancel: false
});
});
},
//接口调用结束
complete: ()=>{
uni.hideLoading();
downloadTask.offProgressUpdate();//取消监听加载进度
}
});
//监听下载进度
downloadTask.onProgressUpdate(res => {
// state.percent = res.progress;
waiting.setTitle("正在下载 - "+res.progress+"%");
// console.log('下载进度百分比:' + res.progress); // 下载进度百分比
// console.log('已经下载的数据长度:' + res.totalBytesWritten); // 已经下载的数据长度,单位 Bytes
// console.log('预期需要下载的数据总长度:' + res.totalBytesExpectedToWrite); // 预期需要下载的数据总长度,单位 Bytes
});
}
});
}

3.用变量接收实现函数(在函数中使用上方封装的函数)并导出

fRequestWithToken为我封装的请求方法,可自行进行使用axios进行请求也行!!!

var fCheckVersion = (cb) => {
// #ifdef APP-PLUS
plus.runtime.getProperty(plus.runtime.appid, function(widgetInfo) {
// console.log(widgetInfo.version)
// console.log(plus.runtime.version)
// console.log(widgetInfo.version)
var nVerSta = compare(plus.runtime.version, widgetInfo.version),
sLaststVer = plus.runtime.version;
if (widgetInfo.version) {
if (nVerSta == 1) {
console.log(plus.runtime.version)
sLaststVer = plus.runtime.version
} else if (nVerSta == -1) {
console.log(widgetInfo.version)
sLaststVer = widgetInfo.version
}
}
console.log(sLaststVer)
//发送请求进行匹对,我这里数据库设定的是如果返回null则版本号一致,反之需要更新!!!
fRequestWithToken({
ajaxOpts: {
url: URLS_COM.d_lastVer,
data: {
versionCode: sLaststVer
}
},
showloading: false,
silence:true
}).then(data => {
console.log(data)
// console.log('################')
if (data) {
var sUrl = '',
type = '';
if (data.wgtName) {
sUrl = data.wgtName;
type = "wgt"
} else {
sUrl = data.pkgName;
type = "pkg";
} updateUseModal({
title: data.title||"",
contents: data.note||'',
is_mandatory: true,
url: sUrl,
platform: 'android',
type: type // 安装包类型
})
}
}).catch((res)=>{
cb&&cb()
console.log(res)
})
})
// #endif
} export {
fCheckVersion
}

使用

可在App.vue中进行使用,根据项目需求而定

1.引入封装好的函数

路径自己记得填写自己封装的位置

import{fCheckVersion} from '@/common/project/checkversion.js'

2.然后可以在onLoad函数中进行触发

onLoad() {
fCheckVersion();//检查更新
}

这样就实现了热更新
然后的话只需要进行打包个热更新的包

后端进行上传至服务器进行更新数据
本地再进行一个云打包,记得在mainifest.json文件中进行版本号的修改,修改成低于热更新包的版本号即可

本文部分内容转载于:

https://blog.csdn.net/m_xiaozhilei/article/details/126485684

如果对您有所帮助,欢迎您点个关注,我会定时更新技术文档,大家一起讨论学习,一起进步。

uniapp热更新和整包更新思路的更多相关文章

  1. uniapp中IOS安卓热更新和整包更新app更新

    在App.vue中 onLaunch: function() { console.log('App Launch'); // #ifdef APP-PLUS this.getVersion(); // ...

  2. uniapp热更新和整包升级

    一. uniapp热更新  (热更新官方文档) 很多人在开发uniapp的时候, 发现热更新失效问题(或者热更新没有更新manifest里的新增模块,SDK,原生插件包括云插件), 其实uniapp官 ...

  3. Anaconda更新和第三方包更新

    更新Anaconda和它所包含的包 1.打开cmd,切换到Anaconda的Scripts目录下:./Anaconda3/Scripts 2.更新Anaconda conda update conda ...

  4. Atitit 热更新资源管理器 自动更新管理器 功能设计

    Atitit 热更新资源管理器 自动更新管理器 功能设计 · 多线程并行下载支持 · 两层进度统计信息:文件级以及字节级 · Zip压缩文件支持 · 断点续传 · 详细的错误报告 · 文件下载失败重试 ...

  5. 关于KB905474正版验证补丁破解办法 KB905474是个微软操作系统正版/盗版监测间谍软件。更新安装后,右下角有个提示说“系统监测到你的操作系统是盗版”。 如果没有安装的: 在系统提示更新的时候注意看一下,如果包含有“更新KB905474”就去掉“更新KB905474”方框前的勾,点击关闭(注意如果没有去掉那个勾得话,会找不到“关闭”,而是“确定”),在不在提示我该消息前打勾。 如果已经安装

    关于KB905474正版验证补丁破解办法 KB905474是个微软操作系统正版/盗版监测间谍软件.更新安装后,右下角有个提示说“系统监测到你的操作系统是盗版”. 如果没有安装的: 在系统提示更新的时候 ...

  6. eclipse导入maven项目后依赖jar包更新问题->update project按钮

    eclipse导入maven项目后依赖jar包更新问题 1.eclipse有专门的导入maven项目按钮,file-import-maven project,eclipse会自动查找指定路径下的pom ...

  7. maven依赖jar包更新,业务jar需同步更新(业务jar依赖API)

    背景: 环境出现问题,定位为依赖jar缺失,修改工程pom文件补充依赖jar. 更新要点说明: 依赖jar,更新提交 业务jar,也需更新提交:maven构建会把依赖jar引用进去,更新环境如果单独更 ...

  8. TCP之心跳包实现思路

    说起网络应用编程,想到最多的就是聊天类的软件.当然,在这类软件中,一般都会有一个用户掉线检测功能.今天我们就通过使用自定义的HeartBeat方式来检测用户的掉线情况. 心跳包实现思路 我们采用的思路 ...

  9. ios开发 数据库版本迁移手动更新迭代和自动更新迭代

    数据库版本迁移顾名思义就是在原有的数据库中更新数据库,数据库中的数据保持不变对表的增.删.该.查. 数据持久化存储: plist文件(属性列表) preference(偏好设置) NSKeyedArc ...

  10. mysql批量更新、多表更新、多表删除

    本文介绍下,mysql中进行批量更新.多表更新.多表删除的一些实例,有需要的朋友可以参考下. 本节主要内容: mysql的批量更新.多表更新.多表删除 一,批量更新: 复制代码代码示例: update ...

随机推荐

  1. Office Online Server Windows Server 2016 部署

    一.准备"武器" 本文是通过虚拟机搭建 OOS 测试环境的,4567是3的前提,武器提取 le73 1.VMWare Workstation 17 Player 2.Windows ...

  2. Linux 中iostat 命令详解

    iostat命令详解 iostat 主要是统计 磁盘活动情况. iostat有以下缺陷: iostat的输出结果大多数是一段时间内的平均值,因此难以反映峰值情况iostat仅能对系统整体情况进行分析汇 ...

  3. NVME(学习笔记二)—CMB

    什么是CMB 在NVMe Express 1.2 Spec中开始支持一个特性,那就是CMB(Controller Memory Buffer),是指SSD控制器内部的读写存储缓冲区,与HMB(Host ...

  4. nginx 配置stream模块代理并开启日志配置

    前言 nginx 1.20.1nginx从1.9.0开始,新增加了一个stream模块确保nginx 安装时开启stream模块 ./configure \ -- \--with-stream \ - ...

  5. SSL证书类型价格和购买

    SSL证书 SSL和HTTPS的工作机制就不多说了, 密钥交换加通道依然是非常靠谱的安全访问方式, 除非你的浏览器连证书和DNS都被劫持, 否则中间节点要解密/篡改HTTPS访问的可能性微乎其微. 现 ...

  6. 【Unity3D】花瓣特效

    1 花瓣绘制原理 ​ 如下图是实现的花瓣特效效果,为方便描述,我们将每个红色的扁状长条称为花瓣,每个花瓣中心的绿点称为花蕊,花朵的正中心称为花心. ​ 我们在 xOz 平面上绘制花朵,假设花心为 O ...

  7. 【framework】IMS启动流程

    1 前言 ​ IMS 是 InputManagerService 的简称,主要负责输入事件管理. 1.1 基本概念 输入设备:屏幕.电源/音量.键鼠.充电口.蓝牙.wifi 等 设备节点:当输入设备可 ...

  8. maven打包更改版本号

    引入依赖 <plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>versions-mave ...

  9. Java并发编程实例--15.在同步代码块中使用条件

    并发编程中有个经典问题: 生产消费者问题. 我们有一个数据缓冲区,一个或多个生产者往其中存入对象,另外一个或多个消费者从中取走. 因此,该数据缓冲区是一个共享数据结构,我们需要对其添加读取同步机制,但 ...

  10. 在vue项目中使用scss语法的准备步骤

    在vue项目中使用scss语法的准备步骤 个人总结: 在项目根目录cmd控制台中使用以下命令行,安装vue项目中使用scss的相关依赖; 在["项目根目录/build/webpack.bas ...