自定义博客皮肤VIP专享

*博客头图:

格式为PNG、JPG,宽度*高度大于1920*100像素,不超过2MB,主视觉建议放在右侧,请参照线上博客头图

请上传大于1920*100像素的图片!

博客底图:

图片格式为PNG、JPG,不超过1MB,可上下左右平铺至整个背景

栏目图:

图片格式为PNG、JPG,图片宽度*高度为300*38像素,不超过0.5MB

主标题颜色:

RGB颜色,例如:#AFAFAF

Hover:

RGB颜色,例如:#AFAFAF

副标题颜色:

RGB颜色,例如:#AFAFAF

自定义博客皮肤

-+
  • 博客(85)
  • 收藏
  • 关注

原创 Android WebView使用总结

#.简介: WebView是Android提供的用来展示展示web页面的View,内部使用webkit浏览器引擎(一个轻量级的浏览器引擎),除了展示Web页面外,还可与Web页面内的JS脚本交互调用。WebView内部的WebSetting对象负责管理WebView的参数配置; WebViewClient负责处理WebView的各种请求和通知事件,在对应事件发生时会执行WebViewClient的对应回调; ChromeWebviewClient辅助Webview处理与JS一些交互......

2022-06-30 19:42:25 3034 1

原创 RxJava使用总结

​##.简介 RxJava是是一个基于事件流处理来实现异步操作的库。对于需要切换线程来异步处理的场景,能够简化代码编写,提高代码可读性。官网地址:ReactiveX一、使用流程概括和示例1.它是按照观察者模式来设计的 被观察者负责生产事件,观察者负责处理事件,一旦观察者 订阅了 被观察者,就会触发被观察者发送事件流。 这里的“事件”是一个抽象概念,它的载体是一个数据结构(例如String/Object/自定义对象等),传递的实际上是数据,只是在观察者模式中它们的角色是“事件”。........

2022-06-09 22:38:21 1324

原创 Retrofit使用总结

​#.整体介绍 Retrofit也是一个网络请求库,它是对OkHttp的进一步封装。 用OkHttp做请求时需要我们自己设置各种请求参数,并且对结果做解析。 Retrofit的网络请求工作实际上还是由OkHttp来完成,而Retrofit针对OkHttp的输入和输出过程做处理,简化了这两个过程的代码编写,提供了强大的功能支持。1.Retrofit封装了上层的网络请求接口,帮助开发者简化OkHttp的Request封装。 可以使用Retrofit提供的一堆注解,来编写网络请...

2022-06-08 22:53:19 438

原创 OkHttp使用总结 & 关键源码分析

​#.整体介绍 OkHttp是Android开发中使用非常普遍的一个网络请求库,它封装和实现了Http协议的相关功能。 官方地址:https://github.com/square/okhttp 既然是对Http协议的实现,那就绕不开Http协议的原理与报文结构,Http报文结构可参考笔记:Http协议报文格式。 很多网络库都是围绕某种网络协议,实现了网络协议的相关功能,并且提供对应接口,封装了内部各种细节,供使用者简化开发过程。Android从最初用HttpUrlC............

2022-06-06 15:40:34 762

原创 Http协议报文格式

一、整体介绍 Http协议在传输层基于TCP协议,在Http1.1之前每次请求在TCP层都需进行一轮连接和释放(三次握手、四次握手),从Http1.1开始默认使用长连接。 Http报文分为两种,请求报文和响应报文,大致格式如下:1.请求报文结构: 请求行{请求方法(get/post等)+ URL +协议版本号 }+ 头部 { 按照规范,根据自己需要来选择性添加 .........

2022-05-29 21:09:31 17266

原创 Kotlin语法简单入门(主要针对与Java语法的差异)

#.一、相对于Java,Kotlin语法整体变化简介0.Kotlin源代码最终也会编译成.class文件,依赖于JVM来运行 但Kotlin是静态类型语言,所有变量和表达式类型在编译时已确定, 而Java支持动态绑定,可以在运行时再具体决定对象的类型。1.Kotlin中一切皆为对象,创建新对象构造方法前不加new关键字。2.类型定义是后置的,类型放在":"后面3.每句代码末尾不需要加";"(但加上也可以,编译时也是正确的)#.二、常量与变量...

2022-05-29 18:02:09 340

原创 Android ActivityManager一些API介绍

Android中Java层的ActivityManager类中封装了很多API,可以供我们查询当前系统的很多信息,包括:内存、进程(Process)、任务栈(Task)、服务(Service)等的相关信息。 利用这些信息可以进行一些有用的判断,例如判断当前系统内存是否不足、指定Service是否在运行中。 (ActivityManager类封装了很多API方法供上层调用,具体负责管理Activity、Service等组件的是ActivityManagerService(AMS...

2022-05-19 20:08:59 662

原创 Android WindowManager & Window属性 & 添加悬浮窗的示例代码

Android中用来管理Window的接口类,具体实现类是WindowManagerImpl,而最终是交给WindowManagerService来真实实现相应功能。Window的属性定义在WindowManager.LayoutParams类中。相关属性有很多种,与应用开发最密切的有四类,分别是Type(Window的类型)、Flag(Window的标志)、SoftInputMode(软键盘相关模式)、位置大小相关属性。悬浮窗是通过WindowManager添加Window窗口来实现的,添加窗口类型的是

2022-05-16 19:43:24 672

原创 三种路径算法:Dijstra、Bellman-Ford、Floyd-Warshall

#.Dijstra算法: 求单源最短路径,不允许出现负边。n-1次外循环。 流程: 1.将所有节点分为:原点集合S、剩余节点集合D,将源节点装入S,用dist记录各个节点的距离。 2.每轮循环都从D中找出当前距离最小的点a,移入S,同时更新D中与a相邻的节点的最小距离: dist[p] = Math.min(dist[p], dist[a] + E(a,p) ); n-1轮循环后,得到的dist记录...

2022-05-16 17:17:08 436

原创 原码、反码、补码简介

有符号数,左边第一位是符号位,0为正,1为负。在java中都是有符号数。##.转换关系原码:最左侧为符号位,用剩余数位来表示绝对值;反码:正数和0,反码为其本身; 负数的反码,符号位不变,其余各位数字取反(0变1,1变0)。补码:正数和0,补码为其本身; 负数的补码,在反码基础上,不考虑符号位,最末一位加1。##.java代码中转化代码中可用位运算,原码转反码,以int值为例,若a为负整数,则a的反码为:int b...

2022-05-15 23:28:04 718 4

原创 软件开发整体认知 & Android系统结构简述

前言:世间一切总是你中有我,我中有你,纠结在一起。对于全能的上帝,绝对是不放过任何一个细节,统筹地思考最高效和准确。但对于我们凡人,我们脑容量有限,精力有限,注意力有限,才智有限,乃至人生也有限,只能将知识切割成很多方面,一生只重点研究几个领域,每次只重点关注一个方面,每个问题只关注主要矛盾。我们把知识分割成很多个学科,但是无论怎么分割,它们都必然你中有我,我中有你,互相依赖和关联。而一切事物的底层根本原理总是特别简单,复杂性是千百层简易的东西、无数细节累积交织在一起形成的!#1.软件开发整体结构认

2022-05-15 17:30:53 648

原创 常见直播协议介绍:RTMP、HTTP-FLV、HLS

#.推流协议:1.RTMP协议(Real Time Message Protocol,实时信息传输协议) 由Adobe公司提出的一种应用层的协议,可用于实时传递音视频媒体数据。它基于传输层的TCP协议,通过与服务端建立长连接来传递数据。相较于其它同类协议,传输稳定,延迟较低,一般在1~3s,非常适合用于直播场景下的推流。 当前手机app端只要是使用该协议来推流。1.1 RTMPS: RTMP的变种,使用HTTPS协议来传输数据,支持数据加密。(可使用Rtmpdump库...

2022-05-14 20:13:13 9598

原创 Android 视频直播的流程总览

视频直播整个流程,就是不断把数据采集端的数据编码后推送到流媒体服务器,经CDN加速后,由播放端拉取这些数据进行解码播放。 在手机客户端主要可分为五个步骤:音视频数据采集、音视频效果处理、数据编码、推流到服务器、拉流播放。(整个过程类似于源源不断送快递,推流端是生产方,生产的货物送到库房(流媒体服务器),由专业的快递公司(CDN),快速送到各个消费者手中(拉流播放端)。没有快递公司当然也能送货,但货物太多、距离太远,送货速度就会慢。)#.Android客户端的主要...

2022-05-13 22:48:11 1418

原创 Android 使用MediaProjection+ImageReader捕捉屏幕画面

#.简介 Android5.0以后提供了MediaProjectionManager系统服务来获取手机屏幕画面。 需要获取相应服务的权限,然后创建虚拟显示器,物理屏幕画面会不断被投影到虚拟现实器,输出到创建虚拟显示器时设置的Surface上。使用过程一般会结合ImageReader或OpenGL来进行。 (在更低的版本,如Android4.4,获取屏幕画面需要通过ADB指令来进行。但目前市面上基本已经见不到比Android5.0版本还低的安卓手机)#.基本使用...

2022-05-13 18:28:44 4767

原创 Android IjkPlayer API介绍

##.简介IjkPlayer是Bilibili推出的一个开源播放器库,很多视频平台都在使用,底层是基于ffmpeg来实现的。官方的github地址:GitHub - bilibili/ijkplayer: Android/iOS video player based on FFmpeg n3.4, with MediaCodec, VideoToolbox support.开源库中封装了IMediaPlayer接口,包括一个播放器正常需要的绝大部分功能。在开源库中为该接口提供几种实现方式,

2022-05-12 22:21:04 1120

原创 Android使用RtmpDump进行RTMP推流介绍

目前Android端APP视频直播一般使用RTMP协议推流到流媒体服务器,通过CDN加速后,然后在播放端通过HTTP-FLV协议或HLS协议拉流播放。#RTMP协议介绍RTMP协议(Real Time Message Protocol,实时信息传输协议),是由Adobe公司提出的一种应用层的协议,可用于实时传递音视频媒体数据。它基于传输层的TCP协议,通过与服务端建立长连接来传递数据。相较于其它同类协议,延迟较低,一般在1~3s,非常适合用于直播场景下的推流。...

2022-05-12 21:49:32 2872

原创 Android Gradle依赖配置与依赖冲突解决

1.implementation 与Gradle2.0的compile对应,会将直接依赖包同时添加到编译时路径、运行时路径,依赖包会被打入输出包中(aar或apk)。差别在于对间接依赖包的处理上,使用implementation时直接依赖包不会向上层传递自己内部的依赖关系。 举例,A依赖B,B依赖C。 对于A而言:B会同时加入A的编译时路径、运行时路径;但C不会加入A的编译时路径,只会加入A的运行时路径。 因此,A在编译时只能访问B对外暴露的类和接口,不能访问C对外暴...

2022-05-12 15:56:38 4278

原创 Android MediaMuxer合成视频文件

#.简介MediaMuxer在Android中可以将编码后的视频、音频数据封装后输出为多媒体文件,支持的文件输出格式包括MP4,webm和3gp。不过MediaMuxer中最多只能添加一条视轨和一条音轨。一般音视频编码由MediaCodec负责,而MediaMuxer处理MediaCodec输出的编码数据,合成多媒体文件。#主要API1.public MediaMuxer(@NonNull String path, @Format int format)

2022-05-11 23:14:53 1288

原创 Android用AudioRecord+MediaCodec采集音频和音频编码 & 音频一些基本概念

#.音频的一些相关概念简介0.整体介绍 声波是一种机械波,我们听到声音,是因为耳朵鼓膜接收到了声波,然后听觉神经做了相应处理。 机械波有震动频率和震动幅度,对于频率和振幅恒定的声波,若以时间为x值,声波振幅为y值,则在坐标系中绘制出的会类似一条正弦函数曲线。不过现实中都是多个声源进行各种复杂的震动,最终的震动波形是很多简单正弦波形相叠加的结果。 计算机中只能存储离散的数据,所能做的只是周期性的采样声波震动数据,然后播放时尽可能地还原出原来的声波效果。

2022-05-11 22:01:44 1667

原创 Android使用MediaCodec进行视频编码 & 视频的一些基础概念介绍

#.视频的一些相关概念简介1.图像的分辨率 描述一张图片中的像素数量,一般用像素宽度*像素高度表示。 摄像头采集的画面是一堆像素点拼接成的,一个像素点可以理解成组成图像的一个色块,大量的色块合在一起就基本能还原出现实中的画面。每个像素点在存储中都记录了对应的颜色特点,具体大小跟颜色格式祥光,例如RGB格式是占用3个byte。 2.视频的帧率(FPS) 我们看到的视频是一张张画面快速切换,在人眼和视觉神经中形成的动态感受,人们感知那是连续不断变化的。...

2022-05-11 17:24:57 3297

原创 Android MediaCodec简单总结

#.MedaiCodec简介 MediaCodec是Android中提供的音视频编码、解码工具。它主要是完成上层接口的封装,提供给开发者使用,编解码功能实际是在native底层服务中完成的。#.MediaCodec工作的宏观流程:##.包换两个缓冲区队列一个输入缓冲区队列,包含一组输入缓冲区(格式ByteBuffer);一个输出缓冲区队列,包含一组输出缓冲区(格式ByteBuffer);##.使用中,需要不断重复以下过程:1.把原始数据放入输入缓冲区队列中一个..

2022-05-10 21:07:48 6078

原创 Android相机(Camera)使用总结及代码示例

#.Android中Camera1使用主要流程:开启相机使用的关键步骤:0.获取相应权限1.检查相机可用性2.遍历相机列表,打开指定方向(前置/后置)相机,获取对应实例对象3.设置预览参数4.设置预览画面偏转方向5.设置预览画面输出方式6.开启预览结束相机使用的关键步骤:1.关闭对应相机预览2.释放对应相机资源##.一些要点1.摄像头相关回调执行的线程Camera.open([index])会启动相应的摄像头,并返回其实例对象。该方法在哪

2022-05-10 17:37:29 3048

原创 Android中相机(Camera)画面旋转角度分析:手机摄像头的“正向”、手机画面自然方向、相机画面的偏转角度

#.概述: 1.如同人眼看东西分上下一样,摄像头也有其“正向”,正常情况下,Android手机后置、前置摄像头的“正向”朝向为手机的“右侧”(默认如此,除非手机厂商修改设置)。(这里运行代码做过测试发现,前置摄像头也是以右侧为正向,而不是有些资料上说的左侧。) 摄像头直接返回的画面,都是以此方向为作为画面的上方向。若不加处理,直接存储到手机中或展示到View中,画面很可能不“正确”。这里的“正确”指界面存储/展示的图像方向,与摄像头拍摄时人肉眼看到的画面方向一致。 ...

2022-05-10 17:21:05 10001

原创 Android OpenGL-ES FBO(Frame Buffer Object)离屏渲染

##.FBO用途: OpenGL绘制需要一块“画布”,常见的方式是把GLSurfaceView中的Surface作为画布的载体,最终绘制好的画面会输出到GLSurfaceView对应的屏幕区域上。 如果希望绘制的画面不显示出来,默默地在后台作画,那就需要换一张“画布”,不要把画面最终输出到屏幕上。 一般是通过FBO方式来实现这一功能。(当然,如果仅仅是希望不显示到屏幕上,那么只要“画布”上的画面最终不被SurfaceFlinger消耗就行了,也并非一定要用FBO)...

2022-05-10 16:46:17 1017

原创 Android中为线程EGL环境创建及代码示例

#.EGL介绍: OpenGl是一套跨平台的接口,它与各个平台本地窗口系统之间的交互,是借助于一个中间控制层,这个中间控制层就是EGL。EGL也有自己的一套标准API,由各个平台的系统来完成其具体实现。 EGL是OpenGL和本地窗口体系进行联系的桥梁,负责管理OpenGL的运行状态、渲染图像到本地窗口或缓冲区等功能。 在Android中,OpenGL的每一步处理,都需要依赖于EGL提供的这些相关功能支持,所以必须先创建EGL环境,才能正常进行OpenGL处理...

2022-05-10 15:54:53 1396

原创 Android OpenGL ES纹理总结、纹理坐标系说明、使用代码示例

#.纹理介绍: 可以简单想象成一张皮,可以贴在OpenGL空间中自己指定的区域之上,从而实现需要的视觉效果。##.使用纹理的好处: 如果想让图形看起来更真实,就必须有足够多的顶点,还要设置相应的颜色属性,会产生很多性能开销。 而如果是使用在指定的位置贴上一层纹理的方式,不需要非常多顶点,只需要纹理绘制满足需要,OpenGl中顶点位置与纹理对应位置关联正确。就可以达到需要的细节显示效果。##."需要蒙皮"位置与纹理的对应关系 纹理有自己内部的坐标系,纹...

2022-05-10 15:27:52 3177

原创 Android OpenGl ES使用原理总结与代码示例

一、相关概念简介:OpenGl : OpenGl是一个定义好的跨平台图形处理接口库,通过它可操作GPU来完成图像处理。它跨平台是因为各个硬件厂家都按照这套接口规范具体实现了对应功能,供上层调用。OpenGl ES: 手机由于性能相对较弱,难以支持OpenGl的全部功能,所以Android中使用的是OpenGl的子集OpenGl ES。EGL: OpenGl是一套跨平台的接口,它与各个平台本地窗口系统之间的交互,是借助于一个中间控制层,这个中间控制层...

2022-05-10 15:07:29 1473

原创 Android GlSurfaceView总结及代码示例讲解

##.GlSurfaceView简介:GlSurfaceView继承自SurfaceView,额外提供了一些能力: 1.支持用OpenGl对图像渲染后显示到Surface上, 2.提供了渲染器类Render,渲染器中有Surface的生命周期回调接口,以及每一帧图像的渲染接口。 3.提供了单独的GlThrad线程,设置渲染器后,该线程会启动,初始化EGL环境,并在线程中根据状态变化做Render中各个API接口的回调。...

2022-05-10 00:56:30 4704

原创 Android SurfaceView总结及代码示例

#一.概述 SurfaceView与普通View不同,View树上的普通View共享一个Surface,而SurfaceView拥有单独的Surface。 而且普通View必须在UI线程中绘制,而SurfaceView可以在非UI线程中完成绘制工作,不占用UI主线程。 SurfaceView可以通过SurfaceHolder获取其Surface的尺寸和状态变化,并通过SurfaceHolder控制在Surface上的控制流程。 从Android 1.0...

2022-05-09 23:23:59 1528

原创 Android Surface & Canvas简介

#.Surface 是图形缓冲区(GraphicBuffer)的封装类,一般用作图像绘制的载体,例如用于承载View画面、承载SurfaceView画面、承载相机拍摄的图像、或者承载MediaCodec的输入/输出画面、承载ImageReader输入缓存画面等等。 Surface都是双缓冲的,从概念上讲,可以简化理解为有两个缓冲区引用,一个frontBuffer和一个backBuffer,backBuffer指向后置缓冲区,用于缓存正在绘制的画面;而frontBuffer指向前置...

2022-05-08 18:30:26 1406

原创 Android 屏幕画面生成的整体大致流程和一些相关概念:Window & Surface

#.屏幕画面生成的整体大致流程下面我们从Window开始说起,逐步讲到屏幕画面输出的大致流程: 我们平时使用的Activity/Dialog/Toast中都包含一个Window对象,从概念上讲,这个Window是所有View的载体,它由PhoneWindow来实现,内部包含一个DecorView,而DecorView是View树的最顶层节点。 但在app使用过程中,一个View我们能找到具体与什么相对应,而Window找不到,因为Window只是Android框架中的...

2022-05-08 17:59:23 1308

原创 Android Imageview的7种裁剪模式 & Bitmap居中裁剪示例代码

#.Imageview的7种裁剪模式1.android:scaleType=“center”:居中显示。 不缩放,如果有超出ImageView部分,则居中裁剪。2.android:scaleType=“centerCrop”:居中裁剪。 等比缩放,直到一边充满ImageView,另一边大于等于ImageView边界,超出ImageView的那一边被居中裁剪。3.android:scaleType=“centerInside”:居中包含。 不缩放或者...

2022-05-08 13:19:16 3883

原创 Android ValueAnimator、ObjectAnimator与插值器interpolator、估值器Evaluator

##.综合介绍1.插值器interpolator 动画的插值器用于控制动画完成度相对于时间的变化规律,使其符合设定的数学函数。(不过大部分情况下,我们主要是选用系统预设的几种插值器来控制速度变化。) 完成度以float类型返回,1.0代表完成100%进度。通过给动画设置插值器,可以控制控制对象的运动轨迹。(上面完成度的值可以超过1.0,例如控制运动对象超过终点位置再返回终点,完成度就先超过1.0再变回1.0)常用的插值器:以下内容摘自Android - 动画(帧动画,补间...

2022-05-05 23:08:44 972

原创 Android View工作原理:测量、布局、绘制流程 & 自定义View

#.概述##.PhoneWindow中的DecorView是根布局 Android中Window是显示和管理View的载体,其实现类是PhoneWindow。 所有View是按照树的逻辑结构来管理的,父View有多个子View节点,子View节点又可以有多个View节点。 而这棵View树的根节点是PhoneWindow中的根布局,一个DecorView,DecorView由两部分构成: ActionBar(操作条区域,顶部用于显示标题和几个操作...

2022-05-05 19:24:35 1099

原创 Android显示长度单位相关:px、ppi、dp、dpi、sp、density

#.概念解释:1.px:屏幕显示的基本单位是像素,px代表一个像素长度。2.ppi:即“屏幕密度”,沿屏幕对角线方向上,一英寸长度上所包含的像素数,即折算成的px长度。(注:一英寸inch 约等于2.54cm)3.dpi:与ppi定义类似,但指的是对角线方向一英寸长度包含的印刷点数量,一般情况下,一个像素对应一个印刷点,所以一般dpi=ppi。4.dp:全称:Density-independent pixel (dp)独立像素密度。 不同屏幕的尺寸大小、分辨率、ppi等不同,为了...

2022-05-05 18:41:13 1593

原创 Android View坐标系与View坐标获取

##.View显示坐标系Android的显示坐标体系与大多数显示系统一样,以左上角为原点,右方为x轴正方向,下方为y轴正方向。屏幕的坐标系以屏幕的左上角为原点,而在一个View内部的坐标系以该View的走上角为原点。##.获取View左上角在屏幕上的坐标//传入一个长度为2的数组,在方法内处理后,数组会获取到对应x、y坐标public void getLocationOnScreen(@Size(2) int[] outLocation)##.在父View坐标系中,获

2022-05-05 18:32:19 1324

原创 Android View的滑动效果

View的滑动效果可分为两类: 1.View本身的位置不变,View内部的内容滑动; 2. View的位置发生变化,View作为一个整体滑动;##.View内部内容的滑动1.通过以下View的以下两个API方法可实现滑动: scrollTo(int x, int y);//滑动到坐标值x、y的位置,初始时x=0,y=0。 //从右向左滑动x增大,从下向上滑动y增大。反之减小。 s...

2022-05-05 18:17:28 1074

原创 Android滑动冲突解决

##.总结 当有滑动效果的多个View嵌套使用的时候,就有可能导致滑动冲突问题。 其本质其实是,滑动事件分发出了问题,即我们希望一个滑动事件在某种状态下交由A View来处理,却交给了B View来处理。解决问题的方法,当然就是根据具体场景的特点,通过滑动轨迹或者业务状态,判断出各种情况下的滑动操作与目标View的对应关系,然后通过View的onInterceptTouchEvent()等方法将滑动事件在各个状态下分发给对应的目标View。常见场景有3种,但解决思路总结起来都是上面...

2022-05-05 18:02:41 804

原创 Android 手势事件工具类GestureDetector和VelocityTracker

Android提供了一些工具类,以便于快速判断手势事件的类型和进行相关处理。例如各种动作的判断(双击、长按等),例如计算滑动速度。这些当然可以在onTouchEvent(MotionEvent)中自己写逻辑进行处理,但借助工具类更简便。1.手势检测器GestureDetector,可用于判断各种手势,双击、长按等。示例代码和注释如下:// 步骤1.创建一个监听回调GestureDetector.SimpleOnGestureListener listener = new G...

2022-05-05 17:53:15 477

原创 Android MotionEvent事件分发介绍与流程总结(伪代码形式)

如果要一句话简单总结的话,就是: 找到一个按照规则“消耗”掉MotionEvent.ACTION_DOWN事件的View,默认情况下,后继会把整个事件流都交给它来处理。#.总体概括 Android手机是可触屏的设备,其它Android设备一般也是可触屏的。 可触屏设备允许用户与屏幕进行一些触碰的互动,系统识别各式各样的触摸操作,然后做出复杂的功能反应。 本文一切都是针对Android手机来分析说明的。 用户手机触摸屏幕的那一瞬间,An...

2022-05-05 17:20:45 931

空空如也

空空如也

TA创建的收藏夹 TA关注的收藏夹

TA关注的人

提示
确定要删除当前文章?
取消 删除