ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

React Native+Node.js开发打车App:从实时抢单到白屏优化全链路

React Native+Node.js开发打车App:从实时抢单到白屏优化全链路 在出行赛道上做App很多团队第一反应是“做一套类似Uber的系统”可真落地时会发现光是一个“司机怎么接单”的交互逻辑就够你折腾半个月。今年我帮朋友团队评估过一个项目他们想用React Native加Node.js快速复刻一个InDriver风格的本地出行应用挑战点在于InDriver和Uber表面都是叫车但计价方式和撮合逻辑完全不同。这篇内容就基于这个实际项目把React Native做客户端、Node.js做服务端从环境搭建到核心业务开发、再到启动白屏排查的完整链路写出来。适合准备切入出行领域、或者想用这套技术栈做实时撮合类App的朋友参考。1. 先确定项目边界InDriver模式和Uber模式对系统设计的影响很多教程上来就写代码但做打车类应用最核心的坑往往在业务模式没有理清楚。同样的订单表、同样的司机池因为撮合规则不同整个服务端的架构设计会差出去一大截。1.1 两种计价模式背后的逻辑差异Uber这类平台乘客看到的价格是平台用算法算好的司机和乘客都没有议价空间。乘客下单后系统根据供需关系、距离、时段做动态定价然后把订单推送给附近的司机司机只能选择“接受”或“拒绝”。InDriver的思路反过来了。乘客提交行程时自己输入一个愿意支付的价格系统把这个订单广播给附近司机司机端看到的是乘客的出价、行程起终点和大致距离。司机觉得划算就抢单觉得不划算可以忽略也可以在心里算一笔账——这个单子有没有跑头。乘客如果长时间没人接单可以主动加价直到有司机接单。这个差异带来的技术影响非常直接Uber模式需要一套比较完整的计价引擎服务端要根据实时供需算出价格而且价格是强约束。InDriver模式不需要复杂计价引擎但需要一套可靠的“广播抢单”机制还要处理“乘客多次加价”“多个司机同时抢同一个单”的并发冲突。我当时的建议是项目起步阶段不要把两套逻辑全做进去先选定一个模式打通闭环。如果你要做的产品形态更接近InDriver服务端核心就应该是“订单广播”和“抢单锁单”如果更接近Uber核心则是“调度派单”和“计价计算”。两种模式混在一起做前期会非常痛苦因为很多页面和接口设计是冲突的。1.2 功能清单与服务端模块划分不管选哪种模式一个打车应用的骨架都是差不多的。我把这个项目的功能拆成五个模块用户模块乘客注册登录、司机注册审核、实名认证信息。这个最简单用手机号加验证码就能跑通。订单模块发布行程、生成订单、状态流转、订单历史。这是整个系统的核心状态机。撮合模块订单广播到附近司机、司机抢单、订单锁定。这个模块最考验服务端的实时性。计费模块InDriver模式里乘客出价和最终支付金额挂钩服务端要记录加价记录Uber模式则需要一套计价规则。消息模块订单推送、司机与乘客的匿名通话或站内消息。这里最常用的是WebSocket而不是普通HTTP请求。在这个项目里我把订单模块和撮合模块放在同一个服务里先开发计费模块简化成“乘客出价即最终价格”只保留加价记录。这样MVP阶段能最快跑通。1.3 为什么后端选Node.js而不是Java或Go服务端我选了Node.js主要原因有三个第一实时推送和Node.js配合得非常自然。打车应用最典型的场景是“乘客下单后订单要实时出现在附近司机的手机屏幕上”。Node.js对WebSocket和长连接的天然亲和力让这个功能不需要额外引入复杂的消息中间件。初期用Socket.IO就能撑住几千个司机同时在线等量大了再迁到专业的推送服务也不迟。第二整个团队只用一门语言。客户端是React Native用的是JavaScript/TypeScript服务端用Node.js还是同一门语言。一个小团队不需要同时维护两套技术栈光这一条就能省掉大量沟通成本。第三I/O密集型场景很合适。打车业务绝大部分请求是短平快的读写操作比如查附近司机、更新位置、拉订单列表。Node.js基于事件循环的非阻塞模型处理这类I/O密集型请求的表现非常稳定。当然等之后用户量大了可以把计算量大的部分比如路径规划单独拆出去用更合适的语言写但这是后话。提示如果你一开始就认定订单量的天花板很高比如百万级日活那就不要犹豫直接上Go或Java。Node.js的优势在于快速迭代和中小规模场景怕的不是Node.js性能不行而是团队对异步模型的掌控力不够。2. Node.js环境搭建Ubuntu下从零配好20版本运行环境很多人在项目第一章就卡住了——不是不会写代码而是开发环境装不对。尤其是Ubuntu服务器上装Node.js这件事网上的教程乱七八糟照着复制命令装出来的版本要么太老要么干脆装不上。这里把我实际踩过的路数完整写一遍。2.1 为什么Ubuntu自带源安装的Node版本极其尴尬在Ubuntu 20.04上如果你直接执行sudo apt install nodejs装出来的Node.js版本是10.x或者12.x具体看系统版本。这个版本跑老项目没问题但用来开发新项目就很尴尬了Node.js 20版本里有很多新特性比如更稳定的WebSocket客户端、更好的ESM支持老版本根本用不上。很多新版的npm包尤其是React Native相关工具链对Node版本有最低要求。我遇到过安装某个依赖时直接报错提示Node版本必须大于等于18当时服务器上装的是12只能重来。npm install在旧版本上偶尔会出一些诡异的问题比如依赖树解析出错、锁文件版本冲突排查起来很浪费时间。所以不要在Ubuntu上用apt默认源装Node.js做新项目。不是说apt不能装而是默认源的版本更新太滞后不适合做现代前端或Node服务开发。2.2 两条可落地的安装路径我推荐两条路径任选其一。路径一用nvm做版本管理。这个方式最适合开发机因为它可以随时切换Node版本同一个机器上可以共存多个版本给不同的项目用。安装步骤如下curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash # 安装完成后重开终端或者执行 source ~/.bashrc nvm install 20 nvm use 20 node -v我自己用的就是这个方式。好处很明显如果某个老项目需要切回Node 16一条nvm use 16就搞定不需要卸载重装。路径二用NodeSource的APT源直接装。这个方式适合服务器生产环境因为服务器上不需要频繁切换版本装一个稳定版本常驻就行。curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash - sudo apt install -y nodejs node -v这里注意一点NodeSource的setup脚本会帮你把NodeSource仓库加进apt源列表安装完的Node版本就有保证同时也把npm一起装好了。如果你需要装的是最新的LTS版本把脚本里的setup_20.x改成setup_22.x即可不过按我的经验20.x是目前生态兼容性最稳的一条线。2.3 安装完要做的三件检查装完Node.js别急着写代码先跑三个命令确认环境没毛病node -v # 查看Node版本确认是v20.x npm -v # 查看npm版本确认是10.x npx -v # 查看npx版本方便后续初始化脚手架如果npm -v输出异常或者执行npm install时报EACCES permission denied说明之前的安装有权限问题。开发机上最简单粗暴的解决方式是给npm全局目录加权限但更优雅的做法是配置npm的prefix到用户目录这里不展开记住一点不要用sudo去运行npm install解决依赖问题这会引入更多麻烦。2.4 从npm下载到初始化Express项目的完整过程Node环境准备好后初始化打车应用的服务端项目。我习惯用Express框架它是Node.js生态里最成熟的Web框架文档多、社区大、出海项目里也遍地都是。mkdir taxi-server cd taxi-server npm init -y npm install express npm install socket.io npm install mysql2 # 如果用MySQL npm install redis # 如果用Redis做缓存和在线状态 npm install jsonwebtoken # 用于登录态token这里解释一下各组件的用途expressHTTP接口框架处理RESTful API。socket.ioWebSocket库用于订单广播、司机位置实时推送。mysql2数据库驱动我用的是MySQL如果团队偏好PostgreSQL换成pg包即可。jsonwebtoken签发和校验登录token所有需要鉴权的接口都用它。初始化完的package.json里记得把start脚本配好scripts: { start: node server.js, dev: nodemon server.js }项目初期用nodemon做热重启改完代码不用手动重启服务开发效率高很多。这个阶段不要追求微服务架构一个单体服务跑通核心流程是性价比最高的选择。3. 服务端核心实现发单、抢单与实时推送服务端的核心不在CRUD而在订单状态的正确流转和实时消息的可靠推送。打车应用的订单数据虽然不复杂但并发场景下很容易出逻辑漏洞。我当时踩过最深的坑就是“多个司机同时抢同一个订单”这两个问题必须在一开始就设计好。3.1 订单状态机不设计好这个后面全是雷订单状态我用一个状态机统一管理每个字段都有明确含义状态含义可跳转状态created乘客创建订单published, cancelledpublished订单已广播给附近司机accepted, cancelledaccepted司机已抢单arrived, cancelledarrived司机已到达上车点in_progress, cancelledin_progress行程进行中completedcompleted行程结束无cancelled订单取消无状态机的核心价值是防止非法跳转。比如一个订单正在行程中乘客想取消服务端要判断是否允许、是否有取消费。我的实现方式是在服务端维护一份状态流转表每次更新订单状态时先校验合法性而不是让客户端随便传一个状态过来就更新。3.2 乘客发布订单API与计价规则简化方案我先做了InDriver简化模式乘客输入起点终点系统预估距离乘客自己填价格订单进入published状态。这个模式下服务端不需要计算结果价格只需要存下来。核心API是POST/api/orders请求体大概是这样的{ passengerId: 123, pickup: { lat: 31.2304, lng: 121.4737 }, dropoff: { lat: 31.2401, lng: 121.4832 }, price: 28.5, isInDriverMode: true }服务端收到后先验登录态再插入订单表并生成订单号。订单号不建议用自增ID因为用户会拿着订单号找客服自增ID既不好看又容易暴露系统规模我用的是时间戳加随机数的组合。价格校验这里有一个细节要设一个合理的价格上下限区间不然乘客填个1块钱的单子挂在广播里不仅自己叫不到车还会污染司机的抢单大厅。我当时的做法是根据预估距离算出一个建议价格区间比如每公里2.5元到5元超出区间的价格直接拦截。3.3 地理距离计算与附近司机筛选广播订单的前提是“找到附近的司机”这里有一个绕不开的计算问题怎么判断司机离乘客有多远最粗浅的做法是用经纬度差值做简单加减但在地球球面上经纬度一度的实际距离在不同纬度差异很大必须用球面距离公式。Haversine公式是业界应用最广的方案直接用代码实现function haversineDistance(lat1, lon1, lat2, lon2) { const R 6371; // 地球半径单位公里 const dLat (lat2 - lat1) * Math.PI / 180; const dLon (lon2 - lon1) * Math.PI / 180; const a Math.sin(dLat/2) * Math.sin(dLat/2) Math.cos(lat1 * Math.PI / 180) * Math.cos(lat2 * Math.PI / 180) * Math.sin(dLon/2) * Math.sin(dLon/2); const c 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); return R * c; // 公里 }实际应用中不用对全量司机算距离那样性能太差。更常见的做法是先用“经纬度范围框”粗筛一遍比如限定在乘客坐标的某个经纬度差值范围内比如0.1度大约10公里再对框内司机算精确距离。这个思路叫“地理围栏粗筛精确距离精算”能省掉大量无意义的计算。3.4 Socket.IO实时抢单广播、抢单、锁单三步走订单进入published状态后服务端要通过Socket.IO把它广播给所有在线且在订单范围内的司机。我用的是Socket.IO的room机制每个司机上线时根据Ta的当前位置加入对应的范围房间订单创建时向相关房间广播。广播逻辑伪代码如下// 订单创建后广播给3公里内的司机 const nearbyDrivers await findNearbyDrivers(order.pickup, 3); nearbyDrivers.forEach(driver { io.to(driver_${driver.id}).emit(new_order, { orderId: order.id, pickup: order.pickup, dropoff: order.dropoff, price: order.price }); });真正考验人的是“多个司机同时抢单”的处理。在没有锁的情况下两个司机同时请求抢同一个订单服务端查订单状态都是published然后都更新成accepted就出问题了。我当时用的方案是数据库条件更新加原子判断只有订单状态是published时才允许更新为accepted并且立即写入抢单司机ID。具体SQL是UPDATE orders SET status accepted, driver_id :driverId, accepted_at NOW() WHERE id :orderId AND status published;然后检查affectedRows如果等于0说明订单已经被别人抢走返回“手慢了”如果等于1说明抢单成功再通过Socket.IO通知乘客和该司机。这个方案的核心价值是“把并发冲突的判断交给数据库去处理”而不是在应用层用锁或事务逻辑简单且可靠。如果你的数据库支持行级锁用SELECT FOR UPDATE也能达到类似效果但影响行数的条件更新方案是最直接易懂的。4. React Native双端开发乘客端与司机端的页面和地图集成客户端用React Native开发最大的优势是一套代码同时跑iOS和Android但在出行场景里有几个绕不开的痛点地图组件、定位权限、后台持续定位。这三个点如果不在项目开始就规划好后面每次改版都痛不欲生。4.1 react-native-maps集成iOS和Android都要兼容的地图方案地图是打车应用的脸面。React Native生态里最主流的方案是react-native-maps但集成过程有几个坑我一个个说。安装命令npm install react-native-mapsiOS上跑pod install后基本就能用。Android上需要确认build.gradle里配置没有问题尤其是如果团队计划在国内使用高德或百度地图那就要去改react-native-maps的底层依赖公开版用的是Google Maps。我的建议是如果你的用户群体在海外直接用Google Maps如果目标市场在国内要么用WebView方案嵌入Web地图要么做好底层替换的预期管理。针对本地出行场景地图上需要展示三类内容乘客端的地图显示当前位置、上车点。司机端的地图显示乘客位置、去接乘客的导航路线。订单派发下的地图显示司机和乘客的相对位置。实现上我用MapView和Marker组合自定义了Marker样式让乘客端和司机端角色一目了然。地图性能是最容易忽视的环节如果地图上有大量Marker频繁更新一定要用trackViewChanges{false}关闭iOS上的实时标注跟踪不然地图会变卡。4.2 乘客端叫单流程页面从发单到等待响应乘客端的核心页面是“发单页”交互流程是选择上车点和目的地。系统根据预估距离给建议价格区间。乘客输入自定义价格或者选择“一口价”。点击发布订单进入等待页。等待页显示当前状态有人接单、正在上车、行程中。这个页面看起来简单但有一件事必须在开发前想清楚等待接单的过程中乘客如果切到后台怎么保证能收到接单通知这里要区分两种状态App在前台通过WebSocket消息直接弹窗。App在后台靠远程推送。iOS走APNsAndroid走FCM如果国内生态则要看具体厂商推送通道。RN的推送方案我一律推荐统一的推送封装比如react-native-notifications或notifee/react-native同时从业务设计上做一个兜底乘客切到后台后如果长时间没有收到推送回到App时根据订单轮询一次最新状态。这个兜底逻辑听起来微不足道但在弱网环境下能救回大量“乘客莫名其妙取消了订单”的体验问题。4.3 司机端抢单大厅用列表还是用地图司机端的核心页面是“抢单大厅”。在这个页面上附近的订单以列表或地图标注形式展示。我最终做的是“地图列表”双模式但MVP阶段只做了列表因为数据量少的时候地图模式反而增加复杂度。订单列表的每一条展示上车点、目的地、乘客出价、预估距离。司机点击某个订单后进入详情页看地图上的起终点位置再决定抢不抢。这里有一个业务细节需要注意抢单页展示的“距离”是直线距离还是驾车距离我见过很多项目直接用Haversine算的直线距离显示给司机结果司机一看2公里觉得很近接单后实际开了5公里体验极差。正确做法是接单详情页用地图SDK的路线规划接口计算实际驾车距离列表页可以先用直线距离但要在界面上标注清楚。4.4 定位与权限处理不小心就白屏崩溃的坑出行App绕不开定位。React Native里用react-native-geolocation-service或react-native-community/geolocation同时要在原生配置里声明定位权限。iOS的Info.plist里必须加上NSLocationWhenInUseUsageDescription和NSLocationAlwaysAndWhenInUseUsageDescriptionAndroid则需要在AndroidManifest.xml里声明定位权限。这里最坑的地方是Android 12API 31开始定位权限弹窗逻辑变了需要在运行时同时申请前台和后台定位权限如果漏了其中一个用户开启后台导航时App就静默挂掉。排查这类问题特别耗时间当时调了很久才发现是权限字段没配全。处理定位还涉及一个技术选型要不要做后台持续定位我的建议是MVP阶段不要做。后台持续定位在高版本的iOS和Android上限制很多要申请特殊权限而且测试真机上的表现不可控。初期方案是司机端上车后每次到关键节点到达上车点、行程开始、行程结束调用一次定位更新服务端记录下来即可。这个方案实现简单、出问题少等业务量上来了再考虑真正的前台服务模式定位。5. 启动白屏问题的完整排查链路React Native冷启动优化实战说到React Native几乎每个开发者都遇到过启动白屏。这个热搜词的出现频率非常高说明这不是个例。我在这也踩过不少坑把这套排查链路完整写出来。5.1 先搞清楚白屏出现在哪个阶段React Native应用启动到显示首帧内容会经历大致四个阶段原生启动阶段从点击App图标到原生入口代码执行。加载JS Bundle阶段从原生代码开始加载打包好的JS文件到JS引擎执行完毕。渲染首帧阶段React组件树渲染最终显示到屏幕上。稳定阶段在首帧之后如果有大数据请求或复杂运算可能出现白屏或卡顿。白屏问题最常出现在第二和第三阶段尤其是JS Bundle的加载和解析耗时太长导致用户长时间盯着一个空白屏幕。注意这里说的白屏不是透明或闪一下而是整个屏幕长时间没有内容这是一种非常糟糕的体验。如果你把白屏当成“偶发抖动”忽略掉用户很容易在启动的3秒内卸载App。5.2 JS Bundle体积和加载耗时白屏的头号元凶React Native启动时原生端要先把JS代码加载进JavaScript引擎。在Debug模式下这一过程通过Metro服务器实时编译首次加载的耗时可能非常夸张Release模式下则是加载打进App包里的bundle文件。我复盘过这个项目启动阶段最大的性能瓶颈就是bundle体积过大。一个功能完整的打车AppReact Native的bundle很容易就到5MB以上。手机在加载5MB JS代码时解析和执行的耗时少说几百毫秒中低端机型可能要一两秒。优化的基础思路从两个方向入手第一启用字节码或更快的JS引擎。RN 0.70以上Hermes引擎是默认选项它把JS代码预编译成Hermes字节码加载和执行速度远超传统的JavaScriptCore。看你的android/app/build.gradle里是否配置了def jscFlavor org.webkit:android-jsc: def enableHermes project.ext.react.get(enableHermes, true)第二拆bundle或者压缩代码体积。React Native提供了ram bundle机制核心思想是只加载启动需要的模块其他模块按需加载。再加上压缩bundle体积的手段比如减少不必要的第三方库、避免在入口文件里一次性import大量页面都是有效做法。我给这个项目定的目标简单粗暴Release包首屏时间控制在2秒内Debug模式白屏是正常现象但不允许超过5秒。5.3 原生启动阶段的白屏SplashScreen和Theme的配合有时候白屏的根源不在JS而在原生层。React Native应用在点击图标的瞬间原生窗口已经创建了但首帧内容还没准备好这段窗口期如果没做处理就是一个纯白屏。解决这个问题最直观的方案是加一个启动页SplashScreen。原生启动页在线程创建和JS加载的过程中是一个静态的Logo或品牌色界面用户看起来就不觉得是“白屏”。等JS引擎加载完毕React组件渲染出首帧后再让启动页消失。实现上不要用复杂动画的启动页因为启动页的展示逻辑是“原生View盖在RN根View上方”你的动画做得越复杂两者切换时越容易出现一闪而过的割裂感。我自己用的是react-native-bootsplash这个库配置简单支持的平台也全按文档走下来即可。5.3 实测记录这个项目从白屏到首屏的调整记录我把当时的排查过程记录如下你可以照着来。第一步先抓播放日志。在原生端打开logcat或Xcode console看ReactNativeJS相关的日志输出。如果bundle加载前没有任何React日志说明瓶颈在原生层。如果日志显示JS已经执行了但界面迟迟没渲染那瓶颈就在React组件的挂载过程。第二步检查入口文件。我当时发现入口文件里一次性初始化了Redux store、Socket.IO连接、地图SDK初始化、版本更新检查五六个任务这些任务全部是同步串行的。地图SDK和Socket.IO连接在弱网环境下会长时间阻塞后续代码执行白屏时间直接翻倍。我的修复方式是把非首屏必须的操作全部放到InteractionManager.runAfterInteractions里延迟执行。import { InteractionManager } from react-native; // 首屏渲染完成后再执行地图SDK初始化 InteractionManager.runAfterInteractions(() { initMapSDK(); initSocket(); checkAppUpdate(); });这个改动带来的效果非常明显首屏时间从3.5秒降到了1.8秒。很多时候性能问题不是某个单点造成而是“所有事情都要在启动时做完”的贪心心态导致的。启动路径上只保留“进入主界面必须做的事”其他都延后。第三步检查Metro配置文件。Debug模式下的白屏经常是Metro的缓存问题。团队里几个人同时开发时Metro缓存偶尔会失效导致每次reload都要等很久。清理缓存的方法npx react-native start --reset-cache这个命令对启动白屏问题有奇效尤其是那种“之前还好好的今天突然白屏”的情况十有八九是缓存问题而不是代码问题。注意React Native启动白屏和初始化白屏是两个概念。前者是应用从零启动时的等待后者是页面切换时的JS线程被阻塞导致的白屏。前者重点排查bundle加载和原生启动页后者重点排查是否有大量同步计算阻塞JS线程。6. 打包上线前的经验清单从开发环境到真机部署很多项目功能都做完了卡在打包和上线环节。这里把最容易踩的坑集中列一下避免你在同样的问题上浪费时间。6.1 环境分离与调试地址配置React Native开发过程中模拟器访问本机的服务端地址是有讲究的Android模拟器访问宿主机要使用10.0.2.2而不是localhost或127.0.0.1。iOS模拟器可以直接用localhost。真机调试时要用电脑在局域网内的IP地址。我建议在项目里做一个环境配置文件把API地址和Socket地址集中管理不要再代码里写死某个IP。举例const ENV { dev: { apiBaseUrl: Platform.OS android ? http://10.0.2.2:3000 : http://localhost:3000, socketUrl: Platform.OS android ? http://10.0.2.2:3000 : http://localhost:3000 }, prod: { apiBaseUrl: https://api.yourdomain.com, socketUrl: https://api.yourdomain.com } };这个配置看似简单但新手经常因为忘记切换环境拿着开发配置打出来的包放到生产环境请求全部打到了本地上。另外要注意Android 9以上默认禁止明文HTTP请求开发版还好打Release包时如果API是HTTP协议要在AndroidManifest.xml里配置usesCleartextTraffic或用HTTPS域名。iOS同样有ATS限制需要配置NSAppTransportSecurity的例外域名但坦白说生产环境直接用HTTPS是最省心的。6.2 Android打包与签名一堆证书和Gradle的问题Android的Release包打包步骤如下生成签名文件keytool -genkey -v -keystore taxi-release.keystore -alias taxi-key -keyalg RSA -keysize 2048 -validity 10000把taxi-release.keystore放到android/app目录下。在android/gradle.properties里配置签名信息。在android/app/build.gradle里配置signingConfigs和buildTypes。执行打包命令cd android ./gradlew assembleRelease这一步最容易出的问题有两个。第一个是Gradle下载依赖太慢中低网络环境下经常超时建议提前把Gradle distributionUrl和阿里云镜像或者公司内网镜像配好。第二个是打包时如果RN版本和Android Gradle Plugin版本不匹配会出现各种莫名其妙的编译报错这种问题没有统一答案最快的办法是去对应的升级迁移文档里查版本对照表不要盲目升级或降级。还有一点很多团队把taxi-release.keystore和签名密码明文写在build.gradle里而且把包含密码的文件提交到了Git仓库。这是极大的安全风险一旦仓库泄露任何人都可以用你的签名重打包应用并盗用品牌。正确的做法是签名信息从环境变量或本地配置文件中读取并且千万不要把keystore文件提交到Git哪怕仓库是私有的也不要。6.3 真机联调与不同机型的兼容坑打车App对设备兼容性要求很高因为司机端要长时间挂在后台乘客端要高频调用地图和定位。我测试时遇到过这么几个问题低端Android手机上地图拖动卡顿、页面切换白屏闪烁。iOS 16刘海屏机型上自定义的底部栏被Home指示条遮挡。部分国产Android机型的后台限制策略非常激进App切后台几秒就被系统杀掉导致司机接单后收不到推送。针对第一类问题主要是减少地图Marker的实时更新频率把乘客位置的轮询间隔从每2秒改成每5秒一次同时使用removeClippedSubviews来裁剪屏幕外的子视图。这个改动需要实际测试不同机型不能一刀切。针对第二类问题用SafeAreaView或react-native-safe-area-context统一处理。针对第三类问题只能走厂商通道的推送SDK纯FCM在国内部分机型上不可靠这里不做过度展开但你要明确一点MVP阶段一定要在主流中端Android机和旧款iPhone上各测一台不然上线后会接到大量“收不到订单通知”的投诉。6.4 省略不了的合规与资质问题最后说一个很多技术类博客不爱提、但实际项目绕不开的事情。做打车应用不管叫车形式是InDriver还是Uber本质上是提供客运服务或为客运服务提供信息撮合。这涉及到当地的道路运输经营许可、网约车平台资质、司机和车辆的入网要求等一系列法规问题。技术文章里我不做法律解读但作为开发者在项目开工之前一定要先确认你在这个地区运营类似服务需要什么样的牌照和合规流程不能技术都做完了才发现无法上线。如果只是作为技术学习和个人项目跑通流程那无所谓但如果打算商业化运营建议先把商务侧和法务侧的准备工作做在前面否则后期会非常被动。收尾这个项目做到现在的真实体会这个项目从环境搭建到跑通核心抢单流程前后用了大概六周时间还是在两个开发者的配置下完成的。实际开发过程中最耗时间的部分其实是React Native的地图组件调优和Socket.IO的并发逻辑测试而不是功能实现本身。最后分享两个小技巧。服务端联调时我会同时开着好几个司机端的模拟器故意用程序模拟同一秒内多个司机同时抢单反复验证数据库条件更新是否可靠这个测试帮我提前发现了不少问题。客户端排查白屏时别光盯着JS代码先用原生日志确认一下白屏到底发生在原生层还是JS层这个定位步骤能帮你节省至少一整天的排查时间。整个技术路线走下来React Native加Node.js做打车类应用是完全可行的尤其适合中小团队快速试错。希望这篇记录能帮到正在做或准备做类似项目的朋友。
返回列表