React Native请求大体积Get API时内存不足的解决方案咨询
问题描述
调用返回60-80MB大小Base64格式PDF/图片的GET接口时,React Native应用崩溃并弹出“Out of Memory”错误,但该接口在Postman和Web应用中可正常响应。相关环境配置及代码如下:
依赖版本
"@react-navigation/drawer": "^5.8.0", "@react-navigation/native": "^5.4.2", "@react-navigation/stack": "^5.3.9", "react": "16.11.0", "react-native": "0.62.2", "react-native-blob-util": "^0.13.18",
app/build.gradle文件
apply plugin: "com.android.application" apply plugin: 'com.google.gms.google-services' import com.android.build.OutputFile project.ext.react = [ enableHermes: false, // clean and rebuild if changing ] apply from: "../../node_modules/react-native/react.gradle" def enableSeparateBuildPerCPUArchitecture = false def enableProguardInReleaseBuilds = true def jscFlavor = 'org.webkit:android-jsc:+' def enableHermes = project.ext.react.get("enableHermes", false); android { compileSdkVersion rootProject.ext.compileSdkVersion compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } defaultConfig { applicationId "com.enterprisesales.uat" minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion versionCode 1 versionName "1.2" multiDexEnabled true } splits { abi { reset() enable enableSeparateBuildPerCPUArchitecture universalApk false // If true, also generate a universal APK include "armeabi-v7a", "x86", "arm64-v8a", "x86_64" } } signingConfigs { release { if (project.hasProperty('MYAPP_RELEASE_STORE_FILE')) { storeFile file(MYAPP_RELEASE_STORE_FILE) storePassword MYAPP_RELEASE_STORE_PASSWORD keyAlias MYAPP_RELEASE_KEY_ALIAS keyPassword MYAPP_RELEASE_KEY_PASSWORD } } } buildTypes { debug { signingConfig signingConfigs.debug } release { // Caution! In production, you need to generate your own keystore file. // see https://facebook.github.io/react-native/docs/signed-apk-android. signingConfig signingConfigs.release minifyEnabled enableProguardInReleaseBuilds shrinkResources enableProguardInReleaseBuilds zipAlignEnabled enableProguardInReleaseBuilds useProguard enableProguardInReleaseBuilds proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro" } } packagingOptions { pickFirst "lib/armeabi-v7a/libc++_shared.so" pickFirst "lib/arm64-v8a/libc++_shared.so" pickFirst "lib/x86/libc++_shared.so" pickFirst "lib/x86_64/libc++_shared.so" pickFirst 'lib/x86_64/libjsc.so' pickFirst 'lib/arm64-v8a/libjsc.so' } dexOptions { javaMaxHeapSize "4g" } // applicationVariants are e.g. debug, release applicationVariants.all { variant -> variant.outputs.each { output -> // For each separate APK per architecture, set a unique version code as described here: // https://developer.android.com/studio/build/configure-apk-splits.html def versionCodes = ["armeabi-v7a": 1, "x86": 2, "arm64-v8a": 3, "x86_64": 4] def abi = output.getFilter(OutputFile.ABI) if (abi != null) { // null for the universal-debug, universal-release variants output.versionCodeOverride = versionCodes.get(abi) * 1048576 + defaultConfig.versionCode } } } } dependencies { implementation fileTree(dir: "libs", include: ["*.jar"]) //noinspection GradleDynamicVersion implementation "com.facebook.react:react-native:+" // From node_modules implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.0.0" implementation 'com.android.support:multidex:1.0.3' // Add the dependencies for any other desired Firebase products // https://firebase.google.com/docs/android/setup#available-libraries debugImplementation("com.facebook.flipper:flipper:${FLIPPER_VERSION}") { exclude group:'com.facebook.fbjni' } debugImplementation("com.facebook.flipper:flipper-network-plugin:${FLIPPER_VERSION}") { exclude group:'com.facebook.flipper' } debugImplementation("com.facebook.flipper:flipper-fresco-plugin:${FLIPPER_VERSION}") { exclude group:'com.facebook.flipper' } if (enableHermes) { def hermesPath = "../../node_modules/hermes-engine/android/"; debugImplementation files(hermesPath + "hermes-debug.aar") releaseImplementation files(hermesPath + "hermes-release.aar") } else { implementation jscFlavor } compile project(':react-native-exit-app') } // Run this once to be able to run the application with BUCK // puts all compile dependencies into folder libs for BUCK to use task copyDownloadableDepsToLibs(type: Copy) { from configurations.compile into 'libs' } apply from: file("../../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesAppBuildGradle(project)
AndroidManifest.xml文件
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.package.uat"> <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.DOWNLOAD_WITHOUT_NOTIFICATION" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <application android:name=".MainApplication" android:allowBackup="false" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:largeHeap="true" android:roundIcon="@mipmap/ic_launcher_round" android:theme="@style/AppTheme" android:usesCleartextTraffic="true"> <activity android:name=".MainActivity" android:configChanges="keyboard|keyboardHidden|orientation|screenSize|uiMode" android:label="@string/app_name" android:launchMode="singleTask" android:screenOrientation="portrait" android:windowSoftInputMode="adjustPan"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> <action android:name="android.intent.action.DOWNLOAD_COMPLETE" /> </intent-filter> </activity> <activity android:name="com.facebook.react.devsupport.DevSettingsActivity" /> </application> </manifest>
API调用函数(Get请求)
const url = `${Config.ApiEndPoints.API_GET_DOCUMENT}` const header = { "Authorization": `Bearer ${Config.UserSession.USER_DATA.token}` } const apiResponse = await Module.ApiModule.GetRequest(url, header) const baseResponse = await fetch(url, { method: TYPE === undefined ? "GET" : TYPE, headers: { "Content-Type": "application/json", ...header }, }).then(r => r.json()).catch(error => { Module.DropDownAlert.showAlert("error", Config.String.SOME_ERROR_OCCURS, Config.String.SOMETHING_WENT_WRONG) return null })
解决方案
1. 绕开JS层处理大Base64数据
当前用fetch直接获取并解析JSON的方式会把整个大Base64字符串加载到JS内存中,这是内存溢出的核心原因。改用已安装的react-native-blob-util直接在原生层处理文件流,跳过JS层的内存占用:
import RNBlobUtil from 'react-native-blob-util'; const downloadLargeFile = async () => { const url = `${Config.ApiEndPoints.API_GET_DOCUMENT}`; const header = { "Authorization": `Bearer ${Config.UserSession.USER_DATA.token}` }; try { // 保存到本地文档目录 const savePath = RNBlobUtil.fs.dirs.DocumentDir + '/target_file.pdf'; await RNBlobUtil.config({ path: savePath, fileCache: true }) .fetch('GET', url, header); // 后续直接用本地路径操作文件(比如预览、上传等) console.log('文件已保存至:', savePath); } catch (error) { Module.DropDownAlert.showAlert("error", Config.String.SOME_ERROR_OCCURS, Config.String.SOMETHING_WENT_WRONG); } };
2. 优化Android内存配置
- 启用Hermes引擎:将
app/build.gradle中的enableHermes改为true,Hermes相比默认JSC引擎内存占用更低,修改后需clean项目并重新编译:project.ext.react = [ enableHermes: true, ] - 精简ABI架构:在
defaultConfig中添加ndk配置,只保留常用架构,减少包体积和内存开销:defaultConfig { // ...其他配置 ndk { abiFilters "armeabi-v7a", "arm64-v8a" } }
3. 清理冗余代码与内存
- 删除重复接口调用:你的代码中同时调用了
Module.ApiModule.GetRequest和fetch,这会重复加载数据,直接移除其中一个冗余调用。 - 主动释放内存:处理完数据后,将不再使用的变量设为
null,触发JS垃圾回收:let baseResponse = await fetch(...); // 处理完数据后 baseResponse = null;
4. 调整ProGuard配置
确保ProGuard没有误删内存优化相关代码,在proguard-rules.pro中添加:
-keep class com.facebook.react.bridge.** { *; } -keep class com.facebook.hermes.** { *; }
内容的提问来源于stack exchange,提问作者Vishwajeet Gade
相关产品推荐
相关产品推荐

