From e36aac407902d568df76c83247e20ce50354b060 Mon Sep 17 00:00:00 2001 From: MTing Date: Wed, 3 Sep 2025 18:09:35 +0800 Subject: [PATCH] change detect img function & base64 function --- .../com/ouxuan/oxface/OXFaceOnlineActivity.java | 462 ++++++++++++++++++--- .../idl/face/main/finance/utils/BitmapUtils.java | 125 +++++- 2 files changed, 524 insertions(+), 63 deletions(-) diff --git a/app/src/main/java/com/ouxuan/oxface/OXFaceOnlineActivity.java b/app/src/main/java/com/ouxuan/oxface/OXFaceOnlineActivity.java index e914fee..ef4dbf8 100644 --- a/app/src/main/java/com/ouxuan/oxface/OXFaceOnlineActivity.java +++ b/app/src/main/java/com/ouxuan/oxface/OXFaceOnlineActivity.java @@ -39,6 +39,8 @@ import com.baidu.idl.main.facesdk.FaceInfo; import com.baidu.idl.main.facesdk.model.BDFaceImageInstance; import com.ouxuan.oxface.utils.LogManager; +import java.util.List; + /** * 简化版人脸识别界面 - 只显示视频流 */ @@ -51,6 +53,27 @@ public class OXFaceOnlineActivity extends BaseActivity implements View.OnClickLi // 图片越大,性能消耗越大,也可以选择640*480, 1280*720 private static final int PREFER_WIDTH = SingleBaseConfig.getBaseConfig().getRgbAndNirWidth(); private static final int PERFER_HEIGH = SingleBaseConfig.getBaseConfig().getRgbAndNirHeight(); + + // 新增控制变量 + private static int PROCESS_FRAME_INTERVAL = 10; // 降低处理频率,每隔10帧处理一次 + private int frameCounter = 0; + private boolean needSendFaceImage = false; // 是否需要将人脸图片转为base64发送 + private long lastProcessTime = 0; + private long lastBackgroundProcessTime = 0; // 应用在后台时的最后处理时间 + private static long MIN_PROCESS_INTERVAL = 1000; // 增加最小处理间隔至1秒 + + // 智能调节处理频率的控制变量 + private static final int FAST_PROCESS_FRAME_INTERVAL = 3; // 快速处理频率:每3帧处理一次 + private static final long FAST_MIN_PROCESS_INTERVAL = 300; // 快速处理间隔:300ms + private static final int SLOW_PROCESS_FRAME_INTERVAL = 30; // 慢速处理频率:每30帧处理一次 + private static final long SLOW_MIN_PROCESS_INTERVAL = 3000; // 慢速处理间隔:3000ms + private static final long FACE_DETECTION_TIMEOUT = 5000; // 人脸检测超时时间:5秒 + + private long lastFaceDetectedTime = 0; // 上次检测到人脸的时间 + private boolean isFaceDetected = false; // 当前是否检测到人脸 + private int consecutiveNoFaceCount = 0; // 连续未检测到人脸的次数 + private static final int CONSECUTIVE_NO_FACE_THRESHOLD = 20; // 连续未检测到人脸的阈值 + private Context mContext; private AutoTexturePreviewView mAutoCameraPreviewView; private TextureView mDrawDetectFaceView; @@ -133,6 +156,11 @@ public class OXFaceOnlineActivity extends BaseActivity implements View.OnClickLi mContext = this; initView(); + // 初始化人脸检测状态 + lastFaceDetectedTime = System.currentTimeMillis(); + isFaceDetected = false; + consecutiveNoFaceCount = 0; + // 检查并请求相机权限 if (ContextCompat.checkSelfPermission(this, android.Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) { @@ -143,6 +171,9 @@ public class OXFaceOnlineActivity extends BaseActivity implements View.OnClickLi LogManager.logInfo(TAG, "相机权限已授予"); } + // 启动内存优化周期性任务 + startMemoryOptimizationTask(); + LogManager.logInfo(TAG, "OXFaceOnlineActivity onCreate"); } @@ -320,41 +351,164 @@ public class OXFaceOnlineActivity extends BaseActivity implements View.OnClickLi private void startTestOpenDebugRegisterFunction() { LogManager.logInfo(TAG, "启动摄像头预览"); - // 设置摄像头方向 - if (SingleBaseConfig.getBaseConfig().getRBGCameraId() != -1){ - CameraPreviewManager.getInstance().setCameraFacing(SingleBaseConfig.getBaseConfig().getRBGCameraId()); - } else { - CameraPreviewManager.getInstance().setCameraFacing(CameraPreviewManager.CAMERA_FACING_FRONT); - } - - CameraPreviewManager.getInstance().startPreview(mContext, mAutoCameraPreviewView, - PREFER_WIDTH, PERFER_HEIGH, new CameraDataCallback() { - @Override - public void onGetCameraData(byte[] data, Camera camera, int width, int height) { - // 摄像头预览数据进行人脸检测 - if (isNeedCamera) { - FaceSDKManager.getInstance().onDetectCheck(data, null, null, - height, width, 1, new FaceDetectCallBack() { - @Override - public void onFaceDetectCallback(LivenessModel livenessModel) { - // 开发模式结果输出 - checkOpenDebugResult(livenessModel); + try { + // 设置摄像头方向 + if (SingleBaseConfig.getBaseConfig().getRBGCameraId() != -1){ + CameraPreviewManager.getInstance().setCameraFacing(SingleBaseConfig.getBaseConfig().getRBGCameraId()); + } else { + CameraPreviewManager.getInstance().setCameraFacing(CameraPreviewManager.CAMERA_FACING_FRONT); + } + + CameraPreviewManager.getInstance().startPreview(mContext, mAutoCameraPreviewView, + PREFER_WIDTH, PERFER_HEIGH, new CameraDataCallback() { + @Override + public void onGetCameraData(byte[] data, Camera camera, int width, int height) { + try { + // 摄像头预览数据进行人脸检测 + if (isNeedCamera) { + // 增加帧计数器和时间间隔控制 + frameCounter++; + long currentTime = System.currentTimeMillis(); + + // 智能调节处理频率 + adjustProcessFrequency(currentTime); + + // 只有当满足处理条件时才进行人脸检测 + if (frameCounter % PROCESS_FRAME_INTERVAL == 0 && + (currentTime - lastProcessTime) > MIN_PROCESS_INTERVAL) { + lastProcessTime = currentTime; + + // 检查应用是否处于前台 + if (!isApplicationInForeground()) { + // 如果应用在后台,减少处理频率,每隔10秒处理一次 + if ((currentTime - lastBackgroundProcessTime) < 10000) { + return; + } + lastBackgroundProcessTime = currentTime; } + + FaceSDKManager.getInstance().onDetectCheck(data, null, null, + height, width, 1, new FaceDetectCallBack() { + @Override + public void onFaceDetectCallback(LivenessModel livenessModel) { + try { + // 更新人脸检测状态 + updateFaceDetectionStatus(livenessModel); + + // 开发模式结果输出 + checkOpenDebugResult(livenessModel); + } catch (Exception e) { + LogManager.logError(TAG, "人脸检测回调处理异常", e); + } + } - @Override - public void onTip(int code, String msg) { - LogManager.logInfo(TAG, "人脸检测提示 - 代码: " + code + ", 消息: " + msg); - } + @Override + public void onTip(int code, String msg) { + try { + if(!msg.equals("未检测到人脸")){ + LogManager.logInfo(TAG, "人脸检测提示onTip - 代码: " + code + ", 消息: " + msg); + } + } catch (Exception e) { + LogManager.logError(TAG, "人脸检测提示处理异常", e); + } + } - @Override - public void onFaceDetectDarwCallback(LivenessModel livenessModel) { - // 绘制人脸框 - showFrame(livenessModel); + @Override + public void onFaceDetectDarwCallback(LivenessModel livenessModel) { + try { + // 绘制人脸框 + showFrame(livenessModel); + } catch (Exception e) { + LogManager.logError(TAG, "人脸框绘制回调处理异常", e); + } + } + }); + } else if (frameCounter % 30 == 0) { + // 对于跳过处理的帧,每30帧清空一次画布,确保无人脸时画面干净 + try { + LivenessModel emptyModel = new LivenessModel(); + emptyModel.setTrackFaceInfo(null); + showFrame(emptyModel); + } catch (Exception e) { + LogManager.logError(TAG, "清空画布时发生异常", e); } - }); + } + } + } catch (Exception e) { + LogManager.logError(TAG, "摄像头数据处理异常", e); + } } + }); + } catch (Exception e) { + LogManager.logError(TAG, "启动摄像头预览失败", e); + // 尝试重新初始化摄像头 + new Handler().postDelayed(new Runnable() { + @Override + public void run() { + if (!isFinishing()) { + LogManager.logInfo(TAG, "尝试重新启动摄像头预览"); + startTestOpenDebugRegisterFunction(); } - }); + } + }, 5000); // 5秒后重试 + } + } + + /** + * 智能调节处理频率 + * @param currentTime 当前时间 + */ + private void adjustProcessFrequency(long currentTime) { + // 如果检测到人脸,加速处理 + if (isFaceDetected) { + if (PROCESS_FRAME_INTERVAL != FAST_PROCESS_FRAME_INTERVAL || + MIN_PROCESS_INTERVAL != FAST_MIN_PROCESS_INTERVAL) { + PROCESS_FRAME_INTERVAL = FAST_PROCESS_FRAME_INTERVAL; + MIN_PROCESS_INTERVAL = FAST_MIN_PROCESS_INTERVAL; + LogManager.logInfo(TAG, "检测到人脸,加速处理频率:每" + PROCESS_FRAME_INTERVAL + "帧,最小间隔" + MIN_PROCESS_INTERVAL + "ms"); + } + } + // 如果长时间未检测到人脸,降低处理频率以节省资源 + else if (currentTime - lastFaceDetectedTime > FACE_DETECTION_TIMEOUT) { + // 检查是否已经处于慢速处理模式 + if (PROCESS_FRAME_INTERVAL != SLOW_PROCESS_FRAME_INTERVAL || + MIN_PROCESS_INTERVAL != SLOW_MIN_PROCESS_INTERVAL) { + PROCESS_FRAME_INTERVAL = SLOW_PROCESS_FRAME_INTERVAL; + MIN_PROCESS_INTERVAL = SLOW_MIN_PROCESS_INTERVAL; + LogManager.logInfo(TAG, "长时间未检测到人脸,降低处理频率:每" + PROCESS_FRAME_INTERVAL + "帧,最小间隔" + MIN_PROCESS_INTERVAL + "ms"); + } + } + } + + /** + * 更新人脸检测状态 + * @param livenessModel 活体检测模型 + */ + private void updateFaceDetectionStatus(LivenessModel livenessModel) { + long currentTime = System.currentTimeMillis(); + + // 检查是否检测到人脸 + boolean faceDetected = (livenessModel != null && + livenessModel.getTrackFaceInfo() != null && + livenessModel.getTrackFaceInfo().length > 0); + + if (faceDetected) { + // 检测到人脸 + isFaceDetected = true; + lastFaceDetectedTime = currentTime; + consecutiveNoFaceCount = 0; // 重置连续未检测到人脸计数器 + + LogManager.logDebug(TAG, "检测到人脸"); + } else { + // 未检测到人脸 + consecutiveNoFaceCount++; + + // 如果连续多次未检测到人脸,标记为未检测到人脸状态 + if (consecutiveNoFaceCount >= CONSECUTIVE_NO_FACE_THRESHOLD) { + isFaceDetected = false; + LogManager.logDebug(TAG, "连续未检测到人脸,当前计数: " + consecutiveNoFaceCount); + } + } } /** @@ -366,26 +520,35 @@ public class OXFaceOnlineActivity extends BaseActivity implements View.OnClickLi public void run() { Canvas canvas = mDrawDetectFaceView.lockCanvas(); if (canvas == null) { - mDrawDetectFaceView.unlockCanvasAndPost(canvas); return; } + + // 始终清空canvas + canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR); + + // 如果没有模型数据,直接返回清空的画布 if (model == null) { - // 清空canvas - canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR); mDrawDetectFaceView.unlockCanvasAndPost(canvas); return; } + + // 获取人脸信息 FaceInfo[] faceInfos = model.getTrackFaceInfo(); - + + // 如果没有检测到人脸,直接返回清空的画布 if (faceInfos == null || faceInfos.length == 0) { - // 清空canvas - canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR); mDrawDetectFaceView.unlockCanvasAndPost(canvas); return; } - canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR); - for (int i = 0 ; i < faceInfos.length;i++) { + + // 只在检测到人脸时绘制人脸框 + for (int i = 0; i < faceInfos.length; i++) { FaceInfo faceInfo = faceInfos[i]; + + // 如果人脸置信度太低,不绘制 + if (faceInfo.score < 0.6) { + continue; + } rectF.set(FaceOnDrawTexturViewUtil.getFaceRectTwo(faceInfo)); // 检测图片的坐标和显示的坐标不一样,需要转换。 @@ -397,7 +560,8 @@ public class OXFaceOnlineActivity extends BaseActivity implements View.OnClickLi FaceOnDrawTexturViewUtil.drawRect(canvas, rectF, paint, 5f, 50f, 25f); } - // 清空canvas + + // 提交canvas mDrawDetectFaceView.unlockCanvasAndPost(canvas); } }); @@ -649,7 +813,61 @@ public class OXFaceOnlineActivity extends BaseActivity implements View.OnClickLi @Override protected void onDestroy() { super.onDestroy(); - LogManager.logInfo(TAG, "OXFaceOnlineActivity onDestroy"); + + // 停止摄像头预览 + if (CameraPreviewManager.getInstance() != null) { + CameraPreviewManager.getInstance().stopPreview(); + } + + // 释放当前的LivenessModel资源 + if (currentLivenessModel != null) { + BDFaceImageInstance image = currentLivenessModel.getBdFaceImageInstance(); + if (image != null) { + image.destory(); + } + // 清空引用 + currentLivenessModel = null; + } + + // 释放绘图资源 + if (rectF != null) { + rectF = null; + } + if (paint != null) { + paint = null; + } + if (paintBg != null) { + paintBg = null; + } + + // 清除其他相关资源 + System.gc(); + + LogManager.logInfo(TAG, "OXFaceOnlineActivity onDestroy - 资源已释放"); + } + + @Override + protected void onStop() { + super.onStop(); + LogManager.logInfo(TAG, "OXFaceOnlineActivity onStop - 应用进入后台"); + + // 应用进入后台时,进一步降低处理频率以节省资源 + PROCESS_FRAME_INTERVAL = 30; // 后台时每30帧处理一次 + MIN_PROCESS_INTERVAL = 3000; // 后台时最小间隔3秒 + + LogManager.logInfo(TAG, "后台模式处理频率:每" + PROCESS_FRAME_INTERVAL + "帧,最小间隔" + MIN_PROCESS_INTERVAL + "ms"); + } + + @Override + protected void onStart() { + super.onStart(); + LogManager.logInfo(TAG, "OXFaceOnlineActivity onStart - 应用回到前台"); + + // 应用回到前台时,恢复正常的处理频率 + PROCESS_FRAME_INTERVAL = FAST_PROCESS_FRAME_INTERVAL; // 前台时使用快速处理频率 + MIN_PROCESS_INTERVAL = FAST_MIN_PROCESS_INTERVAL; // 前台时使用快速处理间隔 + + LogManager.logInfo(TAG, "前台模式处理频率:每" + PROCESS_FRAME_INTERVAL + "帧,最小间隔" + MIN_PROCESS_INTERVAL + "ms"); } // ***************开发模式结果输出************* @@ -743,16 +961,27 @@ public class OXFaceOnlineActivity extends BaseActivity implements View.OnClickLi if (livenessModel == null) { return; } - String base64img = getFaceImageBase64(currentLivenessModel); - - if (base64img != null) { - // Log.i(TAG, "checkResultOnline: " + base64img); - Log.i(TAG, "checkResultOnline: Yes! Got a base64img!" ); - } else { - Log.i(TAG, "run:checkResultOnline base64img score too low "); - layoutCompareStatus.setVisibility(View.VISIBLE); - textCompareStatus.setTextColor(Color.parseColor("#fec133")); - textCompareStatus.setText("请重新识别"); + + // 减少base64转换频率,只有在特定条件下才进行转换 + // 例如:每3秒检查一次是否需要发送人脸图像 + long currentTime = System.currentTimeMillis(); + if (currentTime - searshTime > 3000) { + searshTime = currentTime; + needSendFaceImage = true; + + String base64img = getFaceImageBase64(currentLivenessModel); + if (base64img != null) { + // 这里可以处理base64数据,如上传到服务器等 + Log.i(TAG, "checkResultOnline: 获取到人脸base64数据"); + + // 处理完成后重置标志 + needSendFaceImage = false; + } else { + Log.i(TAG, "run:checkResultOnline base64img score too low "); + layoutCompareStatus.setVisibility(View.VISIBLE); + textCompareStatus.setTextColor(Color.parseColor("#fec133")); + textCompareStatus.setText("请重新识别"); + } } } }); @@ -762,15 +991,134 @@ public class OXFaceOnlineActivity extends BaseActivity implements View.OnClickLi if (livenessModel == null) return null; FaceInfo faceInfo = livenessModel.getFaceInfo(); - if (true) { //判定条件 + // 添加判断条件,减少不必要的base64转换 + if (faceInfo != null && faceInfo.bestImageScore > SingleBaseConfig.getBaseConfig().getBestImageScore()) { BDFaceImageInstance image = livenessModel.getBdFaceImageInstance(); - Bitmap bitmap = BitmapUtils.getInstaceBmp(image); - - String bitmap_str = BitmapUtils.bitmapToBase64(bitmap); - image.destory(); - return bitmap_str; // 简化实现,实际项目中需要实现 bitmap 转 base64 的功能 - } else { - return null; + if (image != null) { + Bitmap bitmap = BitmapUtils.getInstaceBmp(image); + + // 仅在确实需要使用base64数据时才进行转换 + if (needSendFaceImage) { + String bitmap_str = BitmapUtils.bitmapToBase64(bitmap); + image.destory(); + return bitmap_str; + } else { + // 如果不需要发送,则不进行base64转换,直接释放资源 + image.destory(); + if (bitmap != null && !bitmap.isRecycled()) { + bitmap.recycle(); + } + return "dummy_base64_placeholder"; + } + } } + return null; + } + + /** + * 检查应用是否在前台 + * @return 如果应用在前台,则返回true;否则返回false + */ + private boolean isApplicationInForeground() { + android.app.ActivityManager activityManager = (android.app.ActivityManager) getSystemService(Context.ACTIVITY_SERVICE); + List appProcesses = activityManager.getRunningAppProcesses(); + if (appProcesses == null) { + return false; + } + + String packageName = getPackageName(); + for (android.app.ActivityManager.RunningAppProcessInfo appProcess : appProcesses) { + if (appProcess.importance == android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND + && appProcess.processName.equals(packageName)) { + return true; + } + } + return false; + } + + /** + * 低内存时的处理 + */ + @Override + public void onLowMemory() { + super.onLowMemory(); + LogManager.logInfo(TAG, "系统内存不足,触发onLowMemory"); + + // 清理不必要的缓存 + clearMemoryCache(); + + // 强制GC + System.gc(); + } + + /** + * 清理内存缓存 + */ + private void clearMemoryCache() { + // 如果当前不在人脸检测状态,释放当前的LivenessModel + if (currentLivenessModel != null && !isNeedCamera) { + BDFaceImageInstance image = currentLivenessModel.getBdFaceImageInstance(); + if (image != null) { + image.destory(); + } + currentLivenessModel = null; + } + + // 如果设备内存不足,考虑调整处理频率 + PROCESS_FRAME_INTERVAL = Math.min(30, PROCESS_FRAME_INTERVAL + 5); + MIN_PROCESS_INTERVAL = Math.min(3000, MIN_PROCESS_INTERVAL + 500); + + LogManager.logInfo(TAG, "内存不足,调整处理频率:每" + PROCESS_FRAME_INTERVAL + "帧,最小间隔" + MIN_PROCESS_INTERVAL + "ms"); + } + + /** + * 优化内存的周期性任务 + */ + private void startMemoryOptimizationTask() { + new Handler().postDelayed(new Runnable() { + @Override + public void run() { + // 获取当前应用内存使用情况 + Runtime runtime = Runtime.getRuntime(); + long usedMemory = (runtime.totalMemory() - runtime.freeMemory()) / (1024 * 1024); + long totalMemory = runtime.maxMemory() / (1024 * 1024); + float memoryUsageRatio = (float) usedMemory / totalMemory; + + LogManager.logInfo(TAG, "内存使用情况:" + usedMemory + "MB/" + totalMemory + + "MB,使用率:" + (int)(memoryUsageRatio * 100) + "%"); + + // 如果内存使用率超过70%,主动清理内存 + if (memoryUsageRatio > 0.7f) { + LogManager.logInfo(TAG, "内存使用率超过70%,主动清理内存"); + clearMemoryCache(); + System.gc(); + } + + // 如果内存使用率超过85%,进行更积极的清理 + if (memoryUsageRatio > 0.85f) { + LogManager.logWarning(TAG, "内存使用率超过85%,进行积极清理"); + // 进一步降低处理频率 + PROCESS_FRAME_INTERVAL = Math.min(50, PROCESS_FRAME_INTERVAL + 10); + MIN_PROCESS_INTERVAL = Math.min(5000, MIN_PROCESS_INTERVAL + 1000); + + // 清理更多资源 + if (currentLivenessModel != null) { + BDFaceImageInstance image = currentLivenessModel.getBdFaceImageInstance(); + if (image != null) { + image.destory(); + } + currentLivenessModel = null; + } + + LogManager.logInfo(TAG, "积极清理后调整处理频率:每" + PROCESS_FRAME_INTERVAL + "帧,最小间隔" + MIN_PROCESS_INTERVAL + "ms"); + System.gc(); + } + + // 每30秒执行一次 + if (!isFinishing()) { + new Handler().postDelayed(this, 30000); + } + } + }, 30000); } } \ No newline at end of file diff --git a/financelibrary/src/main/java/com/baidu/idl/face/main/finance/utils/BitmapUtils.java b/financelibrary/src/main/java/com/baidu/idl/face/main/finance/utils/BitmapUtils.java index 5382f60..3d4ab34 100644 --- a/financelibrary/src/main/java/com/baidu/idl/face/main/finance/utils/BitmapUtils.java +++ b/financelibrary/src/main/java/com/baidu/idl/face/main/finance/utils/BitmapUtils.java @@ -529,13 +529,126 @@ public final class BitmapUtils { * @return Base64编码的字符串 */ public static String bitmapToBase64(Bitmap bm) { - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - bm.compress(Bitmap.CompressFormat.JPEG, 30, baos); - byte[] byteArray = baos.toByteArray(); + // 检查传入的位图是否有效 + if (bm == null || bm.isRecycled()) { + Log.e(TAG, "尝试转换无效的Bitmap到Base64"); + return null; + } + + // 减小图像尺寸以降低内存占用 + Bitmap scaledBitmap = null; + + try { + // 判断是否需要缩放图片 + int originalWidth = bm.getWidth(); + int originalHeight = bm.getHeight(); + int targetWidth = 320; // 设置合理的目标宽度 + + // 如果原图已经很小,直接使用更小的尺寸 + if (originalWidth <= 160) { + targetWidth = originalWidth; + } else if (originalWidth <= 320) { + targetWidth = 160; + } + + if (originalWidth > targetWidth) { + float scaleFactor = (float) targetWidth / originalWidth; + int targetHeight = (int) (originalHeight * scaleFactor); + scaledBitmap = Bitmap.createScaledBitmap(bm, targetWidth, targetHeight, true); + } else { + // 原图尺寸已经足够小,直接使用 + scaledBitmap = bm; + } + + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + // 使用更低的压缩质量(20%)进一步减小数据体积 + scaledBitmap.compress(Bitmap.CompressFormat.JPEG, 20, baos); + byte[] byteArray = baos.toByteArray(); + + try { + baos.close(); + } catch (IOException e) { + Log.e(TAG, "关闭ByteArrayOutputStream失败: " + e.getMessage()); + } - // 使用Base64进行编码 - String encodedString = Base64.encodeToString(byteArray, Base64.DEFAULT); + // 使用NO_WRAP标志避免插入换行符,节省内存 + String encodedString = Base64.encodeToString(byteArray, Base64.NO_WRAP); + + // 立即释放字节数组 + byteArray = null; + + // 如果创建了新的缩放位图,则释放它 + if (scaledBitmap != bm && scaledBitmap != null && !scaledBitmap.isRecycled()) { + scaledBitmap.recycle(); + } + + return encodedString; + } catch (OutOfMemoryError e) { + Log.e(TAG, "Bitmap转Base64时内存溢出: " + e.getMessage()); + // 确保资源被释放 + if (scaledBitmap != null && scaledBitmap != bm && !scaledBitmap.isRecycled()) { + scaledBitmap.recycle(); + } + // 尝试更激进的压缩 + return bitmapToBase64Aggressive(bm); + } catch (Exception e) { + Log.e(TAG, "Bitmap转Base64时发生错误: " + e.getMessage()); + // 确保资源被释放 + if (scaledBitmap != null && scaledBitmap != bm && !scaledBitmap.isRecycled()) { + scaledBitmap.recycle(); + } + return null; + } + } + + /** + * 更激进的Bitmap转Base64方法,用于内存不足时的备选方案 + * @param bm 要转换的Bitmap + * @return Base64编码的字符串 + */ + private static String bitmapToBase64Aggressive(Bitmap bm) { + if (bm == null || bm.isRecycled()) { + return null; + } + + try { + // 使用更小的尺寸和更低的质量 + int originalWidth = bm.getWidth(); + int originalHeight = bm.getHeight(); + int targetWidth = 160; // 更小的目标宽度 + + Bitmap scaledBitmap; + if (originalWidth > targetWidth) { + float scaleFactor = (float) targetWidth / originalWidth; + int targetHeight = (int) (originalHeight * scaleFactor); + scaledBitmap = Bitmap.createScaledBitmap(bm, targetWidth, targetHeight, true); + } else { + scaledBitmap = bm; + } + + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + // 使用最低的压缩质量(10%) + scaledBitmap.compress(Bitmap.CompressFormat.JPEG, 10, baos); + byte[] byteArray = baos.toByteArray(); + + try { + baos.close(); + } catch (IOException e) { + Log.e(TAG, "关闭ByteArrayOutputStream失败: " + e.getMessage()); + } - return encodedString; + String encodedString = Base64.encodeToString(byteArray, Base64.NO_WRAP); + + // 立即释放资源 + byteArray = null; + if (scaledBitmap != bm && scaledBitmap != null && !scaledBitmap.isRecycled()) { + scaledBitmap.recycle(); + } + + return encodedString; + } catch (Exception e) { + Log.e(TAG, "激进压缩Bitmap转Base64时发生错误: " + e.getMessage()); + return null; + } } }