likes
comments
collection
share

隐私合规-Flutter 频繁读取系统剪贴板问题

作者站长头像
站长
· 阅读数 18

问题表现

在跑自动化检测时,发现Flutter存在频繁读取系统剪贴板问题,堆栈如下

io.flutter.plugin.platform.PlatformPlugin.getClipboardData() 465 <- 
io.flutter.plugin.platform.PlatformPlugin.access$900() 29 <- 
io.flutter.plugin.platform.PlatformPlugin$1.getClipboardData() 118 <- 
io.flutter.embedding.engine.systemchannels.PlatformChannel$1.onMethodCall() 158 <- 
io.flutter.plugin.common.MethodChannel$IncomingMethodCallHandler.onMessage() 233 <- 
io.flutter.embedding.engine.dart.DartMessenger.handleMessageFromDart() 84 <- 
io.flutter.embedding.engine.FlutterJNI.handlePlatformMessage() 865 <- 
android.os.MessageQueue.nativePollOnce() -2 <- 
android.os.MessageQueue.next() 363 <- 
android.os.Looper.loop() 176 <- 
android.app.ActivityThread.main() 8,668 <- 
java.lang.reflect.Method.invoke() -2 <- 
com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run() 513 <- 
com.android.internal.os.ZygoteInit.main() 1,109

很明显是systemchannels调用PlatformPlugin的getClipboardData方法,调用方肯定是Dart层,排查后发现是TextFormField引发的,切换键盘和输入文字都会触发读取系统剪贴板,由于频率过高,会有合规风险。

解决方案

在java层做拦截

可以参考Android隐私合规检测这个库,直接拦掉,但考虑到业务,这会给用户体验带来新的问题,输入框粘贴功能无法使用了,因为你无法预知用户什么时候粘贴了内容,比如前后台切回来,输入过程中复制了内容,如果全拦就影响业务了,因此推荐在dart层做修改

在Dart层修改优化,修改Flutter framework

看源码,发现不合理的调用主要来自两个地方EditableTextState的initState和EditableTextState的didUpdateWidget。 EditableTextState的initState是在首次初始化ClipboardStatusNotifier时,默认赋值的状态是ClipboardStatus.unknown,导致在addListener时读取了一次系统剪贴板。 第一个问题,可以在首次初始化时,把状态改成ClipboardStatus.notPasteable,因为此时并不需要读取系统剪贴板

//EditableText
final ClipboardStatusNotifier? _clipboardStatus = kIsWeb ? null : ClipboardStatusNotifier();
@override
void initState() {
  super.initState();
  _clipboardStatus?.addListener(_onChangedClipboardStatus);
  //...
}


// ClipboardStatusNotifier
@override
void addListener(VoidCallback listener) {
  if (!hasListeners) {
    WidgetsBinding.instance!.addObserver(this);
  }
  // 添加监听的时候,如果状态是unknown,会去读取一次
  if (value == ClipboardStatus.unknown) {
    update();
  }
  super.addListener(listener);
}

第二个问题,EditableTextState的didUpdateWidget是键盘切换,文字输入有变化都会触发,而每次触发都会走到didUpdateWidget重建方法里

//EditableText
@override
void didUpdateWidget(EditableText oldWidget) {
  super.didUpdateWidget(oldWidget);
  //..省略无关代码
  //如果输入框打支持粘贴,每次更新widget都会读取。。。
  if (widget.selectionEnabled && pasteEnabled && widget.selectionControls?.canPaste(this) == true) {
    _clipboardStatus?.update();
  }
}

其实这是完全没必要的,只要是在长按输入框触发粘贴时,读取一次剪贴板就够了,具体源码可以查看_TextSelectionControlsToolbar,而粘贴开关默认都是开的,因此针对这个问题,可以把didUpdateWidget里的这段代码注释掉。 以上修改,为了尽可能减少影响,最好判断下平台,尽量控制只针对Android

其他方式

如果不具备自定义Framework的能力,只能拷贝源码修改或者把paste粘贴开关关掉,牺牲一部分业务功能。

toolbarOptions: ToolbarOptions(copy: true,cut: true,paste: false,selectAll: true),