三星设备上React Native应用后台运行变慢的原因排查
问题背景
我开发的React Native(基于JavaScript)应用在三星Note 20 Ultra的Android系统上,前台运行状态表现正常,但切换至后台后运行速度大幅下降。应用每秒发起一次fetch API请求,新增一个返回大量数据的fetch接口后,处理该数据的循环在前台运行正常,后台时速度降至前台的1/5。
已完成排查:
- 确认代码无任务堆积
- 降低其他fetch请求频率无效
- 后台无屏幕写入操作
- 移除大数据处理逻辑后,后台运行速度与前台一致
已知React为单线程模型,但不认为这是前后台差异的原因,怀疑三星对后台应用的CPU使用做了限制,想了解具体原因及解决办法。
相关处理代码
const collectCurrentStockPrices = async (realTimeStockPriceData) => { console.log('Got Here'); percentageDone = 0; var myCurrentTimestamp = Date.now(); myCurrentTimestamp = myCurrentTimestamp/1000; for ( realTimeStockPriceCounter = 0; realTimeStockPriceCounter < realTimeStockPriceData.tickers.length - 1; realTimeStockPriceCounter++ ) { globalRealTimeStockPriceCounter = realTimeStockPriceCounter; realTimeStockPriceQueryArrayIndex = indexOfTicker( realTimeStockPriceData.tickers[realTimeStockPriceCounter].ticker, trackedStocks, ); if (realTimeStockPriceQueryArrayIndex !== -1 && realTimeStockPriceData.tickers[realTimeStockPriceCounter].day.c !== 0 ) { if (Math.floor(realTimeStockPriceCounter/100) === realTimeStockPriceCounter/100) { percentageDone = (globalRealTimeStockPriceCounter / realTimeStockPriceData.tickers.length) * 100; ReactNativeForegroundService.update({ id: 1244, title: "AI Stock App", message: "Collecting Current Prices", icon: "ic_launcher", button: false, button2: false, buttonText: "", button2Text: "", buttonOnPress: "", setOnlyAlertOnce: true, color: "#000000", progress: { max: 100, curr: percentageDone, }, }); } if (globalDebugOn) { if (Math.floor(realTimeStockPriceCounter/100) === realTimeStockPriceCounter/100) { console.log('Processing Current Price: ' + realTimeStockPriceCounter + " " + realTimeStockPriceData.tickers[realTimeStockPriceCounter].ticker + " Value: " + realTimeStockPriceData.tickers[realTimeStockPriceCounter].day.c ); if (AppState.currentState === 'active') { setDetailedStatusText('Processing Price: ' + realTimeStockPriceCounter); } } } // Use muCurrentTimestamp set above.... you want the timestamp at the // time of the query and not at the time the record was updated // myCurrentTimestamp = Date.now(); // myCurrentTimestamp = (myCurrentTimestamp / 1000).toFixed(0); realTimeStockPriceDateToStore = '{ "CurrentPrice" : ' + realTimeStockPriceData.tickers[realTimeStockPriceCounter].day.c + ', "CurrentPriceTS" : ' + myCurrentTimestamp.toString() + ' }'; try { realTimeJSONStockPriceData = JSON.parse(realTimeStockPriceDateToStore); } catch (error) { console.log(error); console.log( 'JSON Parse 42 failed on: ' + realTimeStockPriceDateToStore, ); } realTimeStringStockPriceData = JSON.stringify(realTimeJSONStockPriceData); try { await AsyncStorage.mergeItem( realTimeStockPriceData.tickers[realTimeStockPriceCounter].ticker, realTimeStringStockPriceData, ); } catch (error) { console.log(error); } // Get the value back to see if it was stored correctly // .... instead, lets just put it directly in storage (speeds things up) try { jsonTrackedStocksRecord = JSON.parse(trackedStocks[realTimeStockPriceQueryArrayIndex][1]); } catch (error) { console.log(error); console.log( 'JSON Parse 43 failed on: ' + trackedStocks[realTimeStockPriceQueryArrayIndex][1], ); } jsonTrackedStocksRecord.CurrentPrice = realTimeStockPriceData.tickers[realTimeStockPriceCounter].day.c; jsonTrackedStocksRecord.CurrentPriceTS = myCurrentTimestamp; trackedStocks[realTimeStockPriceQueryArrayIndex][1] = JSON.stringify(jsonTrackedStocksRecord); // try { // realTimeStockPriceTestValue = await AsyncStorage.getItem( // realTimeStockPriceData.tickers[realTimeStockPriceCounter].ticker, // ); // } catch (error) { // console.log(error); // } // if (realTimeStockPriceCounter === 0 || realTimeStockPriceCounter === 1) { // console.log( // 'Newly Written Stock Price Data: ' + realTimeStockPriceTestValue, // ); // } // Now update the tracked stocks in memory to reflect the changes //trackedStocks[realTimeStockPriceQueryArrayIndex][1] = realTimeStockPriceTestValue; } else { // if ticker in stock price record is not good // console.log( // 'Found bad ticker in stock price query: ' + // stockPriceData.results[stockPriceCounter].T, //); // Delay a couple hundred milliseconds to allow other code to execute while // this is in a hard loop await delay(200); } } // loop through stock price results for (realTimeStockPriceCounter) processingRealTimeStockPrices = false; ReactNativeForegroundService.stop({ id: 1244, title: "AI Stock App", message: "Collecting Current Prices", icon: "ic_launcher" }); }
原因分析
- 三星后台CPU限制:三星One UI系统对后台应用有严格的资源管控策略,应用切换到后台后,系统会降低其CPU优先级、限制CPU使用率,以节省电量和系统资源。大数据处理循环属于CPU密集型操作,后台时会被系统大幅限制运行速度。
- AsyncStorage后台性能损耗:代码中频繁调用
await AsyncStorage.mergeItem,AsyncStorage的IO操作在后台时优先级也会被系统降低,叠加CPU限制后,整体处理速度进一步下降。
解决办法
1. 申请后台高优先级权限
- 在AndroidManifest.xml中添加
android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS权限,引导用户将应用加入电池优化白名单,避免系统在后台限制应用资源。 - 确保
ReactNativeForegroundService配置正确,前台服务的CPU优先级远高于后台应用,能获得更多系统资源。
2. 优化数据处理逻辑
- 批量处理AsyncStorage操作:将多个
mergeItem请求合并为批量操作,比如使用AsyncStorage.multiMerge(若支持),减少IO次数,避免每次循环等待IO完成。 - 简化JSON序列化/反序列化:替换手动拼接JSON字符串的逻辑,直接创建对象后序列化,减少中间步骤:
// 优化后代码 const realTimeJSONStockPriceData = { CurrentPrice: realTimeStockPriceData.tickers[realTimeStockPriceCounter].day.c, CurrentPriceTS: myCurrentTimestamp }; const realTimeStringStockPriceData = JSON.stringify(realTimeJSONStockPriceData); - 拆分大循环:将长循环拆分为多个小任务,使用
setTimeout插入非阻塞延迟,让JavaScript线程有机会释放资源,避免被系统判定为过度占用CPU:if (Math.floor(realTimeStockPriceCounter/100) === realTimeStockPriceCounter/100) { await new Promise(resolve => setTimeout(resolve, 0)); }
3. 调整API请求策略
- 降低大数据接口的请求频率:每秒一次的请求在后台过于频繁,可根据业务需求调整为每3-5秒一次,减少后台数据处理压力。
- 请求过滤后的数据:若后端支持,只请求应用需要的ticker数据,减少前端处理的数据量,从源头降低CPU负载。
内容的提问来源于stack exchange,提问作者RSA
相关产品推荐
相关产品推荐

