当前位置: 首页 > news >正文

Android 中的 本地广播LocalBroadcastManager

Android 中的 本地广播LocalBroadcastManager

文章目录

  • Android 中的 本地广播LocalBroadcastManager
    • 一、LocalBroadcastManager 的基本作用
    • 二 、LocalBroadcastManager 的基本使用
      • 1、包的导入
        • (1)Android 源码中bp文件的导入:
        • (2)Android Studio导入
      • 2、代码调用使用
        • (1)发送
        • (2)接收
    • 三、LocalBroadcastManager 源码
    • 四、LocalBroadcastManager 总结
      • 1、LocalBroadcastManager注册广播只能通过代码注册的方式。传统的广播可以通过代码和xml两种方式注册。
      • 2、LocalBroadcastManager注册广播后,一定要记得取消监听。这一步可以有效的解决内存泄漏的问题。
      • 3、LocalBroadcastManager采用的是Handler的消息机制来处理的广播,所以可以高效在应用内部通讯。

一、LocalBroadcastManager 的基本作用

对于LocalBroadcastManager在google官方文档中也说得很清楚,比较简短,也很好看懂,可以去看看。

Helper to register for and send broadcasts of Intents to local objects within your process. This has a number of advantages over sending global broadcasts with sendBroadcast(Intent):You know that the data you are broadcasting won’t leave your app, so don’t need to worry about leaking private data.
It is not possible for other applications to send these broadcasts to your app, so you don’t need to worry about having security holes they can exploit.
It is more efficient than sending a global broadcast through the system.

大体介绍就是这些,顾名思义,本地广播(注册),数据安全,其他app也不能给你发广播(接收)。

也比系统广播高效。

一般使用在应用内部不同fragment和Activity的交互,或者界面和service 的交互。

BroadcastReceiver设计的初衷是从全局考虑可以方便应用程序和系统、应用程序之间、应用程序内的通信,所以对单个应用程序而言BroadcastReceiver是存在安全性问题的(恶意程序脚本不断的去发送你所接收的广播)。为了解决这个问题LocalBroadcastManager就应运而生了。

二 、LocalBroadcastManager 的基本使用

1、包的导入

(1)Android 源码中bp文件的导入:
    static_libs: ["androidx.core_core","androidx.legacy_legacy-support-v4", // 包含:LocalBroadcastManager。。。],
(2)Android Studio导入

只要有 androidx 的包就可以,第一次使用有可能有右键根据提示导入一下,或者依赖里面加入:

implementation 'androidx.legacy:legacy-support-v4:1.0.0'

如果是非常就的android.support 项目,自行查一下,或者那直接使用本地保存的Java类吧。

其实 LocalBroadcastManager 就是一个普通的工具类,后面有源码提供,直接保存成java文件就可以只用!如果实在无法导包可以这样操作!

2、代码调用使用

(1)发送
LocalBroadcastManager lcm=LocalBroadcastManager.getInstance(mContext);
lcm.sendBroadcast(new Intent(ACTION_LOCATION));//发送
(2)接收
LocalBroadcastManager mLocalBroadcastManager=LocalBroadcastManager.getInstance(this);
mBoradCast = new MyBroadCast(); //定义广播,广播里面接收处理具体事务
IntentFilter intentFilter = new IntentFilter(); //添加监听的广播ActionintentFilter.addAction(XXX);
//重点在这里,本地注册,本地接收。
mLocalBroadcastManager.registerReceiver(mBoradCast, intentFilter);private BroadcastReceiver mReceiver = new BroadcastReceiver() {@Overridepublic void onReceive(Context context, Intent intent) { //处理具体事务String action = intent.getAction();Log.d(TAG, "onReceive: action = " + action);if (action.equals(XXX)) {}}};

Settings 等很多源码应用就使用到了 LocalBroadcastManager 进行消息通讯。

三、LocalBroadcastManager 源码

源码:


package androidx.localbroadcastmanager.content;//复制到本地使用,修改成自己的包名即可。import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.Uri;
import android.os.Handler;
import android.os.Message;
import android.util.Log;import androidx.annotation.NonNull;import java.util.ArrayList;
import java.util.HashMap;
import java.util.Set;/*** Helper to register for and send broadcasts of Intents to local objects* within your process.  This has a number of advantages over sending* global broadcasts with {@link android.content.Context#sendBroadcast}:* <ul>* <li> You know that the data you are broadcasting won't leave your app, so* don't need to worry about leaking private data.* <li> It is not possible for other applications to send these broadcasts to* your app, so you don't need to worry about having security holes they can* exploit.* <li> It is more efficient than sending a global broadcast through the* system.* </ul>*/
public final class LocalBroadcastManager {private static final class ReceiverRecord {final IntentFilter filter;final BroadcastReceiver receiver;boolean broadcasting;boolean dead;ReceiverRecord(IntentFilter _filter, BroadcastReceiver _receiver) {filter = _filter;receiver = _receiver;}@Overridepublic String toString() {StringBuilder builder = new StringBuilder(128);builder.append("Receiver{");builder.append(receiver);builder.append(" filter=");builder.append(filter);if (dead) {builder.append(" DEAD");}builder.append("}");return builder.toString();}}private static final class BroadcastRecord {final Intent intent;final ArrayList<ReceiverRecord> receivers;BroadcastRecord(Intent _intent, ArrayList<ReceiverRecord> _receivers) {intent = _intent;receivers = _receivers;}}private static final String TAG = "LocalBroadcastManager";private static final boolean DEBUG = false;private final Context mAppContext;private final HashMap<BroadcastReceiver, ArrayList<ReceiverRecord>> mReceivers= new HashMap<>();private final HashMap<String, ArrayList<ReceiverRecord>> mActions = new HashMap<>();private final ArrayList<BroadcastRecord> mPendingBroadcasts = new ArrayList<>();static final int MSG_EXEC_PENDING_BROADCASTS = 1;private final Handler mHandler;private static final Object mLock = new Object();private static LocalBroadcastManager mInstance;@NonNullpublic static LocalBroadcastManager getInstance(@NonNull Context context) {synchronized (mLock) {if (mInstance == null) {mInstance = new LocalBroadcastManager(context.getApplicationContext());}return mInstance;}}private LocalBroadcastManager(Context context) {mAppContext = context;mHandler = new Handler(context.getMainLooper()) {@Overridepublic void handleMessage(Message msg) {switch (msg.what) {case MSG_EXEC_PENDING_BROADCASTS:executePendingBroadcasts();break;default:super.handleMessage(msg);}}};}/*** Register a receive for any local broadcasts that match the given IntentFilter.** @param receiver The BroadcastReceiver to handle the broadcast.* @param filter Selects the Intent broadcasts to be received.** @see #unregisterReceiver*/public void registerReceiver(@NonNull BroadcastReceiver receiver,@NonNull IntentFilter filter) {synchronized (mReceivers) {ReceiverRecord entry = new ReceiverRecord(filter, receiver);ArrayList<ReceiverRecord> filters = mReceivers.get(receiver);if (filters == null) {filters = new ArrayList<>(1);mReceivers.put(receiver, filters);}filters.add(entry);for (int i=0; i<filter.countActions(); i++) {String action = filter.getAction(i);ArrayList<ReceiverRecord> entries = mActions.get(action);if (entries == null) {entries = new ArrayList<ReceiverRecord>(1);mActions.put(action, entries);}entries.add(entry);}}}/*** Unregister a previously registered BroadcastReceiver.  <em>All</em>* filters that have been registered for this BroadcastReceiver will be* removed.** @param receiver The BroadcastReceiver to unregister.** @see #registerReceiver*/public void unregisterReceiver(@NonNull BroadcastReceiver receiver) {synchronized (mReceivers) {final ArrayList<ReceiverRecord> filters = mReceivers.remove(receiver);if (filters == null) {return;}for (int i=filters.size()-1; i>=0; i--) {final ReceiverRecord filter = filters.get(i);filter.dead = true;for (int j=0; j<filter.filter.countActions(); j++) {final String action = filter.filter.getAction(j);final ArrayList<ReceiverRecord> receivers = mActions.get(action);if (receivers != null) {for (int k=receivers.size()-1; k>=0; k--) {final ReceiverRecord rec = receivers.get(k);if (rec.receiver == receiver) {rec.dead = true;receivers.remove(k);}}if (receivers.size() <= 0) {mActions.remove(action);}}}}}}/*** Broadcast the given intent to all interested BroadcastReceivers.  This* call is asynchronous; it returns immediately, and you will continue* executing while the receivers are run.** @param intent The Intent to broadcast; all receivers matching this*     Intent will receive the broadcast.** @see #registerReceiver** @return Returns true if the intent has been scheduled for delivery to one or more* broadcast receivers.  (Note tha delivery may not ultimately take place if one of those* receivers is unregistered before it is dispatched.)*/public boolean sendBroadcast(@NonNull Intent intent) {synchronized (mReceivers) {final String action = intent.getAction();final String type = intent.resolveTypeIfNeeded(mAppContext.getContentResolver());final Uri data = intent.getData();final String scheme = intent.getScheme();final Set<String> categories = intent.getCategories();final boolean debug = DEBUG ||((intent.getFlags() & Intent.FLAG_DEBUG_LOG_RESOLUTION) != 0);if (debug) Log.v(TAG, "Resolving type " + type + " scheme " + scheme+ " of intent " + intent);ArrayList<ReceiverRecord> entries = mActions.get(intent.getAction());if (entries != null) {if (debug) Log.v(TAG, "Action list: " + entries);ArrayList<ReceiverRecord> receivers = null;for (int i=0; i<entries.size(); i++) {ReceiverRecord receiver = entries.get(i);if (debug) Log.v(TAG, "Matching against filter " + receiver.filter);if (receiver.broadcasting) {if (debug) {Log.v(TAG, "  Filter's target already added");}continue;}int match = receiver.filter.match(action, type, scheme, data,categories, "LocalBroadcastManager");if (match >= 0) {if (debug) Log.v(TAG, "  Filter matched!  match=0x" +Integer.toHexString(match));if (receivers == null) {receivers = new ArrayList<ReceiverRecord>();}receivers.add(receiver);receiver.broadcasting = true;} else {if (debug) {String reason;switch (match) {case IntentFilter.NO_MATCH_ACTION: reason = "action"; break;case IntentFilter.NO_MATCH_CATEGORY: reason = "category"; break;case IntentFilter.NO_MATCH_DATA: reason = "data"; break;case IntentFilter.NO_MATCH_TYPE: reason = "type"; break;default: reason = "unknown reason"; break;}Log.v(TAG, "  Filter did not match: " + reason);}}}if (receivers != null) {for (int i=0; i<receivers.size(); i++) {receivers.get(i).broadcasting = false;}mPendingBroadcasts.add(new BroadcastRecord(intent, receivers));if (!mHandler.hasMessages(MSG_EXEC_PENDING_BROADCASTS)) {mHandler.sendEmptyMessage(MSG_EXEC_PENDING_BROADCASTS);}return true;}}}return false;}/*** Like {@link #sendBroadcast(Intent)}, but if there are any receivers for* the Intent this function will block and immediately dispatch them before* returning.*/public void sendBroadcastSync(@NonNull Intent intent) {if (sendBroadcast(intent)) {executePendingBroadcasts();}}@SuppressWarnings("WeakerAccess") /* synthetic access */void executePendingBroadcasts() {while (true) {final BroadcastRecord[] brs;synchronized (mReceivers) {final int N = mPendingBroadcasts.size();if (N <= 0) {return;}brs = new BroadcastRecord[N];mPendingBroadcasts.toArray(brs);mPendingBroadcasts.clear();}for (int i=0; i<brs.length; i++) {final BroadcastRecord br = brs[i];final int nbr = br.receivers.size();for (int j=0; j<nbr; j++) {final ReceiverRecord rec = br.receivers.get(j);if (!rec.dead) {rec.receiver.onReceive(mAppContext, br.intent);}}}}}
}

有兴趣可以自行研究一下。

这个工具类主要特点:

1.在获取LocalBroadcastManager对象实例的时候,这里用了单例模式。并且把外部传进来的Context 转化成了ApplicationContext,有效的避免了当前Context的内存泄漏的问题。这一点我们在设计单例模式框架的时候是值得学习的,看源码可以学习到很多东西。2.在LocalBroadcastManager构造函数中创建了一个Handler.可见 LocalBroadcastManager 的本质上是通过Handler机制发送和接收消息的。3.在创建Handler的时候,用了 context.getMainLooper() , 说明这个Handler是在Android 主线程中创建的,广播接收器的接收消息的时候会在Android 主线程,所以我们决不能在广播接收器里面做耗时操作,以免阻塞UI。4、LocalBroadcastManager采用的是Handler的消息机制来处理的广播,而注册到系统中的是通过Binder机制实现的,速度是应用内广播要快很多。不过由于Handler的消息机制是为了同一个进程的多线程间进行通信的,因而跨进程时无法使用应用内广播。

四、LocalBroadcastManager 总结

1、LocalBroadcastManager注册广播只能通过代码注册的方式。传统的广播可以通过代码和xml两种方式注册。

2、LocalBroadcastManager注册广播后,一定要记得取消监听。这一步可以有效的解决内存泄漏的问题。

3、LocalBroadcastManager采用的是Handler的消息机制来处理的广播,所以可以高效在应用内部通讯。

相关文章:

Android 中的 本地广播LocalBroadcastManager

Android 中的 本地广播LocalBroadcastManager 文章目录 Android 中的 本地广播LocalBroadcastManager一、LocalBroadcastManager 的基本作用二 、LocalBroadcastManager 的基本使用1、包的导入&#xff08;1&#xff09;Android 源码中bp文件的导入&#xff1a;&#xff08;2&a…...

题目 1120: C语言训练-“水仙花数“问题2python详解)——练气三层后期

✨博主&#xff1a;命运之光 &#x1f984;专栏&#xff1a;算法修炼之练气篇&#xff08;C\C版&#xff09; &#x1f353;专栏&#xff1a;算法修炼之筑基篇&#xff08;C\C版&#xff09; &#x1f352;专栏&#xff1a;算法修炼之练气篇&#xff08;Python版&#xff09; ✨…...

sheng的学习笔记-【中】【吴恩达课后测验】Course 3 - 结构化机器学习项目 - 第二周测验

课程3_第2周_测验题 目录&#xff1a;目录 要解决的问题 ① 为了帮助你练习机器学习的策略&#xff0c;本周我们将介绍另一个场景&#xff0c;并询问你将如何行动。 ② 我们认为这个在机器学习项目中工作的“模拟器”将给出一个任务&#xff0c;即领导一个机器学习项目可能…...

基于Pytorch的驾驶员分心行为实时检测

本文使用深度学习和Pytorch(PyTorch 2.0.1\Torchvision 0.15.2)实时检测驾驶员的分心行为,并附录完整代码。 检测分心驾驶是现代汽车中最重要的功能之一。无论是自动驾驶汽车还是其它高端汽车,都配备了驾驶员监控系统,以持续跟踪驾驶员的行为。这对确保驾驶员保持目光在道路…...

【uniapp】小程序开发7:自定义组件、自动注册组件

一、自定义轮播图组件、自动注册 以首页轮播图组件为例。 1、创建组件文件src/components/my-swipper.vue 代码如下&#xff1a; <template><view><view class"uni-margin-wrap"><swiper class"swiper" circular :indicator-dots…...

Modbus转MQTT以太网网关MQT-802主要特点和典型应用

随着社会的快速发展&#xff0c;物联网已经潜移默化地深入工控行业的各个领域&#xff0c;其高效的资源整合和强大的数据采集能力&#xff0c;深受客户的喜爱。上海泗博为实现客户在云端平台接收处理世界万物的信息以及实现远程控制&#xff0c;精心打造一款全新物联网产品&…...

Go学习第五章——函数与包

Go学习第五章——函数与包 1 函数1.1 基本语法1.2 函数多返回值1.3 函数的可见性和包级函数1.4 函数调用机制底层原理1.5 值类型和引用类型1.6 注意事项和细节1.7 逃逸机制&#xff08;补&#xff0c;可不看&#xff09; 2 包2.1 快速入门2.2 包的使用细节 3 函数详细讲解3.1 递…...

【Python 常用脚本及命令系列 5 -- 如何使用 BeautifulSoup 解析CSDN网页表格中的数据】

文章目录 Python BeautifulSoup 介绍CSDN 网页表格解析开发问题总结 Python BeautifulSoup 介绍 BeautifulSoup是一个Python库&#xff0c;用于解析HTML和XML文档。它常常用于网络爬虫来提取网页中的信息。 以下是BeautifulSoup的一些主要特性&#xff1a; 解析HTML&#xff…...

OpenFeign实现分析、源码解析

什么是openfeign? 是springcloud全家桶的组件之一&#xff0c;其核心作用是为Rest API提供高效简洁的rpc调用方式。 为什么只定义接口而没有实现类&#xff1f; 源码解读&#xff08;省略&#xff09; 总结&#xff1a; 源码分析&#xff1a;如何发送http请求&#xff1f; …...

2023 10月最新Vmd 下载安装教程,WindowsLinux

文章目录 下载Vmdwindows版本安装LINUX版本安装 下载Vmd 谷歌搜索VMD 点击左下角download VMD 可选择对应版本 注&#xff1a;点击后会出现输入用户名和密码&#xff0c;由于我已注册&#xff0c;界面不见了&#xff0c;所以直接描述一下。 输入用户名和密码然后会出现让登记…...

Photoshop(PS)安装教程(图文教程超详细)

目录 一.简介 二.安装步骤 软件&#xff1a;PS版本&#xff1a;2023语言&#xff1a;简体中文大小&#xff1a;3.20G系统要求&#xff1a;Win10&#xff08;1903&#xff09;及以上版本&#xff0c;64位操作系统硬件要求&#xff1a;CPU2.0GHz 内存8G(或更高&#xff0c;不支…...

C++模版进阶

一、非类型模版参数 之前学习的模版&#xff0c;参数一般是某种类型&#xff0c;但其实非类型的参数也可以定义在模版里面&#xff0c;但也有一定的限制&#xff0c;只可以定义整形家族的参数&#xff0c;而且具有常量性 注意&#xff1a; 1. 浮点数、类对象以及字符串是不允…...

CloudCompare

CloudCompare 源码编译Windows 功能格式转换 源码编译 Windows 源码编译出来的默认基本不带几个插件&#xff0c;包括保存为 .las 的功能 可以直接从 https://www.danielgm.net/cc/ 下载编译好的版本&#xff0c;插件比较多。也有免安装版本 cmake -B build -S . -G "Vi…...

【算法小课堂】深入理解前缀和算法

前缀和是指某序列的前n项和&#xff0c;可以把它理解为数学上的数列的前n项和&#xff0c;而差分可以看成前缀和的逆运算。合理的使用前缀和与差分&#xff0c;可以将某些复杂的问题简单化。 我们通过一个例子来理解前缀和算法的优势&#xff1a; 一维前缀和&#xff1a; ww…...

元对象系统功能

元对象系统功能 建立工程 布局页面 布局页面 修改原件名称 建立元对象 函数作为接口 增加一些固定的属性 #------------------------------------------------- # # Project created by QtCreator 2023-10-24T21:54:44 # #----------------------------…...

【2024秋招】小米中间件后端开发一面2023-9-13-base武汉

1 自我介绍 2 快手实习 2.1 讲讲你写的curd启动器&#xff0c;做了哪些工作呢 答&#xff1a; 2.2 网上也有一些开源的curd代码生成器&#xff0c;你为什么需要自研呢&#xff08;重要&#xff09; 答&#xff1a; &#xff08;1&#xff09;这个必须得自研&#xff0c;因…...

SpringMVC Day 01:入门案例

前言 在我们的日常工作和学习中&#xff0c;Web 开发是一个无法回避的重要环节。而在 Java Web 开发领域&#xff0c;SpringMVC 无疑是一个重量级选手。它以其灵活性、强大功能和清晰的 MVC 结构&#xff0c;赢得了大量开发者的青睐。但是&#xff0c;对于初学者来说&#xff…...

docker、docker-compose安装教程,很详细

docker、docker-compose安装教程&#xff0c;很详细 一、卸载旧版1、查看有没有安装过旧版2、停止docker3、删除安装过docker的相关包4、删除docker相关的镜像和容器 二、docker安装1、设置阿里云镜像2、查看所有docker3、安装最新版本4、安装指定版本 三、使用前准备1、启动do…...

源代码转换:Tangible Software Solutions 23.10 Crack

Tangible Software Solutions The Most Accurate and Reliable Source Code Converters Convert between C#, Java, C, Python, & VB, while saving countless hours of painstaking work and valuable time.源代码转换 Key Benefits Saves valuable time Accurate and com…...

SAD notes

ESKF 总结 prediction 更新误差先验 F F F通过3.42来算 得到 这里有点绕的一点是: 误差状态的 F F F牵涉到名义状态, 而名义状态又需要在时间上推进更新 其中, F中的名义状态的推进通过公式3.41得到, (名义状态不考虑误差, 这一点从3.41d, 3.41e可以看出, 误差状态只考虑…...

铭豹扩展坞 USB转网口 突然无法识别解决方法

当 USB 转网口扩展坞在一台笔记本上无法识别,但在其他电脑上正常工作时,问题通常出在笔记本自身或其与扩展坞的兼容性上。以下是系统化的定位思路和排查步骤,帮助你快速找到故障原因: 背景: 一个M-pard(铭豹)扩展坞的网卡突然无法识别了,扩展出来的三个USB接口正常。…...

React Native 开发环境搭建(全平台详解)

React Native 开发环境搭建&#xff08;全平台详解&#xff09; 在开始使用 React Native 开发移动应用之前&#xff0c;正确设置开发环境是至关重要的一步。本文将为你提供一份全面的指南&#xff0c;涵盖 macOS 和 Windows 平台的配置步骤&#xff0c;如何在 Android 和 iOS…...

服务器硬防的应用场景都有哪些?

服务器硬防是指一种通过硬件设备层面的安全措施来防御服务器系统受到网络攻击的方式&#xff0c;避免服务器受到各种恶意攻击和网络威胁&#xff0c;那么&#xff0c;服务器硬防通常都会应用在哪些场景当中呢&#xff1f; 硬防服务器中一般会配备入侵检测系统和预防系统&#x…...

如何在看板中有效管理突发紧急任务

在看板中有效管理突发紧急任务需要&#xff1a;设立专门的紧急任务通道、重新调整任务优先级、保持适度的WIP&#xff08;Work-in-Progress&#xff09;弹性、优化任务处理流程、提高团队应对突发情况的敏捷性。其中&#xff0c;设立专门的紧急任务通道尤为重要&#xff0c;这能…...

【python异步多线程】异步多线程爬虫代码示例

claude生成的python多线程、异步代码示例&#xff0c;模拟20个网页的爬取&#xff0c;每个网页假设要0.5-2秒完成。 代码 Python多线程爬虫教程 核心概念 多线程&#xff1a;允许程序同时执行多个任务&#xff0c;提高IO密集型任务&#xff08;如网络请求&#xff09;的效率…...

【JavaSE】绘图与事件入门学习笔记

-Java绘图坐标体系 坐标体系-介绍 坐标原点位于左上角&#xff0c;以像素为单位。 在Java坐标系中,第一个是x坐标,表示当前位置为水平方向&#xff0c;距离坐标原点x个像素;第二个是y坐标&#xff0c;表示当前位置为垂直方向&#xff0c;距离坐标原点y个像素。 坐标体系-像素 …...

【C++从零实现Json-Rpc框架】第六弹 —— 服务端模块划分

一、项目背景回顾 前五弹完成了Json-Rpc协议解析、请求处理、客户端调用等基础模块搭建。 本弹重点聚焦于服务端的模块划分与架构设计&#xff0c;提升代码结构的可维护性与扩展性。 二、服务端模块设计目标 高内聚低耦合&#xff1a;各模块职责清晰&#xff0c;便于独立开发…...

MySQL用户和授权

开放MySQL白名单 可以通过iptables-save命令确认对应客户端ip是否可以访问MySQL服务&#xff1a; test: # iptables-save | grep 3306 -A mp_srv_whitelist -s 172.16.14.102/32 -p tcp -m tcp --dport 3306 -j ACCEPT -A mp_srv_whitelist -s 172.16.4.16/32 -p tcp -m tcp -…...

python可视化:俄乌战争时间线关键节点与深层原因

俄乌战争时间线可视化分析&#xff1a;关键节点与深层原因 俄乌战争是21世纪欧洲最具影响力的地缘政治冲突之一&#xff0c;自2022年2月爆发以来已持续超过3年。 本文将通过Python可视化工具&#xff0c;系统分析这场战争的时间线、关键节点及其背后的深层原因&#xff0c;全面…...

Python爬虫(四):PyQuery 框架

PyQuery 框架详解与对比 BeautifulSoup 第一部分&#xff1a;PyQuery 框架介绍 1. PyQuery 是什么&#xff1f; PyQuery 是一个 Python 的 HTML/XML 解析库&#xff0c;它采用了 jQuery 的语法风格&#xff0c;让开发者能够用类似前端 jQuery 的方式处理文档解析。它的核心特…...