Android系统揭秘(一)-Activity启动流程(上)

public ActivityResult execStartActivity(

Context who, IBinder contextThread, IBinder token, Activity target,

Intent intent, int requestCode, Bundle options) {

IApplicationThread whoThread = (IApplicationThread) contextThread;

try {

int result = ActivityManager.getService()

.startActivity(whoThread, who.getBasePackageName(), intent,

intent.resolveTypeIfNeeded(who.getContentResolver()),

token, target != null ? target.mEmbeddedID : null,

requestCode, 0, null, options);

checkStartActivityResult(result, intent);

} catch (RemoteException e) {

throw new RuntimeException(“Failure from system”, e);

}

return null;

}

首先调用ActivityManager的getService方法来获取AMS的代理对象,接着调用它的startActivity方法

这里需要注意的是,Android8.0之前是通过ActivityManagerNative的getDefault未获取AMS的代理对象的, 现在这个逻辑封装到了ActivityManager中而不是ActivityManagerNative中。

frameworks/base/core/java/android/app/ActivityManager.java

@SystemService(Context.ACTIVITY_SERVICE)

public class ActivityManager {

public static IActivityManager getService() {

return IActivityManagerSingleton.get();

}

private static final Singleton IActivityManagerSingleton =

new Singleton() {

@Override

protected IActivityManager create() {

// Context.ACTIVITY_SERVICE值为"activity"

final IBinder b = ServiceManager.getService(Context.ACTIVITY_SERVICE);

final IActivityManager am = IActivityManager.Stub.asInterface(b);

return am;

}

};

}

getService方法调用了IActivityManagerSingleton的get方法,IActivityManagerSingleton是一个Singleton类。

这段代码采用的是AIDL,IActivityManager.java类是由AIDL工具在编译时自动生成的, 要实现进程间通信,服务器端也就是AMS只需要继承IActivityManager.Stub类并实现相应的方法就可以了。

注意Android8.0之前并没有采用AIDL,而是采用了类似AIDL的形式, 用AMS的代理对象ActivityManagerProxy来与AMS进行进程间通信, Android8.0去除了ActivityManagerNative的内部类ActivityManagerProxy, 代替它的是IActivityManager,它是AMS在本地的代理。 回到Instrumentation类的execStartActivity方法中, 从上面得知execStartActivity方法最终调用的是AMS的startActivity方法。

AMS到ApplicationThread的调用过程


我们先看看AMS里面的代码

frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java

@Override

public final int startActivity(IApplicationThread caller, String callingPackage,

Intent intent, String resolvedType, IBinder resultTo, String resultWho, int requestCode,

int startFlags, ProfilerInfo profilerInfo, Bundle bOptions) {

return startActivityAsUser(caller, callingPackage, intent, resolvedType, resultTo,

resultWho, requestCode, startFlags, profilerInfo, bOptions,

UserHandle.getCallingUserId());

}

@Override

public final int startActivityAsUser(IApplicationThread caller, String callingPackage,

Intent intent, String resolvedType, IBinder resultTo, String resultWho, int requestCode,

int startFlags, ProfilerInfo profilerInfo, Bundle bOptions, int userId) {

// 判断调用者进程是否被隔离,如果被隔离则抛出SecurityException异常

enforceNotIsolatedCaller(“startActivity”);

// 检查调用者是否有权限,如果没有权限也会抛出SecurityException异常

userId = mUserController.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),

userId, false, ALLOW_FULL_ONLY, “startActivity”, null);

// TODO: Switch to user app stacks here.

return mActivityStarter.startActivityMayWait(caller, -1, callingPackage, intent,

resolvedType, null, null, resultTo, resultWho, requestCode, startFlags,

profilerInfo, null, null, bOptions, false, userId, null, “startActivityAsUser”);

}

可知,AMS使用ActivityStarter来启动Activity

frameworks/base/services/core/java/com/android/server/am/ActivityStarter.java

final int startActivityMayWait(IApplicationThread caller, int callingUid,

String callingPackage, Intent intent, String resolvedType,

IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,

IBinder resultTo, String resultWho, int requestCode, int startFlags,

ProfilerInfo profilerInfo, WaitResult outResult,

Configuration globalConfig, Bundle bOptions, boolean ignoreTargetSecurity, int userId,

TaskRecord inTask, String reason) {

final ActivityRecord[] outRecord = new ActivityRecord[1];

int res = startActivityLocked(caller, intent, ephemeralIntent, resolvedType,

aInfo, rInfo, voiceSession, voiceInteractor,

resultTo, resultWho, requestCode, callingPid,

callingUid, callingPackage, realCallingPid, realCallingUid, startFlags,

options, ignoreTargetSecurity, componentSpecified, outRecord, inTask,

reason);

return res;

}

}

我们看下startActivityLocked方法,这个方法在ActivityStarter.startActivity前调用,会做一些校验

int startActivityLocked(IApplicationThread caller, Intent intent, Intent ephemeralIntent,

String resolvedType, ActivityInfo aInfo, ResolveInfo rInfo,

IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,

IBinder resultTo, String resultWho, int requestCode, int callingPid, int callingUid,

String callingPackage, int realCallingPid, int realCallingUid, int startFlags,

ActivityOptions options, boolean ignoreTargetSecurity, boolean componentSpecified,

ActivityRecord[] outActivity, TaskRecord inTask, String reason) {

// 不会直接调用startActivity,要先做一些校验

if (TextUtils.isEmpty(reason)) {

throw new IllegalArgumentException(“Need to specify a reason.”);

}

mLastStartReason = reason;

mLastStartActivityTimeMs = System.currentTimeMillis();

mLastStartActivityRecord[0] = null;

mLastStartActivityResult = startActivity(caller, intent, ephemeralIntent, resolvedType,

aInfo, rInfo, voiceSession, voiceInteractor, resultTo, resultWho, requestCode,

callingPid, callingUid, callingPackage, realCallingPid, realCallingUid, startFlags,

options, ignoreTargetSecurity, componentSpecified, mLastStartActivityRecord,

inTask);

if (outActivity != null) {

// mLastStartActivityRecord[0] is set in the call to startActivity above.

outActivity[0] = mLastStartActivityRecord[0];

}

// Aborted results are treated as successes externally, but we must track them internally.

return mLastStartActivityResult != START_ABORTED ? mLastStartActivityResult : START_SUCCESS;

}

接着调用startActivity方法

/** DO NOT call this method directly. Use {@link #startActivityLocked} instead. */

private int startActivity(IApplicationThread caller, Intent intent, Intent ephemeralIntent,

String resolvedType, ActivityInfo aInfo, ResolveInfo rInfo,

IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,

IBinder resultTo, String resultWho, int requestCode, int callingPid, int callingUid,

String callingPackage, int realCallingPid, int realCallingUid, int startFlags,

ActivityOptions options, boolean ignoreTargetSecurity, boolean componentSpecified,

ActivityRecord[] outActivity, TaskRecord inTask) {

int err = ActivityManager.START_SUCCESS;

// Pull the optional Ephemeral Installer-only bundle out of the options early.

final Bundle verificationBundle

= options != null ? options.popAppVerificationBundle() : null;

ProcessRecord callerApp = null;

if (caller != null) {

// 得到的是代表Launcher进程的callerApp对象,它是ProcessRecord类型的,ProcessRecord用于描述一个应用程序进程

callerApp = mService.getRecordForAppLocked(caller);

if (callerApp != null) {

callingPid = callerApp.pid;

callingUid = callerApp.info.uid;

} else {

Slog.w(TAG, "Unable to find app for caller " + caller

  • " (pid=" + callingPid + ") when starting: "

  • intent.toString());

err = ActivityManager.START_PERMISSION_DENIED;

}

}

ActivityRecord r = new startActivity(mService, callerApp, callingPid, callingUid,

callingPackage, intent, resolvedType, aInfo, mService.getGlobalConfiguration(),

resultRecord, resultWho, requestCode, componentSpecified, voiceSession != null,

mSupervisor, options, sourceRecord);

if (outActivity != null) {

outActivity[0] = r;

}

doPendingActivityLaunchesLocked(false);

return startActivity(r, sourceRecord, voiceSession, voiceInteractor, startFlags, true,

options, inTask, outActivity);

}

在启动Activity之前,系统会创建一个startActivity对象,ActivityRecord用于描述一个Activity,用来记录一个Activity的所有信息

private int startActivity(final ActivityRecord r, ActivityRecord sourceRecord,

IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,

int startFlags, boolean doResume, ActivityOptions options, TaskRecord inTask,

ActivityRecord[] outActivity) {

int result = START_CANCELED;

try {

mService.mWindowManager.deferSurfaceLayout();

result = startActivityUnchecked(r, sourceRecord, voiceSession, voiceInteractor,

startFlags, doResume, options, inTask, outActivity);

} finally {

}

return result;

}

startActivityUnchecked方法代码比较多,主要处理与栈管理相关的逻辑。

// Note: This method should only be called from {@link startActivity}.

private int startActivityUnchecked(final ActivityRecord r, ActivityRecord sourceRecord,

IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,

int startFlags, boolean doResume, ActivityOptions options, TaskRecord inTask,

ActivityRecord[] outActivity) {

// If the activity being launched is the same as the one currently at the top, then

// we need to check if it should only be launched once.

final ActivityStack topStack = mSupervisor.mFocusedStack;

final ActivityRecord topFocused = topStack.topActivity();

final ActivityRecord top = topStack.topRunningNonDelayedActivityLocked(mNotTop);

// 启动根Activity时会将Intent的Flag设置为FLAG_ACTIVITY_NEW_TASK

// Should this be considered a new task?

int result = START_SUCCESS;

if (mStartActivity.resultTo == null && mInTask == null && !mAddingToTask

&& (mLaunchFlags & FLAG_ACTIVITY_NEW_TASK) != 0) {

newTask = true;

// setTaskFromReuseOrCreateNewTask方法内部会创建一个新的Activity任务栈

result = setTaskFromReuseOrCreateNewTask(

taskToAffiliate, preferredLaunchStackId, topStack);

} else if (mSourceRecord != null) {

result = setTaskFromSourceRecord();

} else if (mInTask != null) {

result = setTaskFromInTask();

} else {

// This not being started from an existing activity, and not part of a new task…

// just put it in the top task, though these days this case should never happen.

setTaskToCurrentTopOrCreateNewTask();

}

if (result != START_SUCCESS) {

return result;

}

if (mDoResume) {

final ActivityRecord topTaskActivity =

mStartActivity.getTask().topRunningActivityLocked();

if (!mTargetStack.isFocusable()

|| (topTaskActivity != null && topTaskActivity.mTaskOverlay

&& mStartActivity != topTaskActivity)) {

} else {

// If the target stack was not previously focusable (previous top running activity

// on that stack was not visible) then any prior calls to move the stack to the

// will not update the focused stack. If starting the new activity now allows the

// task stack to be focusable, then ensure that we now update the focused stack

// accordingly.

if (mTargetStack.isFocusable() && !mSupervisor.isFocusedStack(mTargetStack)) {

mTargetStack.moveToFront(“startActivityUnchecked”);

}

mSupervisor.resumeFocusedStackTopActivityLocked(mTargetStack, mStartActivity,

mOptions);

}

} else {

mTargetStack.addRecentActivityLocked(mStartActivity);

}

mSupervisor.updateUserStackLocked(mStartActivity.userId, mTargetStack);

mSupervisor.handleNonResizableTaskIfNeeded(mStartActivity.getTask(), preferredLaunchStackId,

preferredLaunchDisplayId, mTargetStack.mStackId);

return START_SUCCESS;

}

接下来调用的是ActivityStackSupervisor的resumeFocusedStackTopActivityLocked方法,从这里开始要处理堆栈的事务了

frameworks/base/services/core/java/com/android/server/am/ActivityStackSupervisor.java

boolean resumeFocusedStackTopActivityLocked(

ActivityStack targetStack, ActivityRecord target, ActivityOptions targetOptions) {

if (!readyToResume()) {

return false;

}

if (targetStack != null && isFocusedStack(targetStack)) {

return targetStack.resumeTopActivityUncheckedLocked(target, targetOptions);

}

//获取要启动的Activity所在校的核顶的不是处于停止状态的ActivityRecord

final ActivityRecord r = mFocusedStack.topRunningActivityLocked();

if (r == null || r.state != RESUMED) {

mFocusedStack.resumeTopActivityUncheckedLocked(null, null);

} else if (r.state == RESUMED) {

// Kick off any lingering app transitions form the MoveTaskToFront operation.

mFocusedStack.executeAppTransition(targetOptions);

}

return false;

}

ActivityStackSupervisor相当于ActivityStack的帮助类,需要ActivityStack进行下一步堆栈的处理

frameworks/base/services/core/java/com/android/server/am/ActivityStack.java

boolean resumeTopActivityUncheckedLocked(ActivityRecord prev, ActivityOptions options) {

if (mStackSupervisor.inResumeTopActivity) {

// Don’t even start recursing.

return false;

}

boolean result = false;

try {

// Protect against recursion.

mStackSupervisor.inResumeTopActivity = true;

result = resumeTopActivityInnerLocked(prev, options);

} finally {

mStackSupervisor.inResumeTopActivity = false;

}

final ActivityRecord next = topRunningActivityLocked(true /* focusableOnly */);

if (next == null || !next.canTurnScreenOn()) {

checkReadyForSleep();

}

return result;

}

resumeTopActivityInnerLocked的内容很多,我们只关心以下代码

private boolean resumeTopActivityInnerLocked(ActivityRecord prev, ActivityOptions options) {

mStackSupervisor.startSpecificActivityLocked(next, true, false);

return true;

}

又回到了ActivityStackSupervisor,调用startSpecificActivityLocked方法

frameworks/base/services/core/java/com/android/server/am/ActivityStackSupervisor.java

void startSpecificActivityLocked(ActivityRecord r,

boolean andResume, boolean checkConfig) {

// Is this activity’s application already running?

ProcessRecord app = mService.getProcessRecordLocked(r.processName,

r.info.applicationInfo.uid, true);

r.getStack().setLaunchTime®;

if (app != null && app.thread != null) {

try {

if ((r.info.flags&ActivityInfo.FLAG_MULTIPROCESS) == 0

|| !“android”.equals(r.info.packageName)) {

// Don’t add this if it is a platform component that is marked

// to run in multiple processes, because this is actually

// part of the framework so doesn’t make sense to track as a

// separate apk in the process.

app.addPackage(r.info.packageName, r.info.applicationInfo.versionCode,

mService.mProcessStats);

}

realStartActivityLocked(r, app, andResume, checkConfig);

return;

} catch (RemoteException e) {

Slog.w(TAG, "Exception when starting activity "

  • r.intent.getComponent().flattenToShortString(), e);

}

// If a dead object exception was thrown – fall through to

// restart the application.

}

// 启动应用程序进程

mService.startProcessLocked(r.processName, r.info.applicationInfo, true, 0,

“activity”, r.intent.getComponent(), false, false, true);

}

接下来就是realStartActivityLocked,到这里堆栈基本处理完毕,准备通知ApplicationThread

final boolean realStartActivityLocked(ActivityRecord r, ProcessRecord app,

boolean andResume, boolean checkConfig) throws RemoteException {

app.thread.scheduleLaunchActivity(new Intent(r.intent), r.appToken,

System.identityHashCode®, r.info,

// TODO: Have this take the merged configuration instead of separate global

// and override configs.

mergedConfiguration.getGlobalConfiguration(),

mergedConfiguration.getOverrideConfiguration(), r.compat,

r.launchedFromPackage, task.voiceInteractor, app.repProcState, r.icicle,

r.persistentState, results, newIntents, !andResume,

mService.isNextTransitionForward(), profilerInfo);

}

这里的app.thread指的是IApplicationThread,它的实现是ActivityThread的内部类ApplicationThread, 其中ApplicationThread继承了IApplicationThread.Stub

ActivityThread 启动Activity 的过程


上一节写到AMS通过各种途径,最终通知到ApplicationThread。而在这一步骤,ApplicationThread通过消息机制告知ActivityThread,

frameworks/base/core/java/android/app/ActivityThread.java#ApplicationThread

@Override

public final void scheduleLaunchActivity(Intent intent, IBinder token, int ident,

ActivityInfo info, Configuration curConfig, Configuration overrideConfig,

CompatibilityInfo compatInfo, String referrer, IVoiceInteractor voiceInteractor,

int procState, Bundle state, PersistableBundle persistentState,

List pendingResults, List pendingNewIntents,

boolean notResumed, boolean isForward, ProfilerInfo profilerInfo) {

updateProcessState(procState, false);

ActivityClientRecord r = new ActivityClientRecord();

r.token = token;

r.ident = ident;

r.intent = intent;

r.referrer = referrer;

r.voiceInteractor = voiceInteractor;

r.activityInfo = info;

r.compatInfo = compatInfo;

r.state = state;

r.persistentState = persistentState;

r.pendingResults = pendingResults;

r.pendingIntents = pendingNewIntents;

r.startsNotResumed = notResumed;

r.isForward = isForward;

r.profilerInfo = profilerInfo;

r.overrideConfig = overrideConfig;

updatePendingConfiguration(curConfig);

sendMessage(H.LAUNCH_ACTIVITY, r);

}

我们看看sendMessage的代码

frameworks/base/core/java/android/app/ActivityThread.java

private void sendMessage(int what, Object obj, int arg1, int arg2, boolean async) {

if (DEBUG_MESSAGES) Slog.v(

TAG, "SCHEDULE " + what + " " + mH.codeToString(what)

  • ": " + arg1 + " / " + obj);

Message msg = Message.obtain();

msg.what = what;

msg.obj = obj;

msg.arg1 = arg1;

msg.arg2 = arg2;

if (async) {

msg.setAsynchronous(true);

}

mH.sendMessage(msg);

}

它向一个mH对象发送的一个what为H.LAUNCH_ACTIVITY(值为100),obj为ActivityClientRecord的Message,H是ActivityThread内的一个Handler

H收到指令后会调用handleLaunchActivity方法

private class H extends Handler {

public static final int LAUNCH_ACTIVITY = 100;

public static final int RELAUNCH_ACTIVITY = 101;

String codeToString(int code) {

if (DEBUG_MESSAGES) {

switch (code) {

case LAUNCH_ACTIVITY: return “LAUNCH_ACTIVITY”;

}

}

return Integer.toString(code);

}

public void handleMessage(Message msg) {

if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));

switch (msg.what) {

case LAUNCH_ACTIVITY: {

Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, “activityStart”);

final ActivityClientRecord r = (ActivityClientRecord) msg.obj;

r.packageInfo = getPackageInfoNoCheck(

r.activityInfo.applicationInfo, r.compatInfo);

handleLaunchActivity(r, null, “LAUNCH_ACTIVITY”);

Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);

} break;

case RELAUNCH_ACTIVITY: {

Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, “activityRestart”);

ActivityClientRecord r = (ActivityClientRecord)msg.obj;

handleRelaunchActivity®;

Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);

} break;

}

Object obj = msg.obj;

if (obj instanceof SomeArgs) {

((SomeArgs) obj).recycle();

}

if (DEBUG_MESSAGES) Slog.v(TAG, "<<< done: " + codeToString(msg.what));

}

}

这里我们先看下getPackageInfoNoCheck方法,这个方法主要用于获取应用的包信息,然后返回LoadedApk对象

public final LoadedApk getPackageInfoNoCheck(ApplicationInfo ai,

CompatibilityInfo compatInfo) {

return getPackageInfo(ai, compatInfo, null, false, true, false);

}

private LoadedApk getPackageInfo(ApplicationInfo aInfo, CompatibilityInfo compatInfo,

ClassLoader baseLoader, boolean securityViolation, boolean includeCode,

boolean registerPackage) {

final boolean differentUser = (UserHandle.myUserId() != UserHandle.getUserId(aInfo.uid));

synchronized (mResourcesManager) {

WeakReference ref;

if (differentUser) {

// Caching not supported across users

ref = null;

} else if (includeCode) {

ref = mPackages.get(aInfo.packageName);

} else {

ref = mResourcePackages.get(aInfo.packageName);

}

LoadedApk packageInfo = ref != null ? ref.get() : null;

if (packageInfo == null || (packageInfo.mResources != null

&& !packageInfo.mResources.getAssets().isUpToDate())) {

if (localLOGV) Slog.v(TAG, (includeCode ? "Loading code package "
"Loading resource-only package ") + aInfo.packageName
  • " (in " + (mBoundApplication != null

? mBoundApplication.processName : null)

  • “)”);

packageInfo =

new LoadedApk(this, aInfo, compatInfo, baseLoader,

securityViolation, includeCode &&

(aInfo.flags&ApplicationInfo.FLAG_HAS_CODE) != 0, registerPackage);

if (mSystemThread && “android”.equals(aInfo.packageName)) {

packageInfo.installSystemApplicationInfo(aInfo,

getSystemContext().mPackageInfo.getClassLoader());

}

if (differentUser) {

// Caching not supported across users

} else if (includeCode) {

mPackages.put(aInfo.packageName,

new WeakReference(packageInfo));

} else {

mResourcePackages.put(aInfo.packageName,

new WeakReference(packageInfo));

}

}

return packageInfo;

}

}

然后接着看handleLaunchActivity方法

private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent, String reason) {

WindowManagerGlobal.initialize();

最后

小编这些年深知大多数初中级Android工程师,想要提升自己,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助

因此我收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人

都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

资料⬅专栏获取
n != null

? mBoundApplication.processName : null)

  • “)”);

packageInfo =

new LoadedApk(this, aInfo, compatInfo, baseLoader,

securityViolation, includeCode &&

(aInfo.flags&ApplicationInfo.FLAG_HAS_CODE) != 0, registerPackage);

if (mSystemThread && “android”.equals(aInfo.packageName)) {

packageInfo.installSystemApplicationInfo(aInfo,

getSystemContext().mPackageInfo.getClassLoader());

}

if (differentUser) {

// Caching not supported across users

} else if (includeCode) {

mPackages.put(aInfo.packageName,

new WeakReference(packageInfo));

} else {

mResourcePackages.put(aInfo.packageName,

new WeakReference(packageInfo));

}

}

return packageInfo;

}

}

然后接着看handleLaunchActivity方法

private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent, String reason) {

WindowManagerGlobal.initialize();

最后

小编这些年深知大多数初中级Android工程师,想要提升自己,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助

因此我收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。

[外链图片转存中…(img-qv6XnfkS-1718993667533)]

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人

都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

资料⬅专栏获取

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

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

相关文章

TCP 和 UDP 可以同时绑定相同的端口吗?

在网络编程中&#xff0c;TCP和UDP都可以绑定到同一个端口上进行通信。TCP和UDP是OSI模型中的传输层协议&#xff0c;它们分别使用不同的端口号来区分不同的应用程序或服务。 TCP&#xff08;Transmission Control Protocol&#xff09;提供了面向连接的、可靠的传输服务&…

新零售解决方案:线上线下融合,驱动现代商业新浪潮-亿发

在数字化和智能化的商业环境中&#xff0c;新零售正在迅速改变传统的商业模式。作为新时代的零售解决方案&#xff0c;新零售通过线上线下深度结合&#xff0c;为企业提供了更灵活、高效的运营方式。本文将探讨新零售的四大特征&#xff0c;并详细描述其在中小企业中的应用&…

虚拟机没关机,电脑直接关机导致虚拟机无法使用

虚拟机没关机&#xff0c;电脑直接关机导致虚拟机无法使用 虚拟机未正常关机 无法打开虚拟机&#xff0c;移除 删除虚拟机目录下的该文件夹CentOSXX.vmx.lck&#xff08;或者重新命名&#xff09; 虚拟机正常打开

二,SpringFramework

二、SpringFramework实战指南 目录 一、技术体系结构 1.1 总体技术体系1.2 框架概念和理解 二、SpringFramework介绍 2.1 Spring 和 SpringFramework概念2.2 SpringFramework主要功能模块2.3 SpringFramework 主要优势 三、Spring IoC容器和核心概念 3.1 组件和组件管理概念3…

一句话、10秒,我用Claude 3.5 Sonnet生成了完整的俄罗斯方块!

大家好&#xff0c;我是木易&#xff0c;一个持续关注AI领域的互联网技术产品经理&#xff0c;国内Top2本科&#xff0c;美国Top10 CS研究生&#xff0c;MBA。我坚信AI是普通人变强的“外挂”&#xff0c;所以创建了“AI信息Gap”这个公众号&#xff0c;专注于分享AI全维度知识…

Python应用开发——30天学习Streamlit Python包进行APP的构建(7)

st.data_editor 显示数据编辑器 widget。 数据编辑器 widget 可让你在类似表格的用户界面中编辑数据框和许多其他数据结构。 警告 When going from st.experimental_data_editor to st.data_editor in 1.23.0, the data editors representation in st.session_state was ch…

2352.相等行列对

给你一个下标从 0 开始、大小为 n x n 的整数矩阵 grid &#xff0c;返回满足 Ri 行和 Cj 列相等的行列对 (Ri, Cj) 的数目。 如果行和列以相同的顺序包含相同的元素&#xff08;即相等的数组&#xff09;&#xff0c;则认为二者是相等的。 示例 1&#xff1a; 输入&#xff1a…

Java——包

一、包 1、简要介绍 在Java编程语言中&#xff0c;包&#xff08;Package&#xff09; 是一种用来组织和管理类&#xff08;Class&#xff09;和接口&#xff08;Interface&#xff09;的机制。包为开发者提供了一种逻辑分组的方式&#xff0c;使代码更加模块化、结构化和易于…

【前端技术】标签页通讯localStorage、BroadcastChannel、SharedWorker的技术详解

&#x1f604; 19年之后由于某些原因断更了三年&#xff0c;23年重新扬帆起航&#xff0c;推出更多优质博文&#xff0c;希望大家多多支持&#xff5e; &#x1f337; 古之立大事者&#xff0c;不惟有超世之才&#xff0c;亦必有坚忍不拔之志 &#x1f390; 个人CSND主页——Mi…

Redis大key有什么危害?如何排查和处理?

什么是 bigkey&#xff1f; 简单来说&#xff0c;如果一个 key 对应的 value 所占用的内存比较大&#xff0c;那这个 key 就可以看作是 bigkey。具体多大才算大呢&#xff1f;有一个不是特别精确的参考标准&#xff1a; String 类型的 value 超过 1MB 复合类型&#xff08;Li…

一个漂亮的网站收藏函数

<!DOCTYPE html> <html lang="zh-CN"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>网站收藏</title><style>body …

云手机群控功能讲解

接触云手机之前&#xff0c;很多企业或者个人卖家都对群控有浓厚的兴趣&#xff0c;云手机群控具体是什么呢&#xff1f;云手机群控&#xff0c;顾名思义&#xff0c;是指能够同时对多台云手机进行集中控制和管理的功能。打破了传统单台手机操作的限制&#xff0c;实现了规模化…

高精度乘法的实现

这是C算法基础-基础算法专栏的第九篇文章&#xff0c;专栏详情请见此处。 引入 上次我们学习了高精度加法的实现&#xff0c;这次我们要学习高精度减法的实现。 高精度乘法与高精度加法的定义、前置过程都是大致相同的&#xff0c;如果想了解具体内容&#xff0c;可以移步至我的…

查看LabVIEW及各个模块和驱动的版本号

要方便地查看当前计算机上安装的LabVIEW版本以及各个模块和驱动的版本号&#xff0c;可以使用以下几种方法&#xff1a; 1. 使用NI MAX (Measurement & Automation Explorer) NI MAX 是一个强大的工具&#xff0c;可以帮助你管理National Instruments硬件、软件和驱动程序…

Docker(三)-Docker常用命令

1.run run命令执行流程:2.帮助启动类命令 2.1 启动docker systemctl start docker2.2 停止docker systemctl stop docker2.3 重启docker systemctl restart docker2.4查看docker状态 systemctl status docker2.5开机启动 systemctl enable docker2.6查看docker概要信息 …

VB.net实战(VSTO):VSTOwpf体验框架打包教程

如果是考虑到Wps用户较多&#xff0c;就不建议采用侧边栏的形式 只是个体验框架&#xff0c;界面未作美化&#xff0c;office的用户可以用任意一种窗体&#xff0c;喜欢那个界面就写那个界面&#xff0c;wps的侧边栏只能弹出一部分&#xff0c;每次需要的手动拖动。 打包了案例…

Java——IO流(一)-(6/8):字节流-FileInputStream 每次读取多个字节(示例演示)、一次读取完全部字节(方式一、方式二,注意事项)

目录 文件字节输入流&#xff1a;每次读取多个字节 实例演示 注意事项 文件字节输入流&#xff1a;一次读取完全部字节 方式一 方式二 注意事项 文件字节输入流&#xff1a;每次读取多个字节 用到之前介绍过的常用方法&#xff1a; 实例演示 需求&#xff1a;用每次读取…

诸茅的黄昏

内容提要 白酒大陆的坍塌终于到达茅台的地盘&#xff0c;一切发生得太快了。突然间&#xff0c;深厚的护城河消失了&#xff0c;医药茅、眼科茅、牙科茅、疫苗茅、酱油茅都挣扎于内需的泥沼中。旧茅衰退&#xff0c;新茅生长&#xff0c;在下行周期&#xff0c;内需仍有结构性…

C#修改 EXE 文件图标和 winForm 窗口图标

修改 EXE 文件图标 1.准备好图片&#xff0c;转换为 Icon 图片&#xff1b; 2.右键工程&#xff0c;选择属性&#xff1b; 3.选择 Icon 图标即可&#xff1b; 4.重新生成可执行文件&#xff0c;查看。 修改 winForm 窗口图标 1.选中 winForm &#xff0c;查看属性&#x…

「51媒体」时尚类媒体邀约宣发资源

传媒如春雨&#xff0c;润物细无声&#xff0c;大家好&#xff0c;我是51媒体网胡老师。 时尚类媒体邀约宣发资源可以多样化且针对性地满足品牌或活动的推广需求。以下是一些主要的资源及其特点&#xff1a; 时尚杂志&#xff1a;国内外知名时尚杂志&#xff0c;如《Vogue》、…