android framework之Applicataion启动流程分析(四)

本文主要学习并了解Application的Activity启动流程。


这边先分析一下Launcher是如何启动进程的Acitivity流程。从Launcher启动Acitivity的时候,它是把启动任务丢给instrumentation模块去协助完成,由它进一步调用AMS的startActivity()方法
去启动(Binder跨 进程调用),从调用启动startActivity()开始到Activity启动调用onResume(),大约经历非常多个步骤。

过一下启动Activity的大致分析(假设以ActivityA->ActivityB):

  • 1.Activity参数解析 (存在于AMS:systemserver进程)

Activity的启动流程是使用Intent的方式,作为参数传入,因此启动Activity的初始重要步骤是需要解析Activity需要的启动参数。

  • 2.Activity的管理(存在于AMS:systemserver进程)

在Framework中Activity是由谁管理的?其实是由activityStack进行管理,但在AMS服务中,它对应的代表是ActivityRecord,也就是说在ActivityStack中管理的对象的ActivityRecord,所以启动activity时,需要借助activityStack,由它去管理与启动activity.比如activityA->activityB时,B启动时,一定会影响A的生命周期,这些都需要由ActivityStack去管理。

  • 3 进程间通信

前面2小点是在AMS中处理,它属于SystemServer进程,但是对于activity的很多工作,需要由Application进程去操作 ,这就涉及到了IBinder进程间通信

  • 4.application的处理

在AMS中,将对应的Application的生命周期的所有事务都会在activityStackSupervisor类中被封装成一个clientTransaction的触发事件,它包含了操作Application activity的一系列的命令,然后借助Binder,将clientTransaction传递给Application,Application拿到命令后,去执行Application Activity的生命周期。

  • 5 Activity-A生命周期stop的执行
     

  • 下面先来学习Activity参数的解析(即上面说的第一阶段:Activity参数解析)

分析过程依据下面的流程进行,

                                                                图1

instrumentation跨进程调用ATMS的startActivity(), ATMS会调用自己的startActivityAsUser(),这是按用户的需求启动activity,这个接口里会去解析
用户的参数需求,
ATMS->startActivityAsUser()://1 理解ActivityStarter类:这是一个重要的类,专门负责activity的启动时的参数解析//2 理解ActivityStartController类:负责生产ActivityStarter对象,由controller创建//3 ActivityStartController在ATMS中的initialize()中被创建:mActivityStartController = new ActivityStartController(this);ATMS里只有一个startController对象,利用它来辅助ATMS来管理starter对象,每个activity对应一个activityStart.-->getActivityStartController().obtainstarter(intent, "startActivityAsUser")....()..().execute(); //会初始化Request内部类对象-->ActivityStartController.java->obtainStarter():-->return mFactory.obtain().setIntent().setReason();-->ActivityStarter.java->execute():-->//插入补充内容: ActivityStarter内部有一个内部类Request:由上面设置,存放activity启动过程中的交互性参数。//如A->B,需要记录A作为caller,caller是IAPPlicationThread对象,是谁启动了B,这个是要记录的。//这里面保存着启动actity启动所需要的各类参数配置,非常重要。executeRequest(mRequest); //利用Request配置好的参数去执行。-->在里面开始解读Request中的各类参数,并进行一系列的参数判断,然后会调用下面这个关键函数//Activity在AMS里的存在代表是ActivityRecord,它里面包含着activity的所有属性final ActivityRecord r = new ActivityRecord();//创建要启动的目标activityRecord对象(即ActivityB),存放到传入数组0索引上mController.doPendingActivityLaunches(false);//启动那些需要启动,但是一直没来得及启动的Activitys.-->starter.startResolvedActivity();-->startActivityUnchecked(); //启动等待的activity...startActivityUnchecked();//启动最后需要启动的activity,也就是当前Activity.--> startActivityInner(); //进一步深入分析,可以感受到starter的价值。-->setInitialState();//初始化配置,mStartActivity,mLaunchMode等computerLauncherTaskFlags();//计算要启动的activity flag标志,也就是计算启动模式。computerSourceStack();//计算源Activity所在的栈。mIntent.setFlags(mLaunchFlags);//LaunchFlags设置给Intent,也就是设置启动模式。//...这里有一个细节,如果是Launcher启动activity时,需要能让用户感知已经在启动,所以在这加了黑白屏操作//如上图所示。mTargetStack.startActivityLocked(mStartActivity,...); //开始启动//需要注意,此时我们要启动的Activity对应的Application进程并未启动,但Activity必须依附到进程上。//RootWindowContainer:窗口容器:定义了windows的存储的层次结构,设备上所有的窗口(window),显示(Disply)都是由它管理的,//每个activity均对应一个windows,每个windows的显示、隐藏与activity的生命周期有关,//所以启动activity时走到了RootWindowContainer这个类里面mRootWindowContainer.resumeFocusedStacksTopActivities();-->RootWindowContainer.java:会恢复对应任务栈顶部的Activity.即将处于栈顶的activity进行恢复,//在启动activity时,就是往这个栈的栈顶上去添加一个新的activity,这边涉及到activity的显示过程。focusedStack.resumeTopActivityUncheckedLocked(); //走到这步就意味着走到了前面文章刚开始说的第2阶段。

  • 下面开始分析第二阶段的工作任务:整体 流程如下图所示

                                                        图2.

前面说过,解读参数的过程中会创建ActivityRecord,最后会走到RootWindowContainer容器类,由它去调用resumeTopActivityUncheckedLocked,进入ActivityStack
类的中去处理,这个是第二阶段,下面来分析下ActivityStack如何去管理  ActivityRecord,管理它的启动的。
ActivityStack.java->resumeTopActivityUncheckedLocked():-->resumeTopActivityInnerLocked(): //管理栈的接口-->if(!hasRunningActivity)://不存在 正在运行的activity的时候return resumeNextFocusableActivityWhenStackIsEmpty();//当A->B时,A要先进入onPauseif(mResumedActivity != NULL){pausing |= startPausingLocked();//停止当前Activity的生命周期-->ActivityStack.java:startPausingLocked()mAtmService.getLifeCycleManager().scheduleTransaction(.., PauseActivityItem.obtain() ); //通过它来停止}//next就是要启动的这个ActivityRecord,它是一个全新的ActivityRecord,所以这里的返回值是false.ActivityRecord next = TopRunningActivity(true);if(next.attachedToProcess()) //需要与Application进程关联,但 由于它是刚创建 ,application进程创建消息还未发送,所以这边是false-->return hasProcess() && app.hasThread();-->return app != NULL; //此时app==NULLelse{ //所以会走这边,这里要注意一下//启动指定的Activity. 从这开始ActivityStackSupervisor类开始登场。它用于管理ActivityStack的类,是ActivityStack的主管。//ActivityStackSupervisor类需要封装clientTransaction事件-->传给 app进程mStackSupervisor.startSpecificActivity(next, true,true);-->//这里进入了创建app进程的起点了。可以参考前面的这篇文章《android framework之Applicataion启动流程分析》if(wpc != null && wpc.hasThread() ) { //进程如果已经创建的话,直接启动Activity.我们现在看这边realStartActivityLocked(r,wpc, andResume=true, checkConfig);--> //创建ClientTransaction对象,这个类extends Parcelable:因为它代表activity生命周期要执行的事件,//这个事件的发送目标是ActivityThread(APP进程), 由ActivityThread收到事件后进一步处理,所以//这边涉及到了跨进程通信,binder跨进程通信的数据必须继承Parcelable数据类型实现序列化。//这个类中主要包含三个内容:1. private List<ClientTransactionItem> mActivityCallbacks; //activity的回调:如onstart,onresume,onpause等2. 此变量定义了activity生命周期的最终状态(执行完一系列事件后的最终状态):private ActivityLifecycleItem mLifecyclesStateRequest.3. 跨进程的目标进程:IApplicationThread mClient; //clientTransaction的发送目标。4. 目标进程对应关联的Activity: private IBinder mActivityToken;final ClientTransaction clientTransaction  = ClientTransaction.obtain(proc.getThread(), r.apptoken);//添加LauncherActivityItem.why?clientTransaction.addCallback(LauncherActivityItem.obtain(new Intend(r.intent),... ));if(andResume)//最终的状态lifecycleItem =  ResumeActivityItem.obtain()elselifecycleItem = PauseActivityItem.obtain();clientTransaction.setLiftcycleStateRequest(liftcycleItem); //设置ResumeActivityItem到事件中。mService.getLiftcycleManager.scheduleTransaction(clientTransaction);//获取生命周期管理 类,并执行事务。}else{ //如果APP进程不存在mService.startProcessAsync(); //进入创建进程的流程。 这边的流程分支已经分析过了}}

  • 下面开始分析第三阶段的工作任务(进程间通信):整体 流程如下图所示

前面第二阶段中,将mService.getLiftcycleManager.scheduleTransaction(clientTransaction);代码执行完就,就开始进入第三阶段的处理了。

                                                                图3

接着前面的代码进行分析,当拿到LifecycleManager对象之后,继续调用它的方法scheduleTransaction(), 进入代码:

mService.getLiftcycleManager.scheduleTransaction(clientTransaction);
mService是ATMS对象mService.getLiftcycleManager():它返回的是ClientLifecycleManager,它的初始化也是在ATMS的构造函数中,
ActivityTaskManagerService(Context context)mLifecycleManager = new ClientLifecycleManager(); //28之后添加的对activity lifecycle的触发器scheduleTransaction()://这个是执行  mLifecycleManager对象中的方法-->//当前的进程还是存在于AMS所在的进程中。transaction.schedule(); //transaction是ClientTransaction对象-->会进入ClientTransaction.java->schedule(); //这个会完成一次跨进程通信-->把this(自己)传递进入:就是ClientTransaction对象-->mClient.scheduleTransaction(this); //通过mClient(目标进程的Binder)也就是IApplicationThread跨进程调用到应用进程

接着,现在源码转至APP进程的ActivityThread.java->scheduleTransaction(this):

  -->ActivityThread.this.scheduleTransaction(transaction); -->先来了解下ActivityThread extends ClientTransactionHandler,后者是ActivityThread的父类,-->这里它调用的是父类ClientTransactionHandler中的方法scheduleTransaction://下面调用发送消息所在的线程不是主线程,它是在ApplicationThread线程中运行的,就是上面分析的mClient对象,它是IBinder对象,属于binder中的ApplicationThread对象-->在里面调用了sendManager(ActivityThread.H.EXECUTE_TRANSACTION, transaction); //发消息给主线程:ActivityThread的Handler-->ActivityThread的handleMessage():-->ClientTransaction transaction = (ClientTransaction)msg.obj; //所以这个消息的传递总的来说,先跨进程,再跨线程,将封装的transaction传递过来-->mTransactionExecutor.execute(transaction); //使用主线程中的触发器去执行transaction-->//这里就进入到activity生命周期的触发管理执行

总结下第三阶段干的任务:上图3中的ClientTransaction流程中,会进行一次跨进程调用,进入到app的进程,在主进程中会调用Binder线程IAppicationThread中的方法(它是app的子线程),就是在ClientTransactionHandler中会向主线程发送消息,这里又进行了一次子线程向主线程的消息传递过程,主线程在处理的时候,就会调用真正的执行触发器   mTransactionExecutor 去执行对应的transaction,触发生命周期的管理。
             

  •   下面分析Acitivity生命周期触发器执行阶段

/第四阶段:
mTransactionExecutor.execute(transaction); //上接第三阶段结束-->executeCallbacks(transaction);-->执行的是LauncherActivityItem: //在第二阶段中有添加这个callbackint size = callbacks.size();for(int i = 0; i < size; ++i){ //从callback数组中取出itemClientTransactionItem item = callback.get(i);//调用launchActivityItem的execute方法item.execute(mTransactionHandler, token, mPendingActtions);-->进入launchActivityItem.java->execute://launcher启动activity时,还没有ActivityClientRecord,需要构建一个。//这个是ActivityThread的静态内部类:ActivityClientRecord:处理client端事件的记录,启动过程中会使用-->ActivityClientRecord r = new ActivityClientRecord();client.handleLauncherActivity(r, pendingActions); //client:ClientTransactionHandler.java//client唯一的实现类就是ActivityThread(继承ClientTransactionHandler)//所以将会进入到ActivityThread.java->handleLauncherActivity():-->final activity a = performLauncherActivity(r, customIntent);-->构建一个activityactivity = mInstrumentation.newActivity();//loadedAPk 构建makeApplication对象Application app = r.packageInfo.makeApplication(false, mInstrumentation);-->如果Application已经存在,直接return,保证单例if(mApplication != null){return mApplication; //在前面分析的bindApplication()中实际上已经创建了Application.}//在这个方法中创建Activity的PhoneWindow, 并绑定对应的WindowManager.activity.attach();-->mWindow = new PhoneWindow(); //这里开始就进入了wms....//执行onCreate生命周期mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);r.setState(ON_CREATE); //设置mLifecycleState成CREATE状态。 //这很关键}executeLifecycleState(transaction);-->获取ActivityLifecycleItem,这里取的是之前添加的ResumeActivityItemActivityLifecycleItem  lifecycleItem = transaction.getLifecycleStateRequest();...//ResumeActivityItem的getTargetState 是ON_RESUME = 3cycleToPath(r, lifecycleItem.getTargetState(), true, transaction);-->start =  r.getLifecycleState(); //这里的start是on_Create状态 = 1-->下面的path:包括ON_START和ON_RESUME-->这里是Activity执行onStart的函数的关键所在-->//mHelper是TransactionExecutorHelper类对象,finish=lifecycleItem.getTargetState(), = ON_RESUME=3-->IntArray path = mHelper.getLifecyclePath(start,finish,excludeLastState); //start = 1, finish = 3-->if(finish > start) //3> 1{if(start == ON_START && finish == ON_STOP){}else{ //走下面这个分支for(int i = start +1; i <= finish; ++i){mLifecycleSequence.add(i); //把ON_START和ON_RESUME添加到mLifecycleSequence中 。加入2和3}}}//excludeLastState = true;所以进入分支移除ON_RESUMEif(excludeLastState && mmLifecycleSequence.size()!=0){mLifecycleSequence.remove(mLifecycleSequence.size() -1);//移除ON_RESUME;}return mLifecycleSequence; //里面只有ON_START=2这个状态了//执行paht中相关的生命周期函数performLifecycleSequence(r,path,transaction);-->int size = path.size(); //只有一个ON_STARTfor(i= 0; i < size; ++i){state = path.get(i);switch(state){case ON_START:mTransactionHanlder.handleStartActivity(r.token, mPendingActions);-->触发进行Activity的onStart流程。//startactivity.performStart("handleStartActivity");r.setState(ON_START);break;}}//执行ResumeActivityItem的executelifecycleItem.execute();//lifecycleItem = ResumeActivityItem-->进入ResumeActivityItem.java->execute():client.handleResumeActivity(); //把事件丢给ActivityThread去执行。lifecycleItem.postExecute(r,path,...);//上接:  client.handleResumeActivity(); //把事件丢给ActivityThread去执行。
ActivityThread->handleResumeActivity():-->r.activity.performResume(r.startsNotResume, reason);r.setState(ON_RESUME);

总结:
LaunchActivityItem:
    launch():
      onCreate();

    触发器的计算触发onStart();生命周期

最后是ResumeAtivityItem->onResume(); 这就包括了完整的生命周期过程,从onCrate->onStart->onStart();

  • 第五阶段:activity的Stop阶段

下面分析:当Activity A--->Activity B时,B的启动和A的Stop处理如何操作?主要是A的Stop阶段如何进行?这个就是第5阶段,还是从ActivityThread的handleResumeActivity()中去分析,

大致由下图流程:

ActivityThread->handleResumeActivity():-->ActivityClientRecord r = performResumeActivity(); //执行resume生命周期...//显示UIwm.addView(decor,l);//调用windowsManagerImpl将decorView添加到window中 WMS阶段去研究吧Looper.myQueue().addIdleHandler(new Idler()) ;//往 looper中添加Idler事件//这个Idler中包括了一个相当重要的事务,包含了一个与AMS的通信。//APP主线程当没有任何消息事务需要处理时,会去处理Idler的MessageQueue
class Idler implements MessageQueue.IdleHandler{public bool queueIdle(){IActivityManager am = ActivityManager.getService();do{am.activityIdle(a.token, a.createdConfig, stopProfiling);prev = a;a = a.nextIdle;prev.nextIdle = null;}(a != NULL); //执行这里,说明主线程是空闲状态。}
}

请看上图的流程中,ASS的第4步,会处理processStoppingAndFinishingActivities();
也就是说,当ActivityThread主线程空闲时,会执行activityIdle(),接着会通过ATMS调用ASS有activityIdleInternal(),然后会执行Stop Activity相关的操作,也就是ActivityA->ActivityB过程时,会先启动B,然后在空闲时,再处理A的Stop事件,就是在这里处理的。

所以先来看看ATMS->activityIdle():
 

ATMS->activityIdle()://mStackSupervisor是对activity生命周期事件进行封装的模块。帮助activityStatemStackSupervisor.activityIdleInternal();-->processStoppingAndFinishingActivities(); //需要停止需要终止的activity都在这里面处理。-->for(int i = mStoppingActivies.size()-1; i >=0; --i){ //需要停止的遍历一遍...readyToStopActivities.add(s);mStoppingActivies.remove(i);}-->for(int i = 0; i < numReadyStops; i++){ActivityRecord r = readyToStopActivities.get(i);if(r.finishing()){r.destroyIfPossible(reason);}else{r.stopIfPossible(); //我们stop走这边-->mATmService.getLifecycleManager().scheduleTransaction(); //把stop事件封装成一个Transaction事务。-->final ClientTransaction clientTransaction = transactionWithState(); //封装scheduleTransaction(clientTransaction);//调用,跨进程,传给application.}}

注意:在android12中,activityStack和ActivityStack已经不存在了,改成了Task.java,启动过程全部转接到Task.java中处理。总体的思路不变。

在Stop事件封装成Transaction后,发给Application线程处理,然后会进入到第4阶段的Activity生命周处理处理流程,只不过可能是Launcher进程(另外一个进程),当然如果是相同进程,则由本进程去处理。只不过对于Stop或Finish等事件,它的优先级不高,所以基本都会等到主线程不繁忙空间的时候去执行清理操作。

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

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

相关文章

怎么处理zk或redis脑裂

很极端场景会出现脑裂 什么是分布式的脑裂 怎么理解zk脑裂 就是ZK&#xff0c;与客户端可能因为网络原因&#xff0c;客户端A还在跑着后续程序&#xff0c;而zk与客户端之前的心跳断了&#xff0c;此zk就把这节点给删除了&#xff0c;这时另一个客户会加锁成功&#xff0c;就样…

荣耀9x使用体验

第一次使用鸿蒙系统&#xff0c;感觉还行&#xff0c;虽然各种操作和手势不太习惯&#xff0c;但是不影响什么&#xff0c;这是已经发布了4年的手机&#xff0c;用起来没什么毛病&#xff0c;各方面比较均衡。 2年前买的&#xff0c;原价1500块&#xff0c;现在&#xff08;20…

Unity 之利用Audio Source(音频源)组件用于播放声音

文章目录 Unity中的Audio Source&#xff08;音频源&#xff09;是一个用于播放声音的组件&#xff0c;通常附加到游戏对象上&#xff0c;以便在游戏中播放音频效果、音乐或对话。以下是Audio Source的详细介绍&#xff1a; 添加Audio Source&#xff1a; 要在Unity中使用Audio…

SAM论文翻译

文章目录 Abstract1、Introduction2、Related Work3、Methodology3.1、Semantic Graph3.2、Semantic Aware Module3.3、Decoder3.4、Loss Function 4、Experiments4.1、Datasets4.2、Implementation Details4.3、Evaluation Protocol4.4、Comparison with State-of-the-Art 论文…

STM32WB55开发(1)----套件概述

STM32WB55开发----1.套件概述 所用器件视频教学样品申请优势支持协议系统控制和生态系统访问功能示意图系统框图跳线设置开发板原理图 所用器件 所使用的器件是我们自行设计的开发板&#xff0c;该开发板是基于 STM32WB55 系列微控制器所构建。STM32WBXX_VFQFPN68 不仅是一款评…

Win10右键 nvidia rtx desktop manager 怎么删除(最新)

在更新了最新的 nvidia后原来的隐藏鼠标右键菜单后不行了&#xff0c;新方法如下&#xff1a; 步骤一&#xff1a;在键盘“WINR”键同时操作下&#xff0c;启动运行框&#xff0c;在框内输入“regedit”&#xff0c;打开深度系统win7 的注册表编辑器。 步骤二&#xff1a;为防…

maven配置nexus私服详解

maven配置nexus私服详解 简介&#xff1a;配置步骤1、本地maven settings.xml配置1.1配置本地仓库位置1.2 server配置1.3 镜像配置1.4 私服仓库配置 2、maven项目pom.xml配置 完整配置模板 简介&#xff1a; 前提是已经搭建好了私服&#xff0c;我们需要在本地maven中配置相关…

半导体厂务液体泄漏问题的挑战与解决方案

在半导体制造领域&#xff0c;液体泄漏是一项极具挑战性的问题。半导体工厂内有着大量的化学品、工艺液体和废水系统&#xff0c;这些液体在制造过程中扮演着至关重要的角色。然而&#xff0c;液体泄漏可能会导致严重的生产中断、环境污染和安全风险。本文将探讨半导体厂务中的…

Qt 5.15编译(MinGW)及集成Crypto++ 8.7.0笔记

一、背景 为使用AES加密库&#xff08;AES/CBC加解密&#xff09;&#xff0c;选用Crypto 库&#xff08;官网&#xff09;。   最新Crypto C库依次为&#xff1a;8.8.0版本&#xff08;2023-6-25&#xff09;、8.7.0&#xff08;2022-8-7&#xff09;和8.6.0&#xff08;202…

c++ day 2

1、封装一个结构体&#xff0c;结构体中包含一个私有数组&#xff0c;用来存放学生的成绩&#xff0c;包含一个私有变量&#xff0c;用来记录学生个数&#xff0c; 提供一个公有成员函数&#xff0c;void setNum(int num)用于设置学生个数 提供一个公有成员函数&#xff1a;v…

新能源商用车软件开发设计规范

目 录 前 言.............................................................................................................. 1 1 范围............................................................................................................... 2 2 规范性…

【前端demo】CSS border-radius可视化 原生实现

文章目录 效果原理代码 前端demo系列目录&#xff1a;https://blog.csdn.net/karshey/article/details/132585901 效果 效果预览&#xff1a;https://codepen.io/karshey/pen/zYyBPBR 参考&#xff1a; Fancy Border Radius Generator (9elements.github.io) https://borde…

zabbix 自动发现

哈喽大家好&#xff0c;我是咸鱼 昨天老大让我初始化一批服务器&#xff0c;吭哧吭哧弄完之后需要把这批机器添加到 zabbix 上去 但是我发现一台一台添加效率好低&#xff0c;而且特别繁琐&#xff0c;当时我没有想出有什么好的方法&#xff0c;今天上网搜了一下相关资料之后…

本地电脑搭建web服务器、个人博客网站并发布公网访问 【无公网IP】(1)

文章目录 前言1. 安装套件软件2. 创建网页运行环境 指定网页输出的端口号3. 让WordPress在所需环境中安装并运行 生成网页4. “装修”个人网站5. 将位于本地电脑上的网页发布到公共互联网上 前言 在现代社会&#xff0c;网络已经成为我们生活离不开的必需品&#xff0c;而纷繁…

系统学习Linux-ELK日志收集系统

ELK日志收集系统集群实验 实验环境 角色主机名IP接口httpd192.168.31.50ens33node1192.168.31.51ens33noed2192.168.31.53ens33 环境配置 设置各个主机的ip地址为拓扑中的静态ip&#xff0c;并修改主机名 #httpd [rootlocalhost ~]# hostnamectl set-hostname httpd [root…

【opencv】多版本安装

安装opencv3.2.0以及对应的付费模块 一、安装多版本OpenCV如何切换 按照如下步骤安装的OpenCV&#xff0c;在CMakeLists.txt文件中&#xff0c;直接指定opencv的版本就可以找到相应版本的OpenCV&#xff0c;为了验证可以在CMakeLists.txt文件中使用如下指令输出版本验证&…

C#写一个UDP程序判断延迟并运行在Centos上

服务端 using System.Net.Sockets; using System.Net;int serverPort 50001; Socket server; EndPoint client new IPEndPoint(IPAddress.Any, 0);//用来保存发送方的ip和端口号CreateSocket();void CreateSocket() {server new Socket(AddressFamily.InterNetwork, SocketT…

SolVES4.1学习1——安装与使用教程

1、下载并安装 SolVES 4版本是QGIS插件&#xff0c;但实际使用过程中发现在最新版的QGIS安装该插件过程中&#xff0c;会报错或异常。因此需安装特定版本的软件。共需安装如下图软件及Java环境等。 根据官方文档安装好后&#xff0c;可以进行相关操作。 2、设置QGIS环境 QG…

消息队列理解

rocketMQ RocketMQ消息存储原理_码上得天下的博客-CSDN博客 领域模型概述 | RocketMQ kafka Kafka基本架构介绍-腾讯云开发者社区-腾讯云 看完这篇Kafka&#xff0c;你也许就会了Kafka_心的步伐的博客-CSDN博客 Apache Kafka

PaddleX:一站式、全流程、高效率的飞桨AI套件

随着ChatGPT引领的AI破圈&#xff0c;各行各业掀起了AI落地的潮流&#xff0c;从智能客服、智能写作、智能监控&#xff0c;到智能医疗、智能家居、智能金融、智能农业&#xff0c;谁能快速将AI与传统业务相结合&#xff0c;谁就将成为企业数字化和智能化变革的优胜者。然而&am…