Android 事件分发介绍

文章目录

  • 一、目的
  • 二、环境
  • 三、相关概念
    • 3.1 事件分发
  • 四、详细设计
    • 4.1应用布局
      • 4.1.1 应用布局结构
      • 4.1.2 LayoutInspector
    • 4.2 关键View&方法
      • 4.2.1 相关View
      • 4.2.2 相关方法
      • 4.2.3 View与方法关系
    • 4.3 事件分发概念图
      • 4.3.1 事件分发类图
      • 4.3.2 事件分发模型图
    • 4.4 Activity组件
      • 4.4.1 Activity->dispatchTouchEvent()
      • 4.4.2 Activity->getWindow()
      • 4.4.3 Activity->onTouchEvent()
    • 4.5 ViewGroup组件
      • 4.5.1 ViewGroup->dispatchTouchEvent()
      • 4.5.2 ViewGroup->dispatchTransformedTouchEvent()
    • 4.6 View组件
      • 4.6.1 View->dispatchTouchEvent()
      • 4.6.2 OnTouchListener->onTouch()
      • 4.6.3 View->onTouchEvent()
    • 4.7 例子-点击事件时序图
  • 五、小结&问题点
  • 六、代码仓库地址
  • 七、参考资料

一、目的

        最开始接触Android时,仅仅是知道Android系统存在的点击事件、触摸事件,但是并不清楚这些事件的由来。
        之后,在面试Oppo和美图时,皆有问到Android的事件分发机制,但是都被问得很懵逼,归根到底都是对于其实现逻辑的不理解。
        随后,想去弥补该模块的不足,浏览很多关于Android事件分发的博文,但仍存在一些疑惑,就想着去阅读下源码,整理下笔记,希望对同学们有帮助。

二、环境

  1. 版本:Android 11
  2. 平台:展锐 SPRD8541E

三、相关概念

3.1 事件分发

        Android 中 View 的布局是一个树形结构,各个 ViewGroup 和 View 是按树形结构嵌套布局的,从而会出现用户触摸的位置坐标可能会落在多个 View 的范围内,这样就不知道哪个 View 来响应这个事件,为了解决这一问题,就出现了事件分发机制。

四、详细设计

4.1应用布局

4.1.1 应用布局结构

        如下为一个Activity打开后,其对应视图的层级结构。

4.1.2 LayoutInspector

        Layout Inspector是google提供给我们进行布局分析的一个工具,也是目前google在弃用Hierarchy View后推荐使用的一款布局分析工具。

4.2 关键View&方法

4.2.1 相关View

组件描述
ActivityAndroid事件分发的起始端,其为一个window窗口,内部持有Decorder视图,该视图为当前窗体的根节点,同时,它也是一个ViewGroup容器。
ViewGroupAndroid中ViewGroup是一个布局容器,可以嵌套多个 ViewGroup 和 View,事件传递和拦截都由 ViewGroup 完成。
View事件传递的最末端,要么消费事件,要么不消费把事件传递给父容器

4.2.2 相关方法

方法描述
dispatchTouchEvent分发事件
onInterceptTouchEvent拦截事件
onTouchEvent触摸事件

4.2.3 View与方法关系

组件dispatchTouchEventonInterceptTouchEventonTouchEvent
Activity
ViewGroup
View

4.3 事件分发概念图

4.3.1 事件分发类图

4.3.2 事件分发模型图

        Android的ACTION_DOWN事件分发如图,从1-9步骤,描述一个down事件的分发过程,如果大家能懂,就不用看下面文字描述了(写完这个篇幅,感觉文字好多,不好理解!)

  1. ACTION_DOWN事件触发。 当我们手指触摸屏幕,tp驱动会响应中断,通过ims输入系统,将down事件的相关信息发送到当前的窗口,即当前的Activity。
  2. Activity事件分发。 会引用dispatchTouchEvent()方法,对down事件分发。Activity本身会持有一个window对象,window对象的实现类PhoneWindow会持有一个DecorView对象,DecorView是一个ViewGroup对象,即我们可以理解为,Activity最终会将事件分发给下一个节点——ViewGroup。
  3. ViewGroup事件拦截。 ViewGroup接收到事件后,会先引用onInterceptTouchEvent(),查看当前的视图容器是否做事件拦截。
  4. ViewGroup消费事件。 如当前的ViewGroup对事件进行拦截,即会调用onTouchEvent(),对事件消费。
  5. ViewGroup事件不拦截。 则ViewGroup会继续遍历自身的子节点,并且当事件的坐标位于子节点上,则继续下发到下一个节点。ViewGroup的子节点有可能是View,也可能是ViewGroup(当然,ViewGroup最后也是继承于View的,突然感觉有点废话)。
  6. ViewGroup事件分发。 目标视图如果是ViewGroup,会引用其super类的dispatchTouchEvent()方法,即事件下发,不管目标视图是View或者ViewGroup最终引用的是View类的分发方法。
  7. View事件消费。 在View的dispatchTouchEvent()方法中会根据当前View是否可以点击、onTouch()是否消费、onTouchEvent()是否消费等条件,来判断当前是否为目标View。
  8. View事件未消费。 View事件未消费,则其父节点,即ViewGroup会调用onTouchEvent()方法,并根据返回值来决定是否消费事件。
  9. ViewGroup事件未消费。 ViewGroup事件未消费,择其父节点,即Actviity会调用onTouchEvent()方法

PS:
(1) ACTION_MOVEACTION_UP事件,流程与ACTION_DOWN的分发过程基本一致,MOVE和UP事件也是通过Activity开始,借助DOWN事件产生的目标View,逐级分发。
(2) ACTION_CANCEL事件,是在down与up、move事件切换过程中,事件被拦截,两次的touchTarget目标view不一致,而产生的事件。用于对之前的目标View做恢复处理,避免down与up/move事件不对称。

4.4 Activity组件

4.4.1 Activity->dispatchTouchEvent()

        底层上报的事件信息,最终会引用到该方法。Activity会持有一个根视图DecordView,事件最终会往该ViewGroup分发,如所有的View都未消费该事件,则最终由Activity的onTouchEvent()
来兜底处理。

@frameworks\base\core\java\android\app\Activity.java
public boolean dispatchTouchEvent(MotionEvent ev) {...if (getWindow().superDispatchTouchEvent(ev)) {//Step 1. 查看Window对应的View是否分发该事件return true;}return onTouchEvent(ev);//Step 2. 如果没有组件消费事件,则由Activity兜底处理
}

4.4.2 Activity->getWindow()

        我们每次启动一个Activity的组件,会先打开一个window窗口,而PhoneWindow是Window唯一的实现类。

@frameworks\base\core\java\android\app\Activity.java
public Window getWindow() {return mWindow;
}final void attach(Context context, ActivityThread aThread...) {...mWindow = new PhoneWindow(this, window, activityConfigCallback);//PhoneWindow是Window窗口唯一的实现类...
}

        PhoneWindow对象内部持有DecorView对象,而该View正是该窗口对应的视图容器,也是根节点。(此部分不具体分析)

@frameworks\base\core\java\com\android\internal\policy\PhoneWindow.java
public class PhoneWindow extends Window implements MenuBuilder.     Callback {...private DecorView mDecor;//...@Overridepublic boolean superDispatchTouchEvent(MotionEvent event) {return mDecor.superDispatchTouchEvent(event);//往View的根节点分发事件}
}

4.4.3 Activity->onTouchEvent()

        Activity的onTouchEvent方法,是在没有任何组件消费事件的情况下,触发的方法。

@frameworks\base\core\java\android\app\Activity.java
public boolean onTouchEvent(MotionEvent event) {if (mWindow.shouldCloseOnTouch(this, event)) {finish();return true;}return false;
}

4.5 ViewGroup组件

        ViewGroup组件在整个事件分发的模型中,既有分发事件的责任,又要具备处理事件的能力,真的典型的当爹又当妈。
        当Activity调用superDispatchTouchEvent,即最终会使用到DecorView的superDispatchTouchEvent方法,而DecorView是继承于ViewGroup,即最终会引用ViewGroup的dispatchTouchEvent方法。

4.5.1 ViewGroup->dispatchTouchEvent()

此方法为事件分发最核心的代码。其主要处理如下四件事情:
Setp 1. 重置事件。 一次完整触摸的事件:DOWN -> MOVE -> UP,即我们可以理解为DOWN是所有触摸事件的起始事件。当输入事件是ACTION_DOWN时,重置触摸事件状态信息,避免产生干扰。
Step 2. 拦截事件。 拦截事件是ViewGroup特有的方法,用于拦截事件,并将该事件分发给自己消费,防止事件继续下发。
Step 3.查找目标View。 查找目标View主要针对于Down事件。当ViewGroup未拦截事件,且输入事件是ACTION_DOWN时,会遍历该ViewGroup的所有子节点,并根据触摸位置的坐标,来决定当前子节点是否是下一级目标View。当找到目标View节点后,会分发Down事件,并记录该节点信息。
Step 4.下发事件。 如果目标View未找到的话,则会将事件交由自己的onTouchEvent()处理;如果目标View已经找到,则Down事件就此结束(此处暂不考虑多指场景);Move和Up事件将继续下发(默认情况下Move、Up和Down事件是成对出现的,如果目标View已经存在,则Down事件已经下发,即意味着Move和Up事件也需要下发给对应的目标View)。

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {...if (actionMasked == MotionEvent.ACTION_DOWN) {//Step 1.重置事件信息,避免影响下一次事件cancelAndClearTouchTargets(ev);resetTouchState();}if (actionMasked == MotionEvent.ACTION_DOWN|| mFirstTouchTarget != null) {final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;if (!disallowIntercept) {intercepted = onInterceptTouchEvent(ev);//Step 2.拦截事件ev.setAction(action); // restore action in case it was changed}} ...if (!canceled && !intercepted) {//Step 3.查找目标Viewif (actionMasked == MotionEvent.ACTION_DOWN|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {...if (newTouchTarget == null && childrenCount != 0) {...for (int i = childrenCount - 1; i >= 0; i--) {//遍历所有的子节点...if (!child.canReceivePointerEvents()|| !isTransformedTouchPointInView(x, y, child, null)) {// 子节点不可以接收事件,或者触摸位置不在子节点的范围上continue;}...if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {//找到目标View...break;}}...}...}}//Step 4.根据找到的目标View情况,继续下发事件if (mFirstTouchTarget == null) {// No touch targets so treat this as an ordinary view.handled = dispatchTransformedTouchEvent(ev, canceled, null,TouchTarget.ALL_POINTER_IDS);//没有找到目标View或者事件被拦截,事件下发给自己} else {...while (target != null) {//多组数据,一般是指多指场景final TouchTarget next = target.next;if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {//此场景一般是down事件handled = true} else {if (dispatchTransformedTouchEvent(ev, cancelChild,target.child, target.pointerIdBits)) {//此场景一般是move、up事件handled = true;}...}predecessor = target;target = next;}...}...return handled;
}

4.5.2 ViewGroup->dispatchTransformedTouchEvent()

事件分发关键方法,主要用于向目标View分发事件,具体逻辑如下:
Step 1.Cancel事件分发。 之前我们提过Down和Up事件是成对存在的,如果Down事件已经下发的情况下,Up事件却因为事件拦截等原因,未能下发给目标View,目标View未收到Up事件,此时就可能产生一些按压状态的异常问题,故,在当前场景下,将会分发一个ACTION_CANCEL事件给目标View。
Step 2.事件处理。 如果事件未找到目标View,则child会为null,此时的事件将由自身处理。
Step 3.事件分发。 如果事件还存在目标View,则此时的事件会再分发。

    private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,View child, int desiredPointerIdBits) {...if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {//Step 1.下发取消事件event.setAction(MotionEvent.ACTION_CANCEL);if (child == null) {handled = super.dispatchTouchEvent(event);} else {handled = child.dispatchTouchEvent(event);}event.setAction(oldAction);return handled;}...if (child == null) {//Step 2.如果事件未找到目标View,则触摸事件会发给自己handled = super.dispatchTouchEvent(transformedEvent);} else {final float offsetX = mScrollX - child.mLeft;final float offsetY = mScrollY - child.mTop;transformedEvent.offsetLocation(offsetX, offsetY);if (! child.hasIdentityMatrix()) {transformedEvent.transform(child.getInverseMatrix());}handled = child.dispatchTouchEvent(transformedEvent);//Step 3.找到目标View,事件下发给子节点}...return handled;}

4.6 View组件

        View组件在事件处理模型中,主要是处理事件。我们知道ViewGroup,也是继承于View,所以ViewGroup也是同样具备View的处理事件能力。

4.6.1 View->dispatchTouchEvent()

Step 1.触发onTouch()方法。 如果当前的View是可点击的,且配置了onTouch事件监听,则触发该View的onTouch()方法。
Step 2.触发onTouchEvent()方法。 如果该事件在上一步的onTouch()函数中未被消费,则触发onTouchEvent()方法。

public boolean dispatchTouchEvent(MotionEvent event) {boolean result = false;...if (onFilterTouchEventForSecurity(event)) {...ListenerInfo li = mListenerInfo;if (li != null && li.mOnTouchListener != null&& (mViewFlags & ENABLED_MASK) == ENABLED&& li.mOnTouchListener.onTouch(this, event)) {//Step 1.触发onTouch事件result = true;}if (!result && onTouchEvent(event)) {//Step 2.如onTouch未消费,触发onTouchEvent事件result = true;}}...return result;
}

4.6.2 OnTouchListener->onTouch()

        View可以设置事件监听,用于监听onTouch事件的回调,当然,像我们常见的onClick()、onLongClick()等事件也可监听,其相关源码如下:

@frameworks\base\core\java\android\view\View.java
public void setOnTouchListener(OnTouchListener l) {//设置onTouch监听getListenerInfo().mOnTouchListener = l;
}ListenerInfo getListenerInfo() {if (mListenerInfo != null) {return mListenerInfo;}mListenerInfo = new ListenerInfo();return mListenerInfo;
}public interface OnTouchListener {//Touch接口,用于回调onTouch事件boolean onTouch(View v, MotionEvent event);
}

4.6.3 View->onTouchEvent()

        事件如未被onTouch消费掉,则会引用到onTouchEvent()方法,该方法会涉及ACTION_UP、ACTION_DOWN、ACTION_CANCEL、ACTION_MOVE事件的处理,View的onClick()、onLongClick()也是由该方法触发。此外,如果当前的View是可点击的话,则直接消费该事件。

public boolean onTouchEvent(MotionEvent event) {...final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE|| (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)|| (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;//当前View是否可点击...if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {switch (action) {case MotionEvent.ACTION_UP://抬起...if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {if (!focusTaken) {removeLongPressCallback();//若有长按事件未处理,则移除长按事件if (mPerformClick == null) {mPerformClick = new PerformClick();}if (!post(mPerformClick)) {//通过Hanlder将点击事件发送到主线程执行performClickInternal();//如果不成功,则直接引用点击事件}}}if (mUnsetPressedState == null) {mUnsetPressedState = new UnsetPressedState();//更新按钮的按压事件}...break;case MotionEvent.ACTION_DOWN://按下...if (isInScrollingContainer) {//在可滚动的容器内,为了容错,延迟点击...postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());} else {setPressed(true, x, y);//设置按下的状态checkForLongClick(ViewConfiguration.getLongPressTimeout(),x,y,TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS);//开启一个长按延时事件}break;case MotionEvent.ACTION_CANCEL://取消...break;case MotionEvent.ACTION_MOVE://移动...break;}return true;//如果是可点击的View,即消费事件}...return false;
}

4.7 例子-点击事件时序图

        如下是Android的点击事件时序图,如果能够理解单击事件的由来,对于整个事件分发的知识要点已大体掌握。

五、小结&问题点

  1. 事件分发流程?包括ACTION_DWON、ACTION_UP、ACTION_MOVE事件的处理过程;
  2. ACTION_CANCEL事件的使用场景?父控件对move事件拦截场景?
  3. 单击、长按、触摸事件的产生过程?
  4. 点击一个View未抬起,同时move该事件直至离开当前View的范围,处理过程如何?
  5. 如果所有View都未消费事件,流程如何?
  6. ViewPage+ListView,左右滑动和上下滑动冲突的解决问题?即事件拦截过程?
  7. 普通的View是根据什么来决定是否消费事件,例如Button?
    =>答:如无重写onTouchEvent事件,根据当前的View是否可点击,来决定是否消费事件。

        我最开始没有看源码,直接去看博客上的内容,弯弯绕绕,似懂非懂。在面试的过程中,面试官举个场景分析流程,我都懵逼,分析不出来,现场很尴尬。之后看源码,整体流程代码量很少,感叹于Android事件分发流程的设计,很少的代码量,却承载了很重要的功能,而没有见过该模块发生过异常。
        多读书,多看报,少吃零食,多睡觉!

六、代码仓库地址

Demo地址:  https://gitee.com/linzhiqin/custom-demo

七、参考资料

https://zhuanlan.zhihu.com/p/623664769?utm_id=0
事件分发视频(总结很好,但是得先理解基本概念,才方便学习)
https://www.bilibili.com/video/BV1sy4y1W7az?p=1&vd_source=f222e3bf3083cad8d9f660629bc47c16

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/611866.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Federated Unlearning for On-Device Recommendation

WSDM 2023 CCF-B Federated Unlearning for On-Device Recommendation 本文工作的主要介绍 本文主要介绍了一种名为FRU(Federated Recommendation Unlearning)的联邦学习框架,用于在设备端的推荐系统中实现用户数据的有效擦除和模型重建。…

0x53 区间DP

0x53 区间DP 到目前为止,我们介绍的线性DP一般从初态开始,沿着阶段的扩张向某个方向递推,直至计算出目标状态。区间DP也属于线性DP中的一种,它以“区间长度”作为DP的“阶段”,使用两个坐标(区间的左右端点…

Python-面向对象

面向对象 1.初识对象1.1理解使用对象完成数据组织的思路 2.成员方法2.1类的定义和使用语法2.2成员方法的使用 3.类和对象4.构造方法4.1使用构造方法向成员变量赋值 5.其他内置方法5.1__str__字符串方法5.2__lt__小于符号比较方法5.3__le__小于等于比较符号5.4__eq__比较运算符实…

VUE3相比VUE2升级了哪些内容

目录 一、Vue 3 、Vue 2 对比及提升项 二、 Vue 3 创建app.vue示例 三、Vue3 的setup、Vue2 的 data对比 一、Vue 3 、Vue 2 对比及提升项 性能提升:Vue 3 做了大量的优化工作,提升了运行时的性能。例如,在模板编译时进行的静态分析和优化…

如何把苹果手机中的备忘录导入到电脑?这种方法特别方便

作为苹果手机的忠实用户,我深知备忘录在日常生活中的重要性。它是我随手记下的灵感闪现,是工作会议的要点记录,更是生活中的小提醒和重要事务的备忘录。然而,有时我需要将这些信息从手机导入到电脑中,以便更方便地查看…

k8s的存储卷---数据卷

前言 容器磁盘上的文件的生命周期是短暂的,这就使得在容器中运行重要应用时会出现一些问题。首先,当容器崩溃时,kubelet 会重启它,但是容器中的文件将丢失——容器以干净的状态(镜像最初的状态)重新启动。其…

【大数据OLAP引擎】StarRocks为什么快?

StarRocks的优势 StarRocks最初主要的优势是性能,当时在单表查询方面与性能标杆ClickHouse不相上下,而join优化特性使其在多表关联查询场景下的性能表现要远远优于ClickHouse,替换ClickHouse自然也就成了StarRocks的第一个目标。 而StarRoc…

three.js实现雷达扫描效果(纹理贴图)

three.js实现雷达扫描效果&#xff08;纹理贴图&#xff09; 图例 步骤 创建两个平面&#xff0c;分别纹理贴图&#xff0c;底图模型.add&#xff08;光波模型&#xff09;关闭材质的深度测试光波旋转 代码 <template><div class"app"><div ref&q…

【算法每日一练]-练习篇 #Tile Pattern #Swapping Puzzle # socks

目录 今日知识点&#xff1a; 二维前缀和 逆序对 袜子配对(感觉挺难的&#xff0c;又不知道说啥) Tile Pattern Swapping Puzzle socks Tile Pattern 331 题意&#xff1a;有一个10^9*10^9的方格。W表示白色方格&#xff0c;B表示黑色方格。每个(i,j)方的颜色由(i…

Unity真机Log工具 SRDebugger使用记录,GM布局管理

SRDebugger 官方文档安装及初始化常用设置选项布局选项快捷键选项高级设置 使用GM工具案例常用特性GM分组排序GM固定页签 官方文档 文档&#xff1a; https://www.stompyrobot.uk/tools/srdebugger/documentation/ 插件地址&#xff1a; https://assetstore.unity.com/package…

数据结构及单链表例题(下)

上次我们已经了解了单链表的数据结构定义以及创建单链表的两种方法,这节介绍几道例题. 文章目录 前言 一、已知L为带头结点的单链表,请依照递归思想实现下列运算 二、单链表访问第i个数据节点 三、在第i个元素前插入元素e 四、删除第i个结点 五、查找带头结点单链表倒数第…

TS 36.322 V12.0.0-过程

​本文的内容主要涉及TS 36.322&#xff0c;版本是C00&#xff0c;也就是V12.0.0。

构建安全可靠的系统:第十一章到第十五章

第三部分&#xff1a;实现系统 原文&#xff1a;Part III. Implementing Systems 译者&#xff1a;飞龙 协议&#xff1a;CC BY-NC-SA 4.0 一旦您分析并设计了您的系统&#xff0c;就该是实现计划的时候了。在某些情况下&#xff0c;实现可能意味着购买现成的解决方案。第十一章…

QT第1天

题目&#xff1a;点击按钮改变文字 需要增加一个count属性&#xff0c;并且只需要定义槽&#xff0c;信号函数已经内置好了 //widget.h#ifndef WIDGET_H #define WIDGET_H#include <QWidget>QT_BEGIN_NAMESPACE namespace Ui { class Widget; } QT_END_NAMESPACEclass Wi…

[C++]多态

目录 C多态&#xff1a;&#xff1a; 多态的概念 多态的定义及实现 多态的构成条件 虚函数 虚函数的重写 虚函数重写的特例 C11 override和final 重载、重写重定义的对比 抽象类 概念 接口继承和实现继承 多态的原理 虚函数表 多态的原理 动态绑定和静态绑定 单继承和…

LeetCode 84:柱状图中的最大矩形

一、题目描述 给定 n 个非负整数&#xff0c;用来表示柱状图中各个柱子的高度。每个柱子彼此相邻&#xff0c;且宽度为 1 。 求在该柱状图中&#xff0c;能够勾勒出来的矩形的最大面积。 示例 1: 输入&#xff1a;heights [2,1,5,6,2,3] 输出&#xff1a;10 解释&#xff1a…

Jmeter+ant+Jenkins 接口自动化框架完整版

接口自动化测试单有脚本是不够的&#xff0c;我们还需要批量跑指定接口&#xff0c;生成接口运行报告&#xff0c;定位报错接口&#xff0c;接口定时任务&#xff0c;邮件通知等功能。批量跑指定接口&#xff1a;我们可以利用ant批量跑指定目录下的Jmeter脚本生成接口运行报告&…

vue3基础类型和引用类型,和store的使用

案例一&#xff1a; 如果我在store创建一个变量&#xff0c;是读取缓存key为name的数据&#xff0c; store.name 默认值是张三 # 声明一个变量 const title ref(store.name) # 然后修改title.value "李四"&#xff0c; # 问&#xff1a;打印store.name&#xff0…

怎么投稿各大媒体网站?

怎么投稿各大媒体网站&#xff1f;这是很多写作者及自媒体从业者经常面临的问题。在信息爆炸的时代&#xff0c;如何将自己的文章推送到广大读者面前&#xff0c;成为了一个不可避免的挑战。本文将为大家介绍一种简单有效的投稿方法——媒介库发稿平台发稿&#xff0c;帮助大家…

5,sharding-jdbc入门-sharding-jdbc广播表

执行sql #在数据库 user_db、order_db_1、order_db_2中均要建表 CREATE TABLE t_dict (dict_id BIGINT (20) NOT NULL COMMENT 字典id,type VARCHAR (50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT 字典类型,code VARCHAR (50) CHARACTER SET utf8 COLLAT…