replugin宿主与插件通信小结

news2024/11/9 10:04:38

近来replugin开发中遇到宿主和插件间需要通信的情形,思来只有进程间通信(IPC)才是比较好的宿主与插件的通信方式。而Android进程间通信主要有2种方式:Messenger和AIDL。

AIDL(Android Interface Definition Language)是Android接口定义语言。AIDL设计用于实现Android进程间通信。Android中每个进程都拥有自己的Dalvik虚拟机(4.4之后是ART虚拟机),以及独立的内存区域,通过AIDL制定的规则,使不同进程可以进行数据交流。

宿主与插件间通信又可细分为,插件主动向宿主发起通信以及宿主主动向插件发起通信2种情况。为了模拟这2中可能的情况,同时对加深对Messenger和AIDL的理解。在宿主和插件中分别使用这两种方式,宿主作为服务端使用Messenger通信,以及插件作为服务端使用AIDL进行通信,分别查看2种方式的使用效果。

一、使用Messenger

1.1 Messenger服务端

1)服务端定义一个Service

<service android:name=".PluginComService"    android:enabled="true"    android:exported="true">  
<intent-filter>        
<action android:name="com.hk.daijun.messengerclient"></action>        
<category android:name="android.intent.category.DEFAULT" /> 
</intent-filter>

 在清单文件中配置Service,exported要配置为true,否则别的进程不能绑定该Service,在intent-filter中设置action。

2) 创建Messenger和Handler实例

创建Messenger和Handler实例,通过Handler来处理消息。

private static class MessengerHandler extends Handler {    
@Override    public void handleMessage(Message msg) {        Log.e(TAG, "handleMessage: what="+msg.what); 
  switch (msg.what) {            case 1001:                
  Log.e(TAG, "handleMessage: 服务端收到消息"+msg.getData().getString("msg"));                
  Log.e(TAG, "handleMessage: 服务端收到消息"+msg.getData().getString("msgJson"));                
  String msgJson = msg.getData().getString("msgJson");               
  Gson gson = new Gson();                
  PushMsgContent pushMsgContent = gson.fromJson(msgJson,PushMsgContent.class);         
  if (pushMsgContent != null) {                    
    Log.e(TAG, "handleMessage: PushMsgContent="+pushMsgContent.sendTime+"  "+pushMsgContent.msgContent.alarmTime); 
  }                
  Messenger mesgerReply = msg.replyTo;                
  Message msgReply = Message.obtain();                
  Bundle bundle = new Bundle();                
  bundle.putString("service_reply","hello,服务端已收到信息,想做什么");                
  msgReply.what = 2001;                
  msgReply.setData(bundle); 
  try {                    
    mesgerReply.send(msgReply);                
  } catch (RemoteException e) {                    
    Log.e(TAG, "handleMessage: 消息发给客户端,发送失败");                   
   e.printStackTrace();                
}                
break; 
}        
super.handleMessage(msg);    
}}
private Messenger messenger = new Messenger(new MessengerHandler());

 

消息传递时可以考虑使用字符串,然后服务端和客户端都用Gson进行解析,因为宿主是第三方开发,插件是自己开发,省去频繁变更接口的麻烦。

3)重写onBind方法

在Service的onBind方法中,用Messenger创建一个IBinder,Service将IBinder在onBind方法中返回给客户端。

1.2 Messenger客户端

1)创建ServiceConnection,绑定服务

客户端创建ServiceConnection实例,绑定服务端第1步中的Service,action要与服务端app中AndroidManifest中的一致,包名(服务端app包名,即AndroidManifest中查看到的包名)也要一致。

private void connectToService(){        
  Intent intent = new Intent();        
  intent.setAction("com.hk.daijun.messengerclient");  
  intent.setPackage("com.hk.daijun.replugintest");//        
  bindService(intent,serviceConnection,BIND_AUTO_CREATE);  
  RePlugin.getHostContext().bindService(intent,serviceConnection,BIND_AUTO_CREATE);    
}

此处需要主要要用宿主的上下文来绑定服务,如果直接用bindService来启动服务,能启动服务,但解绑时会报错服务未注册。

2)实例化Messenger

服务绑定后,在ServiceConnection中的onServiceConnected方法中,用IBinder来实例化Messenger(引用服务端的Handler),用此Messenger来向服务端发送消息。

3)接收服务端回复

若客户端还想从服务端那边收到回复,则需要再定义一个Messenger,messengerAccept来,在给服务端发送消息时,在Message的replyTo字段中赋值为messengerAccept。

public void sendMessageToPlugin(){    
    Log.e(TAG, "sendMessageToHost: ");    
    Message msg = Message.obtain();    
    msg.what = 1001;    
    msg.arg1 = 1001;    
    msg.replyTo = mesgerAccept;    
    Bundle bundle = new Bundle();    
    bundle.putString("msg","我要与宿主通信");          
    bundle.putString("msgJson",getJsonString());      
    msg.setData(bundle);    
    try {        
        mesgerSend.send(msg);    
    } catch (RemoteException e) {        
        e.printStackTrace();        
        Log.e(TAG, "sendMessageToHost:消息发送异常"+e.getMessage());    
    }
}

 接收服务端的回复,创建的Messenger实例mesgerAccept处理服务端的回复。

private void initAcceptMessenger() {    
mesgerAccept = new Messenger(new Handler(){  
    @Override        
    public void handleMessage(Message msg) {            
        switch (msg.what) {                
        case 2001:                    
        Log.e(TAG, "handleMessage: 收到服务端的回复");                    
        Log.e(TAG, "handleMessage: 
        service_reply="+msg.getData().getString("service_reply"));                    
        break;            
      }            
      super.handleMessage(msg);        
    }    
  });
}

4) 实现效果

首先在宿主app中进入插件,然后在插件中绑定宿主Service,然后发送消息。

 

 

二、使用AIDL

AIDL是一个缩写,全称是Android Interface Definition Language,要使用AIDL,必须创建一个定义编程接口的.aidl文件。AIDL文件可以分为两种。一种是用来定义parcelable对象,以供其他AIDL文件使用AIDL中非默认支持的数据类型的。一类是用来定义方法接口,以供系统使用来完成跨进程通信的。

AIDL支持的数据类型,包括Java中八种基本数据类型:byte、short、int、long、double、boolean、char,以及String、List、Map,集合里边的所有元素也必须是AIDL支持的数据类型(包括支持的Java数据类型以及自定义的数据类型)。List可以使用泛型、Map不支持泛型。

自定义数据类型,必须实现parcelable接口,还要注意定向tag。定向tag包括三种:in、out、inout。AIDL中的定向 tag 表示了在跨进程通信中数据的流向,其中 in 表示数据只能由客户端流向服务端, out 表示数据只能由服务端流向客户端,而 inout 则表示数据可在服务端与客户端之间双向流通。一般通常in就够了。

2.1 客户端(宿主)

1)新建自定义数据类型java bean

其中writeToParcel方法即表示支持定向tag为in,而如果要支持为 out 或者 inout 的定向 tag 的话,还需要实现 readFromParcel()方法,此方法不在Parcelable接口中,需要自己手写。

可以看到自定义的数据类型要实现parcelable序列化接口。因为进程是系统进行资源分配和调度的基本单位,不同的进程有不同的内存区域,java中传递对象一般是传递对象在内存堆上的引用,而由于不同进程间不能访问各自的内存区域,所以传递的引用对方进程是访问不到的。所以要将传递的数据进行序列化。在客户端对这个对象进行序列化的操作,将其中包含的数据转化为序列化流,然后将这个序列化流传输到服务端的内存中去,再在服务端对这个数据流进行反序列化的操作,还原其中包含的数据,从而实现进程间传递数据的目的。

2)增加aidl文件目录

在project视图下,在main目录下新建aidl目录,然后新建package,包名与AlarmMessage的包名路径一致。

3)添加aidl文件

 

添加后,aidl文件没在bean目录下,aidl的包名要和对应的javabean包名一致,手动移动aidl文件到bean包名下,然后修改包名,声明序列化类AlarmMessage。

 

 

4)增加aidl接口文件

增加aidl接口文件内容,这个文件里面主要内容是进程间通信的接口。注意要导入自定义数据类型的包。AIDL默认支持一些数据类型,在使用这些数据类型的时候是不需要导包的,但是除了这些类型之外的数据类型,在使用之前必须导包,就算目标文件与当前正在编写的 .aidl 文件在同一个包下。

配置完成后,将Module plugin1进行build,就会生成AlarmMessageMgr.java文件。

 

2.2 服务端(插件)

以上都是插件工程中的配置,现在要在宿主工程中也进行相应配置。配置过程也是几乎一样,需要将java文件和aidl文件都从插件工程中复制到宿主工程中,并且包路径要一样。

在将aidl文件和java bean复制到宿主工程中后。在插件中新建Service类,此次通信以插件作为服务端,而宿主app作为客户端,宿主app主动发起通信。插件中新建Service,其中AlarmMessage.Stub就是aidl文件写完后,对插件工程进行重新编译后产生java文件,在onBind中返回给客户端,客户端据此来进行通信。

public class PluginAidlService extends Service {
    private static final String TAG = "PluginAidlService";
    private List<AlarmMessage> mMsgList = new ArrayList<>();
    private final AlarmMessageMgr.Stub mAlarmMgr = new AlarmMessageMgr.Stub() {
        @Override
        public List<AlarmMessage> getAlarmMessages() throws RemoteException {
            return mMsgList;
        }
        @Override
        public void addAlarmMessage(AlarmMessage msg) throws RemoteException {
            Log.e(TAG, "plugin service addAlarmMessage: ");
            synchronized (this) {
                if (mMsgList == null) {
                    mMsgList = new ArrayList<>();
                }
                if (msg == null) {
                    Log.e(TAG, "addAlarmMessage: msg to add is null");
                    msg = new AlarmMessage();
                    msg.alarmId = 0;
                    msg.alarmTime = "1997-10-7 12:00:00";
                    msg.alarmTitle = "空报警";
                }
                mMsgList.add(msg);
            }
            int size = mMsgList.size();
            for (int i=0;i<size;i++) {
                AlarmMessage alarmMessage = mMsgList.get(i);
                Log.e(TAG, "aidl plugin1 : "+String.format("alarmTitle=%s alarmTime=%s",alarmMessage.alarmTitle,alarmMessage.alarmTime));
            }
        }
    };

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        Log.e(TAG, "onBind: ");
        return mAlarmMgr;
    }

 而客户端即宿主app通过绑定插件中的PluginAidlService服务来进行通信,需要注意的是,由于这里涉及到宿主app启动插件中的四大组件(Activity、Service、BroadcastReceiver、ContentProvider),所以不能直接使用原生的api来启动插件中的Service,需要调用Replugin中的api来启动。

    private void connectToService(){
        Log.e(TAG, "connectToService: ");
        Intent intent1 = RePlugin.createIntent("plugin1","com.hk.daijun.plugin1.PluginAidlService");
        intent1.setAction("com.hk.daijun.replugintest");
        intent1.setPackage("com.hk.daijun.plugin1");
        try {
            PluginServiceClient.bindService(PluginCommunicateActivity.this, intent1, serviceConnection, BIND_AUTO_CREATE);
        } catch (PluginClientHelper.ShouldCallSystem e) {
            Log.e(TAG, "connectToService: "+e.getMessage());
        }
    }

同样,通过bindService()来启动插件中的服务,取消绑定也需要用Replugin的api来进行Service解绑。

PluginServiceClient.unbindService(PluginCommunicateActivity.this,serviceConnection);

 客户端在Service连接建立后,调用AlarmMessageMgr.Stub.asInterface(service)将服务端返回的Binder对象转换成AIDL接口类型的对象,进而可以调用aidl定义的接口来进行调用服务端的远程方法。

private void initServiceConnection(){
    serviceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            Log.e(TAG, "aidlhost onServiceConnected: ");
            mAlarmMgr = AlarmMessageMgr.Stub.asInterface(service);
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            Log.e(TAG, "aidlhost onServiceDisconnected: ");
        }
    };
}

 插件重新build后,宿主以内置模式接入插件,需要将宿主工程后先clean下,清除build目录,再重新编译安装,否则宿主不先clean的话,插件的修改可能并不生效!

实现效果如下,在宿主中发送报警信息,插件中收到信息后,将所有报警信息打印出来。宿主app中主要是2个按钮,一个绑定插件服务,一个发送消息。

 

还可以模拟插件Service处理完一些逻辑后,如进行网络请求,获取第三方系统账户信息等操作,执行成功后再启动插件中的Activity,需要注意的是,在Service中启动Activity,必须设置FLAG_ACTIVITY_NEW_TASK标签,但FLAG_ACTIVITY_NEW_TASK标签必须配合taskAffinity属性使用,如果不设置taskAffinity属性值,将不会生成新task。所以不设置taskAffinity就不会生成新的task。

三、 AIDL原理

掠下aidl实现进程间通信的主要原理。AIDL的底层是Binder,其本质就是为我们提供了一种快速实现Binder的工具,通过定义AIDL,android studio编译后生成了AlarmMessageMgr.java文件,而原理的关键也就是这个文件,这个文件其实也可以通过手动编写。

public static com.hk.daijun.plugin1.AlarmMessageMgr asInterface(android.os.IBinder obj)
{
if ((obj==null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin!=null)&&(iin instanceof com.hk.daijun.plugin1.AlarmMessageMgr))) {
return ((com.hk.daijun.plugin1.AlarmMessageMgr)iin);
}
return new com.hk.daijun.plugin1.AlarmMessageMgr.Stub.Proxy(obj);
}

 asInteface方法用于将服务器的Binder对象转换成客户端所需的AIDL接口类型的对象,这种转换过程是区分进程的,如果客户端和服务端位于统一进程,那么返回服务器的Stub对象本身,否则返回的是系统封装后的Stub.proxy对象。

然后看下addAlarmMessage(AlarmMessage msg)方法的调用过程,转到方法的实现。

@Override public void addAlarmMessage(com.hk.daijun.plugin1.bean.AlarmMessage msg) throws android.os.RemoteException
{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
if ((msg!=null)) {
_data.writeInt(1);
msg.writeToParcel(_data, 0);
}
else {
_data.writeInt(0);
}
mRemote.transact(Stub.TRANSACTION_addAlarmMessage, _data, _reply, 0);
_reply.readException();
}
finally {
_reply.recycle();
_data.recycle();
}
}

 这个方法由客户端调用,运行在客户端,输入的参数msg定向tag是in,代表数据只能由客户端流向服务端。参数信息写入_data中,然后调用transact方法发起RPC(远程过程调用),mRemote就是Binder对象在binder驱动层对应的引用。transact方法调启后,客户端当前线程被挂起,服务端的onTransact方法会被调用。

@Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException
{
switch (code)
{
case INTERFACE_TRANSACTION:
{
reply.writeString(DESCRIPTOR);
return true;
}
case TRANSACTION_getAlarmMessages:
{
data.enforceInterface(DESCRIPTOR);
java.util.List<com.hk.daijun.plugin1.bean.AlarmMessage> _result = this.getAlarmMessages();
reply.writeNoException();
reply.writeTypedList(_result);
return true;
}
case TRANSACTION_addAlarmMessage:
{
data.enforceInterface(DESCRIPTOR);
com.hk.daijun.plugin1.bean.AlarmMessage _arg0;
if ((0!=data.readInt())) {
_arg0 = com.hk.daijun.plugin1.bean.AlarmMessage.CREATOR.createFromParcel(data);
}
else {
_arg0 = null;
}
this.addAlarmMessage(_arg0);
reply.writeNoException();
return true;
}
}
return super.onTransact(code, data, reply, flags);
}

 onTransact方法中4个参数根据参数code,来确定调用哪个方法,data代表输入参数,reply代表返回值。客户端调用服务端的远程方法跟调用本地方法差不多,客户端发起的请求要挂起直到服务端进程处理完,所以远程方法如果是比较耗时的,客户端最好不要在UI线程中发起这个远程方法调用,而应该放到子线程中进行。而服务端的Binder方法是运行在Binder的线程池中。

四、总结

Messenger是执行进程间通信(IPC)最简单的方式,服务端Messenger通过Handler将客户端的请求放到消息循环中排队,然后逐个取出进行处理,而客户端要接收结果需要2个Messenger,一个用于发送消息,一个用于接收消息。其底层原理也是Binder,客户端得到服务端的Binder对象在binder驱动层对应的mRemote引用,然后给服务器发消息,实现跨进程通信。这种方式缺点很明显,服务端以串行方式处理客户端的消息,消息处理结果反馈并不及时,不适合服务端、客户端经常通信的场景。而用AIDL可以并发处理客户端的远程调用,远程方法调用就像本地调用一样,立即执行后就有结果返回。但Messenger的特点是简单,不需要想AIDL一样,需要定义aidl文件,还需要将aidl文件拷贝到服务端和客户端。由于引入了Replugin,使用Messenger、AIDL启动宿主/插件的Service,启动方式有所区别,使用时需要注意。AIDL的底层是Binder,其本质只是为我们提供了一种快速实现Binder的工具。

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

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

相关文章

矩阵和线性代数的应用

矩阵和线性代数是数学中重要的概念&#xff0c;它们被广泛应用于物理、工程、计算机科学、经济学等众多领域。本文将讨论矩阵和线性代数的一些基本概念以及它们在实际应用中的重要性和影响。 一、矩阵和线性代数的基本概念 矩阵是由数字组成的矩形数组。它可以表示线性方程组…

线程池并发服务器

线程池技术 线程池技术是一种典型的生产者-消费者模型。 线程池技术是指能够保证所创建的任一线程都处于繁忙状态&#xff0c;而不需要频繁地为了某一任务而创建和销毁线程&#xff0c;因为系统在创建和销毁线程时所耗费的cpu资源很大。如果任务很多&#xff0c;频率很高&am…

Android中级——系统信息与安全机制

系统信息与安全机制系统信息获取/system/build.prop/procandroid.os.buildSystemPropertyPackageManagerActivityManagerpackages.xmlpermissions标签package标签perms标签安全机制Apk反编译apktooldex2jarjd-guiApk加密系统信息获取 /system/build.prop 存放一些配置信息&am…

Seaborn 变量分布分析

文章目录一、数据集1.1 下载数据集1.2 字段含义说明1.3 导入数据集二、初步分析2.1 缺失值分布查看2.2 异常值分布查看2.3 查看变量分布三、数值变量分析3.1 replot()&#xff1a;多个变量之间的关联关系3.2 lmplot()/regplot&#xff1a;分析两个变量的线性关系3.3 displot()&…

从前序与中序遍历序列构造二叉树——力扣105

题目描述 法一&#xff09;递归 复杂度分析 代码如下 class Solution { private:unordered_map<int, int> index;public:TreeNode* myBuildTree(const vector<int>& preorder, const vector<int>& inorder, int preorder_left, int preorder_ri…

Qt Quick - StackView

StackView 使用总结一、概述二、在应用中使用StackView三、基本的导航1. push Item2. pop Item3. replace Item四、深度链接五、寻找Item六、转换六、Item的所有权七、大小一、概述 StackView可以与一组相互链接的信息页面一起使用。例如&#xff0c;电子邮件应用程序具有单独…

HTML5 <img> 标签

HTML5 <img> 标签 实例 HTML5 <img>标签用于向网页中添加相关图片。 如何插入图像&#xff1a; <img src"smiley-2.gif" alt"Smiley face" width"42" height"42">尝试一下 &#xff08;更多实例见页面底部&…

基于营销类系统运营活动增长带来的数据库设计演进

一、前言 为了支持业务数据的不断增长&#xff0c;在数据库层面的性能提升主要体现在几个维度&#xff1a;1&#xff09;数据降级&#xff1b;2&#xff09;数据主题分而治之&#xff1b;3&#xff09;实时交易转异步&#xff1b;4&#xff09;硬件扩容&#xff0c;当然网上一…

question

4、Mysql高可用有几种方案&#xff0c;分别有什么特点? 特点优点缺点mysql group replication(MGR)组复制组内一半节点同意即可提交更改操作、最多支持 9 个节点、基于MRG插件、多节点写入支持、故障自动检测、引擎必须为 innodb、必须有主键、binlog 为 row强一致、paxos协议…

Arcgis Engine之打开MXD文档

Arcgis Engine之打开MXD文档概述方法一&#xff1a;方法二&#xff1a;概述 图层加载功能将用到MapControl 控件提供的LoadMxFile 方法。 该方法通过指定的*. Mxd文档路径直接获取 该方法第一个参数是文件路径&#xff0c; 第二个参数是MExd文档中地图的名称或索引&#xff0…

1.初识Earth Engine

Earth Engine平台是一个集科学分析和地理信息可视化的综合性平台&#xff0c;该平台提供丰富的API&#xff0c;以及工具帮助方便查看、计算、处理、分析大范围的各种影像等GIS数据。 基础数据 目前Earth Engine上已由几十PB的影像栅格数据及矢量数据数据地址。数据主要分为以…

Prometheus+Grafana从0到1搭建jvm监控

目 录1. 准备工作2. 添加配置2.1 添加maven依赖2.2 application.properties增加配置2.3 新增配置类2.4 配置Prometheus2.5 配置Grafana3. 小结在上一篇博客《 PrometheusMysqld_exporterGrafana从0到1搭建MySQL的可视化监控》&#xff0c;我们完成了对数据库的可视化监控搭建&a…

都说程序员就是吃青春饭,35岁就会被淘汰,我用自己的经历来告诉你事实

上个假期我回家了&#xff0c;遇到三姑六婆总会问我读研没读、工作怎么样、薪资多少等等问题&#xff0c;相信大家也都遇到过。我一般会用“在做程序员&#xff0c;写代码的这种话”来敷衍他们&#xff0c;但没想到他们懂得还挺多的&#xff0c;又搬出了一套关于程序员的理论&a…

当AI遇上元宇宙:智能科技如何助力虚拟世界的发展?

欢迎来到Hubbleverse &#x1f30d; 关注我们 关注宇宙新鲜事 &#x1f4cc; 预计阅读时长&#xff1a;10分钟 本文仅代表作者个人观点&#xff0c;不代表平台意见&#xff0c;不构成投资建议。 人工智能和元宇宙是21世纪最突出的技术之一。它们各自可以在许多方面提高人们…

MySQL、PostgreSQL、Oracle、SQL Server数据库触发器实现同步数据

数据库触发器是一种在数据库中设置的程序&#xff0c;当满足某些特定条件时&#xff0c;它会自动执行。触发器通常与数据表的操作&#xff08;例如插入、更新和删除&#xff09;相关联&#xff0c;它们可以帮助保证数据的完整性和一致性。在本篇博客中&#xff0c;我将介绍各种…

对SQL注入进行的一些总结

简介 SQL注入作为一种攻击方式最早可以追溯到20世纪90年代中期&#xff0c;当时Web应用程序开始流行并广泛使用数据库作为其后端数据存储。最早的SQL注入攻击是通过简单地在Web表单输入框中输入SQL代码来实现的&#xff0c;攻击者可以通过修改输入参数来篡改数据库查询的行为&a…

Pytorch 容器 - 2. Module的属性访问 modules(), children(), parameters(), buffers()等

目录 1. modules() 和 named_modules() 2. children() 和 named_children() 3. parameters() 和 named_parameters() 4. buffers() 和 named_buffers() Module类内置了很多函数&#xff0c;其中本文主要介绍常用的属性访问函数&#xff0c;包括&#xff1a; modules(), nam…

Spring事务源码-EnableTransactionManagement实现解析

Transactional注解 Transactional是spring中声明式事务管理的注解配置方式。Transactional注解可以帮助我们标注事务开启、提交、者回滚、事务传播、事务隔离、超时时间等操作。 而EnableTransactionManagement是开启Spring 事务的入口。 EnableTransactionManagement 标注启…

什么是数字人?数字人可以应用在哪些行业?

数字人指的是由计算机技术、人工智能技术和大数据技术等多种技术手段构建的一种虚拟的人类形态。数字人通常具备丰富的信息处理能力、模拟能力和学习能力&#xff0c;可以根据人们的需求进行智能化定制服务。 数字人 在很多行业领域&#xff0c;数字人都被广泛应用&#xff0…

【并发编程】ConcurrentHashMap源码分析(一)

ConcurrentHashMap源码分析CHM的使用CHM的存储结构和实现CHM源码put源码分析initTable 初始化tabletreeifyBin()和tryPresize()transfer 扩容和数据迁移高低位的迁移ConcurrentHashMap是一个高性能的&#xff0c;线程安全的HashMapHashTable线程安全&#xff0c;直接在get,put方…