为什么android要使用Binder机制

1.linux中大多数标准 IPC 场景(如管道、消息队列、ioctl 等)的进程间通信机制

+------------------+        +------------------+        +------------------+
|  用户进程 A      |        |   内核空间       |        |  用户进程 B      |
|  (User Space A)  |        |  (Kernel Space)  |        |  (User Space B)  |
+------------------+        +------------------+        +------------------+|                            |                            || 1. copy_from_user()        |                            ||---------------------------->                            ||                            |                            ||                            |                            ||                            | 2. copy_to_user()          ||                            | -------------------------->||                            |                            |

📝 图解说明:

  1. 用户进程 A 将数据从用户空间拷贝到内核空间:

    • 使用 copy_from_user() 函数。
    • 此操作将数据从用户缓冲区复制到内核缓冲区。
  2. 内核保存数据

    • 数据暂时存储在内核中的某个缓冲区(如驱动私有缓冲区、socket 缓冲区等)。
  3. 用户进程 B 从内核空间读取数据:

    • 使用 copy_to_user() 函数。
    • 此操作将数据从内核缓冲区复制到用户进程 B 的缓冲区。

✅ 总结:

  • 发生了 单向通信两次数据复制(物理内存拷贝)(用户 → 内核 → 用户)。
  • 虽然性能不如共享内存等零拷贝机制,但适用于大多数标准 IPC 场景(如管道、消息队列、ioctl 等)。
缺点: 物理内存拷贝(Physical Memory Copy)是指在不同内存区域之间实际复制数据内容,而不是通过指针映射或页表共享来访问同一块内存。它对系统性能、资源利用率和程序响应性都有直接影响。

🧠 物理内存拷贝会影响哪些方面?

影响维度描述
性能拷贝大量数据会消耗 CPU 时间,影响整体执行效率。
延迟数据拷贝耗时会导致操作延迟,特别是在实时系统中影响用户体验。
吞吐量频繁的内存拷贝会降低系统的并发处理能力。
内存带宽大量拷贝占用内存总线带宽,可能成为瓶颈。
能耗在移动设备上,频繁拷贝会增加功耗,影响电池寿命。

物理内存拷贝会影响性能、延迟、内存带宽和能耗,尤其是在大数据传输或高频率调用场景下尤为明显。

推荐做法:

  • 小数据量通信:可以接受一定拷贝开销,使用标准 IPC(如 Binder、Pipe)即可。
  • 大数据量通信:优先考虑 共享内存、mmap、MemoryFile、ashmem 等零拷贝方案。
  • 实时性要求高:避免使用同步阻塞 IPC,推荐异步 + 零拷贝机制。






2.Binder机制

不同进程通信时,只有在客户端拿到代理类调用方法的时候,才是Binder进行用户空间和内核空间数据传输的过程!!!

步骤操作描述技术细节(以数据发送为例)
步骤 1Binder 驱动准备为进程通信做准备,实现“内存映射(mmap)”,创建接收缓存区、映射关系(内核缓存区 ↔ Server 用户空间)
步骤 2Client 发送数据Client 进程通过系统调用 copy_from_user,将数据从“用户空间”拷贝到“内核缓存区”,再由 Binder 驱动通知 Server 进程
步骤 3Server 处理请求Server 进程被 Binder 驱动唤醒后,从内核缓存区读取数据、解析,调用目标方法处理
步骤 4Server 返回结果Server 进程通过 copy_to_user,将处理结果从内核缓存区拷贝到 Client 进程的用户空间,Binder 驱动通知 Client 进程

三、优点总结

  1. 传输效率高:数据拷贝次数少(仅 1 - 2 次 ),依托内存映射让“用户空间 ↔ 内核空间”直接共享数据,减少冗余操作。
  2. 灵活适配:为接收进程动态分配“接收缓存区”,无需提前固定大小,适配不同数据量的通信场景。
    (客户端发送数据:客户端通过 transact() 方法发送数据时,只需将数据写入 Parcel 并传递给 Binder 驱动,无需提前声明数据长度。
    服务端接收缓存区创建:Binder 驱动根据客户端实际发送的数据量,为服务端进程动态分配足够大小的内核缓存区(通过 mmap 映射到用户空间),用于接收数据。 )
  3. 死亡通知机制
// 客户端注册死亡通知
mRemote.linkToDeath(new DeathRecipient() {@Overridepublic void binderDied() {// 服务进程崩溃后的回调}
});

实现原理:驱动检测到服务进程退出时,发送 BR_DEAD_BINDER 消息给所有客户端。







3.代码举例

代码中的1-4是代码执行流程!!!!
以下是一个完整的 AIDL 跨进程通信示例,包含服务端和客户端的 Java 代码,实现简单的加法运算功能:

一、服务端代码

1. 创建 AIDL 文件

在 Android 项目的 main 目录下,新建 aidl 文件夹(与 java 目录同级 ),然后创建包名对应的目录(如 com/example/aidlserver ),在该目录下新建 IMyAidlInterface.aidl 文件:

// IMyAidlInterface.aidl
package com.example.aidlserver;interface IMyAidlInterface {int add(int num1, int num2);
}
2. 编写 Service 类

创建 IRemoteService.java ,继承 Service ,实现 AIDL 接口的 Stub

package com.example.aidlserver;import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;public class IRemoteService extends Service {private static final String TAG = "IRemoteService";2.实现 AIDL生成的Stub 类(Stub是服务端Binder实现)private IBinder iBinder = new IMyAidlInterface.Stub() {@Overridepublic int add(int num1, int num2) throws RemoteException {return num1 + num2;}};3.接受客户端发来的bind请求,返回服务端Binder@Overridepublic IBinder onBind(Intent intent) {return iBinder;}@Overridepublic int onStartCommand(Intent intent, int flags, int startId) {Log.d(TAG, "服务已启动");return super.onStartCommand(intent, flags, startId);}
}
3. 注册 Service 到清单文件

AndroidManifest.xml 中声明 Service ,设置可被其他进程访问:

<serviceandroid:name=".IRemoteService"android:exported="true"><intent-filter><action android:name="com.example.aidlserver.IRemoteService" /></intent-filter>
</service>

二、客户端代码

1. 复制 AIDL 文件

将服务端的 IMyAidlInterface.aidl 文件,完整复制到客户端项目的 aidl 目录(同样保持包名路径 com/example/aidlserver )。

2. 编写 Activity 类

创建 MainActivity.java ,绑定服务端 Service 并调用 AIDL 方法:

package com.example.aidlclient;import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;import androidx.appcompat.app.AppCompatActivity;import com.example.aidlserver.IMyAidlInterface;public class MainActivity extends AppCompatActivity {private static final String TAG = "MainActivity";private IMyAidlInterface aidlInterface;private TextView resultTv;private IBinder mRemote; // 保存服务端 Binder 引用private ServiceConnection serviceConnection = new ServiceConnection() {@Overridepublic void onServiceConnected(ComponentName name, IBinder service) {4.服务绑定后,根据传过来的service获取服务端Binder的代理对象!!!// 将 IBinder 转换为 AIDL 接口aidlInterface = IMyAidlInterface.Stub.asInterface(service);// 2. 保存 Binder 引用,用于注册死亡通知mRemote = service;// 3. 注册 Binder 死亡通知(关键步骤)try {mRemote.linkToDeath(deathRecipient, 0);} catch (RemoteException e) {e.printStackTrace();}Log.d(TAG, "服务已连接");}@Overridepublic void onServiceDisconnected(ComponentName name) {aidlInterface = null;Log.d(TAG, "服务已断开");}};// 死亡通知回调实现private IBinder.DeathRecipient deathRecipient = new IBinder.DeathRecipient() {@Overridepublic void binderDied() {// 服务端进程崩溃时的处理逻辑runOnUiThread(() -> {Toast.makeText(MainActivity.this, "服务已崩溃", Toast.LENGTH_SHORT).show();// 重置引用,可选择重新绑定服务if (mRemote != null) {mRemote.unlinkToDeath(this, 0);mRemote = null;}aidlInterface = null;// 可添加自动重连逻辑bindService(...);});}};性能开销:频繁注册 / 解除死亡通知会有一定开销,建议在应用生命周期内合理管理。public void binderDied() {// 重新绑定服务bindService(new Intent(MainActivity.this, IRemoteService.class),serviceConnection, BIND_AUTO_CREATE);}@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);resultTv = findViewById(R.id.result_tv);Button bindBtn = findViewById(R.id.bind_btn);Button callBtn = findViewById(R.id.call_btn);// 绑定服务bindBtn.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {Intent intent = new Intent();// 设置服务端包名和 Service actionintent.setPackage("com.example.aidlserver");intent.setAction("com.example.aidlserver.IRemoteService");1.绑定服务bindService(intent, serviceConnection, BIND_AUTO_CREATE);}});// 调用 AIDL 方法callBtn.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {if (aidlInterface != null) {try {int result = aidlInterface.add(5, 3);resultTv.setText("计算结果:" + result);} catch (RemoteException e) {e.printStackTrace();}} else {resultTv.setText("服务未连接");}}});}@Overrideprotected void onDestroy() {super.onDestroy();// 解绑服务unbindService(serviceConnection);}
}

具体流程拆解

一、流程拆解 + 代码对应(结合之前 AIDL 示例)

1. 客户端 bindService

客户端通过 bindService 发起绑定服务的请求,代码如下(以客户端 MainActivity 为例):

Intent intent = new Intent();
intent.setPackage("com.example.aidlserver"); // 服务端包名
intent.setAction("com.example.aidlserver.IRemoteService"); // 服务端 Service 的 action
bindService(intent, serviceConnection, BIND_AUTO_CREATE); 
  • 作用:通过隐式意图,找到服务端对应的 Service,发起绑定,触发服务端 onBind 执行。
2. 服务端 onBind 返回 IBinder

服务端 IRemoteService 中,onBind 返回 AIDL 生成的 Stub(本质是 IBinder 实现类 ):

public class IRemoteService extends Service {private IBinder iBinder = new IMyAidlInterface.Stub() { // Stub 实现了 IBinder@Overridepublic int add(int num1, int num2) throws RemoteException {return num1 + num2;}};@Overridepublic IBinder onBind(Intent intent) {return iBinder; // 返回 IBinder 实例}
}
  • 说明:Stub 是 AIDL 工具根据 .aidl 文件自动生成的类,它继承 Binder 并实现 AIDL 接口,用于跨进程通信时的 “桥梁”。
3. 客户端 onServiceConnected 回调

客户端通过 ServiceConnectiononServiceConnected 拿到服务端返回的 IBinder,代码:

private ServiceConnection serviceConnection = new ServiceConnection() {@Overridepublic void onServiceConnected(ComponentName name, IBinder service) {// 把 IBinder 转成 AIDL 接口代理aidlInterface = IMyAidlInterface.Stub.asInterface(service); }@Overridepublic void onServiceDisconnected(ComponentName name) {aidlInterface = null;}
};

重点:!!!!

一、asInterface(service) 的工作原理
1. 服务端返回的 service 参数
服务绑定成功后,服务端通过 onBind() 返回的 IBinder 对象(即 Stub 实例)
会被传递到客户端的 onServiceConnected() 回调中,作为 service 参数。
2. asInterface() 方法的源码逻辑
AIDL 自动生成的 Stub.asInterface() 方法大致如下:public static IMyAidlInterface asInterface(android.os.IBinder obj) {if (obj == null) {return null;}// 检查是否在同一进程(关键判断)android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);if (iin != null && iin instanceof IMyAidlInterface) {return (IMyAidlInterface) iin; // 同一进程:直接返回服务端的 Stub 本身}// 跨进程:创建并返回代理对象 Proxyreturn new IMyAidlInterface.Stub.Proxy(obj); 
}同一进程(客户端和服务端在同一个 APK 中):
obj.queryLocalInterface() 返回服务端的 Stub 实例(因为 Stub 实现了 IInterface),
此时客户端直接拿到服务端的 Binder 对象,方法调用等效于本地调用。跨进程(客户端和服务端是不同 APK):
queryLocalInterface() 返回 null,此时创建 Proxy 对象,
它封装了底层的 Binder 通信逻辑
(将方法调用转为 transact() 请求,才开始进行数据复制那些内核态空间的操作),
让客户端通过代理间接访问服务端。二、为什么需要代理对象?
1. 跨进程通信的复杂性
客户端和服务端在不同进程,无法直接调用对方的方法。
代理对象(Proxy)的作用是:
将客户端的方法调用(如 add(5, 3))转换为 Binder 协议的 transact() 请求;
将参数打包为 Parcel 并发送给服务端;
接收服务端返回的结果并解包。2. 对开发者透明的封装
通过 asInterface() 返回代理对象,AIDL 隐藏了底层的跨进程通信细节,
让开发者感觉就像在调用本地对象的方法一样简单。
4. 客户端通过代理类调用接口!!!!!!!!!!!!!!
触发 Binder 驱动的实际数据传输跨进程交互!!!!!!!

拿到 aidlInterface(代理对象)后,直接调用 AIDL 定义的方法,比如:

try {int result = aidlInterface.add(5, 3); // 调用远程服务的 add 方法resultTv.setText("计算结果:" + result);
} catch (RemoteException e) {e.printStackTrace();
}

一、add(5, 3) 调用触发的完整流程

1. 客户端代理对象(Proxy)处理调用

当执行 aidlInterface.add(5, 3) 时,实际调用的是 Stub.Proxy 类的 add() 方法(AIDL 自动生成):

// Proxy 类的 add 方法(伪代码)
@Override
public int add(int num1, int num2) throws RemoteException {// 1. 创建 Parcel 用于打包参数Parcel _data = Parcel.obtain();Parcel _reply = Parcel.obtain();int _result;try {// 2. 将参数写入 Parcel(用户空间)_data.writeInterfaceToken(DESCRIPTOR);_data.writeInt(num1);_data.writeInt(num2);3. 通过 Binder 发送请求到服务端(关键跨进程操作)!!!!!!!!!!!!!mRemote.transact(Stub.TRANSACTION_add, _data, _reply, 0);// 4. 从返回结果中读取数据(用户空间)_reply.readException();_result = _reply.readInt();} finally {_reply.recycle();_data.recycle();}return _result;
}
2. Binder 驱动的数据复制与映射
  • 步骤 3 的 transact() 触发内核操作
    1. 客户端用户空间 → 内核空间
      Binder 驱动将 _data 中的数据(53)从客户端进程的用户空间复制到内核缓存区(第一次内存拷贝)。
    2. 内核空间 → 服务端用户空间
      通过内存映射(mmap),服务端进程的用户空间直接与内核缓存区共享同一块物理内存,无需再次复制(零拷贝)。
    3. 服务端处理请求
      服务端的 StubonTransact() 中解析参数,调用真正的 add() 方法计算结果(5 + 3 = 8)。
    4. 结果返回
      结果(8)从服务端用户空间写入内核缓存区(通过映射直接操作),再由 Binder 驱动复制到客户端的用户空间(第二次内存拷贝)。
3、Binder 高效性的核心
  • 零拷贝优化
    通过内存映射(mmap),服务端可直接访问内核缓存区的数据,避免了传统 IPC(如 Socket)的多次拷贝(用户空间 → 内核空间 → 用户空间)。
  • 两次拷贝(双向的,单项就只有一次拷贝)
    Binder 实际只需要两次拷贝(客户端用户空间 → 内核空间 → 客户端用户空间),是 Android 中最高效的 IPC 机制之一。

而服务绑定(bindService())只是为这个过程建立通信通道,并不涉及实际业务数据的传输。

二、补充说明(帮你更深入理解)

  • 同进程 vs 跨进程:如果客户端和服务端在同一个进程(比如调试时),Stub.asInterface 会直接返回服务端的 Stub 本身,不走跨进程通信逻辑(相当于本地接口调用);跨进程时才会返回 Proxy 代理。
  • 异常处理:跨进程调用可能因服务端崩溃、连接断开等抛 RemoteException,所以客户端调用接口时要捕获异常,做容错处理(比如提示 “服务异常”)。
  • AIDL 作用:帮我们自动生成 Stub(服务端实现)和 Proxy(客户端代理)的模板代码,简化跨进程通信的 “编解码”“Binder 调用” 等复杂流程,让我们像写本地接口一样写跨进程逻辑 。

简单说,整个流程就是:客户端绑定服务 → 服务端返回 Binder → 客户端拿到代理 → 代理帮我们做跨进程调用 ,AIDL 主要是帮我们简化了 “代理创建”“数据编解码” 这些繁琐工作~

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

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

相关文章

OpenCV CUDA模块设备层-----双曲余弦函数cosh()

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 该函数用于计算四维浮点向量&#xff08;float4类型&#xff09;的双曲余弦值&#xff0c;作用于CUDA设备端。双曲余弦函数定义为cosh(x) (eˣ …

48页PPT | 企业数字化转型关键方法论:实践路径、案例和落地评估框架

目录 一、什么是企业数据化转型&#xff1f; 二、为什么要进行数据化转型&#xff1f; 1. 市场复杂性与不确定性上升 2. 内部流程效率与协同难题突出 3. 数字资产沉淀不足&#xff0c;智能化基础薄弱 三、数据化流程管理&#xff1a;从“业务流程”到“数据流程”的对齐 …

VTK中的形态学处理

VTK图像处理代码解析:阈值化与形态学开闭运算 这段代码展示了使用VTK进行医学图像处理的两个关键步骤:阈值分割和形态学开闭运算。下面我将详细解析每个部分的功能和实现原理。 处理前 处理后 1. 阈值分割部分 (vtkImageThreshold) vtkSmartPointer<vtkImageThresho…

xlsx.utils.sheet_to_json() 方法详解

sheet_to_json() 是 SheetJS/xlsx 库中最常用的方法之一&#xff0c;用于将 Excel 工作表&#xff08;Worksheet&#xff09;转换为 JSON 格式数据。下面我将全面讲解它的用法、参数配置和实际应用场景。 基本语法 javascript 复制 下载 const jsonData XLSX.utils.sheet…

〔从零搭建〕BI可视化平台部署指南

&#x1f525;&#x1f525; AllData大数据产品是可定义数据中台&#xff0c;以数据平台为底座&#xff0c;以数据中台为桥梁&#xff0c;以机器学习平台为中层框架&#xff0c;以大模型应用为上游产品&#xff0c;提供全链路数字化解决方案。 ✨杭州奥零数据科技官网&#xf…

合规型区块链RWA系统解决方案报告——机构资产数字化的终极武器

&#xff08;跨境金融科技解决方案白皮书&#xff09; 一、直击机构客户四大痛点 痛点传统方案缺陷我们的破局点✖️ 跨境资产流动性差结算周期30天&#xff0c;摩擦成本超8%▶️ 724h全球实时交易&#xff08;速度提升90%&#xff09;✖️ 合规成本飙升KYC/AML人工审核占成本…

探索阿里云容器:解锁云原生应用的无限可能

引言&#xff1a;容器时代的开启 在数字化浪潮汹涌澎湃的当下&#xff0c;云计算已成为企业创新与发展的关键驱动力。从早期的基础设施即服务&#xff08;IaaS&#xff09;&#xff0c;到如今蓬勃发展的平台即服务&#xff08;PaaS&#xff09;和软件即服务&#xff08;SaaS&a…

spring-ai 1.0.0 (1)模型调用能力

听说1.0是一个非常好用的版本&#xff0c;最后还是扛不住听说的压力&#xff0c;为了落实自己悬浮心理&#xff0c;自己还是着手实践一下了。 第一步pom集成&#xff1a; 参考spring-projects/spring-ai | DeepWiki维基以及官方文档入门 &#xff1a;&#xff1a; Spring AI …

数据分享:汽车行业-汽车属性数据集

说明&#xff1a;如需数据可以直接到文章最后关注获取。 1.数据背景 Automobile数据集源自于对汽车市场深入研究的需求&#xff0c;旨在为汽车行业提供一个全面且详细的资源&#xff0c;以便更好地理解影响汽车价格及性能的各种因素。该数据集最初由卡内基梅隆大学&#x…

C++ 第三阶段:语言改进 - 第四节:nullptr vs NULL

目录 一、背景与概述 二、NULL 的定义与问题 1. NULL 的定义 2. NULL 的问题 三、nullptr 的定义与优势 1. nullptr 的定义 2. nullptr 的优势 四、nullptr 与 NULL 的对比 五、实际应用场景 1. 初始化指针 2. 函数调用与重载 3. 条件判断 4. 模板与泛型编程 六、…

计算机存储器容量扩展设计实例解析

存储器容量扩充是《计算机组成原理》课程的重要知识点。讲解一个例题&#xff0c;以说明进行存储器容量扩充设计的方法。 题目&#xff1a;在32位计算机系统中&#xff0c;用8K16位的SRAM芯片组成一个64KB的存储器&#xff0c;已知起始地址为&#xff1a;6000 0000H。已知&…

转载-秒杀系统—1.架构设计和方案简介

转载&#xff1a; https://mp.weixin.qq.com/s?__bizMzg5MzY5NDM3MQ&mid2247490866&idx1&sn0081517454680c85e0ed23eda4e82df5&chksmc02ba5fef75c2ce8b0c7f54182f3bda539230c75d2d75ed2b514b93decc0ff0c5de548a35dc3&cur_album_id3548464749150224391&…

Kubernetes中的容器生命周期回调

在介绍Kubernetes容器生命周期回调前&#xff0c;展示一个案例。 有个私有化部署的项目需要跑一个redis用作缓存&#xff0c;因redis中的数据不需要持久化&#xff0c;选择在Kubernetes中通过deployment的方式部署&#xff0c;下面是deployment的代码片段&#xff0c; ......…

基于STM32的工业仓库环境智能监控系统设计

文章目录 一、前言1.1 项目介绍【1】项目开发背景【2】设计实现的功能【3】项目硬件模块组成【4】设计意义【5】市面上同类产品研究现状【6】摘要 1.2 设计思路1.3 系统功能总结1.4 开发工具的选择【1】设备端开发【2】上位机开发 1.5 模块的技术详情介绍【1】ESP8266-WIFI模块…

如何在 Manjaro Linux 上启用 AUR 仓库来安装软件包

Manjaro 是基于 Arch 的系统&#xff0c;是了解和学习 Arch Linux 命令的绝佳方式。它自带所有流行的桌面环境界面&#xff0c;无论是 XFCE 还是 Gnome 的爱好者&#xff0c;都可以在 Manjaro 中直接使用。 Manjaro 或 Arch Linux 的默认软件包管理器是 Pacman&#xff0c;我们…

有限上升时间信号的反射波形

有限上升时间信号的反射波形: 从上一节讨论中我们知道&#xff0c;阻抗不连续的点处&#xff0c;反射信号是入射信号的一个副本&#xff0c;并讨论了上升时间为0的信号的反射情况。这些规律对于上升时间不为0的信号同样适用&#xff0c;只不过入射信号和反射信号的叠加稍稍复杂…

Vue 3.4+ defineModel 全面详解 + 实战最佳实践

&#x1f31f; 前言&#xff1a;为什么要关注 defineModel&#xff1f; 过去我们在 Vue 组件中使用 v-model 时&#xff0c;常需要这样写&#xff1a; // 子组件 defineProps([modelValue]) defineEmits([update:modelValue])function update(val) {emit(update:modelValue, …

MySQL事物隔离级别详解

目录 事物隔离级别总结 实际情况演示 脏读&#xff08;未提交&#xff09; 避免脏读&#xff08;读已提交&#xff09; 不可重复读 可重复读 幻读 事物隔离级别总结 SQL标准定义了四种事物隔离级别&#xff0c;用来平衡事物的隔离性&#xff08;Isolation&#xff09;和…

【安卓开发】Kotlin入门教程

一、Kotlin 基础入门 1.1 Kotlin 简介 Kotlin 是一种由 JetBrains 开发的静态类型编程语言&#xff0c;运行在 Java 虚拟机上&#xff0c;也可以编译为 JavaScript 或原生代码。它于 2017 年被 Google 宣布为 Android 官方开发语言。 主要特点&#xff1a; 简洁&#xff1a;…

工业机器人保护气体节约方法

焊接在现代工业生产中作为一项关键技术&#xff0c;其效率和质量直接影响着产品的最终性能和生产成本。随着智能制造的不断推进&#xff0c;工业焊接机器人在自动化生产线中扮演着越来越重要的角色。焊接过程中的气体调节一直是一个技术难题&#xff0c;它直接关系到焊接质量的…