Android Handler用法

news2025/2/24 18:37:06

Android Handler用法

  • 为什么要设计Handler机制?
  • Handler的用法
    • 1、创建Handler
    • 2、Handler通信
      • 2.1 sendMessage 方式
      • 2.2 post 方式
  • Handler常用方法
    • 1、延时执行
    • 2、周期执行
  • HandlerThread用法
    • 主线程-创建Handler
    • 子线程-创建Handler
  • FAQ
    • Message是如何创建
    • 主线程中Looper的轮询死循环为何没有阻塞主线程
    • Handler内存泄漏问题及解决方案
    • Handler为什么会持有Activity的引用?

参考文档:https://developer.android.google.cn/guide/components/processes-and-threads?hl=zh-cn#java
在这里插入图片描述

Android UI操作并非线程安全。因此,请不要在工作线程(即子线程)中操纵界面。您可以通过界面线程对界面进行所有操作。Android 的单线程模型有以下两条规则:

  • 请勿阻塞UI线程
  • 请勿从UI线程以外的线程进行UI操作

为什么要设计Handler机制?

一个耗时的操作,比如需要联网读取数据或者读取本地较大的一个文件或者数据库查询,如果把这些操作放在主线程中,界面会出现假死现象, 如果5秒钟还没有完成的话,阻塞了UI线程会收到Android系统的一个错误提示 “强制关闭”,这糟糕的体验会造成严重的损失,所以不能阻塞UI线程,以确保应用界面的响应能力。
这时候就需要把这些耗时的操作,放到子线程中去执行,但是在子线程执行完以后,又需要将结果更新到UI页面,此时就涉及到子线程UI更新的操作。那为什么安卓规定不能在子线更新UI? 最根本的原因是多线程并发的问题,假设在一个Activity中,有多个线程去更新UI,并且都没有加锁机制,就会产生更新界面错乱,所以子线程中更新UI是不安全的,而在一个线程中更新UI是变得比较合理,那自然就是主线程了,当然主线程也可以叫UI线程了。
综上所述,所以安卓应用需要这样的机制:

  1. 只能在主线程更新UI,并且所有更新UI的操作,都要在主线程的消息队列当中去轮询处理。
  2. 耗时的操作在子线程,更新UI的操作在主线程,他们之间的交互需要实现线程间通信。

Handler用于实现多线程通信和管理UI线程的消息处理。它为开发者提供了一种简单有效的方法来处理异步任务和更新UI界面。
当然,Android已经提供了多种从其他线程访问UI线程的方式。以下列出了几种有用的方法:

  • Activity.runOnUiThread(Runnable)
  • View.post(Runnable)
  • Handler.post(Runnable)

Activity.runOnUiThread(Runnable)方法:如果在UI线程,直接更新UI;如果非UI线程,使用的是post。

new Thread(new Runnable() {
    @Override
    public void run() {
        // todo 在子线程中进行耗时操作
        doSomethings();
        
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                // todo UI更新代码
                doSomethingsAboutUI();
            }
        });
    }
}).start();

View.post(Runnable) 方法:

new Thread(new Runnable() {
    @Override
    public void run() {
		// todo 在子线程中进行耗时操作
        doSomethings();
		
        findViewById(R.id.button_send).post(new Runnable() {
            @Override
            public void run() {
                // todo UI更新代码
                doSomethingsAboutUI();
            }
        });
    }
}).start();

Handler.post(Runnable)方法

private Handler mHandler =new Handler();  //默认主线程
...
new Thread(new Runnable() {
    @Override
    public void run() {
        // todo 在子线程中进行耗时操作
        doSomethings();
        
        mHandlers.post(new Runnable() {
            @Override
            public void run() {
                // todo UI更新代码
                doSomethingsAboutUI();
            }
        });
    }
}).start();

随着操作变得越来越复杂,这种代码也会变得复杂且难以维护。为了处理与工作线程(子线程)的更复杂的交互,建议在UI线程中使用 Handler 处理从子线程传送的消息。

工作原理:Handler运行在主线程(UI线程)中, 它与子线程可以通过Message对象来传递数据和消息,然后把这些消息放入主线程队列中,按照先进先出的原则配合主线程逐个进行更新UI的操作。另外,驱动这套机制运行的核心是Looper.loop() 里的死循环。

@Override
public void run() {
    mTid = Process.myTid();
    Looper.prepare(); // 创建Looper的子线程,然后创建MessageQueue,最后进行绑定。
    synchronized (this) {
        mLooper = Looper.myLooper();  // 获取准备好的Looper对象
        notifyAll();
    }
    Process.setThreadPriority(mPriority);
    onLooperPrepared();
    Looper.loop();  // 启动Looper
    mTid = -1;
}

Handler的用法

下面的这种写法是可以实现刷新UI的功能,但是它违背了单线程模型:Android UI操作并不是线程安全的,并且这些操作必须在UI线程中执行。但是如果与UI无关的操作如上传/下载,数据库,可以使用此种写法。

new Thread(new Runnable() {
    @Override
    public void run() {
        textviewCurrentStatus.invalidate();
    }
}).start();

优点:避免了创建新线程带来的线程切换开销。
缺点:Handler发送的消息会保存在消息队列中,如果一直发送大量的消息,将可能导致消息队列过长,影响应用的响应能力。LiveData和RxJava等现在比较流行的框架,能够替代Handler实现更优异的异步编程和UI更新。

1、创建Handler

一般在主线程中创建Handler如下:

private Handler mHandler = new Handler(Looper.getMainLooper()) {
    public void handleMessage(Message msg) {
        switch (msg.what) {
            case 1:
                // todo 在主线程更新UI
                doSomethingsAboutUI();
                break;
        }
        super.handleMessage(msg);
    }
};

在子线程中创建Handler,最好使用HandlerThread。如果不使用HandlerThread,必须要手动启动Looper,具体如下:

private Handler mHandler;
 
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
 
    new Thread(new Runnable() {
        @Override
        public void run() {
        	// todo 在子线程进行耗时操作
        	doSomethings();
        	
            Looper.prepare();  // 准备Looper
            mHandler= new Handler(new Handler.Callback() {
                @Override
                public boolean handleMessage(@NonNull Message msg) {
                	// todo 实际还是在主线程更新UI
                	doSomethingsAboutUI();
                    return false;
                }
            });
            Looper.loop();    // 启动Looper
        }
    }).start();
}

2、Handler通信

使用Handler通信,有两种方法将消息加入消息队列中:post()方法和sendMessage()方法。
– sendMessage()方法是异步方式。即加入消息到消息队列中后,不会立即执行此消息,而是等待消息阻塞的处理程序返回。 — 存疑
– post()方法是同步方式。即加入消息到消息队列中后,会直接处理此消息,不必等待消息阻塞的处理程序返回。— 存疑

2.1 sendMessage 方式

首先,需要定义好handler需要处理的业务。

private Handler myHandler = new Handler(Looper.getMainLooper()) {
    public void handleMessage(Message msg) {
        switch (msg.what) {
            case 1:
                // todo 在主线程更新UI
                doSomethingsAboutUI();
                break;
        }
        super.handleMessage(msg);
    }
};

在需要的时机,发送消息触发handler调用业务。

new Thread(new Runnable() {
    @Override
    public void run() {
        // todo 在子线程中进行耗时操作
        doSomethings();
        
        Message message = new Message(); //或者Message msg = mHandler.obtainMessage();
        message.what = 1;
        msg.arg1 = 100;
		msg.obj = "message content";
        myHandler.sendMessage(message);
    }
}).start();

另外一种常见写法,本质都是一样的:

private Handler mHandler = new Handler(new Handler.Callback() {
    @Override
    public boolean handleMessage(@NonNull Message msg) {
        switch (msg.what){
            case 1:
                // todo 在主线程更新UI
                doSomethingsAboutUI();
                break;
        }
        return false;
    }
});
....
new Thread(new Runnable() {
     @Override
     public void run() {
         // todo 在子线程中进行耗时操作
         doSomethings();

        Message message = mHandler.obtainMessage(1);
        mHandler.sendMessage(message);
    }
}).start();

2.2 post 方式

通过调用 Handler 的 post() 方法,将 Runnable 对象通过 MessageQueue 发送到消息队列中,即可让主线程处理相应的操作。这种方式可以用于解决在子线程中不能进行 UI 操作的问题,例如我们可以在子线程中通过 post 方式将更新 UI 的任务传递到主线程来完成,这样就不会因为在非 UI 线程中更新 UI 而导致 ANR(Application Not Responding)了。

private Handler mHandler =new Handler();
...
mHandler.post(new Runnable() {
    @Override
    public void run() {
        // todo UI更新代码
        doSomethingsAboutUI();
    }
});

注意:post方法虽然发送的是一个实现了Runnable接口的类对象,但是它并非创建了一个新线程,而是执行了该对象中的run方法。也就是说,整个run中的操作和主线程处于同一个线程。这样对于那些简单的操作,似乎并不会影响。但是对于耗时较长的操作,就会出现“假死”。为了解决这个问题,就需要使得handler绑定到一个新开启线程的消息队列上,在这个处于另外线程的上的消息队列中处理传过来的Runnable对象和消息。

在主线程中使用Handler,可以直接使用getMainLooper()获取主线程Looper对象,并创建Handler实例。例如,在Activity中实现在子线程中更新UI:

private Handler mHandler = new Handler(Looper.getMainLooper());
...
new Thread(new Runnable() {
    @Override
    public void run() {
        // todo 在子线程中进行耗时操作
        doSomething();
        
        mHandler.post(new Runnable() {
            @Override
            public void run() {
                // todo 在主线程更新UI
                doSomethingsAboutUI();
            }
        });
    }
}).start();

Handler常用方法

1、延时执行

3秒后执行UI更新的代码。

mHandler.postDelayed(new Runnable() {
    @Override
    public void run() {
        // todo UI更新代码
        doSomethingsAboutUI();
    }
},3000);

2、周期执行

有时候需要按时反复周期性的执行一些任务
2.1 使用Timer和TimerTask 实现

private Timer mTimer = new Timer();
private TimerTask mTimerTask = new TimerTask() {
    @Override
    public void run() {
        Message message = new Message();
        message.what = 1;
        mHandler.sendMessage(message);
    }
};

private Handler mHandler = new Handler() {
    public void handleMessage(Message msg) {
        switch (msg.what) {
            case 1:
                // todo 处理定时或周期性的业务
                doSomethings();
                
                break;
        }
        super.handleMessage(msg);
    }
};

在需要触发的时机,调用即可

mTimer.schedule(mTimerTask, 10000);

2.2 使用postDelayed和sendMessage实现

private int index = 0;

private Handler mHandler = new Handler(Looper.getMainLooper()) {
    public void handleMessage(Message msg) {
        switch (msg.what) {
            case 1:
                updateOnTick();
                break;
        }
        super.handleMessage(msg);
    }
};

private void updateOnTick(){
    mHandler.postDelayed(new Runnable() {
        @Override
        public void run() {
            // todo 周期性执行
            doSomethings();
            Log.d(TAG, "run: " + (index++));
            mHandler.sendMessage(Message.obtain(mHandler,1));
        }
    }, 2000);  // 2秒执行一次
}

HandlerThread用法

HandlerThread常用于需要在后台执行耗时任务,并与UI线程进行交互的场景。比如,每隔6秒需要切换一下TextView的显示数据,虽然可以在UI线程中执行,但是这样的操作长时间占用UI线程,很容易让UI线程卡顿甚至崩溃,所以最好在子线程HandlerThread中调用这种业务。
HandlerThread能新建一个拥有Looper的线程。这个Looper能够用来新建其他的Handler。但需要注意的是,新建的HandlerThread需要及时回收,否则容易内存泄露。

非UI线程的业务也可以使用HandlerThread消息机制,因为不会干扰或阻塞UI线程,而且通过消息可以多次重复使用当前线程,也可以多个Handler也可以共享一个Looper,节省开支。

一个线程只能创建一个Looper,一个Looper个创建多个Handler。

使用HandlerThread可以实现以下功能和优势:

  1. 后台线程执行任务:HandlerThread在后台创建一个工作线程,可以在该线程中执行耗时任务,而不会阻塞UI线程,保证了应用的响应性和流畅性。
  2. 消息处理和线程间通信:HandlerThread内部封装了Looper和Handler,可以轻松地实现消息的发送和处理,以及线程间的通信。通过HandlerThread,可以将耗时任务的结果发送到UI线程进行更新,或者接收UI线程发送的消息进行处理。
  3. 简化线程管理:HandlerThread将线程的创建和管理进行了封装,开发人员只需要关注业务逻辑的实现,而无需手动创建和管理线程,减少了线程管理的复杂性。

主线程-创建Handler

private Handler mHandler;
private HandlerThread mHandlerThread;
 
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    mHandlerThread = new HandlerThread("子线程HandlerThread");
    mHandlerThread.start();
    mHandler= new Handler(mHandlerThread.getLooper()) {
        @Override
        public void handleMessage(@NonNull Message msg) {
            super.handleMessage(msg);
            switch (msg.what){
                case 1:
                    Log.d(TAG, "handleMessage: " + mHandlerThread.getName());
                    // todo 在主线程更新UI
                    doSomethingsAboutUI();
                    break;
            }
        }
    };
}
...
private void sendMessages(){
    new Thread(new Runnable() {
        @Override
        public void run() {
            // todo 在子线程中进行耗时操作
            doSomethings();

            Message message = mHandler.obtainMessage(1);
            mHandler.sendMessage(message);
        }
    }).start();
}

@Override
protected void onPause() {
    super.onPause();
    // 防止退出界面后Handler还在执行
    mHandler.removeMessages(1);
}

@Override
protected void onDestroy() {
    super.onDestroy();
    // 防止退出界面后Handler还在执行
    mHandler.removeMessages(1);
    // 释放资源
    mHandlerThread.quit();
}

子线程-创建Handler

private Handler mHandler;
private HandlerThread mHandlerThread;
 
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    
	new Thread(new Runnable() {
        @Override
        public void run() {
            mHandlerThread = new HandlerThread("子线程HandlerThread");
            mHandlerThread.start();
            mHandler= new Handler(mHandlerThread.getLooper()) {
                @Override
                public void handleMessage(@NonNull Message msg) {
                    super.handleMessage(msg);
                    switch (msg.what){
                        case 1:
                            Log.d(TAG, "handleMessage: " + mHandlerThread.getName());
                            // todo 在主线程更新UI
                            doSomethingsAboutUI();
                            break;
                    }
                }
            };
        }
    }).start();
}
...
private void sendMessages(){
    new Thread(new Runnable() {
        @Override
        public void run() {
            // todo 在子线程中进行耗时操作
            doSomethings();
			Log.d(TAG, "sendMessages: 耗时操作");
			
            Message message = mHandler.obtainMessage(1);
            mHandler.sendMessage(message);
        }
    }).start();
}

@Override
protected void onPause() {
    super.onPause();
    // 防止退出界面后Handler还在执行
    mHandler.removeMessages(1);  // 删除所有消息 mHandler.removeCallbacksAndMessages(null);
}

@Override
protected void onDestroy() {
    super.onDestroy();
    // 防止退出界面后Handler还在执行
    mHandler.removeMessages(1);   // 删除所有消息 mHandler.removeCallbacksAndMessages(null);
    // 释放资源
    mHandlerThread.quit();
}

注意:1、子线程中创建了Looper,当没有消息的时候子线程将会被block,无法被回收,所以我们需要手动调用quit 方法将消息删除并且唤醒looper,然后next方法返回null退出loop。
2、在主线程和子线程中,使用HandlerThread创建Handler,基本没有区别。但如果没有使用HandlerThread,在子线程中需要先创建Looper,再创建Handler。具体如下:

private Handler mHandler ;
...
new Thread(new Runnable() {
   @Override
   public void run() {
       Looper.prepare(); // 创建Looper的子线程,然后创建MessageQueue,最后进行绑定。
       mHandler = new Handler(new Handler.Callback() {
           @Override
           public boolean handleMessage(@NonNull Message msg) {
               return false;
           }
       });
       Looper.loop();  // 启动Looper
   }
}).start();

FAQ

Message是如何创建

首先考虑一个问题,屏幕刷新率 60Hz(即每秒刷新60次),每次刷新要用到 3 个 Message,也就是每秒钟至少要创建180个Message。这样不断的创建回收,就会出现内存抖动的问题,从而导致 GC、屏幕卡顿等问题。
为了解决上面的问题,采用 Message 了享元的设计模式,使用 obtain() 方法创建。在Handler 中创建两个线程池队列,一个是我们比较熟悉的 MessageQueue,另一个就是回收池 sPool(最大长度是 50 复用池)。MessageQueue 中 Message 回收时,我们将清空数据的 Message 放回到 sPool 队列中。创建 Manager,我们直接从 sPool 池中取出来就可以了。
应用场景:地图、股票、RecyclerView复用等对数据的处理都使用了享元模式。

主线程中Looper的轮询死循环为何没有阻塞主线程

Looper 轮询是死循环,但是当没有消息的时候他会 block(阻塞, 阻塞代码没有执行计时操作),ANR 是当我们处理点击事件的时候 5s 内没有响应,我们在处理点击事件的时候也是用的 Handler,所以一定会有消息执行,并且 ANR 也会发送 Handler 消息,所以不会阻塞主线程。Looper 是通过 Linux 系统的 epoll 实现的阻塞式等待消息执行(有消息执行无消息阻塞),而 ANR 是消息处理耗时太长导致无法执行剩下需要被执行的消息触发了 ANR。Handler底层为什么用epoll,为什么不用select和poll? Socket 非阻塞 IO 中 select 需要全部轮询不适合,poll 也是需要遍历和 copy,效率太低了。epoll 非阻塞式 IO,使用句柄获取 APP 对象,epoll 无遍历,无拷贝。还使用了红黑树(解决查询慢的问题)。

Handler内存泄漏问题及解决方案

内部类持有外部类的引用导致了内存泄漏,如果 Activity 退出的时候,MessageQueue中还有一个 Message 没有执行,这个 Message 持有了 Handler 的引用,而 Handler 持有了 Activity 的引用,导致 Activity 无法被回收,导致内存泄漏。这种问题可以使用 static 关键字修饰,在 onDestory 的时候将消息清除。
简单理解:当 Handler 为非静态内部类时,其持有外部类 Activity 对象,所以导致 static Looper -> mMainLooper -> MessageQueue -> Message -> Handler -> MainActivity,这个引用链中 Message 如果还在 MessageQueue 中等待执行,则会导致 Activity 一直无法被释放和回收。
根本原因:因为Looper需要循环处理消息,但一个线程只有一个Looper,而一个线程中可以有多个Handler,MessageQueue中消息Message 执行时不知道要通知哪个Handler执行任务,所以在Message创建时target引用了Handler对象,用于回调执行的消息。
如果Handler是Activity这种短生命周期对象的非静态内部类时,则创建出来的Handler对象会持有该外部类Activity的引用,当页面销毁时,还在队列的Message持有着Handler对象,而Handler正持有着外部类Activity,就会导致 Activity无法被gc回收,从而导致内存泄漏。
解决办法:
1、Handler不能是Activity这种短生命周期的对象类的内部类;
2、在 Activity销毁时,将创建的 Handler中的消息队列清空并结束所有任务。
3、将handler设置成static,static变量是全局变量,不能够自动引用外部类变量,这时Handler 就不再持有 Activity,Activity就可以正常释放。

Handler为什么会持有Activity的引用?

创建Handler时,采用的是匿名内部类或者成员内部类的方式,而内部类会默认持有外部类的引用,也就是Handler对象会默认持有Activity的引用。

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

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

相关文章

Agent AI智能体的未来

未来社会中的智能使者:Agent AI智能体的可能性与挑战 随着科技的迅速进步,人工智能已深入我们生活的各个领域,而Agent AI智能体作为与人工智能紧密相关的一个分支,其未来发展无疑是值得期待的。Agent AI智能体,或称为…

JAVA Coding 规范

Coding 规范 文章目录 Coding 规范一.文件规范1.1 声明1.2 缩进1.3 空行1.4 空格1.5 对齐1.6 小括号1.7 花括号1.8 代码长度 二.命名规范2.1 命名总则2.2 命名空间2.3 类与接口2.4 方法命名2.5 属性命名2.6 常量命名2.7 变量命名 三.语句规范3.1 语句总则3.2 循环语句3.3 Switc…

【note3】linux驱动基础,

文章目录 1.互斥锁和自旋锁选择:自旋锁(开销少)的自旋时间和被锁住的代码执行时间成正比关系2.linux错误码:64位错误指针指向内核空间最后一页,对于 1.互斥锁和自旋锁选择:自旋锁(开销少&#x…

QT:核心控件-QWidget

文章目录 控件enableobjectNamegeometrysetWindowTitleopacitycursorFonttooltipstyleSheet 控件 什么是控件? 如上所示,就是控件,而本篇要做的就是对于这些控件挑选一些比较有用的常用的进行讲解分析 在QT的右侧,会有对应的空间…

【面试经典 150 | 分治】将有序数组转换为二叉搜索树

文章目录 写在前面Tag题目来源解题思路方法一:中序遍历递归建树 写在最后 写在前面 本专栏专注于分析与讲解【面试经典150】算法,两到三天更新一篇文章,欢迎催更…… 专栏内容以分析题目为主,并附带一些对于本题涉及到的数据结构等…

如何有效地合并和分类多个文件目录?

在现代办公环境中,文件管理和组织是至关重要的。随着科技的发展,我们不再仅仅依赖于纸质文件,而是更多地使用电子设备来存储和管理信息。这种转变带来了一些新的挑战,其中之一就是如何有效地合并和分类多个目录文件。一&#xff0…

Sortable 拖拽行实现el-table表格顺序号完整例子,vue 实现表格拖拽行顺序号完整例子

npm install sortable<template><vxe-modalref"modalRef"v-model"showModal"title"详情"width"70vw"height"60vh"class"his"transfer><el-table ref"tableRef" :data"tableData&q…

树莓派5用docker运行Ollama3

书接上回&#xff0c;树莓派5使用1panel安装 Ollama 点击终端就可以进入容器 输入以下代码 ollama run llama3Llama3 是市场推崇的版本。您的 树莓派5上必须至少有 4.7GB 的可用空间&#xff0c;因此用树莓派玩机器学习就必须配置大容量的固态硬盘。用1panel部署网络下载速度…

4G远程温湿度传感器在农业中的应用—福建蜂窝物联网科技有限公司

解决方案 农业四情监测预警解决方案 农业四情指的是田间的虫情、作物的苗情、气候的灾情和土壤墒情。“四情”监测预警系统的组成包括管式土壤墒情监测站、虫情测报灯、气象站、农情监测摄像机&#xff0c;可实时监测基地状况,可以提高监测的效率和准确性&#xff0c;为农业生…

“Unite“ > MacOS下很不错的网站转应用App的工具

前言 前不久在浏览mac论坛&#xff0c;无意了解到一款非常好的工具&#xff0c;可以将网站转换为app&#xff0c;考虑到我们现在的主要应用都从本地客户端转成web形式使用&#xff0c;但基于本能的使用习惯&#xff0c;还是希望有个快捷的访问信息&#xff0c;这个应用非常适合…

202012青少年软件编程(Python)等级考试试卷(一级)

第 1 题 【单选题】 运行下方代码段&#xff0c;输出是6&#xff0c;则输入的可能是&#xff08; &#xff09;。 a eval(input())print(a)A :8%2 B :8/2 C :3*2 D :3**2 正确答案:C 试题解析: 第 2 题 【单选题】 关于Python变量&#xff0c;下列叙述正确的是&#x…

Offer必备算法33_DFS爆搜深搜回溯剪枝_八道力扣题详解(由易到难)

目录 ①力扣784. 字母大小写全排列 解析代码1_path是全局变量 解析代码2_path是函数参数 ②力扣526. 优美的排列 解析代码 ③力扣51. N 皇后 解析代码 ④力扣36. 有效的数独 解析代码 ⑤力扣37. 解数独 解析代码 ⑥力扣79. 单词搜索 解析代码 ⑦力扣1219. 黄金矿…

[嵌入式系统-58]:RT-Thread-内核:线程间通信,邮箱mailbox、消息队列MsgQueue、信号Signal

目录 线程间通信 1. 邮箱 1.1 邮箱的工作机制 1.2 邮箱控制块 1.3 邮箱的管理方式 &#xff08;1&#xff09;创建和删除邮箱 &#xff08;2&#xff09;初始化和脱离邮箱 &#xff08;3&#xff09;发送邮件 &#xff08;4&#xff09;等待方式发送邮件 &#xff08…

excel 和 text 文件的读写操作

excel 和 text 文件的读写操作 1. text 文件读写包 open语句 在文件存在的时候&#xff0c;即打开文件&#xff08;此时操作会覆盖文件&#xff0c;实际就是删除文件重后重新创建&#xff09;&#xff1b;在文件不存在的时候&#xff0c;即创建文件。 import sys print(sys.…

商务谈判技巧与口才书籍有哪些类型

商务谈判技巧与口才书籍有哪些类型&#xff08;3篇&#xff09; 商务谈判技巧与口才书籍的类型丰富多样&#xff0c;以下从三个角度进行介绍&#xff1a; **篇&#xff1a;基础理论与策略类书籍 这类书籍通常深入剖析谈判的本质&#xff0c;系统介绍谈判的原理、技巧和策略。…

S-Clustr+Nets3e 僵尸网络偷拍照片插件

项目地址:https://github.com/MartinxMax/S-Clustr-Ring 更新内容 本次将Nets3e(https://github.com/MartinxMax/Nets3e/tree/Nets3e_V1.1.4)插件成功嵌入,意味着你可以指定任意节点下的主机进行拍照 一些嵌入式设备与工业PLC设备与个人PC计算机回连控制 核心服务端搭建 最好…

MySQL-----多表查询(二)

目录 一.子查询概述&#xff1a; 二&#xff1a;标量子查询&#xff1a; 三&#xff1a;列子查询&#xff1a; 四&#xff1a;行子查询&#xff1a; 五&#xff1a;表子查询&#xff1a; 六&#xff1a;练习部分&#xff1a; 写在之前&#xff1a;本文承接上文MySQL-----多…

邦注科技 温控箱对企业的重要性

注塑加工是将加热的熔融塑料注入模具中形成所需产品的工艺过程。良好的注塑加工工艺需要控制好许多参数&#xff0c;其中最重要的因素之一就是模具的温度。模具温度的不稳定会导致产品尺寸大小、表面缺陷等方面的问题&#xff0c;甚至会导致生产不良品&#xff0c;加大生产成本…

普冉PY32系列(十五) PY32F0系列的低功耗模式

目录 普冉PY32系列(一) PY32F0系列32位Cortex M0 MCU简介普冉PY32系列(二) Ubuntu GCC Toolchain和VSCode开发环境普冉PY32系列(三) PY32F002A资源实测 - 这个型号不简单普冉PY32系列(四) PY32F002A/003/030的时钟设置普冉PY32系列(五) 使用JLink RTT代替串口输出日志普冉PY32…

【深入浅出MySQL】「性能调优」高性能查询优化MySQL的SQL语句编写

高性能查询优化MySQL的SQL语句编写准则这里写目录标题 总体优化大纲&#xff08;1&#xff09;优化查询性能&#xff1a;通过索引降低全表扫描频率优化方向案例介绍问题分析解决方案建立复合索引建立单独索引 &#xff08;2&#xff09;优化数据表与查询&#xff1a;合理使用非…