Android Span进阶之路——ClickableSpan

news2024/10/7 20:36:41

一、前言

    在Android中,可以使用强大的标记(Span)对象来实现富文本展示,相比 HTML 而言更高效实用。关于 Android Span 的入门篇可以阅读 Android中强大的标记对象-Span。本文将对 ClickableSpan (可点击的Span)展开深入的学习。

二、基本使用

    查看Android Doc 文档可以知道,ClickableSpan 是一个抽象类,它有两个子类,分别是 URLSpanTextLinks.TextLinkSpan(从 API Level 28 开始支持),对于这两个类的使用,这里不做详细讲解,我们主要讲解下如何通过继承 ClickableSpan 实现可点击的标记。

2.1 ClicableSpan 源码剖析

    首先,我们先来看看 ClickableSpan 抽象类的源码:

public abstract class ClickableSpan extends CharacterStyle implements UpdateAppearance {
    private static int sIdCounter = 0;

    private int mId = sIdCounter++;

    /**
     * Performs the click action associated with this span.
     */
    public abstract void onClick(@NonNull View widget);

    /**
     * Makes the text underlined and in the link color.
     */
    @Override
    public void updateDrawState(@NonNull TextPaint ds) {
        ds.setColor(ds.linkColor);
        ds.setUnderlineText(true);
    }

    /**
     * Get the unique ID for this span.
     *
     * @return The unique ID.
     * @hide
     */
    public int getId() {
        return mId;
    }
}

    从上面的源码来看,ClickableSpan 抽象类非常简单,继承该类需要重写的方法也是比较少,其中抽象方法 onClick() 是必须实现,下面讲解重写方法所能实现的效果:

  • public abstract void onClick(@NonNull View widget):抽象方法,必须实现。用以相应可点击标记被点击时的事件相应处理。
  • public void updateDrawState(@NonNull TextPaint ds):配置绘制参数,可以用来更改绘制样式,比如文字颜色、背景颜色、链接颜色、是否包含下划线等等。如果不重载此方法,将会使用默认的绘制样式。

2.2 自定义 ClickableSpan

    从前面的源码我们了解到 ClickableSpan 的成员方法,实现自己的自定义 ClickableSpan 就非常容易了:

/**
 * 自定义 ClickableSpan
 * @param textColor 可点击标记文字颜色
 * @param clickListener 点击时间监听
 */
class CSClickableSpan (@param:ColorInt private val textColor: Int,
                       private val clickListener: View.OnClickListener?) : ClickableSpan() {
    override fun onClick(widget: View) {
        clickListener?.onClick(widget)
    }

    override fun updateDrawState(ds: TextPaint) {
        super.updateDrawState(ds)

        ds.color = textColor // 字体颜色(前景色)
        ds.bgColor = Color.TRANSPARENT  // 背景颜色
        ds.linkColor = textColor // 链接颜色
        ds.isUnderlineText = false // 是否显示下划线
        // 这里还可以配置其他绘制样式,比如下划线的粗细(如果启用下划线)、字体等等
    }
}

2.3 使用自定义的 ClickableSpan

    接下来就可以在 SpannableString 或者 SpannableStringBuilder 中使用自定义的 CSClickableSpan 类。

val tvNormal = findViewById<TextView>(R.id.tv_normal_clickable_span)
// 必须设置 TextView 的 movementMethod 为 LinkMovementMethod,否则标记无法响应点击事件
tvNormal.movementMethod = LinkMovementMethod.getInstance()
tvNormal.setText(SpannableString("我是普通的ClickableSpan").apply {
    setSpan(CSClickableSpan(Color.BLUE, View.OnClickListener {
        Toast.makeText(this@ClickableSpanActivity, tvNormal.text, Toast.LENGTH_SHORT).show()
    }), 5, 18, Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
})

注意事项:在 TextView 中使用 ClickableSpan 时,必须要设置 TextView 对象的 movementMethod 属性为 LinkMovementMethod,否则 ClickableSpan 标记不会响应点击事件。

    运行之后,可以看看效果。

  • 点击前
    自定义ClickableSpan-点击前
  • 点击后
    自定义ClickableS-点击后
        根据上面的例子,我们会发现标记点击后,会有一个背景色,其实这个背景色是 TextView 的高亮颜色,因为 LinkMovementMethod 在标记点击后,会选中标记部分文本。解决这个问题也很简单,只要将 TextViewhighlightColor 设置为透明即可,如下示例:
val tvNormalNoSelection = findViewById<TextView>(R.id.tv_normal_clickable_span_no_selection)
// 将 TextView 的高两色设置为透明,可去除点击后的选择高亮色
tvNormalNoSelection.highlightColor = Color.TRANSPARENT
// 必须设置 TextView 的 movementMethod 为 LinkMovementMethod,否则标记无法响应点击事件
tvNormalNoSelection.movementMethod = LinkMovementMethod.getInstance()
tvNormalNoSelection.setText(SpannableString("我是普通的ClickableSpan(无选中背景)").apply {
    setSpan(CSClickableSpan(Color.BLUE, View.OnClickListener {
        Toast.makeText(this@ClickableSpanActivity, tvNormalNoSelection.text, Toast.LENGTH_SHORT).show()
    }), 5, 18, Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
})

    运行之后看效果,点击标记之后选中高亮色为透明,看起来就是没有高两色的效果,如下图:
ClickableSpan去除点击后的高亮选中颜色

三、高手进阶

3.1 在 ClickableSpan 中实现点击效果

    在前面篇幅中,虽然可以去掉标记选中高亮色,但是这样也并不完美,没有点击效果,用户体验还是有所欠缺。我们首先会想到用TextView 的高亮色,然而高亮色只能设置整型的颜色值,并不能设置ColorList。于是就猜想通过 TextView 的高亮色结合自定义的 CSClickableSpan 实现,笔者刚开始也是从这个角度着手,预想将高亮色设置成按下状态颜色,然后再将高亮色设置为透明色,后来发现这样无法实现,因为 ClickableSpan 这个过程中,会在 onClick() 方法调用之前,前后均会调用 updateDrawState() 更新绘制文本,在如此的调用逻辑下,这种方案是不可行的。=既然无法从 TextView 下手,在示例代码中,我们唯一能寄予希望的就是 TextViewmovementMethod 属性了(也就是 LinkMovementMethod)。

  • LinkMovementMethod类源码剖析
package android.text.method;

import android.annotation.UnsupportedAppUsage;
import android.os.Build;
import android.text.Layout;
import android.text.NoCopySpan;
import android.text.Selection;
import android.text.Spannable;
import android.text.style.ClickableSpan;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
import android.view.textclassifier.TextLinks.TextLinkSpan;
import android.widget.TextView;

/**
 * A movement method that traverses links in the text buffer and scrolls if necessary.
 * Supports clicking on links with DPad Center or Enter.
 */
public class LinkMovementMethod extends ScrollingMovementMethod {
    private static final int CLICK = 1;
    private static final int UP = 2;
    private static final int DOWN = 3;

    private static final int HIDE_FLOATING_TOOLBAR_DELAY_MS = 200;

    @Override
    public boolean canSelectArbitrarily() {
        return true;
    }

    @Override
    protected boolean handleMovementKey(TextView widget, Spannable buffer, int keyCode,
            int movementMetaState, KeyEvent event) {
        switch (keyCode) {
            case KeyEvent.KEYCODE_DPAD_CENTER:
            case KeyEvent.KEYCODE_ENTER:
                if (KeyEvent.metaStateHasNoModifiers(movementMetaState)) {
                    if (event.getAction() == KeyEvent.ACTION_DOWN &&
                            event.getRepeatCount() == 0 && action(CLICK, widget, buffer)) {
                        return true;
                    }
                }
                break;
        }
        return super.handleMovementKey(widget, buffer, keyCode, movementMetaState, event);
    }

    @Override
    protected boolean up(TextView widget, Spannable buffer) {
        if (action(UP, widget, buffer)) {
            return true;
        }

        return super.up(widget, buffer);
    }

    @Override
    protected boolean down(TextView widget, Spannable buffer) {
        if (action(DOWN, widget, buffer)) {
            return true;
        }

        return super.down(widget, buffer);
    }

    @Override
    protected boolean left(TextView widget, Spannable buffer) {
        if (action(UP, widget, buffer)) {
            return true;
        }

        return super.left(widget, buffer);
    }

    @Override
    protected boolean right(TextView widget, Spannable buffer) {
        if (action(DOWN, widget, buffer)) {
            return true;
        }

        return super.right(widget, buffer);
    }

    private boolean action(int what, TextView widget, Spannable buffer) {
        Layout layout = widget.getLayout();

        int padding = widget.getTotalPaddingTop() +
                      widget.getTotalPaddingBottom();
        int areaTop = widget.getScrollY();
        int areaBot = areaTop + widget.getHeight() - padding;

        int lineTop = layout.getLineForVertical(areaTop);
        int lineBot = layout.getLineForVertical(areaBot);

        int first = layout.getLineStart(lineTop);
        int last = layout.getLineEnd(lineBot);

        ClickableSpan[] candidates = buffer.getSpans(first, last, ClickableSpan.class);

        int a = Selection.getSelectionStart(buffer);
        int b = Selection.getSelectionEnd(buffer);

        int selStart = Math.min(a, b);
        int selEnd = Math.max(a, b);

        if (selStart < 0) {
            if (buffer.getSpanStart(FROM_BELOW) >= 0) {
                selStart = selEnd = buffer.length();
            }
        }

        if (selStart > last)
            selStart = selEnd = Integer.MAX_VALUE;
        if (selEnd < first)
            selStart = selEnd = -1;

        switch (what) {
            case CLICK:
                if (selStart == selEnd) {
                    return false;
                }

                ClickableSpan[] links = buffer.getSpans(selStart, selEnd, ClickableSpan.class);

                if (links.length != 1) {
                    return false;
                }

                ClickableSpan link = links[0];
                if (link instanceof TextLinkSpan) {
                    ((TextLinkSpan) link).onClick(widget, TextLinkSpan.INVOCATION_METHOD_KEYBOARD);
                } else {
                    link.onClick(widget);
                }
                break;

            case UP:
                int bestStart, bestEnd;

                bestStart = -1;
                bestEnd = -1;

                for (int i = 0; i < candidates.length; i++) {
                    int end = buffer.getSpanEnd(candidates[i]);

                    if (end < selEnd || selStart == selEnd) {
                        if (end > bestEnd) {
                            bestStart = buffer.getSpanStart(candidates[i]);
                            bestEnd = end;
                        }
                    }
                }

                if (bestStart >= 0) {
                    Selection.setSelection(buffer, bestEnd, bestStart);
                    return true;
                }

                break;

            case DOWN:
                bestStart = Integer.MAX_VALUE;
                bestEnd = Integer.MAX_VALUE;

                for (int i = 0; i < candidates.length; i++) {
                    int start = buffer.getSpanStart(candidates[i]);

                    if (start > selStart || selStart == selEnd) {
                        if (start < bestStart) {
                            bestStart = start;
                            bestEnd = buffer.getSpanEnd(candidates[i]);
                        }
                    }
                }

                if (bestEnd < Integer.MAX_VALUE) {
                    Selection.setSelection(buffer, bestStart, bestEnd);
                    return true;
                }

                break;
        }

        return false;
    }

    @Override
    public boolean onTouchEvent(TextView widget, Spannable buffer,
                                MotionEvent event) {
        int action = event.getAction();

        if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_DOWN) {
            int x = (int) event.getX();
            int y = (int) event.getY();

            x -= widget.getTotalPaddingLeft();
            y -= widget.getTotalPaddingTop();

            x += widget.getScrollX();
            y += widget.getScrollY();

            Layout layout = widget.getLayout();
            int line = layout.getLineForVertical(y);
            int off = layout.getOffsetForHorizontal(line, x);

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

            if (links.length != 0) {
                ClickableSpan link = links[0];
                if (action == MotionEvent.ACTION_UP) {
                    if (link instanceof TextLinkSpan) {
                        ((TextLinkSpan) link).onClick(
                                widget, TextLinkSpan.INVOCATION_METHOD_TOUCH);
                    } else {
                        link.onClick(widget);
                    }
                } else if (action == MotionEvent.ACTION_DOWN) {
                    if (widget.getContext().getApplicationInfo().targetSdkVersion
                            >= Build.VERSION_CODES.P) {
                        // Selection change will reposition the toolbar. Hide it for a few ms for a
                        // smoother transition.
                        widget.hideFloatingToolbar(HIDE_FLOATING_TOOLBAR_DELAY_MS);
                    }
                    Selection.setSelection(buffer,
                            buffer.getSpanStart(link),
                            buffer.getSpanEnd(link));
                }
                return true;
            } else {
                Selection.removeSelection(buffer);
            }
        }

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

    @Override
    public void initialize(TextView widget, Spannable text) {
        Selection.removeSelection(text);
        text.removeSpan(FROM_BELOW);
    }

    @Override
    public void onTakeFocus(TextView view, Spannable text, int dir) {
        Selection.removeSelection(text);

        if ((dir & View.FOCUS_BACKWARD) != 0) {
            text.setSpan(FROM_BELOW, 0, 0, Spannable.SPAN_POINT_POINT);
        } else {
            text.removeSpan(FROM_BELOW);
        }
    }

    public static MovementMethod getInstance() {
        if (sInstance == null)
            sInstance = new LinkMovementMethod();

        return sInstance;
    }

    @UnsupportedAppUsage
    private static LinkMovementMethod sInstance;
    private static Object FROM_BELOW = new NoCopySpan.Concrete();
}

    源码有点多,但是我们的目标是实现点击效果,那么肯定跟触摸事件相关,所以,我们需要处理的也就是 onTouchEvent() 方法。接下来,我们通过继承 LinkMovementMethod 来自定义一个MovementMethod 类,在onTouchEvent() 方法中的 MotionEvent.ACTION_DOWNMotionEvent.ACTION_UP 事件中添加处理逻辑。

/**
 * 可点击标记 MovementMethod
 * @param clickedBgColor 按下背景颜色
 */
class ClickableSpanMovementMethod(@ColorInt val clickedBgColor: Int) : LinkMovementMethod() {
    override fun onTouchEvent(widget: TextView?, buffer: Spannable?,  event: MotionEvent?): Boolean {
        if(null == event || null == widget || null == buffer) {
            return false
        }
        val action = event.action
        if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_DOWN) {
            var x = event.x.toInt()
            var y = event.y.toInt()
            x -= widget.totalPaddingLeft
            y -= widget.totalPaddingTop
            x += widget.scrollX
            y += widget.scrollY
            val layout = widget.layout
            val line = layout.getLineForVertical(y)
            val off = layout.getOffsetForHorizontal(line, x.toFloat())
            val links = buffer!!.getSpans(off, off, ClickableSpan::class.java)
            if (links.isNotEmpty()) {
                val link = links[0]
                if (action == MotionEvent.ACTION_UP) {
                    // ACTION_UP 移除选中
                    Selection.removeSelection(buffer)
                    link.onClick(widget)
                } else if (action == MotionEvent.ACTION_DOWN) {
                    // ACTION_DOWN 设置高亮色为点击色,并选中标记
                    widget.highlightColor = clickedBgColor
                    Selection.setSelection(
                        buffer,
                        buffer.getSpanStart(link),
                        buffer.getSpanEnd(link)
                    )
                }
                return true
            } else {
                Selection.removeSelection(buffer)
            }
        }
        return super.onTouchEvent(widget, buffer, event)
    }
}

    然后将 TextViewmovementMethod 属性值设置为自定义的 MovementMethod 实例对象即可:

val tvStyle = findViewById<TextView>(R.id.tv_clickstyle_clickable_span)
tvStyle.movementMethod = ClickableSpanMovementMethod(Color.argb(0x20, 0x33, 0x33, 0x33))
tvStyle.setText(SpannableString("我是带点击效果的ClickableSpan").apply {
    setSpan(CSClickableSpan(Color.BLUE, View.OnClickListener {
        Toast.makeText(this@ClickableSpanActivity, tvStyle.text, Toast.LENGTH_SHORT).show()
    }), 8, 21, Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
})
  • 实现效果
    ClickableSpan点击效果
        至此,已经完美实现 ClickableSpan 点击效果。上面的示例是通过改变选中高亮色来实现的,下面是通过给 ClickableSpan 重叠一个 BackgroundColorSpan 的实现方案,效果完全一致,代码如下所示:
/**
 * 可点击标记 MovementMethod
 * @param clickedBgColor 按下背景颜色
 */
class ClickableSpanMovementMethod(@ColorInt val clickedBgColor: Int) : LinkMovementMethod() {
    override fun onTouchEvent(widget: TextView?, buffer: Spannable?, event: MotionEvent?): Boolean {
        if(null == event || null == widget || null == buffer) {
            return false
        }
        val action = event.action
        if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_DOWN) {
            var x = event.x.toInt()
            var y = event.y.toInt()
            x -= widget.totalPaddingLeft
            y -= widget.totalPaddingTop
            x += widget.scrollX
            y += widget.scrollY
            val layout = widget.layout
            val line = layout.getLineForVertical(y)
            val off = layout.getOffsetForHorizontal(line, x.toFloat())
            val links = buffer!!.getSpans(off, off, ClickableSpan::class.java)
            if (links.isNotEmpty()) {
                val link = links[0]
                if (action == MotionEvent.ACTION_UP) {
                    // ACTION_UP 给当前标记添加一个透明色的背景Span
                    buffer.setSpan(
                        BackgroundColorSpan(Color.TRANSPARENT), buffer.getSpanStart(link),
                        buffer.getSpanEnd(link), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
                    // 移除选中(如果将TextView高亮色设置为透明,可忽略此行代码)
                    Selection.removeSelection(buffer)
                    link.onClick(widget)
                } else if (action == MotionEvent.ACTION_DOWN) {
                    // ACTION_DOWN 给当前标记添加一个点击色的背景Span
                    buffer.setSpan(BackgroundColorSpan(clickedBgColor), buffer.getSpanStart(link),
                        buffer.getSpanEnd(link), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
                    // 移除选中(如果将TextView高亮色设置为透明,可忽略此行代码)
                    Selection.removeSelection(buffer)
                }
                return true
            } else {
                Selection.removeSelection(buffer)
            }
        }
//            return false
        return super.onTouchEvent(widget, buffer, event)
    }
}

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

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

相关文章

WebDAV之葫芦儿·派盘 + Koder

Koder 支持WebDAV方式连接葫芦儿派盘。 一款可以让你在iPhone、iPad上写各种编程语言代码的app,码农不要错过。 Koder是iPad和iPhone的代码编辑器。它确实具有许多功能,包括语法突出显示,代码段管理器,选项卡式编辑,查找和替换代码,编辑器主题,远程和本地文件连接等等…

OpenCV入门(C++/Python)- 使用OpenCV标注图像(六)

使用OpenCV标注图像用颜色线标注图像绘制圆绘制实心圆绘制矩阵绘制椭圆绘制带轮廓和填充半椭圆使用文本注释图像为图像和视频添加标注的目的不止一个&#xff0c;包含&#xff1a;向视频中添加信息在对象检测的情况下&#xff0c;在对象周围绘制边界框&#xff0c;用不同颜色的…

并查集介绍

文章目录&#xff1a;并查集原理并查集实现并查集的类结构并查集的合并统计集合数量并查集原理 在一些应用问题中&#xff0c;需要将 n 个不同的元素划分成一些不相交的集合。开始时&#xff0c;每个元素自成一个单元素集合&#xff0c;然后按照一定的规律将归于同一组元素的集…

《找对英语学习方法的第一本书》

简 述: 此书写于二十年前&#xff0c;结合我自身情况参照&#xff0c;有了一种理论指导&#xff0c;可在众多学习方法中有效抉择&#xff0c;亦能在不同阶段更换不同策略。本文为读后的一个简要归纳和札记。 文章目录第一章&#xff1a;爱之愈深、误之愈切第二章&#xff1a;我…

李宏毅2022《机器学习/深度学习》——学习笔记(5)

文章目录优化方法CNNCNN和全连接神经网络的区别感受野共享参数CNN和全连接神经网络的总结PoolingCNN流程自注意力机制自注意力机制解决的问题输入是一组向量的例子输入是一组向量时输出的可能自注意力机制核心思想自注意力机制具体细节Self-attention和CNN的关系参考资料优化方…

网络安全之从原理看懂XSS

01、XSS的原理和分类 跨站脚本攻击XSS(Cross Site Scripting)&#xff0c;为了不和层叠样式表(Cascading Style Sheets&#xff0c;CSS)的缩写混淆 故将跨站脚本攻击缩写为XSS&#xff0c;恶意攻击者往Web页面里插入恶意Script代码&#xff0c;当用户浏览该页面时&#xff0c…

七周成为数据分析师 | 业务

为什么业务重要&#xff1f; 唯有理解业务&#xff0c;才能建立业务数据模型 一.经典业务分析指标 模型未动&#xff0c;指标先行 如果你不能衡量它&#xff0c;你就无法增长它 指标建立的要点 ①核心指标 ②好的指标应该是比率 ③好的指标应该能带来显著效果 ④好的指…

民办二本程序员阿里、百度、平安等五厂面经,5 份 offer(含真题)

昨天小休&#xff0c;一位高中同学联系了我&#xff0c;说是要请我吃饭&#xff0c;有这种好事&#xff0c;我当然是毫不犹豫的答应了啦&#xff01; 等等...会不会是找我借钱的&#xff1f; 好慌&#xff0c;怎么办&#xff1f;已经答应过去了。 在后面的交谈中&#xff0c;…

Word控件Spire.Doc 【图像形状】教程(12) 如何在C#中旋转word文档上的形状

Spire.Doc for .NET是一款专门对 Word 文档进行操作的 .NET 类库。在于帮助开发人员无需安装 Microsoft Word情况下&#xff0c;轻松快捷高效地创建、编辑、转换和打印 Microsoft Word 文档。拥有近10年专业开发经验Spire系列办公文档开发工具&#xff0c;专注于创建、编辑、转…

【C语言程序设计】实验 3

目录 1. 水仙花数 2. 五位回文数 3. 输入x&#xff0c;计算y 4. 百分制改为等级制 5. 同构数 6. 月份天数 7. 加一天后日期&#xff08;条件&#xff09; 8. 计算服装款&#xff08;条件&#xff09; 1. 水仙花数 【问题描述】输入一个3位正整数&#xff0c;判断该…

数据可视化之基础图表

一 前言 数据图表则是用来表现数据的一类图表&#xff0c;用来帮助用户理解数据。在这类图表中&#xff0c;以三大类图表最为常用 —— 柱状图&#xff08;条形图&#xff09;、折线图、饼图。据非官方统计&#xff0c;数据图表使用率占所有图表的类型的62%。所以&#xff0c;…

Docker之MySQL_GROUP_REPLICATION组复制(MGR)、宕机节点恢复和Spirngboot整合

三台服务器修改hosts文件 vim /etc/hosts追加内容 192.168.1.11 node1 192.168.1.12 node2 192.168.1.13 node3修改hostname vim /etc/hostname重启网络使配置文件生效 systemctl restart network三台服务器拉取MySQL镜像 docker pull mysql:8.0.23创建配置文件夹 …

PDF文档转TXT怎么转?你不知道的几种方法

PDF文档转TXT怎么转&#xff1f;我们经常需要处理PDF文件&#xff0c;根据不同的要求&#xff0c;我们经常需要将PDF文件进行转换&#xff0c;虽然PDF文件相对于其他大多数文件来说体积已经很小了&#xff0c;但是TXT文件会比PDF文件体积更小一些&#xff0c;这样我们不仅可以节…

Python工程师Java之路(w)数据库连接池Druid

概述 初阶数据库访问的步骤是【创建连接>执行SQL>关闭连接】&#xff0c;有如下不足&#xff1a; 1、创建数据库连接会浪费时间 2、大量访问时&#xff0c;频繁 GC 会导致CPU负载过高 3、如果改为不关闭连接&#xff0c;则会长期占用内存对此&#xff0c;引入“缓冲池”…

物联网各类数据如何轻松获取?秘诀就在定制文件推送服务

当前&#xff0c;数字经济已成为我国经济发展的重要驱动力。随着物联网的蓬勃发展&#xff0c;海量数据伴随着终端联网在各行各业涌现&#xff0c;越来越多的企业已然察觉隐藏在数字中的金矿&#xff0c;加入到数字化转型行列中&#xff0c;通过数据挖掘实现精细化运营&#xf…

高蛋白过敏我们该如何缓解?教你几招远离过敏吃喝无忌

许多朋友回应说&#xff0c;吃海鲜.牛肉、羊肉等高蛋白食物会发生过敏反应&#xff0c;要么脸红肿&#xff0c;要么长痘痘。看着他们贪婪的食物&#xff0c;他们只能避免吃真的很痛苦。为什么现在人们的生活条件越来越好&#xff0c;生活环境也显著改善&#xff0c;但过敏性疾病…

USB插座外壳接地的处理和emi,esd考虑

外壳是否接地&#xff0c;从理想电路环境&#xff08;没有干扰&#xff0c;也不释放干扰&#xff09;和电路原理来说&#xff0c;接和不接没有任何差异&#xff0c;也不会影响正常功能。 但是实际的电子产品的工作环境&#xff0c;是一个处于被各种干扰包围的复杂的电磁场环境&…

[附源码]JAVA毕业设计人才库构建研究(系统+LW)

[附源码]JAVA毕业设计人才库构建研究&#xff08;系统LW&#xff09; 项目运行 环境项配置&#xff1a; Jdk1.8 Tomcat8.5 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&a…

使用icacls命令设置目录及其子目录、文件的所有权限

以前一直使用cacls命令来设置权限&#xff0c;前两天输入这个命令的时候&#xff0c;却发现了一行提示&#xff1a; “注意: 不推荐使用 Cacls&#xff0c;请使用 Icacls。” 如图&#xff1a; 于是研究了一下 Icacls 这个命令。。 先放上微软官方文档&#xff1a; https:/…

单场直播销售额破7亿,11月的抖音带货风向是什么?

双11走过14年&#xff0c;今年的双11有些特别。我们发现&#xff0c;各个平台在交易额战报的发布上都变得更加保守&#xff0c;而无论是天猫还是京东&#xff0c;均首次未公布具体的交易额。在消费市场出现波动的当下&#xff0c;双11正在经历转变&#xff0c;从重视成交额&…