概述
在移动应用开发中,版本更新是一个至关重要的功能。本文基于一个实际项目,详细介绍如何在 React Native 应用中实现完整的自动版本检测和更新功能。该方案支持:
- ✅ 自动版本检查:应用启动时和定期检查版本更新
- ✅ 强制更新支持:支持强制更新和可选更新
- ✅ 智能重试机制:网络失败时自动重试
- ✅ 应用状态监听:从后台回到前台时自动检查
- ✅ 美观的更新UI:提供友好的更新对话框
- ✅ 跨平台支持:支持 Android 和 iOS
架构设计
整体架构图
┌─────────────────────────────────────────────────────────┐
│ VersionUpdateManager │
│ (版本更新管理器 - 顶层组件) │
└────────────────────┬────────────────────────────────────┘
│
┌────────────┴────────────┐
│ │
┌───────▼────────┐ ┌─────────▼──────────┐
│ useVersionCheck│ │ VersionUpdateDialog │
│ (Hook) │ │ (更新对话框) │
└───────┬────────┘ └─────────────────────┘
│
┌───────▼────────┐
│ versionManager │
│ (工具类) │
└───────┬────────┘
│
┌───────▼────────┐
│ versionService │
│ (API服务) │
└────────────────┘
核心模块说明
- VersionUpdateManager:顶层组件,包装整个应用
- useVersionCheck:自定义 Hook,提供版本检查逻辑和状态管理
- VersionUpdateDialog:更新对话框 UI 组件
- versionManager:版本管理工具类,处理版本比较、检查、下载等
- versionService:API 服务层,与后端通信
核心组件详解
1. VersionManager(版本管理工具类)
这是整个版本更新系统的核心,负责:
- 获取当前应用版本
- 版本号比较
- 检查版本更新
- 下载和安装更新包
关键特性:
- 单例模式,确保全局唯一实例
- 版本号语义化比较(支持 x.y.z 格式)
- 支持强制更新判断逻辑
2. useVersionCheck Hook
提供版本检查的 React Hook,封装了:
- 自动检查逻辑
- 定时检查机制
- 应用状态监听
- 重试机制
- 状态管理
关键特性:
- 支持自定义检查间隔
- 智能重试机制(避免频繁请求)
- 监听应用前后台切换
3. VersionUpdateDialog 组件
更新对话框 UI 组件,展示:
- 版本号信息
- 更新内容说明
- 更新包大小
- 更新/取消按钮
关键特性:
- 强制更新时隐藏取消按钮
- 实时显示更新状态(检查中/下载中/安装中)
- 响应式设计
实现步骤
步骤 1:创建版本 API 服务
首先定义版本信息接口和 API 调用方法。
步骤 2:实现版本管理工具类
创建 VersionManager 单例类,实现版本比较和更新检查逻辑。
步骤 3:开发版本检查 Hook
创建 useVersionCheck Hook,封装版本检查的状态管理和逻辑。
步骤 4:构建更新对话框组件
创建 VersionUpdateDialog 组件,提供友好的更新提示界面。
步骤 5:集成版本更新管理器
在应用入口处集成 VersionUpdateManager 组件。
核心代码实现
1. 版本 API 服务(versionService.ts)
typescript
// 使用 axios 或其他 HTTP 客户端
import axios from 'axios';
// 或者使用 fetch API
// const API_BASE_URL = 'https://your-api-server.com';
// 版本信息接口
export interface VersionInfo {
version: string;
minVersion: string;
downloadUrl: string;
releaseNotes: string;
forceUpdate: boolean;
updateSize?: string;
updateTime?: string;
}
// 版本检查响应接口
export interface VersionCheckResponse {
code: number;
data: VersionInfo;
message?: string;
error?: string;
}
// 当前应用版本信息
export interface CurrentVersionInfo {
version: string;
platform: 'android' | 'ios';
appType?: string; // 可选的应用类型,根据实际需求定义
}
// 版本管理服务
export const versionService = {
// 获取版本信息(合并了检查更新和获取最新版本的功能)
getVersionInfo: async (currentVersion: CurrentVersionInfo): Promise<VersionCheckResponse> => {
// 使用 axios(需要配置 baseURL)
const API_BASE_URL = 'https://your-api-server.com'; // 替换为实际的API地址
const response = await axios.post<VersionCheckResponse>(
`${API_BASE_URL}/api/version/info`,
currentVersion
);
return response.data;
// 或者使用 fetch
// const API_BASE_URL = 'https://your-api-server.com';
// const response = await fetch(`${API_BASE_URL}/api/version/info`, {
// method: 'POST',
// headers: { 'Content-Type': 'application/json' },
// body: JSON.stringify(currentVersion),
// });
// const data = await response.json();
// return data;
},
};
说明:
VersionInfo:服务器返回的最新版本信息CurrentVersionInfo:当前应用的基本信息(版本号、平台、应用类型)getVersionInfo:调用后端 API 获取版本信息
2. 版本管理工具类(versionManager.ts)
typescript
import { Platform, Linking } from 'react-native';
import { versionService, VersionInfo, CurrentVersionInfo } from '../api/versionService';
// 根据项目需求,可以导入应用配置或使用默认值
// import { APP_CONFIG } from '../config/appConfig';
// 版本比较结果
export type VersionCompareResult = 'newer' | 'same' | 'older';
// 版本更新状态
export type UpdateStatus =
| 'checking'
| 'available'
| 'upToDate'
| 'downloading'
| 'installing'
| 'completed'
| 'error';
// 版本管理器
export class VersionManager {
private static instance: VersionManager;
private updateStatus: UpdateStatus = 'checking';
private currentVersion: CurrentVersionInfo | null = null;
private latestVersion: VersionInfo | null = null;
private constructor() {}
public static getInstance(): VersionManager {
if (!VersionManager.instance) {
VersionManager.instance = new VersionManager();
}
return VersionManager.instance;
}
/**
* 获取当前应用版本信息
*/
public getCurrentVersion(): CurrentVersionInfo {
if (this.currentVersion) {
return this.currentVersion;
}
// 从package.json动态获取版本信息
let packageVersion = '0.0.1'; // 默认版本
try {
// 读取package.json的版本号
const packageJson = require('../../package.json');
packageVersion = packageJson.version || '0.0.1';
} catch (error) {
console.warn('无法读取package.json版本信息,使用默认版本:', error);
}
this.currentVersion = {
version: packageVersion,
platform: Platform.OS as 'android' | 'ios',
// 根据实际需求设置应用类型,或从配置文件读取
// appType: APP_CONFIG.appType || 'mobile',
};
return this.currentVersion;
}
/**
* 比较版本号
* @param version1 版本1
* @param version2 版本2
* @returns 比较结果
*/
public compareVersions(version1: string, version2: string): VersionCompareResult {
const v1Parts = version1.split('.').map(Number);
const v2Parts = version2.split('.').map(Number);
// 补齐版本号位数
const maxLength = Math.max(v1Parts.length, v2Parts.length);
while (v1Parts.length < maxLength) v1Parts.push(0);
while (v2Parts.length < maxLength) v2Parts.push(0);
for (let i = 0; i < maxLength; i++) {
if (v1Parts[i] > v2Parts[i]) return 'newer';
if (v1Parts[i] < v2Parts[i]) return 'older';
}
return 'same';
}
/**
* 检查版本更新
*/
public async checkForUpdate(): Promise<{
hasUpdate: boolean;
versionInfo?: VersionInfo;
isForceUpdate: boolean;
}> {
try {
this.updateStatus = 'checking';
const currentVersion = this.getCurrentVersion();
const response = await versionService.getVersionInfo(currentVersion);
// 检查业务状态码:支持 0 或 200 都视为成功
const isSuccess = response.code === 0 || response.code === 200;
if (!isSuccess) {
console.error('[版本检查] API返回错误:', response.message || response.error);
return { hasUpdate: false, versionInfo: undefined, isForceUpdate: false };
}
if (!response.data) {
console.error('[版本检查] API响应中没有data字段');
return { hasUpdate: false, versionInfo: undefined, isForceUpdate: false };
}
const latestVersion = response.data;
// 比较当前版本与最新版本
const isOlderThanLatest =
this.compareVersions(currentVersion.version, latestVersion.version) === 'older';
// 比较当前版本与最小版本(如果当前版本小于minVersion,则需要更新)
const isOlderThanMinVersion =
this.compareVersions(currentVersion.version, latestVersion.minVersion) === 'older';
// 判断是否需要更新:当前版本小于最新版本 OR 当前版本小于最小版本
const hasUpdate = isOlderThanLatest || isOlderThanMinVersion;
// 判断是否需要强制更新:
// 1. 接口返回的forceUpdate为true
// 2. 当前版本小于最小版本(必须强制更新)
const isForceUpdate = latestVersion.forceUpdate || isOlderThanMinVersion;
console.log(
`[版本检查] 当前: ${currentVersion.version}, 最新: ${latestVersion.version}, 最小: ${latestVersion.minVersion}, 需要更新: ${hasUpdate ? '是' : '否'}, 强制更新: ${isForceUpdate ? '是' : '否'}`
);
if (hasUpdate) {
this.latestVersion = latestVersion;
this.updateStatus = 'available';
return {
hasUpdate: true,
versionInfo: latestVersion,
isForceUpdate,
};
} else {
this.updateStatus = 'upToDate';
return {
hasUpdate: false,
versionInfo: undefined,
isForceUpdate: false,
};
}
} catch (error: any) {
console.error('版本检查失败:', error);
this.updateStatus = 'error';
return { hasUpdate: false, versionInfo: undefined, isForceUpdate: false };
}
}
/**
* 下载并安装更新
*/
public async downloadAndInstall(downloadUrl: string): Promise<void> {
try {
this.updateStatus = 'downloading';
// 在Android上,直接打开下载链接
if (Platform.OS === 'android') {
const canOpen = await Linking.canOpenURL(downloadUrl);
if (canOpen) {
await Linking.openURL(downloadUrl);
this.updateStatus = 'installing';
} else {
throw new Error('无法打开下载链接');
}
} else {
// iOS通过App Store更新
const canOpen = await Linking.canOpenURL(downloadUrl);
if (canOpen) {
await Linking.openURL(downloadUrl);
} else {
throw new Error('无法打开App Store');
}
}
} catch (error) {
console.error('下载安装失败:', error);
this.updateStatus = 'error';
throw error;
}
}
/**
* 获取更新状态
*/
public getUpdateStatus(): UpdateStatus {
return this.updateStatus;
}
/**
* 获取最新版本信息
*/
public getLatestVersion(): VersionInfo | null {
return this.latestVersion;
}
/**
* 重置更新状态
*/
public resetUpdateStatus(): void {
this.updateStatus = 'checking';
}
/**
* 检查是否为强制更新
*/
public isForceUpdate(): boolean {
return this.latestVersion?.forceUpdate || false;
}
/**
* 清除当前版本缓存,重新读取版本信息
*/
public resetCurrentVersion(): void {
this.currentVersion = null;
}
}
// 导出单例实例
export const versionManager = VersionManager.getInstance();
核心方法说明:
getCurrentVersion():从package.json读取当前版本号,结合平台和应用类型构建版本信息compareVersions():语义化版本比较,支持x.y.z格式,返回newer、same或oldercheckForUpdate():- 调用 API 获取最新版本信息
- 比较当前版本与最新版本、最小版本
- 判断是否需要更新和是否强制更新
downloadAndInstall():使用LinkingAPI 打开下载链接(Android)或 App Store(iOS)
3. 版本检查 Hook(useVersionCheck.ts)
typescript
import { useState, useEffect, useCallback, useRef } from 'react';
import { AppState, AppStateStatus } from 'react-native';
import { versionManager, UpdateStatus } from '../utils/versionManager';
import { VersionInfo } from '../api/versionService';
interface UseVersionCheckOptions {
autoCheck?: boolean;
checkInterval?: number; // 检查间隔(毫秒)
maxRetries?: number; // 最大重试次数
retryDelay?: number; // 重试延迟(毫秒)
onUpdateAvailable?: (versionInfo: VersionInfo) => void;
onUpdateRequired?: (versionInfo: VersionInfo) => void;
}
interface UseVersionCheckReturn {
isChecking: boolean;
hasUpdate: boolean;
latestVersion: VersionInfo | null;
updateStatus: UpdateStatus;
checkForUpdate: () => Promise<void>;
showUpdateDialog: () => void;
hideUpdateDialog: () => void;
isUpdateDialogVisible: boolean;
isForceUpdate: boolean;
}
export const useVersionCheck = (options: UseVersionCheckOptions = {}): UseVersionCheckReturn => {
const {
autoCheck = true,
checkInterval = 5 * 60 * 1000, // 默认5分钟检查一次
maxRetries = 3, // 默认最大重试3次
retryDelay = 30000, // 默认重试延迟30秒
onUpdateAvailable,
onUpdateRequired,
} = options;
const [isChecking, setIsChecking] = useState(false);
const [hasUpdate, setHasUpdate] = useState(false);
const [latestVersion, setLatestVersion] = useState<VersionInfo | null>(null);
const [updateStatus, setUpdateStatus] = useState<UpdateStatus>('checking');
const [isUpdateDialogVisible, setIsUpdateDialogVisible] = useState(false);
const [isForceUpdate, setIsForceUpdate] = useState(false);
// 使用ref存储重试计数和上次错误时间,避免重新渲染
const retryCountRef = useRef(0);
const lastErrorTimeRef = useRef(0);
const lastCheckTimeRef = useRef(0);
const checkForUpdateRef = useRef<(() => Promise<void>) | undefined>(undefined);
// 检查更新
const checkForUpdate = useCallback(async () => {
if (isChecking) return;
// 检查距离上次错误的时间,避免频繁重试
const now = Date.now();
const timeSinceLastError = now - lastErrorTimeRef.current;
if (lastErrorTimeRef.current > 0 && timeSinceLastError < retryDelay) {
return;
}
// 检查是否超过最大重试次数
if (retryCountRef.current >= maxRetries) {
return;
}
// 记录本次检查时间
lastCheckTimeRef.current = now;
try {
setIsChecking(true);
setUpdateStatus('checking');
const result = await versionManager.checkForUpdate();
// 检查成功,重置重试计数
retryCountRef.current = 0;
lastErrorTimeRef.current = 0;
if (result.hasUpdate && result.versionInfo) {
setHasUpdate(true);
setLatestVersion(result.versionInfo);
setIsForceUpdate(result.isForceUpdate);
// 触发回调
if (result.isForceUpdate) {
onUpdateRequired?.(result.versionInfo);
setIsUpdateDialogVisible(true);
} else {
onUpdateAvailable?.(result.versionInfo);
}
} else {
setHasUpdate(false);
setLatestVersion(null);
setIsForceUpdate(false);
}
} catch (error) {
console.error('[版本检查] 失败:', error);
setUpdateStatus('error');
// 增加重试计数
retryCountRef.current += 1;
lastErrorTimeRef.current = Date.now();
} finally {
setIsChecking(false);
}
}, [isChecking, maxRetries, retryDelay, onUpdateAvailable, onUpdateRequired]);
// 更新ref
checkForUpdateRef.current = checkForUpdate;
// 显示更新对话框
const showUpdateDialog = useCallback(() => {
if (hasUpdate && latestVersion) {
setIsUpdateDialogVisible(true);
}
}, [hasUpdate, latestVersion]);
// 隐藏更新对话框
const hideUpdateDialog = useCallback(() => {
if (!isForceUpdate) {
setIsUpdateDialogVisible(false);
}
}, [isForceUpdate]);
// 监听应用状态变化
useEffect(() => {
if (!autoCheck) return;
const handleAppStateChange = (nextAppState: AppStateStatus) => {
if (nextAppState === 'active') {
// 应用从后台回到前台时检查更新
const now = Date.now();
const timeSinceLastCheck = now - lastCheckTimeRef.current;
// 如果距离上次检查超过1分钟,才检查更新
if (timeSinceLastCheck > 60000) {
checkForUpdateRef.current?.();
}
}
};
const subscription = AppState.addEventListener('change', handleAppStateChange);
return () => subscription?.remove();
}, [autoCheck]);
// 定时检查更新
useEffect(() => {
if (!autoCheck) return;
// 立即检查一次
if (checkForUpdateRef.current) {
checkForUpdateRef.current();
} else {
// 延迟一下,等checkForUpdateRef被赋值
setTimeout(() => {
checkForUpdateRef.current?.();
}, 100);
}
// 设置定时器
const interval = setInterval(() => {
checkForUpdateRef.current?.();
}, checkInterval);
return () => clearInterval(interval);
}, [autoCheck, checkInterval]);
// 监听更新状态变化
useEffect(() => {
const interval = setInterval(() => {
const currentStatus = versionManager.getUpdateStatus();
setUpdateStatus(currentStatus);
}, 1000);
return () => clearInterval(interval);
}, []);
return {
isChecking,
hasUpdate,
latestVersion,
updateStatus,
checkForUpdate,
showUpdateDialog,
hideUpdateDialog,
isUpdateDialogVisible,
isForceUpdate,
};
};
核心功能说明:
-
自动检查机制:
- 应用启动时立即检查一次
- 按设定的时间间隔定期检查
- 应用从后台回到前台时检查(距离上次检查超过1分钟)
-
智能重试机制:
- 网络失败时自动重试
- 限制重试次数和重试间隔,避免频繁请求
- 使用
ref存储重试状态,避免不必要的重新渲染
-
状态管理:
- 管理检查状态、更新状态、对话框显示状态
- 实时同步
versionManager的更新状态
4. 更新对话框组件(VersionUpdateDialog.tsx)

typescript
import React, { useState, useEffect } from 'react';
import { View, Text, StyleSheet, ScrollView, TouchableOpacity } from 'react-native';
import { versionManager, UpdateStatus } from '../utils/versionManager';
import { VersionInfo } from '../api/versionService';
interface VersionUpdateDialogProps {
visible: boolean;
versionInfo: VersionInfo;
onUpdate: () => void;
onCancel: () => void;
}
export const VersionUpdateDialog: React.FC<VersionUpdateDialogProps> = ({
visible,
versionInfo,
onUpdate,
onCancel,
}) => {
const [updateStatus, setUpdateStatus] = useState<UpdateStatus>('checking');
useEffect(() => {
if (visible) {
setUpdateStatus(versionManager.getUpdateStatus());
}
}, [visible]);
useEffect(() => {
const interval = setInterval(() => {
setUpdateStatus(versionManager.getUpdateStatus());
}, 1000);
return () => clearInterval(interval);
}, []);
const handleUpdate = async () => {
try {
onUpdate();
await versionManager.downloadAndInstall(versionInfo.downloadUrl);
} catch (error) {
console.error('更新失败:', error);
}
};
const handleCancel = () => {
if (versionInfo.forceUpdate) {
return;
}
onCancel();
};
if (!visible) return null;
return (
<View style={styles.overlay}>
<View style={styles.dialog}>
{/* Header */}
<View style={styles.header}>
<Text style={styles.title}>发现新版本</Text>
<Text style={styles.versionText}>v{versionInfo.version}</Text>
</View>
{/* Content */}
<ScrollView style={styles.scrollContent} showsVerticalScrollIndicator={false}>
<View style={styles.content}>
{/* Release Notes */}
{versionInfo.releaseNotes && (
<View style={styles.releaseNotesContainer}>
<Text style={styles.releaseNotesTitle}>更新内容</Text>
<Text style={styles.releaseNotes}>{versionInfo.releaseNotes}</Text>
</View>
)}
{/* Update Size */}
{versionInfo.updateSize && (
<Text style={styles.updateSize}>大小:{versionInfo.updateSize}</Text>
)}
</View>
</ScrollView>
{/* Footer */}
<View style={styles.footer}>
{!versionInfo.forceUpdate && (
<TouchableOpacity
onPress={handleCancel}
style={[styles.button, styles.cancelButton]}
disabled={updateStatus === 'downloading' || updateStatus === 'installing'}
>
<Text style={styles.cancelButtonText}>稍后更新</Text>
</TouchableOpacity>
)}
<TouchableOpacity
onPress={handleUpdate}
style={[styles.button, styles.updateButton]}
disabled={updateStatus === 'downloading' || updateStatus === 'installing'}
>
<Text style={styles.updateButtonText}>
{updateStatus === 'downloading' || updateStatus === 'installing'
? '更新中...'
: '立即更新'}
</Text>
</TouchableOpacity>
</View>
</View>
</View>
);
};
const styles = StyleSheet.create({
overlay: {
position: 'absolute',
top: 0,
left: 0,
right: 0,
bottom: 0,
backgroundColor: 'rgba(0, 0, 0, 0.5)',
justifyContent: 'center',
alignItems: 'center',
zIndex: 9999,
},
dialog: {
backgroundColor: '#FFFFFF',
borderRadius: 12,
maxWidth: 400,
width: '85%',
maxHeight: '70%',
},
header: {
padding: 20,
alignItems: 'center',
borderBottomWidth: 1,
borderBottomColor: '#F0F0F0',
},
title: {
fontSize: 18,
fontWeight: 'bold',
color: '#333333',
marginBottom: 8,
textAlign: 'center',
},
versionText: {
fontSize: 16,
color: '#007AFF',
fontWeight: '600',
},
scrollContent: {
maxHeight: 250,
},
content: {
padding: 20,
},
releaseNotesContainer: {
marginBottom: 16,
},
releaseNotesTitle: {
fontSize: 16,
fontWeight: '600',
color: '#333333',
marginBottom: 12,
},
releaseNotes: {
fontSize: 14,
color: '#666666',
lineHeight: 22,
},
updateSize: {
fontSize: 14,
color: '#666666',
},
footer: {
flexDirection: 'row',
padding: 20,
paddingTop: 16,
gap: 12,
borderTopWidth: 1,
borderTopColor: '#F0F0F0',
},
button: {
flex: 1,
padding: 12,
borderRadius: 8,
alignItems: 'center',
justifyContent: 'center',
},
cancelButton: {
backgroundColor: '#F5F5F5',
borderWidth: 1,
borderColor: '#DDDDDD',
},
cancelButtonText: {
fontSize: 16,
color: '#333333',
},
updateButton: {
backgroundColor: '#007AFF',
},
updateButtonText: {
fontSize: 16,
color: '#FFFFFF',
fontWeight: '600',
},
});
export default VersionUpdateDialog;
组件特性:
- 模态对话框:使用遮罩层和居中对话框
- 版本信息展示:显示版本号、更新内容、更新包大小
- 强制更新处理:强制更新时隐藏取消按钮
- 状态反馈:实时显示更新状态(检查中/下载中/安装中)
- 响应式设计:适配不同屏幕尺寸
5. 版本更新管理器(VersionUpdateManager.tsx)
typescript
import React from 'react';
import { View } from 'react-native';
import { useVersionCheck } from '../hooks/useVersionCheck';
import { VersionUpdateDialog } from '../components/VersionUpdateDialog';
import { versionManager } from '../utils/versionManager';
interface VersionUpdateManagerProps {
children: React.ReactNode;
autoCheck?: boolean;
checkInterval?: number;
maxRetries?: number;
retryDelay?: number;
}
export const VersionUpdateManager: React.FC<VersionUpdateManagerProps> = ({
children,
autoCheck = true,
checkInterval = 5 * 60 * 1000, // 5分钟
maxRetries = 3,
retryDelay = 30000, // 30秒
}) => {
const {
hasUpdate,
latestVersion,
isUpdateDialogVisible,
isForceUpdate,
showUpdateDialog,
hideUpdateDialog,
checkForUpdate,
} = useVersionCheck({
autoCheck,
checkInterval,
maxRetries,
retryDelay,
onUpdateAvailable: (versionInfo) => {
console.log('发现新版本:', versionInfo.version);
// 可以在这里添加自定义逻辑,比如显示通知
},
onUpdateRequired: (versionInfo) => {
console.log('需要强制更新:', versionInfo.version);
// 强制更新会自动显示对话框
},
});
const handleUpdate = async () => {
if (!latestVersion) return;
try {
await versionManager.downloadAndInstall(latestVersion.downloadUrl);
} catch (error) {
console.error('更新失败:', error);
}
};
const handleCancel = () => {
hideUpdateDialog();
};
return (
<View style={{ flex: 1 }}>
{children}
{hasUpdate && latestVersion && (
<VersionUpdateDialog
visible={isUpdateDialogVisible}
versionInfo={latestVersion}
onUpdate={handleUpdate}
onCancel={handleCancel}
/>
)}
</View>
);
};
export default VersionUpdateManager;
组件职责:
- 包装应用:作为顶层组件,包装整个应用
- 配置管理:接收配置参数(自动检查、检查间隔、重试次数等)
- 事件处理:处理更新和取消操作
- 条件渲染:只在有更新时显示对话框
使用示例
示例 1:在应用入口集成版本更新管理器
typescript
import React from 'react';
import { View } from 'react-native';
import { VersionUpdateManager } from './components/VersionUpdateManager';
const App = (): React.JSX.Element => {
return (
<VersionUpdateManager autoCheck={true} checkInterval={5 * 60 * 1000}>
{/* 你的应用内容 */}
<AppContent />
</VersionUpdateManager>
);
};
export default App;
说明:
- 将
VersionUpdateManager包装在应用的根组件外层 - 设置
autoCheck={true}启用自动检查 - 设置
checkInterval控制检查间隔(5分钟)
示例 2:在设置页面手动检查更新
typescript
import React from 'react';
import { View, Text, Button } from 'react-native';
import { useVersionCheck } from './hooks/useVersionCheck';
const SettingsScreen: React.FC = () => {
const {
isChecking,
hasUpdate,
latestVersion,
checkForUpdate,
showUpdateDialog,
} = useVersionCheck({
autoCheck: false, // 不自动检查
onUpdateAvailable: (versionInfo) => {
console.log('发现新版本:', versionInfo.version);
showUpdateDialog(); // 手动显示对话框
},
});
const handleCheckUpdate = async () => {
await checkForUpdate();
if (hasUpdate) {
showUpdateDialog();
}
};
return (
<View>
<Text>当前版本: v1.0.0</Text>
<Button
title={isChecking ? '检查中...' : '检查更新'}
onPress={handleCheckUpdate}
disabled={isChecking}
/>
{hasUpdate && latestVersion && (
<Text>发现新版本: v{latestVersion.version}</Text>
)}
</View>
);
};
示例 3:自定义检查逻辑
typescript
import { useVersionCheck } from './hooks/useVersionCheck';
const CustomUpdateChecker: React.FC = () => {
const { checkForUpdate, hasUpdate, latestVersion } = useVersionCheck({
autoCheck: true,
checkInterval: 10 * 60 * 1000, // 10分钟检查一次
maxRetries: 5, // 最多重试5次
retryDelay: 60000, // 重试延迟60秒
onUpdateAvailable: (versionInfo) => {
// 自定义处理逻辑
if (versionInfo.version.includes('beta')) {
// Beta版本不自动弹出
console.log('发现Beta版本,不自动提示');
} else {
// 正式版本自动提示
showUpdateDialog();
}
},
onUpdateRequired: (versionInfo) => {
// 强制更新处理
Alert.alert('必须更新', '请更新到最新版本以继续使用');
},
});
// 组件挂载时检查一次
useEffect(() => {
checkForUpdate();
}, []);
return null;
};
最佳实践
1. 版本号管理
- 使用语义化版本号(Semantic Versioning):
主版本号.次版本号.补丁版本号 - 在
package.json中维护版本号,确保与应用版本一致 - 使用自动化脚本同步版本号到原生代码(Android 的
build.gradle、iOS 的Info.plist)
2. 检查频率控制
- 启动时检查:应用启动时立即检查一次
- 定期检查:建议间隔 5-10 分钟,避免过于频繁
- 后台唤醒检查:应用从后台回到前台时检查,但限制最小间隔(如 1 分钟)
3. 错误处理和重试
- 实现智能重试机制,避免网络波动导致的检查失败
- 限制重试次数和重试间隔,防止无限重试
- 记录错误日志,便于排查问题
4. 用户体验优化
- 强制更新:对于重大安全更新或 API 变更,使用强制更新
- 可选更新:常规更新允许用户选择稍后更新
- 更新提示:提供清晰的版本说明和更新内容
- 状态反馈:实时显示更新状态(检查中/下载中/安装中)
5. 安全性考虑
- 验证下载链接的有效性
- 使用 HTTPS 协议传输版本信息
- 对更新包进行签名验证(Android)
- 防止中间人攻击(MITM)
6. 跨平台兼容性
- Android:直接打开下载链接,引导用户安装 APK
- iOS:跳转到 App Store 进行更新
- 根据平台特性提供不同的更新流程
常见问题
Q1: 如何判断是否需要更新?
A: 通过比较当前版本号与服务器返回的最新版本号:
- 如果当前版本 < 最新版本,需要更新
- 如果当前版本 < 最小版本(minVersion),需要强制更新
Q2: 强制更新和可选更新的区别?
A:
- 强制更新:用户无法取消,必须更新才能继续使用应用
- 可选更新:用户可以稍后更新,应用可以继续使用
Q3: 如何避免频繁检查更新?
A:
- 设置合理的检查间隔(建议 5-10 分钟)
- 应用从后台回到前台时,限制最小检查间隔(如 1 分钟)
- 实现智能重试机制,避免网络错误导致的频繁重试
Q4: iOS 应用如何更新?
A: iOS 应用必须通过 App Store 更新,不能直接下载安装包。可以将下载链接设置为 App Store 链接,使用 Linking.openURL() 打开。
Q5: 如何处理版本检查失败?
A:
- 实现重试机制,网络失败时自动重试
- 限制重试次数和重试间隔
- 记录错误日志,便于排查问题
- 失败时不影响应用正常使用
Q6: 如何测试版本更新功能?
A:
- 在测试环境中配置不同的版本号
- 模拟服务器返回不同版本的更新信息
- 测试强制更新和可选更新场景
- 测试网络错误和重试机制
总结
本文详细介绍了一个完整的 React Native 应用版本自动检测和更新方案。该方案具有以下特点:
- 架构清晰:采用分层架构,职责明确,易于维护
- 功能完整:支持自动检查、强制更新、智能重试、状态管理
- 用户体验好:提供友好的更新提示和状态反馈
- 可扩展性强:支持自定义配置和回调函数
- 跨平台支持:同时支持 Android 和 iOS
核心优势
- ✅ 自动化:应用启动和定期自动检查,无需用户手动操作
- ✅ 智能化:智能重试机制,网络失败时自动重试
- ✅ 用户友好:清晰的更新提示和状态反馈
- ✅ 灵活配置:支持自定义检查间隔、重试次数等参数
- ✅ 强制更新支持:支持强制更新和可选更新两种模式
适用场景
- 需要频繁更新应用功能的场景
- 需要快速修复关键 Bug 的场景
- 需要强制用户更新到特定版本的场景
- 需要提供良好更新体验的场景
后续优化方向
- 增量更新:支持增量更新,减少下载包大小
- 下载进度:显示详细的下载进度
- 后台下载:支持后台下载更新包
- 更新统计:统计更新成功率、用户更新行为等
- A/B 测试:支持灰度发布和 A/B 测试