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

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

目前国内剪切板内容主要应用场景是类似淘口令之类的方式,通过读取剪切板的内容,弹出对应的内容;更有甚者,采集用户剪切板数据进行大数据分析,因为用户复制的内容,具备极高的用户兴趣导向,作为大数据训练素材准确性很高。 而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.
    // https://github.com/flutter/flutter/issues/60145
    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. 当用户禁用了交互,且合规问题暴露出来,我们认为婚恋系统源码势必要解决这个问题,于是我提了issue
  2. 合规规定同意用户协议后,才能获取剪切板行为,那么我们完全可以从流程去避开这个问题:

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

写在最后

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

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

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
系统源代码说明书-模板.docx》是一份描述系统源代码的说明书模板。这份模板包含着对系统源代码进行详细解释的内容,有助于其他人员理解和使用系统源代码。 首先,模板中会对系统源代码的整体架构进行描述。这包括系统的各个模块和组件的分层结构、功能划分以及彼此之间的调用关系。通过这样的描述,读者可以对源代码的整体结构有一个清晰的了解。 其次,模板会对每个模块和组件的详细设计进行说明。这包括每个模块和组件的功能介绍、输入输出接口的定义以及内部实现细节的描述。读者可以通过这些详细说明,了解每个模块和组件的具体功能和实现方式。 除此之外,模板还会对系统的核心算法和数据结构进行详细解释。这些算法和数据结构通常是系统的核心部分,对系统的性能和功能至关重要。通过对这些算法和数据结构的说明,读者可以深入了解系统的核心工作原理和实现细节。 最后,模板还会对源代码的编译和部署过程进行说明。这包括源代码的编译环境要求、编译和部署的步骤以及可能遇到的常见问题和解决方案。这部分内容对于需要重新编译或者修改源代码的人员非常有用。 总的来说,《系统源代码说明书-模板.docx》对系统源代码进行了全面的解释和说明,包括了整体架构、模块和组件设计、核心算法和数据结构以及编译和部署过程。通过这份说明书,读者可以更好地理解和使用系统源代码。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值