调用MediaProjection.createVirtualDisplay出现已启动和空指针异常如何解决
错误根因
- 第一个
IllegalStateException: Cannot start already started MediaProjection错误:循环中频繁调用startCapture(),上一次创建的MediaProjection实例还未释放就重复初始化。 - 第二个
NullPointerException错误:四类场景触发:1. 启动服务时未正确传入截图授权的resultCode和resultData,导致getMediaProjection返回空;2.stopCapture方法未对vdisplay做非空判断,释放时报错;3. 多线程并发调用startCapture和stopCapture,状态不同步导致空引用;4. Android 10及以上未启用前台服务,系统自动回收MediaProjection实例。
修复方案
1. 改造AbstractNotificationService类
添加状态标记、并发锁、空值校验:
public abstract class AbstractNotificationService extends AccessibilityService { // 新增截图状态标记 private boolean isCapturing = false; // 原有代码不变... private void stopCapture() { // 加锁保证线程安全 synchronized (this) { if (projection!=null) { projection.stop(); // 补充vdisplay非空判断 if (vdisplay != null) { vdisplay.release(); vdisplay = null; } projection=null; } isCapturing = false; } } public void startCapture() { synchronized (this) { // 先清理未释放的实例,避免重复启动 if (isCapturing || projection != null) { stopCapture(); } // 校验授权参数合法性,避免空指针 if (resultData == null || resultCode == 1337) { Log.e("CaptureError", "截图授权参数未初始化,无法启动"); return; } isCapturing = true; projection=mgr.getMediaProjection(resultCode, resultData); // 校验MediaProjection创建结果 if (projection == null) { Log.e("CaptureError", "MediaProjection创建失败,检查权限有效性"); isCapturing = false; return; } it=new ImageTransmogrifier(this); if (it.getSurface() == null) { Log.e("CaptureError", "Surface实例为空,无法创建虚拟显示器"); stopCapture(); return; } MediaProjection.Callback cb=new MediaProjection.Callback() { @Override public void onStop() { if (vdisplay != null) { vdisplay.release(); vdisplay = null; } isCapturing = false; } }; vdisplay=projection.createVirtualDisplay("andshooter", it.getWidth(), it.getHeight(), getResources().getDisplayMetrics().densityDpi, VIRTUAL_DISPLAY_FLAGS, it.getSurface(), null, handler); projection.registerCallback(cb, handler); } } // 对外暴露状态判断方法 public boolean isCapturing() { return isCapturing; } }
2. 修正MainActivity服务启动时机
确保拿到截图授权后再启动服务,避免传参为空:
@Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { if (requestCode == REQUEST_SCREENSHOT) { if (resultCode == RESULT_OK) { resultCodeX=resultCode; dataX=data; // 拿到截图权限后再请求悬浮窗权限 askForSystemOverlayPermission(); } else { Toast.makeText(this, "截图权限被拒绝,无法使用功能", Toast.LENGTH_SHORT).show(); finish(); } } else if (requestCode == DRAW_OVER_OTHER_APP_PERMISSION) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { if (Settings.canDrawOverlays(this)) { // 两个权限都拿到后再启动服务 startService(); } else { errorToast(); finish(); } } } else { super.onActivityResult(requestCode, resultCode, data); } }
3. 调整自定义线程调用逻辑
避免无间隔频繁发起截图请求:
@Override public void run() { while(循环条件){ // 仅当前无正在执行的截图任务时才发起新请求 if (!notificationService.isCapturing()) { notificationService.startCapture(); } // 按需添加截图间隔,避免占用过多系统资源 try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); break; } } notificationService.sendNotification("ENDED"); }
4. 补充前台服务逻辑
Android 10(API 29)及以上版本使用MediaProjection必须绑定前台服务,你需要放开onStartCommand中注释的foregroundify()逻辑,添加前台服务通知,否则系统会自动回收MediaProjection实例导致空指针。
内容的提问来源于stack exchange,提问作者zaxunobi
相关产品推荐
相关产品推荐

