TextView ClickableSpan 事件分发的坑

TextView 的 ClickableSpan 有两个坑:

  1. 默认情况下,点击 ClickableSpan 的文本时会同时触发绑定在 TextView 的监听事件;
  2. 默认情况下,点击 ClickableSpan 的文本之外的文本时,TextView 会消费该事件,而不会传递给父 View;

本文探究这两个坑的出现的原因,以及第二个坑的解决方法。

0. 关于 ClickableSpan


 
注意红色框中不同颜色的文本 


使用 ClickableSpan 富文本实现在同一个 TextView 中的文本的颜色、大小、背景色等属性的多样化和个性化。如下图红色框内是一个 TextView(也可能是多个 TextView),但是却有两种不同的颜色,这种效果就可以用 Spannale 实现:

 
  1. Spannable richText = new Spannale("<font color=#E3E5F3>Alan海波</font>回复<font color=#E3E5F3>大赞</font>:你走开···");

  2. textView.setText(richText);

  • 1
  • 2
  • 1
  • 2

如果不仅颜色不同,还要对某些文字添加响应事件(如跳转链接等),可以使用如下方式:

 
  1. String username = "Alan 海波";

  2. String content = "你走开……";

  3. SpannableString spannableString = new SpannableString(username);

  4. ClickableSpan span = new ClickableSpan() {

  5. @Override

  6. public void onClick(View widget) {

  7. // do sth.

  8. }

  9. @Override

  10. public void updateDrawState(TextPaint ds) {

  11. ds.setColor(getResources().getColor(R.color.link_color));

  12. ds.setUnderlineText(false);

  13. }

  14. };

  15. spannableString.setSpan(span, 0, username.length(), Spannable.SPAN_INCLUSIVE_INCLUSIVE);

  16. Spanned replyText = Html.fromHtml("<font color=" + getColor(R.color.deep_gray) + ">回复</font>");

  17. Spanned colon = Html.fromHtml("<font color=" +getColor(R.color.link_color) + ">:</font>");

  18. Spanned body = Html.fromHtml("<font color=" + getColor(R.color.text_color) + ">" + content + "</font>");

  19. Spanned richText = (Spanned) android.text.TextUtils.concat(spannableString, replyText, spannableString, colon, body);

  20. textView.setText(richText);

  21. tv.setMovementMethod(LinkMovementMethod.getInstance());

注意:

  • Html.fromHtml(string) 中如果 string 过大,会抛出 java.io.IOException: Push back buffer is full,具体见 Code example for SpannedString(不靠谱的解决方法);
  • Html.fromHtml(string) 会将 string 中的 ‘\r’ 和 ‘\n’ 替换成空格,需要显式的将 ‘\r’(早期 Mac 系统)和 ‘\n’ (Unix 和 Max OS X)和 ‘\r\n’(Windows) 替换成 html 识别的 ‘< br>’,不替换的话,如果 string 中出现 “xx&xx\r” 形式的子串,会发生 IOException:
Html.fromHtml(string.replace("\r\n", "< br>").replace("\n", "< br>")).replace("\r", "< br>"));
  • 1
  • 1
  • 用 ClickableSpan 给 TextView 中的文本设置响应事件 a,再对 TextView 设置响应事件 b,在某些机型上点击文本时会同时触发 a 和 b;
  • 安卓原生仅仅支持以下 HTML tag 标签:(具体见 The CommonsBlog — HTML Tags Supported By TextView):
 
  1. <a href="...">

  2. <b>

  3. <big>

  4. <blockquote>

  5. <br>

  6. <cite>

  7. <dfn>

  8. <div align="...">

  9. <em>

  10. <font size="..." color="..." face="...">

  11. <h1>

  12. <h2>

  13. <h3>

  14. <h4>

  15. <h5>

  16. <h6>

  17. <i>

  18. <img src="...">

  19. <p>

  20. <small>

  21. <strike>

  22. <strong>

  23. <sub>

  24. <sup>

  25. <tt>

  26. <u>

有支持所有的 HTML tag 标签的库,具体见 GitHub - NightWhistler/HtmlSpanner: Android HTML rendering library with CSS support;

1. 从一只 bug 说起

微信版本: 6.3.8,复现机型:Genymotion HTC Evo(4.1.1 系统)或红米1W真机(MIUI-JHBCNBL30.0, Android 4.2.2),其他机型暂时没测。 
正常逻辑:朋友圈里的一条评论,点击评论人或被回复人的昵称可以进入该用户主页(监听事件 A),点击评论内容的其他地方则弹出输入框和软键盘(监听事件 B)。 
Bug 是:点击昵称,既弹出了输入框和软键盘,又进入了用户主页。即,点击昵称时 A 和 B 事件同时被触发了,显然这是不合理的。具体复现过程如下图: 


 

2. Bug 原因分析

我们找出 4.1.1 系统的源码,通过 TextView 事件分发相关的代码我们可以看到:

 
  1. @Override

  2. public boolean onTouchEvent(MotionEvent event) {

  3. final int action = event.getActionMasked();

  4. if (mEditor != null) mEditor.onTouchEvent(event);

  5. final boolean superResult = super.onTouchEvent(event);

  6. /*

  7. * Don't handle the release after a long press, because it will

  8. * move the selection away from whatever the menu action was

  9. * trying to affect.

  10. */

  11. if (mEditor != null && mEditor.mDiscardNextActionUp && action == MotionEvent.ACTION_UP) {

  12. mEditor.mDiscardNextActionUp = false;

  13. return superResult;

  14. }

  15. final boolean touchIsFinished = (action == MotionEvent.ACTION_UP) &&

  16. (mEditor == null || !mEditor.mIgnoreActionUpEvent) && isFocused();

  17. if ((mMovement != null || onCheckIsTextEditor()) && isEnabled()

  18. && mText instanceof Spannable && mLayout != null) {

  19. boolean handled = false;

  20. if (mMovement != null) {

  21. handled |= mMovement.onTouchEvent(this, (Spannable) mText, event);

  22. }

  23. final boolean textIsSelectable = isTextSelectable();

  24. if (touchIsFinished && mLinksClickable && mAutoLinkMask != 0 && textIsSelectable) {

  25. // The LinkMovementMethod which should handle taps on links has not been installed

  26. // on non editable text that support text selection.

  27. // We reproduce its behavior here to open links for these.

  28. ClickableSpan[] links = ((Spannable) mText).getSpans(getSelectionStart(),

  29. getSelectionEnd(), ClickableSpan.class);

  30. if (links.length > 0) {

  31. links[0].onClick(this);

  32. handled = true;

  33. }

  34. }

  35. // more code

当我们点击昵称时,进入 TextView.onTouchEvent()方法,先执行到第 7 行,调用了 super.onTouchEvent(event),触发了绑定在 TextView 上的弹出输入框和软键盘的事件,此时运行并没有结束,而是继续往下执行了第 39 行,调用了 links[0].onClick(this),触发了昵称上的 ClickableSpan 事件,即进入个人主页。故而,两个监听事件都被执行了。

还有一点需要注意。当你在事件 A 和 B 中打印日志时,你会发现 B 事件的日志总是出现在 A 的前面,恰好与代码的执行顺序相反。这是因为,事件 A 是一个 OnClickLisntener,是在一个新的线程中执行的,有延迟,而事件 B 是在主线程中执行的,所以先于 A 执行。

3. 高版本系统上没有该 Bug

虽然如此,但是同样的微信版本,在 4.4 或者 6.0 的机器上就没有此 bug。

究其原因,在 runnable 执行的时候,此时页面已经发生了跳转,由于某种神秘原因,被 post 出去的 Runnable 消息即performClick() 没有执行。

而如果将页面跳转的动作改成打印日志或设置 TextView 文本等操作,则二者都会执行,即 performClick() 正常执行。

如下代码,给 ClickableSpan 设定的点击事件是设置 tv1 的文本为 “clickablespan is clicked“,给 tv2 设置的监听事件是设置 tv2 的文本为 ”textview’s listener is triggered“,从 gif 图的执行结果可以看出,两个事件都被执行了。

WiredClickableSpan.Java

 
  1. public class WiredClickableSpan extends Activity {

  2. @Override

  3. protected void onCreate(Bundle savedInstanceState) {

  4. super.onCreate(savedInstanceState);

  5. setContentView(R.layout.activity_wired_layout);

  6. final TextView tv1 = (TextView) findViewById(R.id.tv1);

  7. final TextView tv2 = (TextView) findViewById(R.id.tv2);

  8. final TextView content = (TextView) findViewById(R.id.comment_item_detail_content);

  9. String text1 = "我是和常常大声点发大水发送到发送到发";

  10. SpannableString str1 = new SpannableString(text1);

  11. ClickableSpan cs1 = new ClickableSpan() {

  12. @Override

  13. public void onClick(View widget) {

  14. // Intent intent = new Intent(WiredClickableSpan.this, DemoClickableSpan.class);

  15. // startActivity(intent);

  16. tv1.setText("clickablespan is clicked");

  17. }

  18. };

  19. str1.setSpan(cs1, 3, 9, Spannable.SPAN_INCLUSIVE_INCLUSIVE);

  20. content.setText(str1);

  21. content.setOnClickListener(new View.OnClickListener() {

  22. @Override

  23. public void onClick(View v) {

  24. tv2.setText("textview's listener is triggered");

  25. }

  26. });

  27. content.setMovementMethod(LinkMovementMethod.getInstance());

  28. }

activity_wired_layout.xml

 
  1. <?xml version="1.0" encoding="utf-8"?>

  2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"

  3. android:id="@+id/ll"

  4. android:layout_width="match_parent"

  5. android:layout_height="wrap_content"

  6. android:gravity="center"

  7. android:orientation="vertical">

  8. <TextView

  9. android:id="@+id/comment_item_detail_content"

  10. android:layout_width="wrap_content"

  11. android:layout_height="wrap_content"

  12. android:textSize="13sp" />

  13. <TextView

  14. android:id="@+id/tv1"

  15. android:layout_width="wrap_content"

  16. android:layout_height="wrap_content"

  17. android:textSize="13sp" />

  18. <TextView

  19. android:id="@+id/tv2"

  20. android:layout_width="wrap_content"

  21. android:layout_height="wrap_content"

  22. android:textSize="13sp" />

  23. </LinearLayout>

执行结果 


 

4. 另一个问题

仔细查看 TextView.onTouchEvent(MotionEvent action) 和 LinkMovementMethod.onTouchEvent(TextView widget, Spannable buffer, MotionEvent event) 的相关代码,你会发现:如果给一个 TextView 的文本的某些字符设置 ClickableSpan,点击 ClickableSpan 区域之外的文本时,TextView 将消费该事件,而不会将其传递给父 View。那么,在这种情况下,如何才能让事件传递给父 View 呢?

这篇博文给出了一个思路:《仿微信朋友圈,点击评论,生成自定义超链接,并处理》。下面我们将采用另外一种方法解决这个问题。

我们看一下大众点评APP点评详情页的效果: 


 

我们在 Android Device Monitor 里面看这个布局: 


 


我们假设一条评论包括头像、昵称、时间、评论内容,由于点击评论的空白部分、时间(无 ClickableSpan 的 TextView)、评论内容(带有 ClickableSpan 的 TextView),都会触发相同的效果:(1)评论背景变色;(2)弹出回复框,所以我们假定变色的 selector 和弹出回复框的事件都绑定在父 View 即 LinearLayout 上,而且回复内容的 TextView 默认会消费事件。

那么问题来了,当我点击回复内容的 ClickableSpan 区域之外的内容时,回复内容所在的 TextView 默认会消费该事件,不会传递给父 View,从而不会产生背景变色和弹出回复框。

这显然是矛盾的。

假设有误,如果事件绑定在父 View 上没有问题,那么问题就出在 TextView 会消费事件。

要想实现该效果,点击 ClickableSpan 区域之外的 TextView 不能消费,要把事件传递给父 View 去处理。

在给出解决方案之前,我们先来探究一下点击 ClickableSpan 区域之外的文本时 TextView 会消费事件原因。

来看 LinkMovementMethod.onTouchEvent(TextView widget, Spnnable buffer, MotionEvent event) 的代码:

 
  1. @Override

  2. public boolean onTouchEvent(TextView widget, Spannable buffer,

  3. MotionEvent event) {

  4. int action = event.getAction();

  5. if (action == MotionEvent.ACTION_UP ||

  6. action == MotionEvent.ACTION_DOWN) {

  7. int x = (int) event.getX();

  8. int y = (int) event.getY();

  9. x -= widget.getTotalPaddingLeft();

  10. y -= widget.getTotalPaddingTop();

  11. x += widget.getScrollX();

  12. y += widget.getScrollY();

  13. Layout layout = widget.getLayout();

  14. int line = layout.getLineForVertical(y);

  15. int off = layout.getOffsetForHorizontal(line, x);

  16. ClickableSpan[] link = buffer.getSpans(off, off, ClickableSpan.class);

  17. if (link.length != 0) {

  18. if (action == MotionEvent.ACTION_UP) {

  19. link[0].onClick(widget);

  20. } else if (action == MotionEvent.ACTION_DOWN) {

  21. Selection.setSelection(buffer,

  22. buffer.getSpanStart(link[0]),

  23. buffer.getSpanEnd(link[0]));

  24. }

  25. return true;

  26. } else {

  27. Selection.removeSelection(buffer);

  28. }

  29. }

  30. return super.onTouchEvent(widget, buffer, event);

  31. }

点击 ClickableSpan 的文本时,执行第 25 行代码 link[0].onClick(widget) ,ClickableSpan 的事件执行,然后第 32 行返回 true,这是正确的过程;

当点击 ClickableSpan 文本之外的文本时,执行最后一行 return super.onTouchEvent(widget, buffer, event) ,单步 debug 你会发现 super.onTouchEvent(widget, buffer, event) = true,最终这个 true 被 TextView.onTouchEvent() 返回给父View,即告诉父 View:这个事件我消费了,你别管了。

这就是本质原因,虽然我们不知道为什么这么设计,但是这个设计显然不符合我们的要求。

既然问题就出在最后一行代码上,改下就好了。

将最后一行代码改为:

return false;
  • 1
  • 1

即可。

但是,改完之后发现 TextView 还是会消费事件。究其原因,是因为我们在调用 TextView.setMovementMethod() 的时候源码调用了 fixFocusableAndClickableSettigns() 方法:

 
  1. public final void setMovementMethod(MovementMethod movement) {

  2. if (mMovement != movement) {

  3. mMovement = movement;

  4. if (movement != null && !(mText instanceof Spannable)) {

  5. setText(mText);

  6. }

  7. fixFocusableAndClickableSettings();

  8. // SelectionModifierCursorController depends on textCanBeSelected, which depends on

  9. // mMovement

  10. if (mEditor != null) mEditor.prepareCursorControllers();

  11. }

  12. }

  13. private void fixFocusableAndClickableSettings() {

  14. if (mMovement != null || (mEditor != null && mEditor.mKeyListener != null)) {

  15. setFocusable(true);

  16. setClickable(true);

  17. setLongClickable(true);

  18. } else {

  19. setFocusable(false);

  20. setClickable(false);

  21. setLongClickable(false);

  22. }

  23. }

在 fixFocusableAndClickableSettings() 方法中,会执行:

 
  1. setFocusable(true);

  2. setClickable(true);

  3. setLongClickable(true);

  • 1
  • 2
  • 3
  • 1
  • 2
  • 3

根据我的另一篇博客《浅尝安卓事件分发机制》,我们知道执行这三行代码之后,TextView 仍然会消费事件,于是我们得显式的执行:

 
  1. setFocusable(false);

  2. setClickable(false);

  3. setLongClickable(false);

综上,完整的代码如下:

 
  1. class ClickableMovementMethod extends BaseMovementMethod {

  2. private static ClickableMovementMethod sInstance;

  3. public static ClickableMovementMethod getInstance() {

  4. if (sInstance == null) {

  5. sInstance = new ClickableMovementMethod();

  6. }

  7. return sInstance;

  8. }

  9. @Override

  10. public boolean onTouchEvent(TextView widget, Spannable buffer, MotionEvent event) {

  11. int action = event.getActionMasked();

  12. if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_DOWN) {

  13. int x = (int) event.getX();

  14. int y = (int) event.getY();

  15. x -= widget.getTotalPaddingLeft();

  16. y -= widget.getTotalPaddingTop();

  17. x += widget.getScrollX();

  18. y += widget.getScrollY();

  19. Layout layout = widget.getLayout();

  20. int line = layout.getLineForVertical(y);

  21. int off = layout.getOffsetForHorizontal(line, x);

  22. ClickableSpan[] link = buffer.getSpans(off, off, ClickableSpan.class);

  23. if (link.length > 0) {

  24. if (action == MotionEvent.ACTION_UP) {

  25. link[0].onClick(widget);

  26. } else {

  27. Selection.setSelection(buffer, buffer.getSpanStart(link[0]),

  28. buffer.getSpanEnd(link[0]));

  29. }

  30. return true;

  31. } else {

  32. Selection.removeSelection(buffer);

  33. }

  34. }

  35. return false;

  36. }

  37. @Override

  38. public void initialize(TextView widget, Spannable text) {

  39. Selection.removeSelection(text);

  40. }

  41. }

  42. // more code

  43. TextView content = new TextView(getContext());

  44. // more code

  45. content.setMovementMethod(ClickableMovementMethod.getInstance());

  46. content.setFocusable(false);

  47. content.setClickable(false);

  48. content.setLongClickable(false);

这样修改会不会有副作用?

我们知道,安卓应用层的事件分发是比较复杂的,轻易不要改写,而且由于 EditText 继承 TextView,所以 EditText 的长按选择文本等操作都跟 LinkMovementMethod 有关。

抛开上述考虑,这种改写是没问题的,即第2个坑的解决方法仅限于该场景使用,不保证这种改写在其他场景下能够正确。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

MySQL运维实战(2.2)忘记密码如何处理

作者&#xff1a;俊达 引言 当你突然忘记了一个普通用户的密码&#xff0c;而又想着通过管理员账号去改密码时&#xff0c;却猛的发现所有管理员账号的密码都离谱地被你忘了。嗨呀&#xff0c;这可真是个尴尬的大麻烦&#xff01;root账户通常是MySQL中的大boss&#xff0c;你…

Redis(一)

1、redis Redis是一个完全开源免费的高性能&#xff08;NOSQL&#xff09;的key-value数据库。它遵守BSD协议&#xff0c;使用ANSI C语言编写&#xff0c;并支持网络和持久化。Redis拥有极高的性能&#xff0c;每秒可以进行11万次的读取操作和8.1万次的写入操作。它支持丰富的数…

【LeetCode:69. x 的平方根 | 二分】

&#x1f680; 算法题 &#x1f680; &#x1f332; 算法刷题专栏 | 面试必备算法 | 面试高频算法 &#x1f340; &#x1f332; 越难的东西,越要努力坚持&#xff0c;因为它具有很高的价值&#xff0c;算法就是这样✨ &#x1f332; 作者简介&#xff1a;硕风和炜&#xff0c;…

程序员提问的艺术:28.4K Star指南,告别成为办公室讨厌鬼!

Github: https://github.com/ryanhanwu/How-To-Ask-Questions-The-Smart-Way 原文&#xff1a;http://www.catb.org/~esr/faqs/smart-questions.html ✅为什么讨厌某些提问者 未自行尝试解决问题&#xff1a; ❌“怎么用Java写一个排序算法&#xff1f;” &#x1f44d;&#…

Plantuml之EBNF语法介绍(二十七)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 优质专栏&#xff1a;多媒…

java spring核心技术AOP面向切面编程图文并茂包含例子demo

base Aspect-oriented programming面向切面的程序设计用于将那些与业务无关,但却对多个对象产生影响的公共行为和逻辑,抽取并封装为一个可重用的模块,这个模块被命名为“切面”(Aspect)场景: 权限认证,日志和事务处理.demo 基本背景 // 背景: 1. 模拟数据库操作增删改查 …

tomcat session cookie值设置逻辑

tomcat session cookie 值设置&#xff0c;tomcat jsessionid设置 ##调用request.getSession() Controller RequestMapping("/cookie") public class CookieController {RequestMapping("/tomcatRequest")ResponseBodypublic String tomcatRequest(HttpS…

CentOS 7 实战指南:文本处理命令详解

前言 在Linux系统中&#xff0c;文本处理是非常基础却又必不可少的一项技能。如果你正在使用CentOS系统&#xff0c;那么学会如何利用文本操作命令来高效地处理文本文件无疑将会是一个强有力的工具。 本篇文章将介绍一些最常用和最实用的文本操作命令&#xff0c;并通过详尽的…

医院配电能效监管方案

摘要:本文以医院能源监管系统为研究对象,采用智能化技术组建数据库、构建智能化的能耗信息管理系统,实现对医院的能源利用状况进行实时、准确的动态监管。具体而言,该系统建设的主要功能是对医院的能源消耗进行采集、上报、汇总与分析,并生成动态的数据和报表曲线,以及利用分析…

访问学者J1签证的申请流程

访问学者J1签证是许多人前往美国进行学术研究和文化交流的重要途径之一。申请J1签证需要经过一系列步骤和程序&#xff0c;让知识人网小编带大家来了解一下申请流程吧。 首先&#xff0c;申请者需要确认自己符合J1签证的资格要求。这包括被美国的赞助机构或组织接受&#xff0c…

uniapp中uview组件库的Input 输入框 的使用方法

目录 #平台差异说明 #基本使用 #输入框的类型 #可清空字符 #下划线 #前后图标 #前后插槽 API #Props #Events #Methods #Slots 去除fixed、showWordLimit、showConfirmBar、disableDefaultPadding、autosize字段 此组件为一个输入框&#xff0c;默认没有边框和样式…

mysql查询表里的重复数据方法:

1 2 3 4 INSERT INTO hk_test(username, passwd) VALUES (qmf1, qmf1),(qmf2, qmf11) delete from hk_test where usernameqmf1 and passwdqmf1 MySQL里查询表里的重复数据记录&#xff1a; 先查看重复的原始数据&#xff1a; 场景一&#xff1a;列出username字段有重读的数…

jdk动态代理中invoke的return返回的值有什么用?

目录 首先在接口中定义一个行为再定义一个目标角色实现接口&#xff0c;实现行为去代理角色类中解决一下报错&#xff0c;但是什么都不要写 invoke的return返回的值是调用方法中返回的值 下面我们来实例看一下 首先在接口中定义一个行为 public String toMarry02();再定义一个…

金和OA C6 UploadFileEditorSave.aspx 文件上传漏洞复现

0x01 产品简介 金和OA协同办公管理系统软件(简称金和OA),本着简单、适用、高效的原则,贴合企事业单位的实际需求,实行通用化、标准化、智能化、人性化的产品设计,充分体现企事业单位规范管理、提高办公效率的核心思想,为用户提供一整套标准的办公自动化解决方案,以帮助…

关于执行 roslaunch xxxxx yyyy.launch 后,没能进入 RViz 就卡死的问题

Problem 话不多说&#xff0c;先看图。 终端也会提示有报错&#xff08;可能是这种&#xff0c;但不确定&#xff09;&#xff1a; 这是发现问题所在之后&#xff0c;故意改错&#xff0c;然后尝试的。☝ Solution 总以为是显卡的问题&#xff0c;一直在研究怎么在 Ubuntu2…

适合前后端开发的可视化编辑器(拖拽控件)

分享一个面向研发人群使用的前后端分离的低代码软件——JNPF。 JNPF与市面上其他的低代码&#xff08;轻流、宜搭、微搭、简道云、轻流、活字格等等&#xff09;&#xff0c;后者更倾向于非编程人员使用&#xff0c;让业务线人员自行构建应用程序。而 JNPF 这款低代码产品是面向…

AIGC带给开发者的冲击

未来会有两种开发者&#xff0c;一种是会使用AIGC工具的开发者另一种是不会使用AIGC的开发者&#xff0c;AIGC的出现提高了开发效率和代码质量&#xff0c;对开发者意味着需要不断学习和适应新的技术和工作范式&#xff0c;开发者可以把更多的精力放在高级抽象的定义以及更高维…

2024年阿里云优惠券领取及使用教程

阿里云作为国内领先的云计算服务提供商&#xff0c;一直致力于为客户提供优质、高效的服务。为了更好地回馈客户&#xff0c;阿里云经常会推出各种优惠活动&#xff0c;其中就包括阿里云优惠券。本文将详细介绍如何领取及使用阿里云优惠券。 一、阿里云优惠券介绍 阿里云优惠券…

【网络技术】【Kali Linux】Wireshark嗅探(四)域名系统(DNS)

一、实验目的 本次实验使用wireshark流量分析工具进行网络嗅探&#xff0c;旨在了解域名系统&#xff08;DNS&#xff09;的工作原理。 二、域名系统概述 简单来说&#xff0c;域名系统&#xff08;Domain Name System, DNS&#xff09;将域名&#xff08;可以理解为“网址”…

2024年MySQL学习指南(一),探索MySQL数据库,掌握未来数据管理趋势

文章目录 前言1. 数据库的相关概念1.1 数据1.2 数据库1.3 数据库管理系统1.4 数据库系统1.5 SQL 2. MySQL数据库2.1 MySQL安装2.2 MySQL配置2.2.1 添加环境变量2.2.2 新建配置文件2.2.3 初始化MySQL2.2.4 注册MySQL服务2.2.5 启动MySQL服务 2.3 MySQL登录和退出2.4 MySQL卸载2.…