使用相机包授予权限会在异常时暂停调试器

Posted

技术标签:

【中文标题】使用相机包授予权限会在异常时暂停调试器【英文标题】:Granting permission using camera package pauses the debugger on an exception 【发布时间】:2019-11-21 23:59:54 【问题描述】:

我正在使用 camera 包来实现简单的功能。我主要关注example 提供的包。当我打开摄像头小部件页面时,包会自动提示提供摄像头和麦克风的权限。单击允许这两个权限后,调试器将暂停并出现异常:

Exception has occurred.
FlutterError (A CameraController was used after being disposed.
Once you have called dispose() on a CameraController, it can no longer be used.).

这是所需的代码:

class CameraPage extends StatefulWidget 
  @override
  _CameraPageState createState() => _CameraPageState();


class _CameraPageState extends State<CameraPage>
    with WidgetsBindingObserver 
  CameraController _controller;
  List<CameraDescription> _availableCameras;
  ...

  @override
  void initState() 
    super.initState();
    WidgetsBinding.instance.addObserver(this);
    _initialize();
  

  Future<void> _initialize() async 
    await _getCameras();
    _controller = CameraController(_availableCameras[0], ResolutionPreset.high);
    await _controller.initialize();
    if (!mounted) 
      return;
    
    setState(() );
  

  @override
  void didChangeAppLifecycleState(AppLifecycleState state) 
    if (state == AppLifecycleState.inactive) 
      _controller?.dispose();
     else if (state == AppLifecycleState.resumed) 
      if (_controller != null) 
        _setCurrentCamera(_controller.description);
      
    
  

  @override
  void dispose() 
    WidgetsBinding.instance.removeObserver(this);
    _controller.dispose();
    super.dispose();
  


  Future<List<CameraDescription>> _getCameras() async 
    List<CameraDescription> camDescriptions;
      camDescriptions = await availableCameras();

      _availableCameras = camDescriptions;
    return camDescriptions;
  

  @override
  Widget build(BuildContext context) 
    ...
  


  Future<void> _setCurrentCamera(CameraDescription cameraDescription) async 
    if (_controller != null) 
      await _controller.dispose();
    
    _controller = CameraController(
      cameraDescription,
      ResolutionPreset.high,
      enableAudio: false,
    );

    // If the _controller is updated then update the UI.
    _controller.addListener(() 
      if (mounted) setState(() );

      if (_controller.value.hasError) 
        print('Camera error $_controller.value.errorDescription');
      
    );

    try 
      await _controller.initialize();
     on CameraException catch (e) 
      _showCameraException(e);
    

    if (mounted) 
      setState(() );
    
  

  void _switchCamera() 
    if (_controller != null && !_controller.value.isRecordingVideo) 
      CameraLensDirection direction = _controller.description.lensDirection;
      CameraLensDirection required = direction == CameraLensDirection.front
          ? CameraLensDirection.back
          : CameraLensDirection.front;
      for (CameraDescription cameraDescription in _availableCameras) 
        if (cameraDescription.lensDirection == required) 
          _setCurrentCamera(cameraDescription);
          return;
        
      
    
  


  void _showCameraException(CameraException e) 
    String errorText = 'Error: $e.code\nError Message: $e.description';
    print(errorText);
  


调试器在这里指出异常:

  Future<void> _initialize() async 
    await _getCameras();
    _controller = CameraController(_availableCameras[0], ResolutionPreset.high);
    //-------------HERE------------------
    await _controller.initialize();
    if (!mounted) 
      return;
    
    setState(() );
  

一旦我恢复调试器并尝试再次打开此相机页面,就不会再出现错误/异常。仅在第一次接受权限后才会发生。

【问题讨论】:

【参考方案1】:

可能真正的罪魁祸首是didChangeAppLifecycleState

一旦您调用await _controller.initialize(); 并显示权限对话框,就会触发生命周期事件AppLifecycleState.inactive 并根据您在didChangeAppLifecycleState 中的代码处理当前控制器,因此当应用程序在授予权限后恢复并尝试继续时,它会引发错误。

尝试删除

if (state == AppLifecycleState.inactive) 
  _controller?.dispose();

或者有一个局部变量来检查是否初始化并在初始化时忽略dispose

Future<void> _initialize() async 
  await _getCameras();
  _controller = CameraController(_availableCameras[0], ResolutionPreset.high);
  _initializing = true;
  await _controller.initialize();
  _initializing = false;
  if (!mounted) 
    return;
  
  setState(() );

didChangeAppLifecycleState

if (state == AppLifecycleState.inactive && !_initializing) 
  _controller?.dispose();


编辑:

可能是,我想我找到了问题,实际问题是didChangeAppLifecycleState,正如预期的那样,didChangeAppLifecycleState 中的if 子句,如果事实证明是真的,_controller 正在被处置,如果不是_setCurrentCamera 只是在处理任何活动控制器。因此,当您调用初始化并等待权限时,在权限未来解析之前,_controller 将由didChangeAppLifecycleState 处理。

我的解决方案只需进行简单的更改即可。将您的 initState 更改为

@override
void initState() 
  super.initState();
  _initializing = true; // set to true
  WidgetsBinding.instance.addObserver(this);
  _initialize();

在初始化之后将_initialize 函数更改为_initializing = false

Future<void> _initialize() async 
  await _getCameras();
  _controller = CameraController(_availableCameras[0],ResolutionPreset.high);
  await _controller.initialize();
  _initializing = false; // set to false
  if (!mounted) 
    return;
  
  setState(() );

和你的didChangeAppLifecycleState

@override
void didChangeAppLifecycleState(AppLifecycleState state) 
  if(_initializing)
    return;
  
  if (state == AppLifecycleState.inactive) 
    _controller?.dispose();
   else if (state == AppLifecycleState.resumed) 
    if (_controller != null) 
      _setCurrentCamera(_controller.description);
    
  

这样,如果_initializing == true 你永远不会释放当前控制器。


希望有帮助!

【讨论】:

感谢您的回复。我试过了。但又是同样的问题。? 我们不能使用controller.value.isIntialized 来执行这些检查吗? 我想你可以,我只是忘了CameraController 有一个状态字段:D 试试if((!controller?.value?.isIntialized)??false) return; 你能分享一下sn-p你是怎么检查的吗?

以上是关于使用相机包授予权限会在异常时暂停调试器的主要内容,如果未能解决你的问题,请参考以下文章

为啥 Chrome 会在 jQuery 中的某行暂停?

如何调试Android App以授予特权权限?

如何调试/重置 Android 6.0 权限?

CLR 调试体系结构

首次异常和二次异常

VSCode 调试器在节点模块文件中意外暂停