ITPub博客

首页 > 应用开发 > IT综合 > 婚恋系统源码如何合规获取剪切板内容

婚恋系统源码如何合规获取剪切板内容

IT综合 作者:云豹科技阿星 时间:2021-10-13 16:55:51 0 删除 编辑

获取剪切板内容的应用场景

目前国内剪切板内容主要应用场景是类似淘口令之类的方式,通过读取剪切板的内容,弹出对应的内容;更有甚者,采集用户剪切板数据进行大数据分析,因为用户复制的内容,具备极高的用户兴趣导向,作为大数据训练素材准确性很高。 而Flutter输入框为何也获取剪切板内容, 有留意过长按输入框的交互吗? 长按会有toolbar提供粘贴、复制等功能, 而粘贴就必须先获取剪切板的内容。 然后基本上婚恋系统源码的登录页都有输入框, 只要你在用户同意隐私协议之前,显示了Flutter中的TextField,就必然会触发这个潜在的合规问题。 🐶

Flutter输入框是如何获取剪切板数据的

这个问题需要我们一步步来跟踪源码。

  1. 首先看TextField的源码,有一个属性 enableInteractiveSelection,可以理解为启用交互式选择。从婚恋系统源码的业务逻辑出发,把这个属性设为false,应该就不会出现toolbar了,那应该不需要获取剪切板数据以提供粘贴功能。
/// text_field.dart
/// TextField的常量构造函数
const TextField({
    Key? key,
    this.controller,
    this.focusNode,
    this.decoration = const InputDecoration(),
    TextInputType? keyboardType,
    this.textInputAction,
    this.textCapitalization = TextCapitalization.none,
    this.style,
    this.strutStyle,
    this.textAlign = TextAlign.start,
    this.textAlignVertical,
    this.textDirection,
    this.readOnly = false,
    ToolbarOptions? toolbarOptions,
    this.showCursor,
    this.autofocus = false,
    this.obscuringCharacter = '•',
    this.obscureText = false,
    this.autocorrect = true,
    SmartDashesType? smartDashesType,
    SmartQuotesType? smartQuotesType,
    this.enableSuggestions = true,
    this.maxLines = 1,
    this.minLines,
    this.expands = false,
    this.maxLength,
    @Deprecated(
      'Use maxLengthEnforcement parameter which provides more specific '
      'behavior related to the maxLength limit. '
      'This feature was deprecated after v1.25.0-5.0.pre.',
    )
    this.maxLengthEnforced = true,
    this.maxLengthEnforcement,
    this.onChanged,
    this.onEditingComplete,
    this.onSubmitted,
    this.onAppPrivateCommand,
    this.inputFormatters,
    this.enabled,
    this.cursorWidth = 2.0,
    this.cursorHeight,
    this.cursorRadius,
    this.cursorColor,
    this.selectionHeightStyle = ui.BoxHeightStyle.tight,
    this.selectionWidthStyle = ui.BoxWidthStyle.tight,
    this.keyboardAppearance,
    this.scrollPadding = const EdgeInsets.all(20.0),
    this.dragStartBehavior = DragStartBehavior.start,
    this.enableInteractiveSelection = true // 这个属性
  })
/// 确实也是通过这个变量控制交互toolbar的显示与否
class _TextFieldSelectionGestureDetectorBuilder extends TextSelectionGestureDetectorBuilder {
  _TextFieldSelectionGestureDetectorBuilder({
    required _TextFieldState state,
  }) : _state = state,
       super(delegate: state);
  final _TextFieldState _state;
  @override
  void onForcePressStart(ForcePressDetails details) {
    super.onForcePressStart(details);
    if (delegate.selectionEnabled && shouldShowSelectionToolbar) {
      editableText.showToolbar();
    }
  }
  @override
  void onForcePressEnd(ForcePressDetails details) {
    // Not required.
  }
// 省略源码 *****
}

通过婚恋系统源码可以知道,TextField的真实渲染对象是editableText,editableText中会判断传入的enableInteractiveSelection,为false不去获取剪切板内容

/// editable_text.dart
bool get selectionEnabled => enableInteractiveSelection;
@override
  void didUpdateWidget(EditableText oldWidget) {
    super.didUpdateWidget(oldWidget);
    // 省略代码*****
    if (widget.style != oldWidget.style) {
      final TextStyle style = widget.style;
      // The _textInputConnection will pick up the new style when it attaches in
      // _openInputConnection.
      if (_hasInputConnection) {
        _textInputConnection!.setStyle(
          fontFamily: style.fontFamily,
          fontSize: style.fontSize,
          fontWeight: style.fontWeight,
          textDirection: _textDirection,
          textAlign: widget.textAlign,
        );
      }
    }
    // selectionEnabled即enableInteractiveSelection,
    // 为false不调用update()。update方法后面会讲到,其实就是这个方法在获取剪切板内容
    if (widget.selectionEnabled && pasteEnabled && widget.selectionControls?.canPaste(this) == true) {
      _clipboardStatus?.update();
    }
  }

到这里,一切都很顺利,因为婚恋系统源码的一些业务不需要启用交互,那么Flutter就没理由随意获取剪切板数据。然而坑就出在这里, 即便enableInteractiveSelection设置为false,Flutter还是在另一个地方获取了剪切板内容,而且没有属性可配置!!!🔥 我们来到EditableTextState类,里面有_clipboardStatus私有变量, 监听系统剪切板变化的变量,通过ValueNotifier进行通知。

/// editable_text.dart
void _onChangedClipboardStatus() {
    setState(() {
      // Inform the widget that the value of clipboardStatus has changed.
    });
  }
  // State lifecycle:
  @override
  void initState() {
    super.initState();
    _clipboardStatus?.addListener(_onChangedClipboardStatus);
    widget.controller.addListener(_didChangeTextEditingValue);
    _focusAttachment = widget.focusNode.attach(context);
    widget.focusNode.addListener(_handleFocusChanged);
    _scrollController = widget.scrollController ?? ScrollController();
    _scrollController!.addListener(() { _selectionOverlay?.updateForScroll(); });
    _cursorBlinkOpacityController = AnimationController(vsync: this, duration: _fadeDuration);
    _cursorBlinkOpacityController.addListener(_onCursorColorTick);
    _floatingCursorResetController = AnimationController(vsync: this);
    _floatingCursorResetController.addListener(_onFloatingCursorResetTick);
    _cursorVisibilityNotifier.value = widget.showCursor;
  }

initState是必定要走addListener方法的,而addListener里面就自动调用了前面的_clipboardStatus.update()方法,读取了剪切板内容

/// text_selection.dart
 @override
  void addListener(VoidCallback listener) {
    if (!hasListeners) {
      WidgetsBinding.instance!.addObserver(this);
    }
    if (value == ClipboardStatus.unknown) {
      update();
    }
    super.addListener(listener);
  }
/// Check the [Clipboard] and update [value] if needed.
  Future<void> update() async {
    // iOS 14 added a notification that appears when an app accesses the
    // clipboard. To avoid the notification, don't access the clipboard on iOS,
    // and instead always show the paste button, even when the clipboard is
    // empty.
    // TODO(justinmc): Use the new iOS 14 clipboard API method hasStrings that
    // won't trigger the notification.
    // 
    switch (defaultTargetPlatform) {
      case TargetPlatform.iOS:
        value = ClipboardStatus.pasteable;
        return;
      case TargetPlatform.android:
      case TargetPlatform.fuchsia:
      case TargetPlatform.linux:
      case TargetPlatform.macOS:
      case TargetPlatform.windows:
        break;
    }
    ClipboardData? data;
    try {
      // 这里获取了剪切板数据
      data = await Clipboard.getData(Clipboard.kTextPlain); 
    } catch (stacktrace) {
      // In the case of an error from the Clipboard API, set the value to
      // unknown so that it will try to update again later.
      if (_disposed || value == ClipboardStatus.unknown) {
        return;
      }
      value = ClipboardStatus.unknown;
      return;
    }
    final ClipboardStatus clipboardStatus = data != null && data.text != null && data.text!.isNotEmpty
        ? ClipboardStatus.pasteable
        : ClipboardStatus.notPasteable;
    if (_disposed || clipboardStatus == value) {
      return;
    }
    value = clipboardStatus;
  }

解析完毕,坑的原因找出来了,但是填坑却没那么简单!

如何避坑

既然婚恋系统源码实现如此,要改只能改源码,但我并不建议这么改,改源码对于协同开发很不友好。

  1. 当用户禁用了交互,且合规问题暴露出来,我们认为婚恋系统源码势必要解决这个问题,于是我提了
  2. 合规规定同意用户协议后,才能获取剪切板行为,那么我们完全可以从流程去避开这个问题:

用户未同意协议前,不要进入到带有输入框的页面;现在很多婚恋系统源码也是这样做的,未同意协议就停留在闪屏页吧,能省好多事; ② 流程实在难改,就把输入框先换成普通的Container,同意后再换成textField就可以啦。

写在最后

合规问题处理起来确实是很繁琐的事情,特别是各种第三方库的坑,排查起来又非常难。但是呢,锤子🔨之所以是锤子,是因为它把所有的事情都看成钉子。 理清思路,逐一排查,认真阅读婚恋系统源码,同时编写一些工具去验证你的排查成果往往事半功倍。

我们一起学习、进步!!!

声明:本文由云豹科技转发自Karl_wei博客,如有侵权请联系作者删除


来自 “ ITPUB博客 ” ,链接:http://blog.itpub.net/69982461/viewspace-2795795/,如需转载,请注明出处,否则将追究法律责任。

请登录后发表评论 登录
全部评论

注册时间:2020-08-24

  • 博文量
    229
  • 访问量
    78021