React Native热更新 #

概述 #

热更新允许在不重新发布应用的情况下更新 JavaScript 代码和资源。CodePush 是微软提供的开源热更新服务。

安装配置 #

安装 CLI #

bash
npm install -g code-push-cli

注册账号 #

bash
code-push register

创建应用 #

bash
code-push app add MyApp ios react-native
code-push app add MyApp android react-native

安装 SDK #

bash
npm install react-native-code-push
cd ios && pod install

iOS 配置 #

ios/MyApp/Info.plist 中添加:

xml
<key>CodePushDeploymentKey</key>
<string>YOUR_DEPLOYMENT_KEY</string>

Android 配置 #

android/app/src/main/java/com/myapp/MainApplication.java 中:

java
import com.microsoft.codepush.react.CodePush;

public class MainApplication extends Application implements ReactApplication {
  private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) {
    @Override
    protected String getJSBundleFile() {
      return CodePush.getJSBundleFile();
    }

    @Override
    public boolean getUseDeveloperSupport() {
      return BuildConfig.DEBUG;
    }

    @Override
    protected List<ReactPackage> getPackages() {
      return Arrays.<ReactPackage>asList(
        new MainReactPackage(),
        new CodePush("YOUR_DEPLOYMENT_KEY", MainApplication.this, BuildConfig.DEBUG)
      );
    }
  };
}

基本使用 #

包装组件 #

tsx
import codePush from 'react-native-code-push';

const App = () => {
  return (
    <NavigationContainer>
      {}
    </NavigationContainer>
  );
};

const codePushOptions = {
  checkFrequency: codePush.CheckFrequency.ON_APP_START,
  installMode: codePush.InstallMode.IMMEDIATE,
};

export default codePush(codePushOptions)(App);

配置选项 #

tsx
interface CodePushOptions {
  checkFrequency: codePush.CheckFrequency;
  installMode: codePush.InstallMode;
  mandatoryInstallMode: codePush.InstallMode;
  minimumBackgroundDuration: number;
  updateDialog: UpdateDialog | false;
  rollbackRetryOptions: RollbackRetryOptions;
}

const options: CodePushOptions = {
  checkFrequency: codePush.CheckFrequency.ON_APP_START,
  installMode: codePush.InstallMode.ON_NEXT_RESTART,
  mandatoryInstallMode: codePush.InstallMode.IMMEDIATE,
  minimumBackgroundDuration: 0,
  updateDialog: {
    title: '更新可用',
    mandatoryUpdateMessage: '必须安装更新',
    optionalUpdateMessage: '有新版本可用,是否安装?',
    optionalIgnoreButtonLabel: '稍后',
    optionalInstallButtonLabel: '安装',
    mandatoryContinueButtonLabel: '继续',
  },
};

export default codePush(options)(App);

检查频率 #

选项 说明
ON_APP_START 应用启动时检查
ON_APP_RESUME 应用恢复时检查
MANUAL 手动检查

安装模式 #

选项 说明
IMMEDIATE 立即安装
ON_NEXT_RESTART 下次重启安装
ON_NEXT_RESUME 下次恢复时安装

手动控制 #

手动检查更新 #

tsx
import codePush from 'react-native-code-push';

const checkForUpdate = async () => {
  const update = await codePush.checkForUpdate();
  
  if (update) {
    console.log('有新版本可用');
    console.log('版本:', update.appVersion);
    console.log('大小:', update.packageSize);
    console.log('是否强制:', update.isMandatory);
  } else {
    console.log('已是最新版本');
  }
};

下载并安装更新 #

tsx
const downloadAndUpdate = async () => {
  const update = await codePush.checkForUpdate();
  
  if (!update) {
    return;
  }

  update.download(progress => {
    console.log(`下载进度: ${progress.receivedBytes} / ${progress.totalBytes}`);
  }).then(newPackage => {
    newPackage.install(codePush.InstallMode.IMMEDIATE).then(() => {
      console.log('安装完成');
    });
  });
};

同步更新 #

tsx
const syncUpdate = async () => {
  const result = await codePush.sync(
    {
      installMode: codePush.InstallMode.IMMEDIATE,
      updateDialog: true,
    },
    (status) => {
      switch (status) {
        case codePush.SyncStatus.CHECKING_FOR_UPDATE:
          console.log('检查更新中...');
          break;
        case codePush.SyncStatus.AWAITING_USER_ACTION:
          console.log('等待用户确认...');
          break;
        case codePush.SyncStatus.DOWNLOADING_PACKAGE:
          console.log('下载中...');
          break;
        case codePush.SyncStatus.INSTALLING_UPDATE:
          console.log('安装中...');
          break;
        case codePush.SyncStatus.UP_TO_DATE:
          console.log('已是最新版本');
          break;
        case codePush.SyncStatus.UPDATE_INSTALLED:
          console.log('更新已安装');
          break;
        case codePush.SyncStatus.UNKNOWN_ERROR:
          console.log('未知错误');
          break;
      }
    },
    (progress) => {
      console.log(`下载进度: ${progress.receivedBytes} / ${progress.totalBytes}`);
    },
  );
  
  return result;
};

部署环境 #

查看部署 #

bash
code-push deployment ls MyApp

创建部署 #

bash
code-push deployment add MyApp Staging
code-push deployment add MyApp Production

获取部署密钥 #

bash
code-push deployment ls MyApp -k

切换环境 #

tsx
import codePush from 'react-native-code-push';

const getDeploymentKey = () => {
  if (__DEV__) {
    return 'STAGING_KEY';
  }
  return 'PRODUCTION_KEY';
};

export default codePush({
  deploymentKey: getDeploymentKey(),
})(App);

发布更新 #

发布到 Staging #

bash
code-push release-react MyApp ios
code-push release-react MyApp android

发布到 Production #

bash
code-push release-react MyApp ios -d Production
code-push release-react MyApp android -d Production

发布选项 #

bash
code-push release-react MyApp ios \
  --deploymentName Production \
  --description "修复登录问题" \
  --mandatory \
  --targetBinaryVersion "1.0.0"
选项 说明
–deploymentName 部署环境
–description 更新描述
–mandatory 强制更新
–targetBinaryVersion 目标版本
–rollout 灰度比例

灰度发布 #

bash
code-push release-react MyApp ios --rollout 20

逐步扩大:

bash
code-push patch MyApp ios --rollout 50
code-push patch MyApp ios --rollout 100

回滚更新 #

回滚到上一版本 #

bash
code-push rollback MyApp ios
code-push rollback MyApp android

回滚到指定版本 #

bash
code-push rollback MyApp ios --targetRelease v2

查看历史 #

查看发布历史 #

bash
code-push deployment history MyApp Production

查看更新指标 #

bash
code-push deployment history MyApp Production --displayAuthor

最佳实践 #

版本兼容 #

使用 targetBinaryVersion 确保更新兼容:

bash
code-push release-react MyApp ios --targetBinaryVersion "1.0.0 - 1.0.9"

强制更新 #

对于关键修复,使用强制更新:

bash
code-push release-react MyApp ios --mandatory

灰度发布 #

先小范围测试再全量发布:

bash
code-push release-react MyApp ios --rollout 10
code-push patch MyApp ios --rollout 50
code-push patch MyApp ios --rollout 100

监控更新 #

tsx
const syncWithAnalytics = async () => {
  const result = await codePush.sync(
    {updateDialog: true},
    (status) => {
      analytics.track('codepush_status', {status});
    },
    (progress) => {
      analytics.track('codepush_progress', {
        received: progress.receivedBytes,
        total: progress.totalBytes,
      });
    },
  );
  
  analytics.track('codepush_result', {result});
};

完整示例 #

tsx
import React, {useState, useEffect} from 'react';
import {
  View,
  Text,
  Button,
  StyleSheet,
  Alert,
  Modal,
  ProgressBarAndroid,
} from 'react-native';
import codePush from 'react-native-code-push';

const App = () => {
  const [updateModal, setUpdateModal] = useState(false);
  const [downloadProgress, setDownloadProgress] = useState(0);
  const [syncStatus, setSyncStatus] = useState('');

  useEffect(() => {
    checkForUpdate();
  }, []);

  const checkForUpdate = async () => {
    const update = await codePush.checkForUpdate();
    if (update) {
      Alert.alert(
        '更新可用',
        `发现新版本 (${update.appVersion}),是否更新?`,
        [
          {text: '稍后', style: 'cancel'},
          {text: '更新', onPress: () => downloadUpdate(update)},
        ],
      );
    }
  };

  const downloadUpdate = async (update) => {
    setUpdateModal(true);
    setSyncStatus('下载中...');

    try {
      await update.download(progress => {
        const percent = (progress.receivedBytes / progress.totalBytes) * 100;
        setDownloadProgress(percent);
      });

      setSyncStatus('安装中...');
      await update.install(codePush.InstallMode.IMMEDIATE);
      setSyncStatus('安装完成');
      
      setTimeout(() => {
        codePush.notifyAppReady();
        codePush.restartApp();
      }, 1000);
    } catch (error) {
      setSyncStatus('更新失败');
      Alert.alert('错误', error.message);
    }
  };

  return (
    <View style={styles.container}>
      <Text style={styles.title}>CodePush 热更新示例</Text>
      
      <Button title="检查更新" onPress={checkForUpdate} />
      
      <Modal visible={updateModal} transparent animationType="fade">
        <View style={styles.modalContainer}>
          <View style={styles.modalContent}>
            <Text style={styles.statusText}>{syncStatus}</Text>
            <ProgressBarAndroid
              styleAttr="Horizontal"
              indeterminate={false}
              progress={downloadProgress / 100}
              style={styles.progress}
            />
            <Text style={styles.progressText}>
              {Math.round(downloadProgress)}%
            </Text>
          </View>
        </View>
      </Modal>
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    padding: 16,
  },
  title: {
    fontSize: 24,
    fontWeight: 'bold',
    marginBottom: 32,
  },
  modalContainer: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: 'rgba(0, 0, 0, 0.5)',
  },
  modalContent: {
    backgroundColor: '#fff',
    padding: 24,
    borderRadius: 12,
    width: '80%',
    alignItems: 'center',
  },
  statusText: {
    fontSize: 18,
    marginBottom: 16,
  },
  progress: {
    width: '100%',
    height: 10,
  },
  progressText: {
    marginTop: 8,
    fontSize: 14,
    color: '#666',
  },
});

const codePushOptions = {
  checkFrequency: codePush.CheckFrequency.MANUAL,
};

export default codePush(codePushOptions)(App);

总结 #

CodePush 热更新要点:

  • 安装配置:正确配置 iOS 和 Android
  • 检查更新:自动或手动检查
  • 下载安装:支持进度显示
  • 部署环境:Staging 和 Production
  • 发布更新:使用 CLI 发布
  • 灰度发布:逐步推广更新
  • 回滚更新:快速回滚问题版本

热更新是 React Native 的重要特性,可以快速修复问题和发布新功能。

最后更新:2026-03-28