UniApp 安卓端版本检查更新功能完整实现

5 阅读5分钟

UniApp 安卓端版本检查更新功能完整实现

功能概述

这是一个完整的 UniApp Android 应用版本检查更新功能,包含以下特性:

  • ✅ 自动检测服务器最新版本
  • ✅ 支持强制更新和普通更新
  • ✅ 下载进度实时显示
  • ✅ Android 8.0+ 安装权限自动处理
  • ✅ 美观的弹窗 UI 界面
  • ✅ 安装成功后自动重启应用

目录结构

app/
├── components/
│   └── common/
│       └── zUpdate.vue          # 版本更新弹窗组件
├── interfaces/
│   └── version/
│       └── index.js             # API接口定义
├── utils/
│   └── update.js                # 更新核心工具类
├── config/
│   └── index.js                 # 配置文件
└── static/img/
    └── icon-update-close.png    # 关闭按钮图标

核心代码实现

1. 更新工具类 - utils/update.js

import { API_BASE_URL as apiBaseUrl } from '@/config/index.js'
import { versionLatest } from '@/interfaces/version';

export default {
    /**
     * 检查版本更新
     */
    checkUpdate() {
        console.log('检查版本更新...')
        return new Promise((resolve, reject) => {
            // 获取当前版本信息
            const currentVersion = plus.runtime.version
            const currentVersionCode = plus.runtime.versionCode || 0

            console.log('请求服务器版本信息:', currentVersion, currentVersionCode)
            // 请求服务器版本信息
            versionLatest().then((data) => {
                console.log('检查版本更新成功:', data)
                const serverVersion = data.version
                const serverVersionCode = data.versionCode
                const downloadUrl = data.downloadUrl
                // 新版本长度大于当前版本长度或者新版本号首位数字大于当前版本号首位数字,强制更新
                const forceUpdate = serverVersionCode.length > currentVersionCode.length || 
                    this.compareVersionFirst(serverVersionCode, currentVersionCode) === 1
                const updateContent = data.updateContent || '优化体验,修复已知问题'

                // 比较版本号
                console.log('检查版本更新成功22:', serverVersionCode, currentVersionCode, 
                    this.compareVersion(serverVersionCode, currentVersionCode) > 0)
                if (this.compareVersion(serverVersionCode, currentVersionCode) > 0) {
                    resolve({
                        needUpdate: true,
                        forceUpdate,
                        downloadUrl: data.apkPath,
                        updateContent: data.updateDesc,
                        serverVersion: data.versionName,
                        serverVersionCode: data.versionCode,
                        currentVersion
                    })
                } else {
                    resolve({ needUpdate: false })
                }
            }).catch((err) => {
                console.log('检查版本更新失败:', err)
                reject(err)
            })
        })
    },

    /**
     * 新版本号首位数字大于当前版本号首位数字
     */
    compareVersionFirst(v1, v2) {
        if (v1.length == v2.length && v1[0] > v2[0]) {
            return 1
        }
        return 0
    },

    /**
     * 版本号比较
     */
    compareVersion(v1, v2) {
        v1 = parseInt(v1)
        v2 = parseInt(v2)
        if (v1 > v2) return 1
        if (v1 < v2) return -1
        return 0
    },

    /**
     * 下载并安装 APK
     */
    downloadAndInstall(downloadUrl, options = {}) {
        console.log('下载并安装 APK:', downloadUrl)
        return new Promise((resolve, reject) => {
            // 创建下载任务
            const downloadTask = uni.downloadFile({
                url: downloadUrl,
                success: (downloadResult) => {
                    if (downloadResult.statusCode === 200) {
                        this.installApk(downloadResult.tempFilePath)
                        resolve()
                    } else {
                        uni.showToast({
                            title: '下载失败',
                            icon: 'error'
                        })
                        reject(new Error('下载失败'))
                    }
                },
                fail: (err) => {
                    uni.hideLoading()
                    uni.showToast({
                        title: '下载失败',
                        icon: 'error'
                    })
                    reject(err)
                }
            })

            // 监听下载进度
            downloadTask.onProgressUpdate((res) => {
                const progress = res.progress
                if (progress < 100) {
                    options.progress?.(progress)
                }
            })
        })
    },

    /**
     * 安装 APK
     */
    installApk(filePath) {
        // Android 8.0+ 需要请求安装权限
        if (uni.getSystemInfoSync().platform === 'android' &&
            uni.getSystemInfoSync().platformVersion >= '8') {
            // 检查是否有安装权限
            const hasPermission = plus.android.requestPermissions(
                ['android.permission.REQUEST_INSTALL_PACKAGES']
            )

            if (!hasPermission) {
                uni.showModal({
                    title: '提示',
                    content: '需要允许安装未知来源应用权限',
                    showCancel: false,
                    success: () => {
                        // 跳转到设置页面
                        const Intent = plus.android.importClass('android.content.Intent')
                        const Settings = plus.android.importClass('android.provider.Settings')
                        const Uri = plus.android.importClass('android.net.Uri')

                        const intent = new Intent()
                        intent.setAction(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES)
                        const uri = Uri.parse('package:' + plus.runtime.appid)
                        intent.setData(uri)

                        const main = plus.android.runtimeMainActivity()
                        main.startActivityForResult(intent, 0)
                    }
                })
                return
            }
        }

        // 安装 APK
        plus.runtime.install(
            filePath,
            {
                force: false
            },
            () => {
                // 安装完成后重启应用
                setTimeout(() => {
                    plus.runtime.restart()
                }, 1500)
            },
            (error) => {
                console.error('安装失败:', error)
                uni.showModal({
                    title: '安装失败',
                    content: '请手动安装下载的APK文件',
                    showCancel: false
                })
            }
        )
    }
}

2. API 接口定义 - interfaces/version/index.js

import request from '@/utils/request';

// 最新版本信息
export function versionLatest() {
    return request.get('/v1/version/latest');
}

3. 更新弹窗组件 - components/common/zUpdate.vue

<template>
    <uni-popup ref="updatePopup" type="dialog" :mask-click="false">
        <view class="fixed-content">
            <view class="fixed-content-main" v-if="progress == 0">
                <view class="fixed-content-title">{{ title }}</view>
                <view class="fixed-content-msg">{{ message }}</view>
                <view class="fixed-content-submit" @click="handleUpdate">立即升级</view>
                <view v-if="!updateInfo.forceUpdate" class="fixed-content-cancel" @click="handleCancel">暂不升级</view>
            </view>
            <view class="fixed-content-main" v-else>
                <view class="fixed-content-title">下载中...</view>
                <view class="fixed-content-msg progress-container">
                    <progress :percent="progress" activeColor="#10AEFF" stroke-width="3" />
                </view>
            </view>
            <view v-if="!updateInfo.forceUpdate" class="fixed-content-close">
                <image src="/static/img/icon-update-close.png" mode="" class="close" @click="handleCancel"></image>
            </view>
        </view>
    </uni-popup>
</template>

<script>
import updateUtil from '@/utils/update.js'
export default {
    name: 'zUpdate',
    props: {
        // 弹窗标题
        title: {
            type: String,
            default: '发现新版本'
        },
        // 弹窗消息内容
        message: {
            type: String,
            default: '为保证服务正常使用,请立即升级'
        },
        // 是否显示弹窗
        visible: {
            type: Boolean,
            default: false
        }
    },
    data(){
        return {
            progress: 0,
            updateInfo: {
                title: '发现新版本',
                message: '为保证服务正常使用,请立即升级',
                downloadUrl: '',
                forceUpdate: false
            }
        }
    },
    watch: {
        // 监听visible属性变化,控制弹窗显示/隐藏
        visible: {
            handler(newVal) {
                console.log('visible changed:', newVal);
                if (newVal) {
                    this.open();
                } else {
                    this.close();
                }
            },
            immediate: true
        }
    },
    created() {
        console.log('zUpdate created')

        // 应用启动时检查版本更新
        this.checkAppUpdate()
    },
    methods: {
        // 检查应用版本更新
        checkAppUpdate() {
            // 只在App环境下检查更新
            // #ifdef APP-PLUS
            try {
                updateUtil.checkUpdate().then(res => {
                    console.log('检查版本更新成功 app:', res)
                    if (res.needUpdate) {
                        this.$emit('needUpdate', res);
                        // 有新版本
                        this.updateInfo.title = `发现新版本 v${res.serverVersion}`
                        this.updateInfo.message = res.message
                        this.updateInfo.downloadUrl = res.downloadUrl
                        this.updateInfo.forceUpdate = res.forceUpdate
                        this.onOpen();
                    }
                }).catch(err => {
                    console.error('版本检查失败:', err)
                })
            } catch (error) {
                console.error('版本检查异常:', error)
            }
            // #endif
        },
        // 打开弹窗
        open() {
            this.$nextTick(() => {
                this.$refs.updatePopup && this.$refs.updatePopup.open();
            })
        },
        // 关闭弹窗
        close() {
            this.$refs.updatePopup && this.$refs.updatePopup.close();
            this.$emit('close');
        },
        // 处理立即升级点击事件
        handleUpdate() {
            // 隐藏更新对话框
            // this.close()

            // 开始下载更新
            if (this.updateInfo.downloadUrl) {
                updateUtil.downloadAndInstall(this.updateInfo.downloadUrl, {
                    progress: (progress) => {
                        this.progress = progress   
                    }
                }).then(() => {
                    // 下载安装成功
                }).catch(err => {
                    console.error('更新下载失败:', err)
                })
            }
            this.$emit('update');
        },
        // 处理暂不升级点击事件
        handleCancel() {
            this.close();
            this.$emit('cancel');
        },
        onOpen(){
            this.$emit('open');
        }
    }
}
</script>

<style lang="scss" scoped>
.fixed-content {
    .fixed-content-main {
        background-color: #fff;
        border-radius: 8px;
        padding: 30px 30px 20px;
        box-sizing: border-box;

        .fixed-content-title {
            text-align: center;
            font-size: 22px;
            color: $uni-text-color;
        }

        .fixed-content-msg {
            text-align: center;
            font-size: 16px;
            color: $uni-text-color-grey;
            margin: 20px 0;

            &.progress-container{
                width: 200px;
            }
        }

        .fixed-content-submit {
            width: 100%;
            text-align: center;
            background-color: $uni-color-primary;
            height: 40px;
            line-height: 40px;
            border-radius: 40px;
            color: $uni-text-color-inverse;
            font-size: 16px;
            margin: 20px 0;
        }

        .fixed-content-cancel {
            text-align: center;
            color: $uni-text-color-grey;
            font-size: 16px;
            margin: 20px 0 0;
        }
    }

    .fixed-content-close {
        text-align: center;
        margin: 20px 0 0;

        .close {
            width: 36px;
            height: 36px;
        }
    }
}
</style>

4. 配置文件 - config/index.js

// 配置文件 - 集中管理项目中的各种配置项

// API服务基础URL
// export const API_BASE_URL = 'http://192.168.1.100:4049';
export const API_BASE_URL = 'https://api.example.com';

// 文件上传服务基础URL
// export const UPLOAD_BASE_URL = 'http://192.168.1.100:4048';
export const UPLOAD_BASE_URL = 'https://upload.example.com';

// H5代理路径
export const H5_PROXY_PATH = '/api';

服务器接口格式

后端接口 /v1/version/latest 返回格式:

{
  "versionName": "1.2.0",
  "versionCode": "10200",
  "apkPath": "http://your-domain.com/app-release.apk",
  "updateDesc": "1. 修复已知问题\n2. 优化用户体验",
  "forceUpdate": false
}

使用方式

方式一:组件自动检测(推荐)

App.vue 或首页中引入组件,组件会自动检测更新:

<template>
  <view>
    <!-- 页面内容 -->
    <zUpdate />
  </view>
</template>

<script>
import zUpdate from '@/components/common/zUpdate.vue'

export default {
  components: { zUpdate }
}
</script>

方式二:手动控制显示

通过 visible 属性控制弹窗显示:

<template>
  <view>
    <!-- 页面内容 -->
    <zUpdate 
      :visible="showUpdate" 
      @close="showUpdate = false" 
      @needUpdate="handleNeedUpdate"
    />
  </view>
</template>

<script>
import zUpdate from '@/components/common/zUpdate.vue'

export default {
  components: { zUpdate },
  data() {
    return {
      showUpdate: false
    }
  },
  methods: {
    handleNeedUpdate(info) {
      console.log('检测到新版本:', info)
      this.showUpdate = true
    }
  }
}
</script>

功能亮点总结

特性说明
强制更新大版本更新时强制用户升级,无法跳过
普通更新小版本更新允许用户选择"暂不升级"
进度显示实时显示下载进度条,提升用户体验
权限处理自动处理 Android 8.0+ 的未知来源安装权限
自动重启安装成功后自动重启应用
条件编译通过 #ifdef APP-PLUS 仅在 App 环境生效
错误处理完善的异常捕获和错误提示

注意事项

  1. 依赖 uni-popup 组件:需要安装 uni-app 官方的 uni-popup 插件
  2. 后端接口:需要后端提供版本查询接口,返回正确的 JSON 格式
  3. APK 下载地址:确保 apkPath 是可访问的 APK 文件地址
  4. 版本号比较:版本号应为纯数字,用于整数比较
  5. Android 权限:需要在 manifest.json 中配置相关权限

相关权限配置

manifest.json 中添加:

{
  "app-plus": {
    "distribute": {
      "android": {
        "permissions": [
          "<uses-permission android:name=\"android.permission.REQUEST_INSTALL_PACKAGES\"/>",
          "<uses-permission android:name=\"android.permission.INTERNET\"/>",
          "<uses-permission android:name=\"android.permission.WRITE_EXTERNAL_STORAGE\"/>"
        ]
      }
    }
  }
}

这个功能可以直接集成到任何 UniApp Android 项目中,开箱即用!