官网:https://expo.dev/ # 初始化项目 创建项目命令: ```bash pnpm create expo-app@latest --template blank-typescript ``` [官网模板](https://docs.expo.dev/more/create-expo/)如下: | 模板名称 | 描述 | | ------------------------------------------------------------ | ------------------------------------------------------------ | | [`default`](https://github.com/expo/expo/tree/main/templates/expo-template-default) | 默认模板,用于构建多页面应用。包含推荐工具,如 **Expo CLI**、**Expo Router** 路由库,并启用 **TypeScript** 配置。适合大多数应用。 | | [`blank`](https://github.com/expo/expo/tree/main/templates/expo-template-blank) | 最简模板,仅安装最基本依赖,不包含导航配置。 | | [`blank-typescript`](https://github.com/expo/expo/tree/main/templates/expo-template-blank-typescript) | 空白模板,但启用了 **TypeScript** 支持。 | | [`tabs`](https://github.com/expo/expo/tree/main/templates/expo-template-tabs) | 启用基于文件的路由系统(Expo Router),并默认使用 **TypeScript**,适合多标签页结构的应用。 | | [`bare-minimum`](https://github.com/expo/expo/tree/main/templates/expo-template-bare-minimum) | 带有原生目录(`android` 与 `ios`)的空白模板,创建时会自动运行 [`npx expo prebuild`](https://docs.expo.dev/workflow/prebuild)。适合需要直接修改原生代码的项目。 | **启动项目:** ```bash npx expo start ``` # 基础配置 ## 集成 [antfu-eslint](https://github.com/antfu/eslint-config) 可以使用快捷命令: ```bash pnpm dlx @antfu/eslint-config@latest ``` 安装: ```bash pnpm i -D eslint @antfu/eslint-config ``` 新建 `eslint.config.mjs` 文件: ```js import antfu from '@antfu/eslint-config' export default antfu({ stylistic: { indent: 2, // 缩进为 2 个空格(可选 4 或 'tab') quotes: 'single', // 使用单引号(可选 'double') }, rules: { 'unused-imports/no-unused-imports': 'warn', 'unused-imports/no-unused-vars': 'warn', '@typescript-eslint/no-use-before-define': 'off', // 关闭 TS 的“定义前使用”警告 'no-console': 'off', // 关闭 console 报错 }, }) ``` 配置 vscode 模板,修改 `.vscode/settings.json` : > 注意安装 eslint 插件 ```json { // Disable the default formatter, use eslint instead "prettier.enable": false, "editor.formatOnSave": false, // Auto fix "editor.codeActionsOnSave": { "source.fixAll.eslint": "explicit", "source.organizeImports": "never" }, // Silent the stylistic rules in you IDE, but still auto fix them "eslint.rules.customizations": [ { "rule": "style/*", "severity": "off", "fixable": true }, { "rule": "format/*", "severity": "off", "fixable": true }, { "rule": "*-indent", "severity": "off", "fixable": true }, { "rule": "*-spacing", "severity": "off", "fixable": true }, { "rule": "*-spaces", "severity": "off", "fixable": true }, { "rule": "*-order", "severity": "off", "fixable": true }, { "rule": "*-dangle", "severity": "off", "fixable": true }, { "rule": "*-newline", "severity": "off", "fixable": true }, { "rule": "*quotes", "severity": "off", "fixable": true }, { "rule": "*semi", "severity": "off", "fixable": true } ], // Enable eslint for all supported languages "eslint.validate": [ "javascript", "javascriptreact", "typescript", "typescriptreact", "vue", "html", "markdown", "json", "jsonc", "yaml", "toml", "xml", "gql", "graphql", "astro", "svelte", "css", "less", "scss", "pcss", "postcss" ] } ``` ## 集成 [nativewind](https://www.nativewind.dev/v5/getting-started/installation) 直接参考官网:https://www.nativewind.dev/docs/getting-started/installation 1. 安装依赖 ```bash npx expo add nativewind react-native-reanimated@~3.17.4 react-native-safe-area-context@5.4.0 npx expo add tailwindcss@^3.4.17 -- --save-dev ``` 2. 运行 `npx tailwindcss init` 生成 `tailwind.config.js` 文件,添加配置: ```js /** @type {import('tailwindcss').Config} */ module.exports = { // NOTE: Update this to include the paths to all files that contain Nativewind classes. content: ["./App.tsx", "./components/**/*.{js,jsx,ts,tsx}"], presets: [require("nativewind/preset")], theme: { extend: {}, }, plugins: [], } ``` 3. 创建 `global.css` 文件: ```css @tailwind base; @tailwind components; @tailwind utilities; ``` 注意在全局引入 4. 修改 `babel.config.js` 文件: > 如果没有该文件,执行 `npx expo customize babel.config.js` 命令创建。 ```js module.exports = function (api) { api.cache(true); return { presets: [ ["babel-preset-expo", { jsxImportSource: "nativewind" }], "nativewind/babel", ], }; }; ``` 5. 修改 `metro.config.js` 文件: > 如果没有该文件,执行 `npx expo customize metro.config.js` 命令创建。 ```js const { getDefaultConfig } = require("expo/metro-config"); const { withNativeWind } = require('nativewind/metro'); const config = getDefaultConfig(__dirname) module.exports = withNativeWind(config, { input: './global.css' }) ``` 6. 新建 `src/types/nativewind-env.d.ts` 文件: ```ts /// ``` 7. 检验是否生效 ```tsx import "./global.css" import { Text, View } from "react-native"; export default function App() { return ( Welcome to Nativewind! ); } ``` 如果不生效,记得修改 `tailwind.config.js` 的目标路径: ```js /** @type {import('tailwindcss').Config} */ module.exports = { // NOTE: Update this to include the paths to all files that contain Nativewind classes. content: ["./src/app/_layout.tsx", "./src/components/**/*.{js,jsx,ts,tsx}"], presets: [require("nativewind/preset")], theme: { extend: {}, }, plugins: [], } ``` ## 集成 [gluestack-ui](https://gluestack.io/ui/docs/home/getting-started/installation) ### 方式一:CLI ```bash npx gluestack-ui init ``` ### 方式二:Manual **前置条件:**需要安装 `nativewind` 1. 安装依赖: ```bash npx expo add @gluestack-ui/core @gluestack-ui/utils react-native-svg @gluestack/ui-next-adapter ``` 2. 从 [github](https://github.com/gluestack/gluestack-ui/tree/main/src/components/ui/gluestack-ui-provider) 复制必须的组件到 `@/components/ui/gluestack-ui-provider`。 3. 修改 `tailwind.config.js` 文件: ```bash /** @type {import('tailwindcss').Config} */ module.exports = { darkMode: process.env.DARK_MODE ? process.env.DARK_MODE : 'class', content: [ './app/**/*.{html,js,jsx,ts,tsx,mdx}', './components/**/*.{html,js,jsx,ts,tsx,mdx}', './utils/**/*.{html,js,jsx,ts,tsx,mdx}', './*.{html,js,jsx,ts,tsx,mdx}', './src/**/*.{html,js,jsx,ts,tsx,mdx}', ], presets: [require('nativewind/preset')], important: 'html', safelist: [ { pattern: // eslint-disable-next-line regexp/no-dupe-disjunctions /(bg|border|text|stroke|fill)-(primary|secondary|tertiary|error|success|warning|info|typography|outline|background|indicator)-(0|50|100|200|300|400|500|600|700|800|900|950|white|gray|black|error|warning|muted|success|info|light|dark|primary)/, }, ], theme: { extend: { colors: { primary: { 0: 'rgb(var(--color-primary-0)/)', 50: 'rgb(var(--color-primary-50)/)', 100: 'rgb(var(--color-primary-100)/)', 200: 'rgb(var(--color-primary-200)/)', 300: 'rgb(var(--color-primary-300)/)', 400: 'rgb(var(--color-primary-400)/)', 500: 'rgb(var(--color-primary-500)/)', 600: 'rgb(var(--color-primary-600)/)', 700: 'rgb(var(--color-primary-700)/)', 800: 'rgb(var(--color-primary-800)/)', 900: 'rgb(var(--color-primary-900)/)', 950: 'rgb(var(--color-primary-950)/)', }, secondary: { 0: 'rgb(var(--color-secondary-0)/)', 50: 'rgb(var(--color-secondary-50)/)', 100: 'rgb(var(--color-secondary-100)/)', 200: 'rgb(var(--color-secondary-200)/)', 300: 'rgb(var(--color-secondary-300)/)', 400: 'rgb(var(--color-secondary-400)/)', 500: 'rgb(var(--color-secondary-500)/)', 600: 'rgb(var(--color-secondary-600)/)', 700: 'rgb(var(--color-secondary-700)/)', 800: 'rgb(var(--color-secondary-800)/)', 900: 'rgb(var(--color-secondary-900)/)', 950: 'rgb(var(--color-secondary-950)/)', }, tertiary: { 50: 'rgb(var(--color-tertiary-50)/)', 100: 'rgb(var(--color-tertiary-100)/)', 200: 'rgb(var(--color-tertiary-200)/)', 300: 'rgb(var(--color-tertiary-300)/)', 400: 'rgb(var(--color-tertiary-400)/)', 500: 'rgb(var(--color-tertiary-500)/)', 600: 'rgb(var(--color-tertiary-600)/)', 700: 'rgb(var(--color-tertiary-700)/)', 800: 'rgb(var(--color-tertiary-800)/)', 900: 'rgb(var(--color-tertiary-900)/)', 950: 'rgb(var(--color-tertiary-950)/)', }, error: { 0: 'rgb(var(--color-error-0)/)', 50: 'rgb(var(--color-error-50)/)', 100: 'rgb(var(--color-error-100)/)', 200: 'rgb(var(--color-error-200)/)', 300: 'rgb(var(--color-error-300)/)', 400: 'rgb(var(--color-error-400)/)', 500: 'rgb(var(--color-error-500)/)', 600: 'rgb(var(--color-error-600)/)', 700: 'rgb(var(--color-error-700)/)', 800: 'rgb(var(--color-error-800)/)', 900: 'rgb(var(--color-error-900)/)', 950: 'rgb(var(--color-error-950)/)', }, success: { 0: 'rgb(var(--color-success-0)/)', 50: 'rgb(var(--color-success-50)/)', 100: 'rgb(var(--color-success-100)/)', 200: 'rgb(var(--color-success-200)/)', 300: 'rgb(var(--color-success-300)/)', 400: 'rgb(var(--color-success-400)/)', 500: 'rgb(var(--color-success-500)/)', 600: 'rgb(var(--color-success-600)/)', 700: 'rgb(var(--color-success-700)/)', 800: 'rgb(var(--color-success-800)/)', 900: 'rgb(var(--color-success-900)/)', 950: 'rgb(var(--color-success-950)/)', }, warning: { 0: 'rgb(var(--color-warning-0)/)', 50: 'rgb(var(--color-warning-50)/)', 100: 'rgb(var(--color-warning-100)/)', 200: 'rgb(var(--color-warning-200)/)', 300: 'rgb(var(--color-warning-300)/)', 400: 'rgb(var(--color-warning-400)/)', 500: 'rgb(var(--color-warning-500)/)', 600: 'rgb(var(--color-warning-600)/)', 700: 'rgb(var(--color-warning-700)/)', 800: 'rgb(var(--color-warning-800)/)', 900: 'rgb(var(--color-warning-900)/)', 950: 'rgb(var(--color-warning-950)/)', }, info: { 0: 'rgb(var(--color-info-0)/)', 50: 'rgb(var(--color-info-50)/)', 100: 'rgb(var(--color-info-100)/)', 200: 'rgb(var(--color-info-200)/)', 300: 'rgb(var(--color-info-300)/)', 400: 'rgb(var(--color-info-400)/)', 500: 'rgb(var(--color-info-500)/)', 600: 'rgb(var(--color-info-600)/)', 700: 'rgb(var(--color-info-700)/)', 800: 'rgb(var(--color-info-800)/)', 900: 'rgb(var(--color-info-900)/)', 950: 'rgb(var(--color-info-950)/)', }, typography: { 0: 'rgb(var(--color-typography-0)/)', 50: 'rgb(var(--color-typography-50)/)', 100: 'rgb(var(--color-typography-100)/)', 200: 'rgb(var(--color-typography-200)/)', 300: 'rgb(var(--color-typography-300)/)', 400: 'rgb(var(--color-typography-400)/)', 500: 'rgb(var(--color-typography-500)/)', 600: 'rgb(var(--color-typography-600)/)', 700: 'rgb(var(--color-typography-700)/)', 800: 'rgb(var(--color-typography-800)/)', 900: 'rgb(var(--color-typography-900)/)', 950: 'rgb(var(--color-typography-950)/)', white: '#FFFFFF', gray: '#D4D4D4', black: '#181718', }, outline: { 0: 'rgb(var(--color-outline-0)/)', 50: 'rgb(var(--color-outline-50)/)', 100: 'rgb(var(--color-outline-100)/)', 200: 'rgb(var(--color-outline-200)/)', 300: 'rgb(var(--color-outline-300)/)', 400: 'rgb(var(--color-outline-400)/)', 500: 'rgb(var(--color-outline-500)/)', 600: 'rgb(var(--color-outline-600)/)', 700: 'rgb(var(--color-outline-700)/)', 800: 'rgb(var(--color-outline-800)/)', 900: 'rgb(var(--color-outline-900)/)', 950: 'rgb(var(--color-outline-950)/)', }, background: { 0: 'rgb(var(--color-background-0)/)', 50: 'rgb(var(--color-background-50)/)', 100: 'rgb(var(--color-background-100)/)', 200: 'rgb(var(--color-background-200)/)', 300: 'rgb(var(--color-background-300)/)', 400: 'rgb(var(--color-background-400)/)', 500: 'rgb(var(--color-background-500)/)', 600: 'rgb(var(--color-background-600)/)', 700: 'rgb(var(--color-background-700)/)', 800: 'rgb(var(--color-background-800)/)', 900: 'rgb(var(--color-background-900)/)', 950: 'rgb(var(--color-background-950)/)', error: 'rgb(var(--color-background-error)/)', warning: 'rgb(var(--color-background-warning)/)', muted: 'rgb(var(--color-background-muted)/)', success: 'rgb(var(--color-background-success)/)', info: 'rgb(var(--color-background-info)/)', light: '#FBFBFB', dark: '#181719', }, indicator: { primary: 'rgb(var(--color-indicator-primary)/)', info: 'rgb(var(--color-indicator-info)/)', error: 'rgb(var(--color-indicator-error)/)', }, }, fontFamily: { 'heading': undefined, 'body': undefined, 'mono': undefined, 'jakarta': ['var(--font-plus-jakarta-sans)'], 'roboto': ['var(--font-roboto)'], 'code': ['var(--font-source-code-pro)'], 'inter': ['var(--font-inter)'], 'space-mono': ['var(--font-space-mono)'], }, fontWeight: { extrablack: '950', }, fontSize: { '2xs': '10px', }, boxShadow: { 'hard-1': '-2px 2px 8px 0px rgba(38, 38, 38, 0.20)', 'hard-2': '0px 3px 10px 0px rgba(38, 38, 38, 0.20)', 'hard-3': '2px 2px 8px 0px rgba(38, 38, 38, 0.20)', 'hard-4': '0px -3px 10px 0px rgba(38, 38, 38, 0.20)', 'hard-5': '0px 2px 10px 0px rgba(38, 38, 38, 0.10)', 'soft-1': '0px 0px 10px rgba(38, 38, 38, 0.1)', 'soft-2': '0px 0px 20px rgba(38, 38, 38, 0.2)', 'soft-3': '0px 0px 30px rgba(38, 38, 38, 0.1)', 'soft-4': '0px 0px 40px rgba(38, 38, 38, 0.1)', }, }, }, } ``` 4. 使用 `GluestackUIProvider` 包括着整个组件: ```tsx // App.tsx import { GluestackUIProvider } from '@/components/ui/gluestack-ui-provider'; import '@/global.css'; // Make sure this path is correct export default function App() { return ( {/* Add your app code here */} ); } ``` ### 安装组件测试 下载对应组件: ```bash npx gluestack-ui add button ``` 引入使用: ```tsx import { StyleSheet, Text, View } from 'react-native' import { SafeAreaView } from 'react-native-safe-area-context' import { Button, ButtonText } from '@/components/ui/button' import { GluestackUIProvider } from '@/components/ui/gluestack-ui-provider' import '@/styles/global.css' export default function Layout() { return ( Welcome to Nativewind! ) } const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: '#f5f5f5', paddingHorizontal: 16, } }) ``` ## 集成[@expo/vector-icons](https://docs.expo.dev/guides/icons/#expovector-icons) 图标网站地址:https://icons.expo.fyi/Index 该插件默认跟着expo安装了。可以直接使用,如果没有的话,则执行命令安装: ``` npx expo install @expo/vector-icons ``` 测试: ```tsx import { View, StyleSheet } from 'react-native'; import Ionicons from '@expo/vector-icons/Ionicons'; export default function App() { return ( ); } const styles = StyleSheet.create({ container: { flex: 1, justifyContent: 'center', alignItems: 'center', }, }); ``` 如果需要没有想要的图标,可以用下面的方式: 安装 `svg` 依赖 ```bash npx expo install react-native-svg ``` 然后在components下面创建一个 `Icons.tsx` 文件,用来管理所有的图标 ```tsx import Svg, { Circle, Rect, Path } from 'react-native-svg'; interface IconProps { size?: string | number; color?: string; } // 测试图标 export const DemoIcon: React.FC = ({ size = 30, color = '#fff', ...rest }) => { return ( ); } // 招聘 export const ZhaoPinIcon: React.FC = ({ size = 30, color = '#fff', ...rest }) => { return ( ); } ``` ## 集成[expo-router](expo-router) 1. 安装相关依赖 ```bash npx expo install expo-router react-native-safe-area-context react-native-screens expo-linking expo-constants expo-status-bar ``` 2. 修改 `package.json` ``` { "main": "expo-router/entry" } ``` 3. 将根目录的 `App.tsx` 和 `index.ts` 文件删除,新增 `_layout.tsx` 文件 ### 布局 1. 新建 `app/_layout.tsx` 文件: ```tsx import { Slot } from 'expo-router' import { StyleSheet, Text } from 'react-native' import { SafeAreaView } from 'react-native-safe-area-context' export default function Layout() { return ( Header Footer ) } const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: '#f5f5f5', paddingHorizontal: 16, }, header: { fontSize: 20, fontWeight: 'bold', paddingVertical: 12, textAlign: 'center', backgroundColor: '#4f46e5', color: '#fff', borderRadius: 8, marginVertical: 8, }, footer: { fontSize: 16, paddingVertical: 10, textAlign: 'center', backgroundColor: '#e5e7eb', borderRadius: 8, marginVertical: 8, }, }) ``` 2. 新建 ## 集成 [lottie-react-native](https://github.com/lottie-react-native/lottie-react-native) 安装依赖: ```bash npx expo install lottie-react-native ``` 使用案例: ```tsx import React from "react"; import LottieView from "lottie-react-native"; export default function Animation() { return ( ); } ``` lottie例子网站:https://lottiefiles.com/free-animations ## 配置深度链接 作用:通过链接就可以跳转到该app。 在 `app.json` 文件下进行配置: ```json { "expo": { "scheme":"oneao", } } ``` 然后就可以通过这个配置 从别的应用或者浏览器跳转到你的app: ``` oneao://courses/react-native ``` ## [定义 src 路径别名 @](https://docs.expo.dev/guides/typescript/) 修改 `tsconfig.json` 文件: ```json { "extends": "expo/tsconfig.base", "compilerOptions": { "baseUrl": "./", "paths": { "@/*": ["src/*"] }, "strict": true } } ``` # 打包 ## 云打包 1. 安装 EAS CLI ```bash npm install -g eas-cli ``` 2. 注册并登录 Expo 账户 - 访问 https://expo.dev/ 注册账户,然后命令行登录: ```bash expo login ``` 3. 配置项目构建 - 输入以下命令,选择 Android 平台即可。 ```bash eas build:configure ``` 4. 构建 Android 包 - 默认构建 AAB 包(上传 Google Play): ```bash eas build --platform android ``` - 构建 APK(可直接安装): ```bash eas build -p android --profile preview ``` ## 本地打包(EAS方式) ## 本地打包(原生方式) ### 1. 安装 [Android Studio]( https://developer.android.com/?hl=zh-cn) 1. 下载安装 - 按照官网提示依次安装 SDK 工具和必要组件。 - 安装完成后,记住 SDK 的安装路径(例如:`D:\data\sdk`,默认位置是:`C:\Users\<你的用户名>\AppData\Local\Android\Sdk`),后续需要用到。 2. 配置环境变量 - 打开 **系统属性 → 高级 → 环境变量**。 - 在 **系统变量** 中新增: ``` 变量名:ANDROID_HOME 变量值:D:\data\sdk ``` - 在 **Path** 中新增: ```bash %ANDROID_HOME%\platform-tools %ANDROID_HOME%\tools %ANDROID_HOME%\tools\bin ``` - 保存设置。 3. 验证安装 - 打开命令行,执行: ``` echo %ANDROID_HOME% ``` - 如果输出 SDK 路径,则说明配置成功。 ### 2. 安装 [jdk17](https://adoptium.net/zh-CN/temurin/releases?version=8&os=any&arch=any) 1. 下载安装 - 下载对应版本解压即可。 - 安装成功后,记住 jdk 的安装路径,后续需要用到。 2. 配置环境变量 - 打开 **系统属性 → 高级 → 环境变量**。 - 在 **系统变量** 中新增: ```bash 变量名:JAVA_HOME 变量值:D:\devtool\jdk\jdk-17.0.16+8 ``` - 在 **Path** 中新增: ```bash %JAVA_HOME%\bin ``` - 保存设置。 3. 验证安装 - 打开命令行,执行: ```bash java --version ``` - 如果输出 jdk 的信息,则说明配置成功。 ### 3. Expo 项目启用 [Prebuild](https://docs.expo.dev/workflow/prebuild/) 1. 生成原生项目 ```bash npx expo prebuild ``` 然后执行完毕后,会生成 `android` 目录。 2. 清除并重新生成原生项目 ```bash npx expo prebuild --clean ``` 会删除已有的 `android` 和 `ios` 目录,然后重新生成。 ### 4. 配置 Keystore #### 4.1 生成密钥 ```bash keytool -genkey -v -keystore android-release.keystore -alias my-key-alias -keyalg RSA -keysize 2048 -validity 10000 ``` 说明: - `my-release-key.keystore`:生成的密钥文件名,可自定义路径 - `my-key-alias`:密钥别名 - `RSA`:加密算法 - `2048`:密钥长度 - `10000`:有效期(天) #### 4.2 手动配置 修改 `android/app/build.gradle` 文件 1. 修改 `signingConfigs` ```bash signingConfigs { release { storeFile file('android-release.keystore') // keystore 文件路径 storePassword '你的Keystore密码' // keystore 密码 keyAlias '你的KeyAlias' // 密钥别名 keyPassword '你的Key密码' // key 密码 } debug { storeFile file('debug.keystore') storePassword 'android' keyAlias 'androiddebugkey' keyPassword 'android' } } ``` 2. 在 `buildTypes` 中设置 release 使用 release 签名 ```bash buildTypes { release { signingConfig signingConfigs.release } debug { signingConfig signingConfigs.debug } } ``` #### 4.3 脚本配置(推荐) 1. 在 expo 项目根目录下新增 `plugins\withAndroidSignature.js` 文件,内容如下: ```js // plugins/withAndroidSignature.js const { withAppBuildGradle } = require("@expo/config-plugins"); const fs = require("fs"); const path = require("path"); module.exports = function withAndroidSignature(config) { return withAppBuildGradle(config, config => { if (config.modResults.language === "groovy") { config.modResults.contents = setAndroidSignature(config.modResults.contents); } else { throw new Error("如果不是 groovy,则无法在 app/build.gradle 中设置 signingConfigs"); } return config; }); }; function setAndroidSignature(appBuildGradle) { if (!fs.existsSync(path.resolve(__dirname, "../credentials.json"))) { console.warn("警告:没有设置正式版本的 Android Keystore 文件,因为 credentials.json 不存在。"); return appBuildGradle; } const info = JSON.parse(fs.readFileSync(path.resolve(__dirname, "../credentials/credentials.json"), { encoding: "utf8" })); // 使用正则表达式插入签名信息 let output = appBuildGradle.replace( /(signingConfigs\s*\{)/, `$1 release { storeFile file(${JSON.stringify(path.resolve(__dirname, "../credentials/android-release.keystore"))}) storePassword ${JSON.stringify(info.android.keystore.keystorePassword)} keyAlias ${JSON.stringify(info.android.keystore.keyAlias)} keyPassword ${JSON.stringify(info.android.keystore.keyPassword)} }`, ); // 使用正则表达式替换 signingConfig output = output.replace( /(release\s*\{)[^}]*?signingConfig\s+signingConfigs\.debug/s, `$1 signingConfig signingConfigs.release `, ); return output; } ``` 2. 在 expo 项目根目录下新增 `/credentials/credentials.json` 文件(该文件主要是 Keystore 的信息): ```json { "android": { "keystore": { "keystorePath": "credentials/android-release.keystore", // Android keystore 文件路径 "keystorePassword": "123456", // keystore 密码 "keyAlias": "my-key-alias", // Key 别名 "keyPassword": "123456" // Key 密码 } }, "ios": { "provisioningProfilePath": "ios/certs/profile.mobileprovision", // iOS 描述文件路径 "distributionCertificate": { "path": "ios/certs/dist-cert.p12", // iOS 分发证书路径 "password": "password" // 证书密码 } } } ``` 3. 在 `app.json` 中指定这个项目级插件: ```json { "expo": { "plugins": [ "./plugins/withAndroidSignature" ] } } ``` ### 5. 打包 - 编译成 Google Play 商店用的 .aab 文件 ```bash ./gradlew bundleRelease ``` - 直接安装的 .apk 文件 ```bash ./gradlew assembleRelease ``` **注意:**这个时候可能会下载 `gradle` 、`apk` 等资源,网络速度可能过慢。 **解决方法:**在 `android/gradle.properties` 添加网络代理: ```bash systemProp.http.proxyHost=127.0.0.1 systemProp.http.proxyPort=7897 systemProp.https.proxyHost=127.0.0.1 systemProp.https.proxyPort=7897 ``` 打包结果在 `android/app/build/outputs/apt/release` 包下。 # 打包体积优化 默认初始项目打包就有 **80MB** 左右,是因为 expo 默认将 armeabi-v7a、arm64-v8a、x86、x86_64 这些架构的包都打包进来了。 | 架构名称 | 位数 | 设备类型 | 应用场景 | 备注 | | --------------- | ----- | ------------------------------------------ | -------------- | ------------------------------------ | | **armeabi-v7a** | 32 位 | 老旧的 ARM 芯片手机(如部分安卓 5/6 设备) | 兼容旧设备 | 已逐渐被淘汰,但仍有少数旧机型使用 | | **arm64-v8a** | 64 位 | 主流安卓手机(大多数真机) | ✅ 主力目标平台 | 目前最主流、性能最佳的架构 | | **x86** | 32 位 | 早期安卓模拟器(Intel 架构) | 开发调试 | 真机几乎不会用 | | **x86_64** | 64 位 | 现代安卓模拟器 | 开发调试 | 用于 Android Studio 模拟器或测试环境 | ## 方法1(简单) 找到 `android/gradle.properties` 文件,找到 `reactNativeArchitectures` ,根据需要删除即可 ```properties reactNativeArchitectures=armeabi-v7a,arm64-v8a,x86,x86_64 ``` ## 方法2(原生方法 选择更多) 在 `android/app/build.gradle` 文件中添加或修改以下配置: ``` android { // 其他配置... splits { abi { enable true // 启用按 CPU 架构拆分 reset() // 重置默认架构列表 include 'arm64-v8a', 'armeabi-v7a' universalApk false // 是否生成包含所有架构的通用 APK(false 表示只生成分架构的独立包) // 还有一个 exclude 配置是排除指定架构的 } } // 其他配置... } ``` **说明:** - `enable true`:启用架构拆分功能。 - `reset()`:清空默认的架构配置,避免重复。 - `include`:指定要生成的架构包,比如只打 `arm64-v8a` 和 `armeabi-v7a`。 - `universalApk false`:不生成通用包(即包含所有架构的 APK)。 **举例:** 执行打包命令后,将生成多个包,例如: ``` app-arm64-v8a-release.apk app-armeabi-v7a-release.apk ``` 每个 APK 只包含对应架构的二进制文件,体积更小。 如果把 `universalApk` 设置为 `true`,则只会生成一个: ``` app-universal-release.apk ``` 这个包能在 `arm64-v8a` 和 `armeabi-v7a` 架构的设备上运行,但体积最大。 # 报错 ## Cannot find module 'react-native-worklets/plugin' ```bash npx expo add react-native-worklets ``` ​