uniapp热更新和整包更新思路
这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助
我们知道,在打包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
注意事项:
保证以上文件的versionName
和versionCode
均保持一致。
热更新核心代码
以下为热更新的核心代码:
// #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进行安装。
更新接口
实际上,在这之前,我们还需要判断是否需要更新,这就涉及到接口的部分。在此,只讲讲思路:
- 获取安装的版本名、版本号等信息,将其当做参数调用对应的更新接口;
- 接口取到这些信息,与最新版本进行对比,如果版本已经更新,返回需要更新的信息;
- 接口可以自行约定,怎么方便这么来。
我自己做的话,根本没写什么接口,只是创建了一个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('====================');
以上打包脚本,做了以下工作:
- 验证版本号和版本名是否正确,如果不正确,终止脚本
- 修改当前APP版本号
- 生成APP打包资源
- 将打包资源做成zip包(扩展名改为wgt)
- 上传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('====================');
以上打包脚本,做了以下工作:
- 验证版本号和版本名是否正确,如果不正确,终止脚本
- 修改当前APP版本名
- 生成APP打包资源
- 打包Android APP(扩展名apk)
- 上传apk到OSS
一键式操作,打包为apk只需要执行:
yarn apk
安装更新
我们看看plus.runtime.install
的官方文档:
void plus.runtime.install(filePath, options, installSuccessCB, installErrorCB);
支持以下类型安装包:
- 应用资源安装包(wgt),扩展名为'.wgt';
- 应用资源差量升级包(wgtu),扩展名为'.wgtu';
- 系统程序安装包(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热更新和整包更新思路的更多相关文章
- uniapp中IOS安卓热更新和整包更新app更新
在App.vue中 onLaunch: function() { console.log('App Launch'); // #ifdef APP-PLUS this.getVersion(); // ...
- uniapp热更新和整包升级
一. uniapp热更新 (热更新官方文档) 很多人在开发uniapp的时候, 发现热更新失效问题(或者热更新没有更新manifest里的新增模块,SDK,原生插件包括云插件), 其实uniapp官 ...
- Anaconda更新和第三方包更新
更新Anaconda和它所包含的包 1.打开cmd,切换到Anaconda的Scripts目录下:./Anaconda3/Scripts 2.更新Anaconda conda update conda ...
- Atitit 热更新资源管理器 自动更新管理器 功能设计
Atitit 热更新资源管理器 自动更新管理器 功能设计 · 多线程并行下载支持 · 两层进度统计信息:文件级以及字节级 · Zip压缩文件支持 · 断点续传 · 详细的错误报告 · 文件下载失败重试 ...
- 关于KB905474正版验证补丁破解办法 KB905474是个微软操作系统正版/盗版监测间谍软件。更新安装后,右下角有个提示说“系统监测到你的操作系统是盗版”。 如果没有安装的: 在系统提示更新的时候注意看一下,如果包含有“更新KB905474”就去掉“更新KB905474”方框前的勾,点击关闭(注意如果没有去掉那个勾得话,会找不到“关闭”,而是“确定”),在不在提示我该消息前打勾。 如果已经安装
关于KB905474正版验证补丁破解办法 KB905474是个微软操作系统正版/盗版监测间谍软件.更新安装后,右下角有个提示说“系统监测到你的操作系统是盗版”. 如果没有安装的: 在系统提示更新的时候 ...
- eclipse导入maven项目后依赖jar包更新问题->update project按钮
eclipse导入maven项目后依赖jar包更新问题 1.eclipse有专门的导入maven项目按钮,file-import-maven project,eclipse会自动查找指定路径下的pom ...
- maven依赖jar包更新,业务jar需同步更新(业务jar依赖API)
背景: 环境出现问题,定位为依赖jar缺失,修改工程pom文件补充依赖jar. 更新要点说明: 依赖jar,更新提交 业务jar,也需更新提交:maven构建会把依赖jar引用进去,更新环境如果单独更 ...
- TCP之心跳包实现思路
说起网络应用编程,想到最多的就是聊天类的软件.当然,在这类软件中,一般都会有一个用户掉线检测功能.今天我们就通过使用自定义的HeartBeat方式来检测用户的掉线情况. 心跳包实现思路 我们采用的思路 ...
- ios开发 数据库版本迁移手动更新迭代和自动更新迭代
数据库版本迁移顾名思义就是在原有的数据库中更新数据库,数据库中的数据保持不变对表的增.删.该.查. 数据持久化存储: plist文件(属性列表) preference(偏好设置) NSKeyedArc ...
- mysql批量更新、多表更新、多表删除
本文介绍下,mysql中进行批量更新.多表更新.多表删除的一些实例,有需要的朋友可以参考下. 本节主要内容: mysql的批量更新.多表更新.多表删除 一,批量更新: 复制代码代码示例: update ...
随机推荐
- logstash4j-用于日志的输入、转换处理、输出, java 开发者自己的 logstash
项目简介 logstash4j 用于日志的输入.转换处理.输出, java 开发者自己的 logstash 特性 input output filter metric 开源地址 logstash4j ...
- java.lang.System快速指南
1.介绍 在本教程中,我们将快速了解java.lang.System类及其特性和核心功能. 2.IO 系统类是java.lang的一部分,它的一个主要特性是让我们能够访问标准的I/O流. 简单地说,它 ...
- 基于角色的权限控制(RBAC)介绍
什么是RBAC? RBAC(Role-Based Access Control)基于角色的权限控制.其基本思想是,对系统操作的各种权限不是直接授予具体的用户,而是在用户集合与权限集合之间建立一个角色集 ...
- nodejs+express4实现文件上传下载删除和列表展示功能
0.效果展示 1.创建项目 创建文件夹:express_file_upload npm init # 入口文件选择server.js 安装插件 npm install express npm inst ...
- 深入理解Go语言(01): interface源码分析
分析接口的赋值,反射,断言的实现原理 版本:golang v1.12 interface底层使用2个struct表示的:eface和iface 一:接口类型分为2个 1. 空接口 //比如 var i ...
- 1-Django框架简介以及基本操作
安装 注意:安装的磁盘目录,以及后续通过Django创建目录的时候,不要出现中文,否则会出现预料之外的错误 建议:禁止套娃,即不要在A项目中创建B项目 # 如果不指定版本号,默认最新版 pip ins ...
- Centos系统下,各种服务重启
1.sudo systemctl start firewalld 2../redis-server /usr/local/bin/redis.conf 3.mongod -f /etc/mongod ...
- 【Azure 事件中心】Event Hub服务中的度量值指标介绍
问题描述 Event Hub服务中的度量值指标解说 1)request和message的区别 2)capture backlog 和 capture message 怎么理解 3)quota exce ...
- CSP 2023 My Codes
T1 小苹果 题目描述 小 Y 的桌子上放着 \(n\) 个苹果从左到右排成一列,编号为从 \(1\) 到 \(n\). 小苞是小 Y 的好朋友,每天她都会从中拿走一些苹果. 每天在拿的时候,小苞都是 ...
- 3、zookeeper在java使用的API
引入maven包 <dependency> <groupId>com.101tec</groupId> <artifactId>zkclient< ...