Android --- 消息机制与异步任务

在Android中,只有在UIThread(主线程)中才能直接更新界面,

在Android中,长时间的工作联网都需要在workThread(分线程)中执行

在分线程中获取服务器数据后,需要立即到主线程中去更新UI来显示数据,

所以,如何实现线程间的通信(消息机制)

消息机制原理

消息机制原理 尚硅谷

handler发送消息到 MessageQueue 消息队列中,消息队列内部是一个链表的结构,Looper会取出消息队列中待处理的消息,调用handler的dispatchMessage()分发消息,handler中处理消息的方法有三种,最常用的是第三种

Message的callback

handler的callback

handler的handleMessage方法

消息机制相关API

Message 消息

可理解为线程间通信的数据单元,可通过message携带需要的数据

创建对象:Message.obtain()、new Message()

Message.obtain()使用了Message中的消息池,比直接new一个对象更高效

常用参数:

  • what: id标识,用以区分来自哪个线程
  • arg1/arg2: 子线程需要向主线程传递整型参数
  • obj: Object 任意对象

Handler 处理器

handler并不是只能用在处理message上,定时循环调度等工作也能使用它。

Handler 是Message 的处理器,也负责消息的发送和移除工作。

  • 发送即时消息 
public final boolean sendMessage(@NonNull Message msg),实际上调用了sendMessageDelayed()发送一个延迟时间为0的消息
public final boolean sendMessage(@NonNull Message msg) {return sendMessageDelayed(msg, 0);}public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {if (delayMillis < 0) {delayMillis = 0;}return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);}
  • 发送延时消息,并不是指延时发送,而是延时处理
public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis)
public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {if (delayMillis < 0) {delayMillis = 0;}return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);}
  • 发送空消息
public final boolean sendEmptyMessage(int what){return sendEmptyMessageDelayed(what, 0);}public final boolean sendEmptyMessageDelayed(int what, long delayMillis) {Message msg = Message.obtain();msg.what = what;return sendMessageDelayed(msg, delayMillis);}public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {if (delayMillis < 0) {delayMillis = 0;}return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);}

不管是发送什么消息,最后都要调用 sendMessageAtTime 方法

public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) {MessageQueue queue = mQueue;if (queue == null) {RuntimeException e = new RuntimeException(this + " sendMessageAtTime() called with no mQueue");Log.w("Looper", e.getMessage(), e);return false;}return enqueueMessage(queue, msg, uptimeMillis);}private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,long uptimeMillis) {msg.target = this;msg.workSourceUid = ThreadLocalWorkSource.getUid();if (mAsynchronous) {msg.setAsynchronous(true);}return queue.enqueueMessage(msg, uptimeMillis);}
  • 处理消息 (回调方法) 在主线程中执行
public interface Callback {/*** @param msg A {@link android.os.Message Message} object* @return True if no further handling is desired*/boolean handleMessage(@NonNull Message msg);}
  • 移除未处理消息,比如发送的延时消息:让消息队列调用 removeMessages 方法移除消息
    public final void removeCallbacks(@NonNull Runnable r) {mQueue.removeMessages(this, r, null);}

MessageQueue 消息队列

它是一个按Message的when(被处理时间)排序的优先级队列,用来存放通过 Handler 发送的消息。

实例

创建Handler对象,并重写 handleMessage 方法

在主/分线程创建Message对象,利用handler对象发送消息

在 handleMessage 中处理消息

  • 创建Handler对象,并重写 handleMessage 方法 

只要 handle 发了消息,就一定会触发 handleMessage 方法,并传入一个 Message 对象,根据Message 对象的what属性判断属于哪个线程。

public class HandleActivity extends AppCompatActivity {TextView textView ;String httpDate = "";@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_handle);}// 利用 handller 处理// 1.实例化一个 HandlerHandler handler = new Handler(// 只要 handle 发了消息,就一定会触发 handleMessage 方法,并传入一个 Message 对象new Handler.Callback() {@Override// 3.由handle对象接收消息并处理,在Handler的内部类中处理public boolean handleMessage(@NonNull Message msg) {Log.e("handler 处理消息", "这是handler发送的消息,此时是空");// 根据 Message 的 what 属性,区分来源于哪个线程if (msg.what == 1) {textView1 = findViewById(R.id.t1);textView1.setText(httpDate);} else if (msg.what == 2) {textView2 = findViewById(R.id.t2);textView2.setText(httpDate);} else if (msg.what == 3) {textView3 = findViewById(R.id.t3);textView3.setText("msg.what = "+msg.what+"\n"+msg.obj.toString());}return false;}});
}
  • 在主/分线程创建Message对象,利用handler对象发送消息

当消息没有包含数据时,可以使用handler.sendEmptyMessage(int what) 发送一个空消息

new Thread() {@Overridepublic void run() {super.run();getHttp();// 当 handler 需要传送数据时,需要使用到 Message,使用 sendMessage 方法Message message = new Message();message.what = 3;DataBean dataBean = new DataBean(1,"157181@qq.com","ybr","scl","asiudh");message.obj = dataBean;handler.sendMessage(message);}}.start();
  • 设置一个网络操作方法 
 // 网络操作private void getHttp() {try {// 1. 实例化一个 URL 对象URL url = new URL("https://reqres.in/api/users");// 2. 获取 HttpURLConnection 实例,使用URL的HttpURLConnection httpURLConnection = (HttpURLConnection) url.openConnection();// 3. 设置请求相关属性httpURLConnection.setRequestMethod("GET"); // 请求方法httpURLConnection.setConnectTimeout(6000); // 超时时间// 4. 获取响应码 200 成功 404 未请求到指定资源 500 服务器异常(此时已经发起请求)// 5. 判断响应码并获取响应数据(响应的正文)if (httpURLConnection.getResponseCode() == HttpURLConnection.HTTP_OK) {InputStream inputStream = httpURLConnection.getInputStream(); // 获取了服务器返回的输入流byte[] bytes = new byte[1024]; // bytes 数组,每次最多可以存放 1024 个字节ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();// 存储从输入流中读取的数据int len = 0;while ((len = inputStream.read(bytes)) > -1) {// 将字节数组中的内容写入到缓存流/* 参数1:要存入的字节数组* 参数2:起点* 参数3:要存入的长度*/byteArrayOutputStream.write(bytes, 0, len);}httpDate = new String(byteArrayOutputStream.toByteArray());Log.e("GET返回的数据", httpDate);}} catch (ProtocolException ex) {throw new RuntimeException(ex);} catch (MalformedURLException ex) {throw new RuntimeException(ex);} catch (IOException ex) {throw new RuntimeException(ex);}}

 全部代码

package com.example.androidstudiostudy;import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.view.View;
import android.widget.TextView;
import com.example.androidstudiostudy.data.DataBean;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.ProtocolException;
import java.net.URL;public class HandleActivity extends AppCompatActivity {TextView textView1, textView2, textView3;String httpDate = "";// 利用 handller 处理// 1.实例化一个 HandlerHandler handler = new Handler(// 只要 handle 发了消息,就一定会触发 handleMessage 方法,并传入一个 Message 对象new Handler.Callback() {@Override// 3.由handle对象接收消息并处理,在Handler的内部类中处理public boolean handleMessage(@NonNull Message msg) {Log.e("handler 处理消息", "这是handler发送的消息,此时是空");// 根据 Message 的 what 属性,区分来源于哪个线程if (msg.what == 1) {textView1 = findViewById(R.id.t1);textView1.setText(httpDate);} else if (msg.what == 2) {textView2 = findViewById(R.id.t2);textView2.setText(httpDate);} else if (msg.what == 3) {textView3 = findViewById(R.id.t3);textView3.setText("msg.what = "+msg.what+"\n"+msg.obj.toString());}return false;}});@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_handle);}// 此时有两个线程,这两线程发送的消息都能被 Handle接收,但如何区分不同线程发送的消息从而做不同处理呢?public void getDate(View view) {int id = view.getId();// 第一个线程if (id == R.id.handle1) {new Thread() {@Overridepublic void run() {super.run();getHttp();// 此时会报错:Only the original thread that created a view hierarchy can touch its views./*TextView textView = findViewById(R.id.t1);textView.setText(httpDate);*/// 解决办法 1 runOnUiThread 方法 ==> 相当于在主线程中跑 (初学阶段)/*runOnUiThread(new Runnable() {@Overridepublic void run() {TextView textView = findViewById(R.id.t1);textView.setText(httpDate);}});*/// 解决办法 2 利用 Handle 处理// 2.在子线程中发送(空)消息,此时发送的是空消息handler.sendEmptyMessage(1);}}.start();}// 第二个线程else if (id == R.id.handle2) {new Thread() {@Overridepublic void run() {super.run();getHttp();httpDate = httpDate + "\n第2个线程";handler.sendEmptyMessage(2);}}.start();}// 第三个线程else if (id == R.id.handle3) {new Thread() {@Overridepublic void run() {super.run();getHttp();// 当 handler 需要传送数据时,需要使用到 Message,使用 sendMessage 方法/* 1. 实例化一个 Message* 2. 参数** what:用于区分 handler 发送消息的不同线程来源** arg1/arg2: 子线程需要向主线程传递整型参数** obj: Object 任意对象*/Message message = new Message();message.what = 3;DataBean dataBean = new DataBean(1,"157181@qq.com","ybr","scl","asiudh");message.obj = dataBean;handler.sendMessage(message);}}.start();}}// 网络操作private void getHttp() {try {// 1. 实例化一个 URL 对象URL url = new URL("https://reqres.in/api/users");// 2. 获取 HttpURLConnection 实例,使用URL的HttpURLConnection httpURLConnection = (HttpURLConnection) url.openConnection();// 3. 设置请求相关属性httpURLConnection.setRequestMethod("GET"); // 请求方法httpURLConnection.setConnectTimeout(6000); // 超时时间// 4. 获取响应码 200 成功 404 未请求到指定资源 500 服务器异常(此时已经发起请求)// 5. 判断响应码并获取响应数据(响应的正文)if (httpURLConnection.getResponseCode() == HttpURLConnection.HTTP_OK) {InputStream inputStream = httpURLConnection.getInputStream(); // 获取了服务器返回的输入流byte[] bytes = new byte[1024]; // bytes 数组,每次最多可以存放 1024 个字节ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();// 存储从输入流中读取的数据int len = 0;while ((len = inputStream.read(bytes)) > -1) {// 将字节数组中的内容写入到缓存流/* 参数1:要存入的字节数组* 参数2:起点* 参数3:要存入的长度*/byteArrayOutputStream.write(bytes, 0, len);}httpDate = new String(byteArrayOutputStream.toByteArray());Log.e("GET返回的数据", httpDate);}} catch (ProtocolException ex) {throw new RuntimeException(ex);} catch (MalformedURLException ex) {throw new RuntimeException(ex);} catch (IOException ex) {throw new RuntimeException(ex);}}
}

Looper 循环器

  • 负责循环取出 MessageQueue 中当前需要处理的Message
  • 交给对应的handler进行处理
  • 处理完后,将Message缓存到消息池中,以备复用

每个线程都可以有一个关联的Looper对象,用于处理该线程的消息队列。主要功能是接收来自消息队列的消息并将其分发给对应的Handler处理。

在Android中,主线程已经自动创建了一个Looper对象,并启动了消息循环,我们可以在主线程中方便地使用Handler来处理UI事件。

而对于其他线程,如果需要处理消息,就需要手动创建一个Looper对象,并调用Looper.loop()方法来启动消息循环。

  • 必须确保在创建Handler对象之前,在线程中调用了Looper.prepare()方法创建Looper对象并初始化。
  • 通过调用Looper.loop()方法,线程会进入一个无限循环,不断地从消息队列中取出消息,并将其分发给对应的Handler进行处理。当调用Looper.quit()方法时,消息循环会停止,线程会退出循环。
 Handler handler2;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_handle);// 开一个新的线程,用于接收主线程向子线程传递消息new Thread(new Runnable() {@Overridepublic void run() {// 系统会自动为主线程开启消息循环(自动调用这句代码),与此同时创建一个 Looper 对象,不断的从 MessageQue中读取消息,交给主线程处理Looper.prepare(); // 准备开启一个消息循环handler2 = new Handler(new Handler.Callback() {@Overridepublic boolean handleMessage(@NonNull Message msg) {Log.e("主线程向子线程中发送消息", "" + msg.what + "" + msg.arg1);return false;}});Looper.loop(); //开始消息循环,相当于 while(true) ,在这里等待消息}}).start();}// 此时有两个线程,这两线程发送的消息都能被 Handle接收,但如何区分不同线程发送的消息从而做不同处理呢?public void getDate(View view) {int id = view.getId();// 第一个线程if (id == R.id.handle1) {new Thread() {@Overridepublic void run() {super.run();getHttp();// 此时会报错:Only the original thread that created a view hierarchy can touch its views./*TextView textView = findViewById(R.id.t1);textView.setText(httpDate);*/// 解决办法 1 runOnUiThread 方法 ==> 相当于在主线程中跑 (初学阶段)/*runOnUiThread(new Runnable() {@Overridepublic void run() {TextView textView = findViewById(R.id.t1);textView.setText(httpDate);}});*/// 解决办法 2 利用 Handle 处理// 2.在子线程中发送(空)消息,此时发送的是空消息handler.sendEmptyMessage(1);}}.start();}// 第二个线程else if (id == R.id.handle2) {new Thread() {@Overridepublic void run() {super.run();getHttp();httpDate = httpDate + "\n第2个线程";handler.sendEmptyMessage(2);}}.start();}// 第三个线程else if (id == R.id.handle3) {new Thread() {@Overridepublic void run() {super.run();getHttp();                  Message message = new Message();message.what = 3;DataBean dataBean = new DataBean(1, "157181@qq.com", "ybr", "scl", "asiudh");message.obj = dataBean;handler.sendMessage(message);}}.start();}// 第四个线程- 主线程向子线程发送消息else if (id == R.id.handle4) {Message message2 = new Message();message2.what = 999;message2.arg1 = 1234;handler2.sendMessage(message2);}}

线程中更新UI

runOnUiThread 方法

==> 相当于在主线程中跑 (初学阶段)

public void getDate() {new Thread() {@Overridepublic void run() {super.run();getHttp();// 此时会报错:Only the original thread that created a view hierarchy can touch its views./*TextView textView = findViewById(R.id.t1);textView.setText(httpDate);*/// 解决办法 1 runOnUiThread 方法 ==> 相当于在主线程中跑 (初学阶段)runOnUiThread(new Runnable() {@Overridepublic void run() {TextView textView = findViewById(R.id.t1);textView.setText(httpDate);}});}}.start();}

利用Handler 

 new Thread() {@Overridepublic void run() {super.run();getHttp();// 此时会报错:Only the original thread that created a view hierarchy can touch its views.// 2.在子线程中发送(空)消息,此时发送的是空消息handler.sendEmptyMessage(1);}
}.start();
Handler handler = new Handler(new Handler.Callback() {@Override// 3.由handle对象接收消息并处理,在Handler的内部类中处理public boolean handleMessage(@NonNull Message msg) {Log.e("handler 处理消息", "这是handler发送的消息,此时是空");textView = findViewById(R.id.t1);textView.setText(httpDate);return false;}});

什么是异步任务?

逻辑上:以多线程的方式完成的功能需求

API上:指AsyncTask类

在没有 AsyncTask 前,我们可以使用 Thread+Handler 实现异步任务,而 AsyncTask 是对Thread+Handler 功能的封装,使用更简洁,效率更高效

AsyncTask 是一个抽象类,需要指定3个泛型参数

public abstract class AsyncTask<Params, Progress, Result>
  • Params: 这个泛型指定的是我们传递给异步任务执行时的参数的类型。
  • Progress: 这个泛型指定的是我们的异步任务在执行的时候将执行的进度返回给UI线程的参数的类型。
  • Result: 这个泛型指定的异步任务执行完后返回给UI线程的结果的类型。

而 AsyncTask目前已被弃用

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

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

相关文章

手撕spring框架(2)

相关系列 java中spring底层核心原理解析&#xff08;1&#xff09;-CSDN博客 java中spring底层核心原理解析(2)-CSDN博客 手撕spring框架&#xff08;1&#xff09;-CSDN博客 手撕spring框架&#xff08;3&#xff09;-CSDN博客 手撕spring框架&#xff08;4&#xff09;-CSDN博…

用龙梦迷你电脑福珑2.0做web服务器

用龙梦迷你电脑福珑2.0上做web服务器是可行的。已将一个网站源码放到该电脑&#xff0c;在局域网里可以访问网站网页。另外通过在同一局域网内的一台windows10电脑上安装花生壳软件&#xff0c;也可以在外网访问该内网服务器网站网页。该电脑的操作系统属于LAMP。在该电脑上安装…

Qt Creator导入第三方so库和jar包——Qt For Android

前言 之前了解了在Android Studio下导入so库和jar包&#xff0c;现在实现如何在Qt上导入so库和jar包。 实现 下面是我安卓开发&#xff08;需调用安卓接口的代码&#xff09;的目录&#xff08;图1&#xff09;&#xff0c;此目录结构和原生态环境&#xff08;Android Studi…

详细分析Java中的脱敏注解(附Demo)

目录 前言1. 基本知识2. 核心逻辑3. Demo4. 模版 前言 对于隐私信息&#xff0c;需要做特殊处理&#xff0c;比如身份证或者手机号等 对于Java的相关知识推荐阅读&#xff1a;java框架 零基础从入门到精通的学习路线 附开源项目面经等&#xff08;超全&#xff09; 1. 基本知…

软件定义汽车落地的五大关键要素

1、架构升级 1.1 软件架构&#xff1a;分层解耦、服务化、API 接口标准化 随着企业向软件定义汽车开发方法的转变&#xff0c;软件架构也需要同步进行升级&#xff0c;引入面向服务的架构&#xff08;Service-Oriented Architecture&#xff0c;简称 SOA&#xff09;方法论。…

LeetCode刷题之买卖股票的最佳时机

文章目录 1. 买卖股票的最佳时机1.1 描述1.2 分析1.3 解答 2.买卖股票的最佳时机II2.1 描述2.2 分析2.3 解答2.4 拓展2.5 拓展二 1. 买卖股票的最佳时机 题121 买卖股票的最佳时机 1.1 描述 给定一个数组 prices &#xff0c;它的第 i 个元素 prices[i] 表示一支给定股票第 …

【Android学习】按钮监听代码

1. 简介 Button组件是Android中常用的组件&#xff0c;Button常需要和View.OnClickListener配合使用。这里记录下Button配置监听的过程。 2. 代码分析 2.1 Layout的XML代码 <Buttonandroid:id"id/btn"android:layout_width"match_parent"android:lay…

ThreeJS:响应式画布与全屏控制

响应式画布 响应式画布&#xff1a;在用户缩放浏览器窗口时&#xff0c;为便于动态更新画布尺寸与宽高比例&#xff0c;需要通过监听resize事件&#xff0c;来实现响应式画布。 window.onresize function () {//TODO:重置渲染器宽高比renderer.setSize(window.innerWidth, wi…

图文、视频处理等自媒体工具

文章目录 文本转文本图片canva同类竞品文本生成PDF,PDF再导出为图片贴入笔记类应用(如小米笔记App)中然后选择以图片形式分享UU在线工具的文字生成长图醒图App其他竞品文本转配音视频剪映将文本生成一段朗读该文本的配音视频(需要自行切割多段内容并分配时间轴)腾讯智影将…

为人处事电影解说,全新升级瀚海跑道一分钟一条视频,全平台可推广,轻轻松松日入1000

自古以来&#xff0c;我国流行的一种现象是&#xff0c;大多数人都会与领导或上司打交道。由于某些话题不宜公开讨论&#xff0c;因此出现了许多含蓄的表达方式。随着年龄的增长&#xff0c;人们的态度也发生了变化&#xff0c;从最初的轻视到现在的重视。 下 载 地 址&#…

VG做mirror引起的块偏移

事件起因 Oracle10.2环境 Aix操作系统使用aix的lvm技术。制作vg的mirror。以此来替换掉老的存储。 做mirror前&#xff0c;数据库已完全关闭 故障现象 在启动数据库时&#xff0c;发现IO错误。该系统的spfile&#xff0c;ctl&#xff0c;dbf均是用lv做的裸设备。其中dbf是使…

WebGL是啥

WebGL&#xff08;全写为Web Graphics Library&#xff09;是一种3D绘图协议&#xff0c;这种绘图技术标准允许把JavaScript和OpenGL ES 2.0结合在一起&#xff0c;通过增加OpenGL ES 2.0的一个JavaScript绑定&#xff0c;WebGL可以为HTML5 Canvas提供硬件3D加速渲染。这样&…

【重学C语言】十二、指针高级-函数指针

【重学C语言】十二、指针高级-函数指针 函数指针小案例回调函数如何看懂复杂的指针右左法则案例走起1.int (\*p[5])(int\*)2. int (\*fun)(int \*p,int (\*pf)(int \*))3. int (\*(\*fun)[5])(int \*p)4. int (\*(\*fun)(int \*p))[5]5. int(\*(\*fun())())()函数指针 函数指针…

cmake的使用方法: 编译生成库文件

一. 简介 前面文章学习了针对单个 .c文件&#xff0c;cmake 工具是如何编译的&#xff1f; 针对包含多个 .c文件&#xff0c;cmake工具又是如何编译的&#xff1f;文章如下&#xff1a; cmake的使用方法: 单个源文件的编译-CSDN博客 cmake的使用方法: 多个源文件的编译-CS…

Java入门-final关键字

final关键字 修饰基本类型 变量为只读&#xff0c;不能修改变量的内容。 final int SIZE 3;修饰引用类型 引用的对象不能改变&#xff0c;但是对象的内容可以修改。 final Car c new Car( );c.setColor("红色");修饰类的属性 类的属性不能被修改。 第一种方式&…

Linux 进程间通信之命名管道

&#x1f493;博主CSDN主页:麻辣韭菜&#x1f493;   ⏩专栏分类&#xff1a;Linux知识分享⏪   &#x1f69a;代码仓库:Linux代码练习&#x1f69a;   &#x1f339;关注我&#x1faf5;带你学习更多Linux知识   &#x1f51d; 目录 前言 命名管道 创建一个命名管道 …

Pandas库的基本使用

什么是Pandas? 一个开源的Python类库&#xff1a;用于数据分析、数据处理、数据可视化 高性能容易使用的数据结构容易使用的数据分析工具 很方便和其它类库一起使用&#xff1a; numpy&#xff1a;用于数学计算scikit-learn&#xff1a;用于机器学习 怎样下载安装Pandas …

Flask后端之建立模型类间的外键联系

1、模型类代码&#xff1a; 定义了三个模型类&#xff1a;User、Goods 和 Sign&#xff1a; class User(db.Model):__tablename__ userid db.Column(db.Integer, primary_keyTrue)name db.Column(db.String(100), nullableFalse)email db.Column(db.String(100), uniqueT…

商城数据库(36 37 38 39 40)

36 CREATE TABLE sxh_log_operates (operateId int(11) NOT NULL AUTO_INCREMENT COMMENT 自增ID,staffId int(11) NOT NULL DEFAULT 0 COMMENT 职员ID,operateTime datetime NOT NULL COMMENT 操作时间,menuId int(11) NOT NULL COMMENT 所属菜单ID,operateDesc varchar(255)…

UI自动化与接口自动化比较

UI自动化与接口自动化优比较&#xff1a; 1、执行效率 接口自动化执行效率比UI自动化执行效率更高(调用接口比打开页面要快很多) 2、稳定性 UI自动化容易受设备卡顿&#xff0c;系统弹框等因素影响而导致脚本执行失败、接口自动化不存在此问题&#xff0c;因此接口自动化测试…