Android Service 启动流程

在早些年学习Android的时候,对Service有过总结,但是主要是如何去使用,注意事项,startService和bindService的区别。

Android Service_public int onstartcommand(intent intent, int flags-CSDN博客

但是今天从源码来总结下framework层的启动流程大致是什么样的。

一、startService()

平时,在我们的activity里,我们通过startService去启动一个service服务。

1.context.startService

使用例子:

//testActivity@Overridepublic void onClick(View v) {Intent it=new Intent(this, SimpleService.class);switch (v.getId()){case R.id.startService:startService(it);break;case R.id.stopService:stopService(it);break;}

这儿,实际上是调用的context上下文去调用的这个方法,那我们去看下里面的代码

@Override
public ComponentName startService(Intent service) {warnIfCallingFromSystemProcess();return startServiceCommon(service, false, mUser);
}private ComponentName startServiceCommon(Intent service, boolean requireForeground,UserHandle user) {try {validateServiceIntent(service);service.prepareToLeaveProcess(this);ComponentName cn = ActivityManager.getService().startService(mMainThread.getApplicationThread(), service, service.resolveTypeIfNeeded(getContentResolver()), requireForeground,getOpPackageName(), user.getIdentifier());...return cn;} catch (RemoteException e) {throw e.rethrowFromSystemServer();}
}

2.ActivityManager.getService().startService()

可以看到:核心代码为ActivityManager.getService().startService()

在 Activity 中使用的 startService 方法是定义在 Context 的抽象类中,它的真正实现类是 ContextImpl,所以先进入 ContextImpl 类。先从startService开始,然后进入本类的startServiceCommon方法,并最终调用ActivityManagerNative.getDefault()对象的 startService 方法。ActivityManager.getService()获取到IActivityManager对象,并且是通过单利模式创建的。

public static IActivityManager getService() {return IActivityManagerSingleton.get();
}private static final Singleton<IActivityManager> IActivityManagerSingleton =new Singleton<IActivityManager>() {@Overrideprotected IActivityManager create() {final IBinder b = ServiceManager.getService(Context.ACTIVITY_SERVICE);final IActivityManager am = IActivityManager.Stub.asInterface(b);return am;}};

看下ActivityManager.getService()这个如果看过app启动流程就知道,这玩意儿在创建application和activity时也看到过。这儿用到了binder实现进程间通信,最后走到了AMS里面

看下ActivityManagerService中startService()代码

这里面走到了startServiceLocked()方法,其中mServices是ActiveServices。

ActiveServices这里面进行了以下步骤:

1.通过 retrieveServiceLocked 方法来解析 service 这个 Intent,就是解析前面我们在 AndroidManifest.xml 定义的 Service 标签的 intent-filter 相关内容,然后将解析结果放在 res.record 中。

2.调用 startServiceInnerLocked 方法。

在startServiceInnerLocked 方法中会调用 bringUpServiceLocked 方法。

3.bringUpServiceLocked方法中,当 Service 所在的进程存在时,将调用realStartServiceLocked 方法来启动 Service,否则的话调用 startProcessLocked 方法来启动新进程。

情况一:realStartServiceLocked()

这里面会调用 app.thread.scheduleCreateService(r, r.serviceInfo, mAm.compatibilityInfoForPackageLocked(r.serviceInfo.applicationInfo), app.repProcState);

这里会走到ApplicationThread去sendMessage,最后去创建service,执行onCreate方法,后面有详细说明。因为它运行的进程已存在,就直接去创建了,如果不存在,接着往下走

情况二:startProcessLocked() 

 4.startProcessLocked()是ActivityManagerSevice中的方法,

//在ActivityManagerService类中
private final void startProcessLocked(ProcessRecord app, String hostingType, String hostingNameStr, String abiOverride, String entryPoint, String[] entryPointArgs) {boolean isActivityProcess = (entryPoint == null);if (entryPoint == null) entryPoint = "android.app.ActivityThread";checkTime(startTime, "startProcess: asking zygote to start proc");//通过 processName,uid 等启动新进程Process.start(entryPoint, app.processName, uid, uid, gids, debugFlags, mountExternal, app.info.targetSdkVersion, app.info.seinfo, requiredAbi, instructionSet, app.info.dataDir, entryPointArgs);
}//在Process类中
public static final ProcessStartResult start(final String processClass,final String niceName,int uid, int gid, int[] gids,int debugFlags, int mountExternal,int targetSdkVersion,String seInfo,String abi,String instructionSet,String appDataDir,String invokeWith,String[] zygoteArgs) {return zygoteProcess.start(processClass, niceName, uid, gid, gids,debugFlags, mountExternal, targetSdkVersion, seInfo,abi, instructionSet, appDataDir, invokeWith, zygoteArgs);
}

从这儿,启动了一个新的进程,创建一个新的进程过后就会走到熟悉的ActivityThread类的main方法里面去

3.启动新进程去ActivityThread

熟悉的源码

public static void main(String[] args) {Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");CloseGuard.setEnabled(false);// 初始化应用中需要使用的系统路径Environment.initForCurrentUser();Looper.prepareMainLooper();//创建ActivityThread 对象ActivityThread thread = new ActivityThread();thread.attach(false);Looper.loop();throw new RuntimeException("Main thread loop unexpectedly exited");
}

大致流程:

  • 1.绑定应用进程到ActivityManagerService
    • 在 Android 应用程序中,每一个进程对应一个 ActivityThread 实例,然后这里创建了 ActivityThread 对象并调用了其 attach 方法
  • 2.主线程Handler消息处理
    • 启动looper轮询器,所以在activity或者service创建handler对象时,不需要手动调用looper。原因就是在这里
    • 首先Looper.prepareMainLooper();是为主线程创建了Looper,然后thread.getHandler();是保存了主线程的Handler,最后Looper.loop();进入消息循环。

其余的不用看了,主要看thread.attach(false)

  • main()方法通过thread.attach(false)绑定应用进程。ActivityManagerNative通过getDefault()方法返回ActivityManagerService实例,ActivityManagerService通过attachApplication将ApplicationThread对象绑定到ActivityManagerService,而ApplicationThread作为Binder实现ActivityManagerService对应用进程的通信和控制
  • 在ActivityManagerService内部,attachApplication实际是通过调用attachApplicationLocked实现的,这里采用了synchronized关键字保证同步。
//ActivityThread.java
private void attach(boolean system) {final IActivityManager mgr = ActivityManagerNative.getDefault();try {//这里调用了 ActivityManagerProxy.attachApplication 方法。mgr.attachApplication(mAppThread);} catch (RemoteException ex) {// Ignore}
}//ActivityManagerService.java 然后看看attachApplication方法
@Override
public final void attachApplication(IApplicationThread thread) {synchronized (this) {int callingPid = Binder.getCallingPid();final long origId = Binder.clearCallingIdentity();attachApplicationLocked(thread, callingPid);Binder.restoreCallingIdentity(origId);}
}

往里面走,里面的代码很多,这儿根据我看过的源码,分为三个方向

1.关于application,他会走到 thread.bindApplication去绑定application,执行后续操作(创建application,执行onCreate生命周期)

2.关于activity,他会走到mStackSupervisor.attachApplicationLocked(app)执行有关activity的操作(走到scheduleLaunchActivity)

上面两个步骤,具体可看我之前的博客 Android App启动流程和源码详解-CSDN博客

3.关于service,他会走到 didSomething |= mServices.attachApplicationLocked(app, processName);执行service的后续操作。

关键代码:

private final boolean attachApplicationLocked(IApplicationThread thread,int pid) {if (app.instr != null) {//app的thread.bindApplication(processName, appInfo, providers,app.instr.mClass,profilerInfo, app.instr.mArguments,app.instr.mWatcher,app.instr.mUiAutomationConnection, testMode,mBinderTransactionTrackingEnabled, enableTrackAllocation,isRestrictedBackupMode || !normalMode, app.persistent,new Configuration(getGlobalConfiguration()), app.compat,getCommonServicesLocked(app.isolated),mCoreSettingsObserver.getCoreSettingsLocked(),buildSerial);} else {thread.bindApplication(processName, appInfo, providers, null, profilerInfo,null, null, null, testMode,mBinderTransactionTrackingEnabled, enableTrackAllocation,isRestrictedBackupMode || !normalMode, app.persistent,new Configuration(getGlobalConfiguration()), app.compat,getCommonServicesLocked(app.isolated),mCoreSettingsObserver.getCoreSettingsLocked(),buildSerial);}// See if the top visible activity is waiting to run in this process...if (normalMode) {try {//activity的if (mStackSupervisor.attachApplicationLocked(app)) {didSomething = true;}} catch (Exception e) {Slog.wtf(TAG, "Exception thrown launching activities in " + app, e);badApp = true;}}// Find any services that should be running in this process...if (!badApp) {try {//sevice的didSomething |= mServices.attachApplicationLocked(app, processName);checkTime(startTime, "attachApplicationLocked: after mServices.attachApplicationLocked");} catch (Exception e) {Slog.wtf(TAG, "Exception thrown starting services in " + app, e);badApp = true;}}}

上面第三步,mServices.attachApplicationLocked看源码,会走到app.thread.scheduleCreateService(r, r.serviceInfo, mAm.compatibilityInfoForPackageLocked(r.serviceInfo.applicationInfo), app.repProcState);方法,哎,你看他又回去了,走到了ApplicationThread里去了,ApplicationThread是Activity的内部类(上面不创建进程也是调用的词方法),我们点进去看下源码:

public final void scheduleCreateService(IBinder token,  ServiceInfo info, CompatibilityInfo compatInfo, int processState) {updateProcessState(processState, false);CreateServiceData s = new CreateServiceData();s.token = token;s.info = info;s.compatInfo = compatInfo;sendMessage(H.CREATE_SERVICE, s);
}

哎哟喂,殊途同归啊,和application和activity一样,最后都用到了用handler去处理消息。

来嘛,看下handler的handleMessage()方法

...
case CREATE_SERVICE:Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, ("serviceCreate: " + String.valueOf(msg.obj)));handleCreateService((CreateServiceData)msg.obj);Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);break;
...

继续往里走:handleCreateService((CreateServiceData)msg.obj);

1.通过类加载器 ClassLoader 来加载 Service 对象

2.创建一个 ContextImpl 对象,每个 Activity 和 Service 都有一个 Context 对象。

private void handleCreateService(CreateServiceData data) {Service service = null;try {//(1)通过类加载器来加载 Service 对象java.lang.ClassLoader cl = packageInfo.getClassLoader();service = (Service) cl.loadClass(data.info.name).newInstance();} catch (Exception e) {//......}//(2)这里创建 ContextImpl 对象ContextImpl context = ContextImpl.createAppContext(this, packageInfo);context.setOuterContext(service);Application app = packageInfo.makeApplication(false, mInstrumentation);service.attach(context, this, data.info.name, data.token, app,ActivityManagerNative.getDefault());//(3)这里调用 Service 的 onCreate 方法service.onCreate();mServices.put(data.token, service);
}

哎,这不就来了吗,最后执行了service的onCreate()生命周期。

二、bindService()

1.ContextImpl类中bindService()

具体使用:

TestActivity.java
private void test(){Intent intent = new Intent(this, XXXService.class);// bindService 的具体实现在 ContextImpl// BIND_AUTO_CREATE 参数具体使用的代码 ActivityServicesbindService(intent, conn, BIND_AUTO_CREATE);
}private ServiceConnection conn = new ServiceConnection() {  @Override  public void onServiceConnected(ComponentName name, IBinder service) {  // 绑定成功}  @Override  public void onServiceDisconnected(ComponentName name) { // 绑定结束 }
}

bindService源码:

@Override
public boolean bindService(Intent service, ServiceConnection conn,int flags) {// mMainThread.getHandler(),传入的 handle 是主线程的 Handlereturn bindServiceCommon(service, conn, flags, mMainThread.getHandler(),Process.myUserHandle());
}private boolean bindServiceCommon(Intent service, ServiceConnection conn, int flags, Handler handler, UserHandle user) {IServiceConnection sd;if (mPackageInfo != null) {// 1,将传入的 ServiceConnection 转化为 IServiceConnection 返回// mPackgeInfo 是 LoadedApksd = mPackageInfo.getServiceDispatcher(conn, getOuterContext(), handler, flags);}validateServiceIntent(service);try {IBinder token = getActivityToken();...// 2,Binder 调用 AMS 的 bindService 方法,下面具体分析int res = ActivityManagerNative.getDefault().bindService(mMainThread.getApplicationThread(), getActivityToken(), service,service.resolveTypeIfNeeded(getContentResolver()),sd, flags, getOpPackageName(), user.getIdentifier());return res != 0;} //...
}

getServiceDispatcher方法

public final IServiceConnection getServiceDispatcher(ServiceConnection c, Context context, Handler handler, int flags) {synchronized (mServices) {LoadedApk.ServiceDispatcher sd = null;// private final ArrayMap<Context,// ArrayMap<ServiceConnection, LoadedApk.ServiceDispatcher>> mServices// 根据当前的 Context 获取 ArrayMap<ServiceConnection,  LoadedApk.ServiceDispatcher>ArrayMap<ServiceConnection, LoadedApk.ServiceDispatcher> map = mServices.get(context);if (map != null) {// 如果存在,尝试根据当前的 ServiceConnection 获取 ServiceDispatchersd = map.get(c);}if (sd == null) {// 如果与 ServiceConnection 对应的 ServiceDispatcher 不存在,创建一个保存了当前 // ServiceConnection 的 ServiceDispatcher 对象,// 并将之前传入的主线的 Handle 保存,同时创建一个 InnerConnection 对象保存sd = new ServiceDispatcher(c, context, handler, flags);if (map == null) {map = new ArrayMap<ServiceConnection, LoadedApk.ServiceDispatcher>();mServices.put(context, map);}// 将该 ServiceConnection 与 ServiceDispatcher 关系保存map.put(c, sd);} else {// 如果最开始就获取到 ServiceDispatcher,比如多次 bindService,// 就会调用 ServiceDispatcher 的 validate 判断此次 bindService 是否合法// validate 的判断逻辑比较简单:// 1.判断当前的 context 是否和之前 bindService 的一样 // 2.判断当前 handler 是否是主线程的 handle// 以上两个条件都满足的情况下正常执行,反之抛出相应的异常sd.validate(context, handler);}return sd.getIServiceConnection();}
}

主要看ActivityManagerService.bindService()

2.ActivityManagerService.bindService()

public int bindService(IApplicationThread caller, IBinder token, Intent service, String resolvedType, IServiceConnection connection, int flags, String callingPackage, int userId) throws TransactionTooLargeException {//...synchronized(this) {// 调用 ActiveServices 的 bindServiceLocked 方法return mServices.bindServiceLocked(caller, token, service,resolvedType, connection, flags, callingPackage, userId);}
}

看到这里面调用了ActiveServices 的 bindServiceLocked,往里走:这里面代码长,但是主要调用是这样的:ActiveServices.bindServiceLocked() -> bringUpServiceLocked() -> realStartServiceLocked()

看realStartServiceLocked方法,刚才在我们startService的时候也调用了这个方法,但是因为它是bind的,所以有些许不同

private final void realStartServiceLocked(ServiceRecord r, ProcessRecord app, boolean execInFg) throws RemoteException {//...try {// 第一步,调用 ApplicationThread 的 scheduleCreateService 方法,// 之后会实例化 Service 并调用 Service 的 onCreate 方法,这里的过程跟上面 startService 中一样。// 不会调用 onStartCommandapp.thread.scheduleCreateService(r, r.serviceInfo, mAm.compatibilityInfoForPackageLocked(r.serviceInfo.applicationInfo),app.repProcState);} //...// 第二步,调用 requestServiceBindingLockedrequestServiceBindingLocked(r, execInFg);updateServiceClientActivitiesLocked(app, null, true);// 第三步// If the service is in the started state, and there are no// pending arguments, then fake up one so its onStartCommand() will// be called.if (r.startRequested && r.callStart && r.pendingStarts.size() == 0) {r.pendingStarts.add(new ServiceRecord.StartItem(r, false, r.makeNextStartId(), null, null));}// StartItem 的 taskRemoved 如果是 false 的话,// 调用下面方法会调用 Service 的 onStartCommandsendServiceArgsLocked(r, execInFg, true);
}private final boolean requestServiceBindingLocked(ServiceRecord r, IntentBindRecord i, boolean execInFg, boolean rebind) throws TransactionTooLargeException {if ((!i.requested || rebind) && i.apps.size() > 0) {try {// 调用 ApplicationThread 的 scheduleBindService 方法r.app.thread.scheduleBindService(r, i.intent.getIntent(), rebind,r.app.repProcState);} }return true;
}

3.ApplicationThread.scheduleBindService()方法

来来来,看下ApplicationThread.scheduleBindService()方法

  • 调用 ApplicationThread 的 scheduleBindService,scheduleBindService 通过 mH 发送一个 H.BIND_SERVICE 消息,mH 收到该消息调用 handleBindService(BindServiceData data)。

 

 private void handleBindService(BindServiceData data) {Service s = mServices.get(data.token);if (DEBUG_SERVICE)Slog.v(TAG, "handleBindService s=" + s + " rebind=" + data.rebind);if (s != null) {try {data.intent.setExtrasClassLoader(s.getClassLoader());data.intent.prepareToEnterProcess();try {if (!data.rebind) {// 调用 Service 的 onBind,返回给客户端调用的 BinderIBinder binder = s.onBind(data.intent);// 调用 AMS 的 publishService,进而通知客户端连接成功ActivityManager.getService().publishService(data.token, data.intent, binder);} else {s.onRebind(data.intent);ActivityManager.getService().serviceDoneExecuting(data.token, SERVICE_DONE_EXECUTING_ANON, 0, 0);}ensureJitEnabled();} catch (RemoteException ex) {throw ex.rethrowFromSystemServer();}} catch (Exception e) {if (!mInstrumentation.onException(s, e)) {throw new RuntimeException("Unable to bind to service " + s+ " with " + data.intent + ": " + e.toString(), e);}}}}

 

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

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

相关文章

使用Python类的构造函数和析构函数

1、问题背景 当使用Python类时&#xff0c;可以使用构造函数和析构函数来初始化和清理类实例。构造函数在创建类实例时自动调用&#xff0c;而析构函数在删除类实例时自动调用。 在上面的代码示例中&#xff0c;Person类具有一个构造函数__init__和一个析构函数__del__。构造…

【代码】自定义函数

你有没有听过 n! ? n!n(n-1)(n-2)21 你想不想有一个c函数 jc() 专门用来计算n!&#xff1f; 不好意思&#xff0c;没有&#xff01;c函数库说。没有咱自己可以造呀&#xff01; 哈喽大家好&#xff0c;我是学霸小羊&#xff0c;今天讲讲自定义函数。 自定义函数的定义格式…

网易面试:手撕定时器

概述&#xff1a; 本文使用STL容器-set以及Linux提供的timerfd来实现定时器组件 所谓定时器就是管理大量定时任务&#xff0c;使其能按照超时时间有序地被执行 需求分析&#xff1a; 1.数据结构的选择&#xff1a;存储定时任务 2.驱动方式&#xff1a;如何选择一个任务并执…

CSS,HTML,JS 以及Vue前端面试题八股文总结【看完你就变高手】

■ 符号说明 &#x1f498; 主题 &#x1f31f; 常见重要 &#x1f31b; 需要有印象的 &#x1f195; v3新特性 ■ 杂谈 &#x1f31b; SEO优化 合理的title、description、keywords&#xff1a;搜索对着三项的权重逐个减小&#xff0c;title值强调重点即可&#xff1b;descrip…

东软的第三个研发基地,为什么选择了武汉?

继沈阳、大连之后&#xff0c;东软集团在国内打造的第三个研发基地——武汉东软软件园&#xff0c;于2024年5月25日正式开园。 “占地面积158亩、建筑面积14万余平方米的武汉东软软件园&#xff0c;从开工到竣工仅仅用了18个月的时间。这样的建设速度&#xff0c;充分体现了武汉…

2.开发环境介绍

开发环境介绍三种&#xff1a;第一种是在线开发环境、第二种是Windows下的开发环境、第三种是Linux下的开发环境。 1.在线开发环境 2.Windows下的开发环境 用的比较多的是Devc&#xff0c;新手适合使用&#xff0c;上手快&#xff0c;简单&#xff0c;方便。 Devc使用&#x…

蓝桥杯练习系统(算法训练)ALGO-932 低阶行列式计算

资源限制 内存限制&#xff1a;64.0MB C/C时间限制&#xff1a;1.0s Java时间限制&#xff1a;3.0s Python时间限制&#xff1a;5.0s 问题描述 给出一个n阶行列式(1<n<9)&#xff0c;求出它的值。 输入格式 第一行给出两个正整数n,p&#xff1b;   接下来n行&…

【JavaScript】P2 JavaScript 书写位置

本博文总结&#xff1a; JavaScript 书写位置&#xff1a; 内部外部行内 注意事项&#xff1a; 书写的位置尽量写到 </body> 之前外部 js 标签中间不写任何内容&#xff0c;因为不予以展示 正文&#xff1a; 交互效果示例 一个简单的交互效果示例&#xff1b; <…

【从零开始学习RabbitMQ | 第一篇】如何确保生产者的可靠性

目录 前言&#xff1a; 生产者重连机制&#xff1a; 生产者确认机制&#xff1a; Publisher Confirm&#xff08;生产者者确认&#xff09; Publish Return&#xff08;发布返回&#xff09; 总结&#xff1a; 前言&#xff1a; 在现代的分布式系统中&#xff0c;消息队…

【NumPy】关于numpy.divide()函数,看这一篇文章就够了

&#x1f9d1; 博主简介&#xff1a;阿里巴巴嵌入式技术专家&#xff0c;深耕嵌入式人工智能领域&#xff0c;具备多年的嵌入式硬件产品研发管理经验。 &#x1f4d2; 博客介绍&#xff1a;分享嵌入式开发领域的相关知识、经验、思考和感悟&#xff0c;欢迎关注。提供嵌入式方向…

zabbix监控mysql

一、mysql数据库监控的内容有 mysql的吞吐量 mysql的常规操作&#xff08;增删改查&#xff09; QPS&#xff08;Questions Per second:&#xff09;每秒能处理多少次请求数 TPS&#xff08;Transactions Per Second&#xff09;每秒查询处理的事务数 mysql库大小和表大小 监控…

欧科云链:Web3.0时代 具备链上数据分析能力的公司愈发凸显其价值

在当今激烈的市场竞争中&#xff0c;新兴互联网领域迅速崛起&#xff0c;Web2.0已相对成熟&#xff0c;用户创造数据&#xff0c;但不拥有数据。被视为新的价值互联网的Web3.0&#xff0c;赋予用户真正的数据自主权&#xff0c;它的到来被认为是打破Web2.0垄断的机遇。 在Web3…

迅狐跨境商城系统源码

在当今全球化的商业环境中&#xff0c;跨境电商的兴起为商家提供了无限的可能性。为了满足这一需求&#xff0c;跨境商城系统源码的开发显得尤为重要。本文将探讨跨境商城系统源码的优势&#xff0c;以及如何利用这些优势来构建一个成功的跨境电商平台。 独立开发&#xff0c;…

LLM 大模型学习必知必会系列(十三):基于SWIFT的VLLM推理加速与部署实战

LLM 大模型学习必知必会系列(十三)&#xff1a;基于SWIFT的VLLM推理加速与部署实战 1.环境准备 GPU设备: A10, 3090, V100, A100均可. #设置pip全局镜像 (加速下载) pip config set global.index-url https://mirrors.aliyun.com/pypi/simple/ #安装ms-swift pip install ms-…

windows11如何安装IIS

目录 IIS是什么&#xff1f; 为什么要配置IIS&#xff1f; 1.打开控制面板进入程序 2.点击启用或者关闭windos功能 3.勾选IIS相关的web项 4.点击确定等待一分钟程序变更即可 5.主页搜索internet 点击进入 6.进入IIS进行查看配置&#xff0c;并测试&#xff0c;也可以浏…

重学java 49 List接口

但逢良辰&#xff0c;顺颂时宜 —— 24.5.28 一、List接口 1.概述: 是collection接口的子接口 2.常见的实现类: ArrayList LinkedList Vector 二、List集合下的实现类 1.ArrayList集合的使用及源码分析 1.概述 ArrayList是List接口的实现类 2.特点 a.元素有序 —> 按照什么顺…

常见SSL证书品牌关系图

常见SSL证书品牌关系图 在SSL证书市场上&#xff0c;有几个主要的品牌和他们之间的复杂关系。以下是一些主要的SSL证书提供商及其关系的简要概述&#xff1a; DigiCert&#xff1a; DigiCert 是最大的SSL证书颁发机构之一。它收购了Symantec的SSL和PKI业务&#xff0c;其中包括…

深度合作!博睿数据联合中国信通院开展公网服务质量评估工作!

近日&#xff0c;中国信息通信研究院&#xff08;简称“中国信通院”&#xff09;算网质量保障工作全面启动&#xff0c;博睿数据&#xff08;bonree.com&#xff0c;股票代码688229&#xff09;作为信通院算网质量测试独家技术支持单位&#xff0c;提供公网服务质量测评整体解…

Linux之多进程

文章目录 c程序获取进程pid和ppid进程相关命令进程的创建多进程进程退出exit()函数_exit函数 进程的等待wait函数waitpid函数 进程的替换进程间的通信一、无名管道二、有名管道三、信号kill函数raise函数pause() 函数自定义信号处理函数SIGALARM信号子进程退出信号SIGCHLD 四、…

基于mybatis-plus的多语言扩展

概览 对于表中字段&#xff0c;需要实现多语言的方案探讨&#xff1a; 1.表中横向扩展多个字段分别存储中文&#xff0c;英文&#xff0c;俄语等语言字段&#xff0c;查询时&#xff0c;根据需要查询的语言&#xff0c;进行查询 2.增加一张多语言表&#xff0c;存储多语言信…