Xposed-Hook
配置 Xposed 模块的 AndroidManifest.xml:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"package="your.package.name"><applicationandroid:label="Your Xposed Module Name"android:icon="@mipmap/ic_launcher"><meta-dataandroid:name="xposedmodule"android:value="true" /><meta-dataandroid:name="xposeddescription"android:value="这是一个用于监控字符串操作的 Xposed 模块" /><meta-dataandroid:name="xposedminversion"android:value="53" /><meta-dataandroid:name="xposedscope"android:resource="@array/xposed_scope" /></application>
</manifest>
在app/src/main/assets创建一个xposed_init文件 。
xposed_init 文件是 Xposed 模块必需的一个配置文件,它用来指定模块的入口类。这个文件需要包含你的 Xposed 模块的主类的完整类名(包含包名):
your.package.name.MainHook
app/build.gradle配置一下:
dependencies {implementation 'androidx.appcompat:appcompat:1.6.1'implementation 'com.google.android.material:material:1.9.0'// Xposed Framework APIcompileOnly 'de.robv.android.xposed:api:82'compileOnly 'de.robv.android.xposed:api:82:sources'// 如果需要使用 LSPosed API(可选)// compileOnly 'org.lsposed.hiddenapibypass:hiddenapibypass:4.3'
}
LoadPackageParam (简称 lpparam) 是 Xposed 框架中的一个重要参数类,它包含了被加载的应用程序的相关信息。
LoadPackageParam 是 XC_LoadPackage 的一个内部类,以下是它的所有成员变量:
public static final class LoadPackageParam extends XCallback.Param {/** 应用的包名 */public String packageName;/** 进程的名称 */public String processName;/** 应用的 ClassLoader */public ClassLoader classLoader;/** 应用的 Application 对象 */public ApplicationInfo appInfo;/** 是否是第一次加载 */public boolean isFirstApplication;/** 系统服务的进程名(如果是系统服务) */public String[] initiatingPackages;/** 系统服务的进程名(如果是系统服务) */public String initiatingPackage;
}
// 获取当前加载的应用包名String pkgName = lpparam.packageName;// 例如: "com.android.chrome"
// 获取当前进程名String procName = lpparam.processName;// 可能是: "com.android.chrome"// 或者: "com.android.chrome:sandbox"
类加载器
- 它负责找到并加载你需要的工具(类)
// 相当于说"帮我找到这个工具"
Class<?> targetClass = lpparam.classLoader.loadClass("com.example.Target");
// 找到后就可以使用这个类了
XposedHelpers.findAndHookMethod(targetClass, "方法名", ...);
// 获取应用的类加载器ClassLoader loader = lpparam.classLoader;// 用于加载目标应用中的类Class<?> targetClass = loader.loadClass("com.example.Target");
// 获取应用的信息ApplicationInfo info = lpparam.appInfo;// 可以获取很多应用相关信息String sourceDir = info.sourceDir; // APK 路径String nativeLibDir = info.nativeLibraryDir; // native库路径int targetSdkVersion = info.targetSdkVersion; // 目标SDK版本
XposedHelpers
查找和 Hook 方法
// 1. 基本的方法 Hook
XposedHelpers.findAndHookMethod("com.example.Class", // 类名lpparam.classLoader, // 类加载器"methodName", // 方法名String.class, // 参数类型1int.class, // 参数类型2new XC_MethodHook() {@Overrideprotected void beforeHookedMethod(MethodHookParam param) {// 方法执行前的处理}@Overrideprotected void afterHookedMethod(MethodHookParam param) {// 方法执行后的处理}}
);// 2. Hook 构造方法
XposedHelpers.findAndHookConstructor(targetClass, // 类String.class, // 参数类型new XC_MethodHook() { ... }
);
获取/设置字段值
// 获取字段值
Object fieldValue = XposedHelpers.getObjectField(object, "fieldName");
int intValue = XposedHelpers.getIntField(object, "fieldName");
String stringValue = XposedHelpers.getStaticObjectField(class, "fieldName");// 设置字段值
XposedHelpers.setObjectField(object, "fieldName", newValue);
XposedHelpers.setIntField(object, "fieldName", 123);
XposedHelpers.setStaticObjectField(class, "fieldName", newValue);
调用方法
// 调用实例方法
Object result = XposedHelpers.callMethod(object, "methodName", arg1, arg2);// 调用静态方法
Object result = XposedHelpers.callStaticMethod(class, "methodName", arg1, arg2);
创建新实例
// 创建对象实例
Object newInstance = XposedHelpers.newInstance(class);
Object newInstance = XposedHelpers.newInstance(class, "构造参数1", "构造参数2");
查找类
// 查找类
Class<?> class = XposedHelpers.findClass("com.example.Class", lpparam.classLoader);
Hook Api
GravityBox
GravityBox 是一个非常著名的 Xposed 模块,它是一个系统级的调整工具箱。
-
更改赋予 Android 终端硬件按钮的功能
-
修改状态栏的外观和显示的选项
-
改变可以在手机或平板电脑屏幕上显示的亮度(这个是积极的,尤其是最小的,以节省电池)
-
直接应用程序分配给设备的触摸按钮
-
管理终端 RAM 的使用,了解此应用程序的消耗
Xposed Hook
- choose: 查找某个类的所有实例对象
- enumerateClassLoaders: 查找所有的类加载器
Java.choose() 和 Java.enumerateClassLoaders() 是 Frida 中两个不同的 API,它们的用途不同:
// 用于查找指定类的所有实例
Java.choose("com.example.TargetClass", {onMatch: function(instance) {// 每找到一个实例就会调用一次console.log("找到实例:", instance);console.log("实例字段值:", instance.fieldName);},onComplete: function() {// 搜索完成时调用console.log("搜索完成");}
});
// 用于列举所有的类加载器
Java.enumerateClassLoaders({onMatch: function(loader) {// 每找到一个类加载器就会调用一次console.log("类加载器:", loader);// 可以尝试用这个加载器加载类try {loader.loadClass("com.example.TargetClass");console.log("这个加载器可以加载目标类");} catch(e) {console.log("这个加载器无法加载目标类");}},onComplete: function() {console.log("搜索完成");}
});
主要区别:
- choose: 查找某个类的所有实例对象
- enumerateClassLoaders: 查找所有的类加载器
简单说:
- 想找对象用 choose
- 想找类加载器用 enumerateClassLoaders
// 1. 最简单的使用方式
Java.perform(function() {// 获取默认的类加载器var targetClass = Java.use("com.example.TargetClass");
});// 2. 当默认加载器找不到类时,可以遍历所有"图书管理员"
Java.enumerateClassLoaders({onMatch: function(loader) {try {// 让每个"管理员"都尝试找这本"书"loader.loadClass("com.example.TargetClass");console.log("找到了!这个管理员可以找到这本书");} catch(e) {console.log("这个管理员找不到这本书");}}
});
让我用更简单的方式解释 IXposedHookLoadPackage:
想象你是一个保安,站在商场门口:
- 每当有人(应用)要进商场时,你都会被通知
- 你可以检查他们的身份(包名),决定要不要对他们做什么
// 你就是这个保安
public class MainHook implements IXposedHookLoadPackage {@Overridepublic void handleLoadPackage(LoadPackageParam lpparam) throws Throwable {// 1. 检查是不是你要找的人if (lpparam.packageName.equals("com.taobao.qianniu")) {// 是千牛应用XposedBridge.log("发现千牛启动了!");// 2. 对这个应用做一些事// 比如:监控它的某个方法XposedHelpers.findAndHookMethod("com.taobao.qianniu.MainActivity", // 类名lpparam.classLoader, // 类加载器"onCreate", // 方法名Bundle.class, // 参数类型new XC_MethodHook() {@Overrideprotected void beforeHookedMethod(MethodHookParam param) {XposedBridge.log("千牛正在启动...");}});}}
}
handleLoadPackage 是 IXposedHookLoadPackage 接口中唯一需要实现的方法。它的作用是:
public class MainHook implements IXposedHookLoadPackage {@Overridepublic void handleLoadPackage(LoadPackageParam lpparam) throws Throwable {// 这个方法会在每个应用启动时被调用// 1. 可以获取应用的包名String packageName = lpparam.packageName;// 2. 可以获取应用的类加载器ClassLoader classLoader = lpparam.classLoader;// 3. 实际使用示例if (packageName.equals("com.taobao.qianniu")) {// 找到目标应用后,就可以开始 Hook 了XposedBridge.log("找到千牛了!");// Hook 示例XposedHelpers.findAndHookMethod("目标类名",classLoader,"方法名",new XC_MethodHook() {@Overrideprotected void beforeHookedMethod(MethodHookParam param) {// 方法执行前的处理}});}}
}
// Xposed 模块中使用 XposedBridge.log
XposedBridge.log("发现目标应用:" + TARGET_PACKAGE); // 这是 Java 代码
// Frida 脚本中使用 console.log
console.log("发现目标应用:" + TARGET_PACKAGE); // 这是 JavaScript 代码
XC_MethodHook 是 Xposed 框架中用来 Hook 方法的核心类。它让你可以在方法执行前后添加自己的代码。
XposedHelpers 最常用的方法
// 1. Hook 相关
XposedHelpers.findAndHookMethod( // Hook 实例方法className, // 类名classLoader, // 类加载器methodName, // 方法名parameterTypes, // 参数类型callback // 回调
);XposedHelpers.findAndHookConstructor( // Hook 构造方法className,classLoader,parameterTypes,callback
);// 2. 查找类
Class<?> cls = XposedHelpers.findClass("com.example.TargetClass",classLoader
);// 3. 调用方法
// 调用实例方法
Object result = XposedHelpers.callMethod(object, // 对象"methodName", // 方法名params // 参数
);// 调用静态方法
Object result = XposedHelpers.callStaticMethod(className, // 类名"methodName", // 方法名params // 参数
);// 4. 获取/设置字段
// 获取实例字段
Object value = XposedHelpers.getObjectField(object, // 对象"fieldName" // 字段名
);// 获取静态字段
Object value = XposedHelpers.getStaticObjectField(className, // 类名"fieldName" // 字段名
);// 设置实例字段
XposedHelpers.setObjectField(object, // 对象"fieldName", // 字段名newValue // 新值
);// 设置静态字段
XposedHelpers.setStaticObjectField(className, // 类名"fieldName", // 字段名newValue // 新值
);// 5. 创建新实例
Object newObj = XposedHelpers.newInstance(className, // 类名params // 构造参数
);
setAccessible 是 Java 反射中用来绕过访问限制的方法。它可以让你访问私有成员:
// 1. 基本用法
Field field = targetClass.getDeclaredField("privateField");
field.setAccessible(true); // 允许访问私有字段
Object value = field.get(object); // 现在可以访问了// 2. 实际例子
try {// 获取私有方法Method method = targetClass.getDeclaredMethod("privateMethod");method.setAccessible(true); // 设置可访问method.invoke(object); // 调用私有方法// 获取私有字段Field field = targetClass.getDeclaredField("privateField");field.setAccessible(true); // 设置可访问field.set(object, newValue); // 修改私有字段值
} catch (Exception e) {XposedBridge.log("访问失败: " + e);
}
- Java.use().$new(): 创建新的 Java 对象
- Java.cast(): 转换对象类型
- Java.choose(): 查找已存在的对象实例
$new() 里面的参数对应 Java 类的构造函数参数。让我用具体例子说明:
Java.perform(function() {// 1. 无参数构造函数var StringBuilder = Java.use("java.lang.StringBuilder");var sb1 = StringBuilder.$new(); // 等同于 new StringBuilder()// 2. 带参数构造函数var sb2 = StringBuilder.$new("Hello"); // 等同于 new StringBuilder("Hello")// 3. String 类例子var String = Java.use("java.lang.String");var str1 = String.$new(); // new String()var str2 = String.$new("Hello"); // new String("Hello")// 4. 自定义类例子var MyClass = Java.use("com.example.MyClass");// 如果 MyClass 构造函数需要两个参数:String 和 intvar myObj = MyClass.$new("参数1", 123); // new MyClass("参数1", 123)
});
下面是Java.cast(): 转换对象类型:
- View 就像是一个"容器"
- 通过 cast 告诉系统:"这个容器其实是个按钮"
- 转换后就可以用按钮特有的功能了
Java.perform(function() {// 1. 找到一个普通的 ViewJava.choose("com.example.MainActivity", {onMatch: function(activity) {// findViewById 返回的是 View 类型var view = activity.findViewById(123); // 这时候只能用 View 的方法// 把 View 转成 Buttonvar button = Java.cast(view, Java.use("android.widget.Button"));// 现在可以用 Button 特有的方法了button.setText("点击我"); // 设置按钮文字button.setEnabled(true); // 设置按钮可点击button.setOnClickListener(/* ... */); // 设置点击事件}});
});
// TextView 转换
var textView = Java.cast(view, Java.use("android.widget.TextView"));
textView.setText("这是文本");// ImageView 转换
var imageView = Java.cast(view, Java.use("android.widget.ImageView"));
imageView.setImageResource(R.drawable.icon);// EditText 转换
var editText = Java.cast(view, Java.use("android.widget.EditText"));
editText.setHint("请输入...");
主要区别:
- $new() 是 Frida (JavaScript) 的方法
- newInstance 是 Xposed (Java) 的方法
- 功能是一样的,都是创建新对象
简单说:
- 在 Frida 脚本中用 $new()
- 在 Xposed 模块中用 newInstance
Frida 的 $new()
Java.perform(function() {// Frida 方式创建对象var String = Java.use("java.lang.String");var str = String.$new("Hello"); // 创建字符串var ArrayList = Java.use("java.util.ArrayList");var list = ArrayList.$new(); // 创建列表
});
Xposed 的 newInstance
// Xposed 方式创建对象
String str = (String) XposedHelpers.newInstance(String.class, // 类"Hello" // 参数
);ArrayList list = (ArrayList) XposedHelpers.newInstance(ArrayList.class // 类
);
简单说就是三步:
- 找到类 (findClass)
- 创建实例 (newInstance)
- 调用方法 (callMethod)
public void handleLoadPackage(LoadPackageParam lpparam) throws Throwable {// 1. 首先查找类Class<?> targetClass = XposedHelpers.findClass("com.example.TargetClass", // 类名lpparam.classLoader // 类加载器);// 2. 创建类的实例Object instance = XposedHelpers.newInstance(targetClass);// 如果构造函数有参数// Object instance = XposedHelpers.newInstance(targetClass, "参数1", 123);// 3. 调用实例的方法XposedHelpers.callMethod(instance, // 实例对象"methodName", // 方法名"参数1", // 方法参数123 // 更多参数);
}
NanoHTTPD
NanoHTTPD 是一个轻量级的 HTTP 服务器库。
相关文章:
Xposed-Hook
配置 Xposed 模块的 AndroidManifest.xml: <?xml version"1.0" encoding"utf-8"?> <manifest xmlns:android"http://schemas.android.com/apk/res/android"package"your.package.name"><applicationandr…...

设计模式Python版 原型模式
文章目录 前言一、原型模式二、原型模式示例三、原型管理器 前言 GOF设计模式分三大类: 创建型模式:关注对象的创建过程,包括单例模式、简单工厂模式、工厂方法模式、抽象工厂模式、原型模式和建造者模式。结构型模式:关注类和对…...

QT:图像上绘制图形
需求描述 1、展示一张图像 2、在图像上可以使用数据绘制图像:矩形、不规则图形、线条 3、有按键可以选择 概要设计 规划布局如下 1、左边是Qlabel 用于展示图片 2、右边是三个按钮 具体实现 1、 首先设计 UI 界面,对控件进行布局 在 mainwindow.u…...

GPU上没程序在跑但是显存被占用
原因:存在僵尸线程,运行完但是没有释放内存 查看僵尸线程 fuser -v /dev/nvidia*关闭僵尸线程 pkill -9 -u 用户名 程序名 举例:pkill -9 -u grs python参考:https://blog.csdn.net/qq_40206371/article/details/143798866...
wordpress代码结构解析
WordPress 是一个基于 PHP 和 MySQL 的开源内容管理系统(CMS),广泛用于构建网站和博客。要解析 WordPress 代码,首先需要了解其核心结构、主要文件和常用的函数。以下是 WordPress 代码解析的基本指南: --- ### 1. *…...

【Unity3D】实现2D小地图效果
目录 一、玩家脚本Player 二、Canvas组件设置 三、小地图相关 四、GameLogicMap脚本修改 基于:【Unity3D】Tilemap俯视角像素游戏案例-CSDN博客 2D玩家添加Dotween移动DOPath效果,移动完成后进行刷新小地图(小地图会顺便刷新大地图&…...

关联传播和 Python 和 Scikit-learn 实现
文章目录 一、说明二、什么是 Affinity Propagation。2.1 先说Affinity 传播的工作原理2.2 更多细节2.3 传播两种类型的消息2.4 计算责任和可用性的分数2.4.1 责任2.4.2 可用性分解2.4.3 更新分数:集群是如何形成的2.4.4 估计集群本身的数量。 三、亲和力传播的一些…...

https数字签名手动验签
以bing.com 为例 1. CA 层级的基本概念 CA 层级是一种树状结构,由多个层级的 CA 组成。每个 CA 负责为其下一层级的实体(如子 CA 或终端实体)颁发证书。层级结构的顶端是 根 CA(Root CA),它是整个 PKI 体…...

LeetCode:62.不同路径
跟着carl学算法,本系列博客仅做个人记录,建议大家都去看carl本人的博客,写的真的很好的! 代码随想录 LeetCode:62.不同路径 一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为 “Start” &…...

如果我想设计一款复古风格的壁纸,应该选什么颜色?
设计复古风格的壁纸时,选择合适的颜色是营造怀旧和经典氛围的关键。复古风格通常使用一些温暖、柔和且带有岁月痕迹的色调。以下是一些适合复古风格壁纸的颜色选择和搭配建议: 一、复古风格的主色调 棕色系: 特点:棕色是复古风格的…...
【数据结构】树的基本:结点、度、高度与计算
树是数据结构中一种重要的非线性结构,广泛应用于计算机科学的各个领域,例如文件系统、数据库索引、编译器等。理解树的各种性质,如结点数、度、高度等,对于解决实际问题至关重要。 本文将会探讨树的基本概念,以及给出几…...

【Pytest】生成html报告中,中文乱码问题解决方案
链接上一篇文章:https://blog.csdn.net/u013080870/article/details/145369926?spm1001.2014.3001.5502 中文乱码问题,python3,Python3.7后,还一个文件就是result.py 因为中文可以在内容中,也可能在文件名,类名&…...

week08_文本匹配任务
1、文本匹配任务概述 狭义: 给定一组文本,判断其是否语义相似 今天天气不错 match 今儿个天不错呀 √ 今天天气不错 match 你的代码有bug 以分值形式给出相似度 今天天气不错 match 今儿个天不错呀 0.9 今天天气不错 match…...

JUC--ConcurrentHashMap底层原理
ConcurrentHashMap底层原理 ConcurrentHashMapJDK1.7底层结构线程安全底层具体实现 JDK1.8底层结构线程安全底层具体实现 总结JDK 1.7 和 JDK 1.8实现有什么不同?ConcurrentHashMap 中的 CAS 应用 ConcurrentHashMap ConcurrentHashMap 是一种线程安全的高效Map集合…...

【2024年华为OD机试】(C卷,200分)- 推荐多样性 (JavaScriptJava PythonC/C++)
一、问题描述 问题描述 我们需要从多个已排序的列表中选取元素,以填充多个窗口。每个窗口需要展示一定数量的元素,且元素的选择需要遵循特定的穿插策略。具体来说,我们需要: 从第一个列表中为每个窗口选择一个元素,然后从第二个列表中为每个窗口选择一个元素,依此类推。…...

【教学类-89-01】20250127新年篇01—— 蛇年红包(WORD模版)
祈愿在2025蛇年里, 伟大的祖国风调雨顺、国泰民安、每个人齐心协力,共同经历这百年未有之大变局时代(国际政治、AI技术……) 祝福亲友同事孩子们平安健康(安全、安全、安全)、巳巳如意! 背景需…...

[权限提升] 操作系统权限介绍
关注这个专栏的其他相关笔记:[内网安全] 内网渗透 - 学习手册-CSDN博客 权限提升简称提权,顾名思义就是提升自己在目标系统中的权限。现在的操作系统都是多用户操作系统,用户之间都有权限控制,我们通过 Web 漏洞拿到的 Web 进程的…...
DeepSeek异军突起,重塑AI格局
DeepSeek异军突起,重塑AI格局这两天AI 圈发生了比过年更令人兴奋的事情,“Meta内部反水事件”、“黄仁勋的底盘问题”,以及AI格局的大动荡,一切都是因为那个叫DeepSeek的“中国自主AI”!它由幻方量化开发,以…...

git的理解与使用
本地的git git除了最经典的add commit push用来做版本管理,其实他的分支管理也非常强大 可以说你学好了分支管理,就可以完成团队的配合协作了 git仓库 我们可以使用git init来初始化一个git仓库,只要能看见.git文件夹,就代表这…...

Baklib打造内容中台新模式助力企业智能化升级
内容概要 在如今数字化日渐渗透各个行业的背景下,内容中台逐渐成为推动企业智能化转型的重要工具。内容中台不仅仅是一个信息管理平台,更是一个整合多种内容资源,提升企业反应能力与市场适应力的创新模式。随着数据量的激增,传统…...

深入剖析AI大模型:大模型时代的 Prompt 工程全解析
今天聊的内容,我认为是AI开发里面非常重要的内容。它在AI开发里无处不在,当你对 AI 助手说 "用李白的风格写一首关于人工智能的诗",或者让翻译模型 "将这段合同翻译成商务日语" 时,输入的这句话就是 Prompt。…...

springboot 百货中心供应链管理系统小程序
一、前言 随着我国经济迅速发展,人们对手机的需求越来越大,各种手机软件也都在被广泛应用,但是对于手机进行数据信息管理,对于手机的各种软件也是备受用户的喜爱,百货中心供应链管理系统被用户普遍使用,为方…...

Day131 | 灵神 | 回溯算法 | 子集型 子集
Day131 | 灵神 | 回溯算法 | 子集型 子集 78.子集 78. 子集 - 力扣(LeetCode) 思路: 笔者写过很多次这道题了,不想写题解了,大家看灵神讲解吧 回溯算法套路①子集型回溯【基础算法精讲 14】_哔哩哔哩_bilibili 完…...

LeetCode - 394. 字符串解码
题目 394. 字符串解码 - 力扣(LeetCode) 思路 使用两个栈:一个存储重复次数,一个存储字符串 遍历输入字符串: 数字处理:遇到数字时,累积计算重复次数左括号处理:保存当前状态&a…...
电脑插入多块移动硬盘后经常出现卡顿和蓝屏
当电脑在插入多块移动硬盘后频繁出现卡顿和蓝屏问题时,可能涉及硬件资源冲突、驱动兼容性、供电不足或系统设置等多方面原因。以下是逐步排查和解决方案: 1. 检查电源供电问题 问题原因:多块移动硬盘同时运行可能导致USB接口供电不足&#x…...

页面渲染流程与性能优化
页面渲染流程与性能优化详解(完整版) 一、现代浏览器渲染流程(详细说明) 1. 构建DOM树 浏览器接收到HTML文档后,会逐步解析并构建DOM(Document Object Model)树。具体过程如下: (…...

Maven 概述、安装、配置、仓库、私服详解
目录 1、Maven 概述 1.1 Maven 的定义 1.2 Maven 解决的问题 1.3 Maven 的核心特性与优势 2、Maven 安装 2.1 下载 Maven 2.2 安装配置 Maven 2.3 测试安装 2.4 修改 Maven 本地仓库的默认路径 3、Maven 配置 3.1 配置本地仓库 3.2 配置 JDK 3.3 IDEA 配置本地 Ma…...
今日学习:Spring线程池|并发修改异常|链路丢失|登录续期|VIP过期策略|数值类缓存
文章目录 优雅版线程池ThreadPoolTaskExecutor和ThreadPoolTaskExecutor的装饰器并发修改异常并发修改异常简介实现机制设计原因及意义 使用线程池造成的链路丢失问题线程池导致的链路丢失问题发生原因 常见解决方法更好的解决方法设计精妙之处 登录续期登录续期常见实现方式特…...
管理学院权限管理系统开发总结
文章目录 🎓 管理学院权限管理系统开发总结 - 现代化Web应用实践之路📝 项目概述🏗️ 技术架构设计后端技术栈前端技术栈 💡 核心功能特性1. 用户管理模块2. 权限管理系统3. 统计报表功能4. 用户体验优化 🗄️ 数据库设…...
Go 语言并发编程基础:无缓冲与有缓冲通道
在上一章节中,我们了解了 Channel 的基本用法。本章将重点分析 Go 中通道的两种类型 —— 无缓冲通道与有缓冲通道,它们在并发编程中各具特点和应用场景。 一、通道的基本分类 类型定义形式特点无缓冲通道make(chan T)发送和接收都必须准备好࿰…...