HarmonyOS鸿蒙Next应用如何实现应用内更新?

HarmonyOS鸿蒙Next应用如何实现应用内更新? 应用内更新可以让用户在不离开应用的情况下更新版本,本文介绍如何检测新版本、下载安装包、实现静默更新等功能。

5 回复

开发者您好,对于开发者,应用程序包的更新,首先需要更新app.json5配置文件中的versionCode版本号字段,通过DevEco Studio打包后在应用市场发布,发布流程与首次发布一致。对于终端设备用户,新版本发布后,可以通过以下两种方式更新应用程序包。

  • 应用市场内更新:应用市场通知用户该应用有新版本,用户根据通知到应用市场(客户端)进行升级。
  • 应用内检测升级:开发者根据更新指导实现版本更新提醒功能,应用启动完成或用户在应用中主动检查新版本时,会弹出升级对话框,用户根据对话框提示升级。

更多关于HarmonyOS鸿蒙Next应用如何实现应用内更新?的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


一、应用内更新架构设计

1. 更新流程

检测版本 → 下载安装包 → 验证签名 → 安装更新 → 重启应用

2. 核心功能模块

  • 版本检测模块
  • 下载管理模块
  • 安装管理模块
  • UI 提示模块

二、版本检测实现

1. 创建版本检测工具(utils/app-update.js)

/**
 * 应用内更新工具
 * 支持版本检测、下载、安装
 */

// 版本服务器配置
const UPDATE_CONFIG = {
    // 版本检查接口
    checkUrl: 'https://your-api.com/api/version/check',
    // 下载地址(可选,如果接口返回)
    downloadUrl: '',
    // 当前版本(从 manifest.json 读取)
    currentVersion: '',
    // 应用包名
    bundleName: 'com.ysy.coms'
}

/**
 * 获取当前版本信息
 */
export function getCurrentVersion() {
    // #ifdef APP-HARMONY
    try {
        const manifest = require('@/manifest.json')
        return {
            versionName: manifest.versionName || '1.0.0',
            versionCode: manifest.versionCode || '100',
            bundleName: manifest['app-harmony']?.distribute?.bundleName || UPDATE_CONFIG.bundleName
        }
    } catch (e) {
        console.error('获取版本信息失败:', e)
        return {
            versionName: '1.0.0',
            versionCode: '100',
            bundleName: UPDATE_CONFIG.bundleName
        }
    }
    // #endif
    
    // #ifndef APP-HARMONY
    return {
        versionName: '1.0.0',
        versionCode: '100',
        bundleName: UPDATE_CONFIG.bundleName
    }
    // #endif
}

/**
 * 检查是否有新版本
 * @param {Object} options 配置选项
 * @returns {Promise<Object>} 版本信息
 */
export async function checkUpdate(options = {}) {
    const {
        checkUrl = UPDATE_CONFIG.checkUrl,
        showLoading = true,
        silent = false  // 静默检查,不显示提示
    } = options
    
    try {
        // 显示加载提示
        if (showLoading && !silent) {
            uni.showLoading({
                title: '检查更新中...',
                mask: true
            })
        }
        
        // 获取当前版本
        const currentVersion = getCurrentVersion()
        
        // 请求版本信息
        const response = await uni.request({
            url: checkUrl,
            method: 'GET',
            data: {
                bundleName: currentVersion.bundleName,
                currentVersion: currentVersion.versionName,
                versionCode: currentVersion.versionCode,
                platform: 'harmony'
            },
            timeout: 10000
        })
        
        if (showLoading && !silent) {
            uni.hideLoading()
        }
        
        if (response.statusCode === 200 && response.data) {
            const serverVersion = response.data
            
            // 比较版本
            const hasUpdate = compareVersion(
                currentVersion.versionName,
                serverVersion.versionName
            ) < 0
            
            if (hasUpdate) {
                return {
                    hasUpdate: true,
                    currentVersion: currentVersion.versionName,
                    serverVersion: serverVersion.versionName,
                    downloadUrl: serverVersion.downloadUrl || serverVersion.apkUrl,
                    updateLog: serverVersion.updateLog || serverVersion.description || '新版本更新',
                    forceUpdate: serverVersion.forceUpdate || false,  // 是否强制更新
                    fileSize: serverVersion.fileSize || 0,  // 文件大小(字节)
                    md5: serverVersion.md5 || ''  // 文件 MD5 校验
                }
            } else {
                if (!silent) {
                    uni.showToast({
                        title: '已是最新版本',
                        icon: 'none'
                    })
                }
                return {
                    hasUpdate: false,
                    currentVersion: currentVersion.versionName
                }
            }
        } else {
            throw new Error('版本检查失败')
        }
    } catch (error) {
        console.error('检查更新失败:', error)
        
        if (showLoading && !silent) {
            uni.hideLoading()
        }
        
        if (!silent) {
            uni.showToast({
                title: '检查更新失败',
                icon: 'none'
            })
        }
        
        return {
            hasUpdate: false,
            error: error.message
        }
    }
}

/**
 * 比较版本号
 * @param {String} version1 版本1
 * @param {String} version2 版本2
 * @returns {Number} -1: version1 < version2, 0: 相等, 1: version1 > version2
 */
function compareVersion(version1, version2) {
    const v1Parts = version1.split('.').map(Number)
    const v2Parts = version2.split('.').map(Number)
    
    const maxLength = Math.max(v1Parts.length, v2Parts.length)
    
    for (let i = 0; i < maxLength; i++) {
        const v1Part = v1Parts[i] || 0
        const v2Part = v2Parts[i] || 0
        
        if (v1Part < v2Part) {
            return -1
        } else if (v1Part > v2Part) {
            return 1
        }
    }
    
    return 0
}

三、下载管理实现

2. 下载功能实现

/**
 * 下载安装包
 * @param {String} downloadUrl 下载地址
 * @param {Object} options 配置选项
 * @returns {Promise<Object>} 下载结果
 */
export async function downloadUpdate(downloadUrl, options = {}) {
    const {
        onProgress = null,  // 进度回调
        onSuccess = null,   // 成功回调
        onError = null      // 错误回调
    } = options
    
    return new Promise((resolve, reject) => {
        // #ifdef APP-HARMONY
        try {
            // 创建下载任务
            const downloadTask = uni.downloadFile({
                url: downloadUrl,
                success: (res) => {
                    if (res.statusCode === 200) {
                        const filePath = res.tempFilePath
                        
                        if (onSuccess) {
                            onSuccess(filePath)
                        }
                        
                        resolve({
                            success: true,
                            filePath: filePath,
                            statusCode: res.statusCode
                        })
                    } else {
                        const error = new Error(`下载失败: ${res.statusCode}`)
                        if (onError) {
                            onError(error)
                        }
                        reject(error)
                    }
                },
                fail: (error) => {
                    console.error('下载失败:', error)
                    if (onError) {
                        onError(error)
                    }
                    reject(error)
                }
            })
            
            // 监听下载进度
            downloadTask.onProgressUpdate((res) => {
                const progress = res.progress || 0
                if (onProgress) {
                    onProgress({
                        progress: progress,
                        totalBytesWritten: res.totalBytesWritten || 0,
                        totalBytesExpectedToWrite: res.totalBytesExpectedToWrite || 0
                    })
                }
            })
        } catch (error) {
            console.error('创建下载任务失败:', error)
            if (onError) {
                onError(error)
            }
            reject(error)
        }
        // #endif
        
        // #ifndef APP-HARMONY
        reject(new Error('当前环境不支持下载'))
        // #endif
    })
}

/**
 * 验证文件 MD5
 * @param {String} filePath 文件路径
 * @param {String} expectedMd5 期望的 MD5 值
 * @returns {Promise<Boolean>} 是否匹配
 */
export async function verifyFileMd5(filePath, expectedMd5) {
    if (!expectedMd5) {
        return true  // 没有 MD5 值,跳过验证
    }
    
    // #ifdef APP-HARMONY
    try {
        // 使用 plus API 计算文件 MD5
        // 注意:需要引入 MD5 计算库或使用原生能力
        // 这里简化处理,实际需要实现 MD5 计算
        return new Promise((resolve) => {
            // 实际实现需要使用 MD5 库
            // 例如:使用 crypto-js 或原生 plus API
            resolve(true)  // 暂时返回 true
        })
    } catch (error) {
        console.error('MD5 验证失败:', error)
        return false
    }
    // #endif
    
    // #ifndef APP-HARMONY
    return true
    // #endif
}

四、安装管理实现

3. 安装功能实现

/**
 * 安装应用
 * @param {String} filePath 安装包路径
 * @param {Object} options 配置选项
 * @returns {Promise<Boolean>} 是否成功
 */
export async function installUpdate(filePath, options = {}) {
    const {
        onSuccess = null,
        onError = null
    } = options
    
    return new Promise((resolve, reject) => {
        // #ifdef APP-HARMONY
        try {
            // 使用 plus API 安装应用
            plus.runtime.install(
                filePath,
                {
                    force: false  // 是否强制安装
                },
                () => {
                    // 安装成功
                    console.log('安装成功')
                    if (onSuccess) {
                        onSuccess()
                    }
                    resolve(true)
                },
                (error) => {
                    // 安装失败
                    console.error('安装失败:', error)
                    if (onError) {
                        onError(error)
                    }
                    reject(error)
                }
            )
        } catch (error) {
            console.error('安装应用失败:', error)
            if (onError) {
                onError(error)
            }
            reject(error)
        }
        // #endif
        
        // #ifndef APP-HARMONY
        reject(new Error('当前环境不支持安装'))
        // #endif
    })
}

/**
 * 完整的更新流程
 * @param {Object} updateInfo 更新信息
 * @param {Object} options 配置选项
 * @returns {Promise<Object>} 更新结果
 */
export async function performUpdate(updateInfo, options = {}) {
    const {
        showProgress = true,
        verifyMd5 = true
    } = options
    
    try {
        // 1. 下载安装包
        const downloadResult = await downloadUpdate(updateInfo.downloadUrl, {
            onProgress: (progressInfo) => {
                if (showProgress) {
                    const percent = Math.round(progressInfo.progress)
                    uni.showLoading({
                        title: `下载中 ${percent}%`,
                        mask: true
                    })
                }
            },
            onSuccess: (filePath) => {
                console.log('下载成功:', filePath)
            },
            onError: (error) => {
                uni.hideLoading()
                uni.showToast({
                    title: '下载失败',
                    icon: 'none'
                })
            }
        })
        
        uni.hideLoading()
        
        // 2. 验证文件(可选)
        if (verifyMd5 && updateInfo.md5) {
            uni.showLoading({
                title: '验证文件中...',
                mask: true
            })
            
            const isValid = await verifyFileMd5(downloadResult.filePath, updateInfo.md5)
            uni.hideLoading()
            
            if (!isValid) {
                throw new Error('文件校验失败,请重新下载')
            }
        }
        
        // 3. 安装应用
        uni.showLoading({
            title: '准备安装...',
            mask: true
        })
        
        await installUpdate(downloadResult.filePath, {
            onSuccess: () => {
                uni.hideLoading()
                uni.showModal({
                    title: '安装成功',
                    content: '应用将在重启后更新',
                    showCancel: false,
                    success: () => {
                        // 重启应用
                        plus.runtime.restart()
                    }
                })
            },
            onError: (error) => {
                uni.hideLoading()
                uni.showModal({
                    title: '安装失败',
                    content: error.message || '请手动安装',
                    showCancel: false
                })
            }
        })
        
        return {
            success: true,
            filePath: downloadResult.filePath
        }
    } catch (error) {
        console.error('更新失败:', error)
        uni.hideLoading()
        
        return {
            success: false,
            error: error.message
        }
    }
}

五、UI 组件实现

4. 更新提示组件(components/update-dialog/update-dialog.vue)

<template>
    <view class="update-dialog-mask" v-if="visible" @tap="handleMaskTap">
        <view class="update-dialog" @tap.stop>
            <view class="dialog-header">
                <text class="dialog-title">发现新版本</text>
                <text class="dialog-version">v{{ updateInfo.serverVersion }}</text>
            </view>
            
            <view class="dialog-content">
                <view class="update-log">
                    <text class="log-title">更新内容:</text>
                    <text class="log-text">{{ updateInfo.updateLog }}</text>
                </view>
                
                <view class="update-info" v-if="updateInfo.fileSize">
                    <text class="info-text">文件大小:{{ formatFileSize(updateInfo.fileSize) }}</text>
                </view>
            </view>
            
            <view class="dialog-footer">
                <button 
                    v-if="!updateInfo.forceUpdate"
                    class="btn-cancel" 
                    @tap="handleCancel"
                >
                    稍后更新
                </button>
                <button 
                    class="btn-update" 
                    :class="{ 'btn-full': updateInfo.forceUpdate }"
                    @tap="handleUpdate"
                >
                    立即更新
                </button>
            </view>
        </view>
    </view>
</template>

<script>
export default {
    name: 'UpdateDialog',
    props: {
        visible: {
            type: Boolean,
            default: false
        },
        updateInfo: {
            type: Object,
            default: () => ({})
        }
    },
    methods: {
        handleMaskTap() {
            // 强制更新时不允许关闭
            if (!this.updateInfo.forceUpdate) {
                this.$emit('cancel')
            }
        },
        handleCancel() {
            this.$emit('cancel')
        },
        handleUpdate() {
            this.$emit('update')
        },
        formatFileSize(bytes) {
            if (!bytes) return '0 B'
            const k = 1024
            const sizes = ['B', 'KB', 'MB', 'GB']
            const i = Math.floor(Math.log(bytes) / Math.log(k))
            return Math.round(bytes / Math.pow(k, i) * 100) / 100 + ' ' + sizes[i]
        }
    }
}
</script>

<style scoped>
.update-dialog-mask {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    background: rgba(0, 0, 0, 0.5);
    display: flex;
    align-items: center;
    justify-content: center;
    z-index: 9999;
}

.update-dialog {
    width: 600rpx;
    background: #FFFFFF;
    border-radius: 24rpx;
    overflow: hidden;
}

.dialog-header {
    padding: 40rpx 32rpx 24rpx;
    text-align: center;
    border-bottom: 1rpx solid #E0E0E0;
}

.dialog-title {
    display: block;
    font-size: 36rpx;
    font-weight: 600;
    color: #333333;
    margin-bottom: 12rpx;
}

.dialog-version {
    display: block;
    font-size: 28rpx;
    color: #666666;
}

.dialog-content {
    padding: 32rpx;
    max-height: 400rpx;
    overflow-y: auto;
}

.update-log {
    margin-bottom: 24rpx;
}

.log-title {
    display: block;
    font-size: 28rpx;
    font-weight: 500;
    color: #333333;
    margin-bottom: 16rpx;
}

.log-text {
    display: block;
    font-size: 26rpx;
    color: #666666;
    line-height: 1.6;
    white-space: pre-wrap;
}

.update-info {
    padding-top: 24rpx;
    border-top: 1rpx solid #F0F0F0;
}

.info-text {
    font-size: 24rpx;
    color: #999999;
}

.dialog-footer {
    display: flex;
    padding: 24rpx 32rpx 32rpx;
    gap: 24rpx;
}

.btn-cancel,
.btn-update {
    flex: 1;
    height: 88rpx;
    line-height: 88rpx;
    text-align: center;
    border-radius: 44rpx;
    font-size: 32rpx;
    border: none;
}

.btn-cancel {
    background: #F5F5F5;
    color: #666666;
}

.btn-update {
    background: linear-gradient(135deg, #6BBF59 0%, #AED581 100%);
    color: #FFFFFF;
}

.btn-full {
    flex: 1;
    width: 100%;
}
</style>

六、集成到项目

5. 在 App.vue 中集成

// App.vue
import { checkUpdate, performUpdate } from '@/utils/app-update.js'
import UpdateDialog from '@/components/update-dialog/update-dialog.vue'

export default {
    components: {
        UpdateDialog
    },
    data() {
        return {
            updateInfo: null,
            showUpdateDialog: false,
            isUpdating: false
        }
    },
    onLaunch: async function() {
        // ... 其他初始化代码
        
        // 延迟检查更新(不阻塞启动)
        setTimeout(() => {
            this.checkAppUpdate(true)  // 静默检查
        }, 3000)
    },
    methods: {
        /**
         * 检查应用更新
         * @param {Boolean} silent 是否静默检查
         */
        async checkApp

鸿蒙Next应用内更新可通过AppGallery Connect的版本发布功能实现。开发者需集成AGC的App Update SDK,调用checkAppUpdate接口检测新版本,支持静默安装和用户确认两种更新方式。静默更新需在config.json中配置allowAppUpdatePermission权限。

在HarmonyOS Next中实现应用内更新,核心是使用@ohos.update模块。以下是关键步骤和代码示例:

1. 检测新版本

通过UpdateService检查是否有可用更新:

import update from '@ohos.update';

let updateService = update.getUpdateService();
updateService.checkNewVersion((err, data) => {
  if (!err && data.hasNewVersion) {
    // 有新版本,获取版本信息
    let versionInfo = data.newVersionInfo;
    console.log(`新版本:${versionInfo.versionName}`);
  }
});

2. 下载安装包

确认更新后,开始下载:

updateService.download((err, data) => {
  if (err) {
    console.error('下载失败', err);
    return;
  }
  // 监听下载进度
  console.log(`下载进度:${data.receivedSize}/${data.totalSize}`);
}, {
  allowNetwork: update.NetType.NET_MOBILE // 允许使用的网络类型
});

3. 安装更新包

下载完成后自动触发安装,或手动调用:

// 自动安装(需用户授权)
updateService.install();

// 或使用静默安装(需系统权限)
updateService.install({
  mode: update.InstallMode.SILENT
});

4. 静默更新实现

对于系统应用或有权限的应用,可配置静默更新:

// 在config.json中声明权限
"reqPermissions": [
  {
    "name": "ohos.permission.INSTALL_BUNDLE"
  }
]

// 静默下载并安装
updateService.downloadAndInstall({
  installMode: update.InstallMode.SILENT,
  allowNetwork: update.NetType.NET_WIFI // 仅WiFi下静默更新
});

5. 更新策略配置

可设置更灵活的更新策略:

updateService.setPolicy({
  autoDownload: true, // 有更新时自动下载
  installMode: update.InstallMode.USER_INITIATED, // 用户确认后安装
  allowedNetworks: [update.NetType.NET_WIFI] // 限制网络类型
});

注意事项:

  • 非系统应用通常需要用户确认才能安装
  • 静默安装需要INSTALL_BUNDLE系统权限
  • 建议在WiFi环境下进行大版本更新
  • 需处理更新失败的回退机制

通过以上API组合,可实现从检测到安装的完整应用内更新流程。根据应用类型和权限不同,可选择用户交互更新或静默更新方案。

回到顶部