• 大纲:
    • 问题描述
    • 结论先行
    • Android的DragAndDrop机制
    • 网页元素拖拽与Android DragAndDrop机制的结合
    • 负责网页元素拖拽到Android的实现

问题描述

  • 最近开发遇到网页公式元素拖拽到android应用端内接收,结果显示乱码的问题。具体见视频:
  • 对比查看从浏览器拖拽常规元素(文本、图片)到Android应用中接收,发现无乱码现象,此时intent的ClipData中,数据可以正常预览
  • 问题链接:

    结论先行

    • 这里涉及到Android的DragAndDrop机制,关于拖拽有以下的情况:
      • Android应用原生控件的拖拽:View→View
      • 网页元素控件拖拽:Web→WebView→View
        • 普通网页控件拖拽
        • 复杂自定义网页控件拖拽
    • 对于不同的拖拽方式,处理方案:
      • View→View:
        • 拖拽实现:拖拽事件Android默认提供
        • 数据传递:通过拖拽的时候,利用DragEvent中的ClipData进行数据传递
      • 普通网页控件拖拽:
        • 拖拽实现:web端有默认实现,为特定的控件设置draggable为true即可
        • 数据传递:由webView对基础控件数据提供默认数据封装传递,具体为将DropDataAndroid实例转化为ClipData
      • 复杂自定义网页控件拖拽:需要自定义协议
        • 拖拽实现:web控件拖拽由网页自行实现,通过事件回传响应具体的拖拽事件
        • 数据传递:使用JSBridge,进行拖拽时的自定义数据传递,如传递latex公式

    Android的DragAndDrop机制

    Android 的拖拽框架是一个系统级的、功能完备的事件处理模型,它不仅仅是视图的视觉移动,更是一套包含手势识别、数据传输和状态反馈的完整机制。

    核心类

    • DragEvent: 代表一个拖拽过程中的特定事件,如 ACTION_DRAG_STARTED, ACTION_DRAG_ENTERED, ACTION_DROP, ACTION_DRAG_ENDED 等。它是拖拽状态机流转的载体。
    • ClipData: 拖拽操作中跨进程/跨应用传递数据的标准容器。包含一个或多个 ClipData.Item,每个 Item 可以承载文本、URI 或 Intent。
    • ClipDescription: 对 ClipData 中数据类型的描述,主要通过 MIME 类型数组来定义。接收方通过检查 ClipDescription 来判断自己是否能处理这份数据。
    • View.DragShadowBuilder: 用于创建拖拽过程中的“拖拽阴影”(即用户手指下跟随移动的视觉反馈图像)。可以自定义其外观和行为。

    拖拽生命周期与事件分发

    拖拽的生命周期始于 View.startDragAndDrop() 的调用,终于 ACTION_DRAG_ENDED 事件被分发。

    事件分发

    1. 发起: 当 View.startDragAndDrop() 被调用时,它实际上是通过 View 的 mParent 引用,一路向上调用,最终到达 ViewRootImpl。
    1. 系统服务介入: ViewRootImpl 通过 Binder IPC 调用 WindowManagerService 的 performDrag() 方法。这一步是拖拽操作从应用进程空间进入系统服务空间的关键,使得拖拽可以跨越应用窗口。
    1. 全局广播: WindowManagerService 负责在系统层面管理拖拽会话。它会向所有可见的窗口(Window)广播一个 ACTION_DRAG_STARTED 事件。
    1. 分发至目标窗口: 每个应用的 ViewRootImpl 接收到来自 WindowManagerService 的 DragEvent 后,通过其内部的 Handler 将事件分发到其视图树的根部。
    1. 视图树内分发: ViewGroup 负责将 DragEvent 向下分发给子 View。只有设置了 OnDragListener 的 View 才会真正处理这些事件。这个过程与 TouchEvent 的分发类似,但目标是寻找“愿意接收”的 View,而不是寻找“触摸点下”的 View。
    技巧:具体的事件调用链,可以在View中,添加拖拽监听,并在其中将调用堆栈打印,查看其调用链
    注意:对于拖拽过程中传递的数据,在startDrag的时候,就已经进行封装并创建出ClipData进行承载,一并放入DragEvent进行传递

    网页元素拖拽

    普通元素拖拽进Android的数据定义

    • 由Android源码查看发现,发现在事件分发过程中,ClipData已经创建完毕,分析日志发现WindowManager在进入拖拽时,会有对应拖拽视图数据的日志。在WebView中的元素拖拽时,是由Chromium根据拖拽DOM元素类型,进行创建并映射到Android DragAndDrop 机制的ClipData。
    不同元素的clipData可见下图:
    notion image
    可以看到,不同的元素类型,会创建出不同内容的ClipData,并承载不同的数据(仅支持特定内容

    复杂自定义元素拖拽进Android

    • 在上面的分析中,WebView网页默认仅提供有限的网页元素拖拽和数据传递,如果有复杂的元素需要支持拖拽和数据传递,默认的拖拽处理就不能满足
    • 这时候就需要自定义拖拽事件、定义拖拽协议,并同时在Web网页端和Android端同时处理并实现协议
    • 具体的自定义协议,根据需要参考其他资料

    源码:Chromium Android WebView对于拖拽数据的转化

    这里根据这题的问题进行跟踪,探索为什么拖拽网页公式到Android应用中,无法正常显示的原因。
    跟踪Chromium android_webview的实现,可以定位到最终创建ClipData的逻辑如下,getDragTargetType返回类型为TEXT,所以mimeType返回为 text/plain chrome/text
    notion image
    从 getDragTargetType的注释看出,类型匹配时,结果将偏向纯文本>图像>链接:
    notion image
    isPlainText的判断逻辑如下:不是URL且文本内容非空,则判定为plaintext。
    通过F12进资源页面查看,发现拖拽元素的div没有url标签,所以url是空。
    可以推断该拖拽元素,有TEXT内容
    notion image
    关于DropDataAndroid的实例则是由Native层创建传递过来的,就不做细究了。
    notion image

    总结

    至此,Android端的ClipData对象是由WebView Java层根据进入拖拽时刻的元素信息进行创建的,网页的资源被封装到Web View内部的AndroidDragData,再在Android WebView根据AndroidDragData创建ClipData。AndroidDragData只有HTML标签的基本信息,比如文本、URL、超链接、图片这些。而这题里的拖拽原始是三方资源的公式类型,原生不支持,需要走自定义协议,用JS Bridge进行通信。

    参考

    AI回复:
    notion image
     
    开发记录:使用 Profile 辅助定位和解决 Android 多线程绘图时序问题实践Claude-task-master 学习记录
    Loading...