当前位置: 首页 > news >正文

Android activity 启动流程

Android activity 启动流程

本文主要记录下acitivty的启动流程.

1: Activity

我们都知道启动activity调用方法:

startActivity(Intent intent)startActivity(Intent intent, @Nullable Bundle options)startActivityForResult(@RequiresPermission Intent intent, int requestCode)startActivityForResult(@RequiresPermission Intent intent, int requestCode,@Nullable Bundle options)

首先我们先跟踪下代码

  1. context.startActivity(new Intent(context, TestActivity.class));
  2. this.startActivity(intent, null);
  3. startActivityForResult(intent, -1);
  4. startActivityForResult(intent, requestCode, null);

我们可以看下startActivityForResult(intent, requestCode, null)的代码.

public void startActivityForResult(@RequiresPermission Intent intent, int requestCode,@Nullable Bundle options) {if (mParent == null) {options = transferSpringboardActivityOptions(options);Instrumentation.ActivityResult ar =mInstrumentation.execStartActivity(this, mMainThread.getApplicationThread(), mToken, this,intent, requestCode, options);if (ar != null) {mMainThread.sendActivityResult(mToken, mEmbeddedID, requestCode, ar.getResultCode(),ar.getResultData());}if (requestCode >= 0) {// If this start is requesting a result, we can avoid making// the activity visible until the result is received.  Setting// this code during onCreate(Bundle savedInstanceState) or onResume() will keep the// activity hidden during this time, to avoid flickering.// This can only be done when a result is requested because// that guarantees we will get information back when the// activity is finished, no matter what happens to it.mStartedActivity = true;}cancelInputsAndStartExitTransition(options);// TODO Consider clearing/flushing other event sources and events for child windows.} else {if (options != null) {mParent.startActivityFromChild(this, intent, requestCode, options);} else {// Note we want to go through this method for compatibility with// existing applications that may have overridden it.mParent.startActivityFromChild(this, intent, requestCode);}}
}

可以看到最终调用了 Instrumentation 的 execStartActivity.

2: Instrumentation

源码可参考:https://www.androidos.net.cn/android/10.0.0_r6/xref/frameworks/base/core/java/android/app/Instrumentation.java

 public ActivityResult execStartActivity(Context who, IBinder contextThread, IBinder token, Activity target,Intent intent, int requestCode, Bundle options) {IApplicationThread whoThread = (IApplicationThread) contextThread;Uri referrer = target != null ? target.onProvideReferrer() : null;if (referrer != null) {intent.putExtra(Intent.EXTRA_REFERRER, referrer);}if (mActivityMonitors != null) {synchronized (mSync) {final int N = mActivityMonitors.size();for (int i=0; i<N; i++) {final ActivityMonitor am = mActivityMonitors.get(i);ActivityResult result = null;if (am.ignoreMatchingSpecificIntents()) {result = am.onStartActivity(intent);}if (result != null) {am.mHits++;return result;} else if (am.match(who, null, intent)) {am.mHits++;if (am.isBlocking()) {return requestCode >= 0 ? am.getResult() : null;}break;}}}}try {intent.migrateExtraStreamToClipData();intent.prepareToLeaveProcess(who);int result = ActivityTaskManager.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;}

可以看到 最终调用 :ActivityTaskManager.getService().startActivity.

3: ActivityTaskManager

源码可参考: https://www.androidos.net.cn/android/10.0.0_r6/xref/frameworks/base/core/java/android/app/ActivityTaskManager.java

首先看下getService方法:

 public static IActivityTaskManager getService() {return IActivityTaskManagerSingleton.get();}

可以看到返回的是一个类型为IActivityTaskManager的对象.

下面是IActivityTaskManagerSingleton的代码:

@UnsupportedAppUsage(trackingBug = 129726065)
private static final Singleton<IActivityTaskManager> IActivityTaskManagerSingleton =new Singleton<IActivityTaskManager>() {@Overrideprotected IActivityTaskManager create() {final IBinder b = ServiceManager.getService(Context.ACTIVITY_TASK_SERVICE);return IActivityTaskManager.Stub.asInterface(b);}};

4: IActivityTaskManager.aidl

源码参考:https://www.androidos.net.cn/android/10.0.0_r6/xref/frameworks/base/core/java/android/app/IActivityTaskManager.aidl

interface IActivityTaskManager {int startActivity(in IApplicationThread caller, in String callingPackage, in Intent intent,in String resolvedType, in IBinder resultTo, in String resultWho, int requestCode,int flags, in ProfilerInfo profilerInfo, in Bundle options);...   
}

我们可以根据aidl来搜索继承IActivityTaskManager.Stub ,从而找到ActivityTaskManagerService.

5: ActivityTaskManagerService

源码参考: https://www.androidos.net.cn/android/10.0.0_r6/xref/frameworks/base/services/core/java/com/android/server/wm/ActivityTaskManagerService.java

我们看下startActivity的调用链路:

   @Overridepublic 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());}

startActivityAsUser(… UserHandle.getCallingUserId())

 @Overridepublic int startActivityAsUser(IApplicationThread caller, String callingPackage,Intent intent, String resolvedType, IBinder resultTo, String resultWho, int requestCode,int startFlags, ProfilerInfo profilerInfo, Bundle bOptions, int userId) {return startActivityAsUser(caller, callingPackage, intent, resolvedType, resultTo,resultWho, requestCode, startFlags, profilerInfo, bOptions, userId,true /*validateIncomingUser*/);}

startActivityAsUser(… ,UserHandle.getCallingUserId(), true)

 int startActivityAsUser(IApplicationThread caller, String callingPackage,Intent intent, String resolvedType, IBinder resultTo, String resultWho, int requestCode,int startFlags, ProfilerInfo profilerInfo, Bundle bOptions, int userId,boolean validateIncomingUser) {enforceNotIsolatedCaller("startActivityAsUser");userId = getActivityStartController().checkTargetUser(userId, validateIncomingUser,Binder.getCallingPid(), Binder.getCallingUid(), "startActivityAsUser");// TODO: Switch to user app stacks here.return getActivityStartController().obtainStarter(intent, "startActivityAsUser").setCaller(caller).setCallingPackage(callingPackage).setResolvedType(resolvedType).setResultTo(resultTo).setResultWho(resultWho).setRequestCode(requestCode).setStartFlags(startFlags).setProfilerInfo(profilerInfo).setActivityOptions(bOptions).setMayWait(userId).execute();}

根据getActivityStartController()

  ActivityStartController getActivityStartController() {return mActivityStartController;}

根据ActivityStartController的obtainStarter()返回ActivityStarter.

/*** @return A starter to configure and execute starting an activity. It is valid until after*         {@link ActivityStarter#execute} is invoked. At that point, the starter should be*         considered invalid and no longer modified or used.*/ActivityStarter obtainStarter(Intent intent, String reason) {return mFactory.obtain().setIntent(intent).setReason(reason);}

6: ActivityStarter

 int execute() {try {// TODO(b/64750076): Look into passing request directly to these methods to allow// for transactional diffs and preprocessing.if (mRequest.mayWait) {return startActivityMayWait(mRequest.caller, mRequest.callingUid,mRequest.callingPackage, mRequest.realCallingPid, mRequest.realCallingUid,mRequest.intent, mRequest.resolvedType,mRequest.voiceSession, mRequest.voiceInteractor, mRequest.resultTo,mRequest.resultWho, mRequest.requestCode, mRequest.startFlags,mRequest.profilerInfo, mRequest.waitResult, mRequest.globalConfig,mRequest.activityOptions, mRequest.ignoreTargetSecurity, mRequest.userId,mRequest.inTask, mRequest.reason,mRequest.allowPendingRemoteAnimationRegistryLookup,mRequest.originatingPendingIntent, mRequest.allowBackgroundActivityStart);} else {return startActivity(mRequest.caller, mRequest.intent, mRequest.ephemeralIntent,mRequest.resolvedType, mRequest.activityInfo, mRequest.resolveInfo,mRequest.voiceSession, mRequest.voiceInteractor, mRequest.resultTo,mRequest.resultWho, mRequest.requestCode, mRequest.callingPid,mRequest.callingUid, mRequest.callingPackage, mRequest.realCallingPid,mRequest.realCallingUid, mRequest.startFlags, mRequest.activityOptions,mRequest.ignoreTargetSecurity, mRequest.componentSpecified,mRequest.outActivity, mRequest.inTask, mRequest.reason,mRequest.allowPendingRemoteAnimationRegistryLookup,mRequest.originatingPendingIntent, mRequest.allowBackgroundActivityStart);}} finally {onExecutionComplete();}}

查看startActivity方法:

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,SafeActivityOptions options,boolean ignoreTargetSecurity, boolean componentSpecified, ActivityRecord[] outActivity,TaskRecord inTask, boolean allowPendingRemoteAnimationRegistryLookup,PendingIntentRecord originatingPendingIntent, boolean allowBackgroundActivityStart) {mSupervisor.getActivityMetricsLogger().notifyActivityLaunching(intent);...ActivityRecord r = new ActivityRecord(mService, callerApp, callingPid, callingUid,callingPackage, intent, resolvedType, aInfo, mService.getGlobalConfiguration(),resultRecord, resultWho, requestCode, componentSpecified, voiceSession != null,mSupervisor, checkedOptions, sourceRecord);if (outActivity != null) {outActivity[0] = r;}...mService.onStartActivitySetDidAppSwitch();mController.doPendingActivityLaunches(false);final int res = startActivity(r, sourceRecord, voiceSession, voiceInteractor, startFlags,true /* doResume */, checkedOptions, inTask, outActivity, restrictedBgActivity);mSupervisor.getActivityMetricsLogger().notifyActivityLaunched(res, outActivity[0]);return res;
}

创建ActivityRecord对象. 调用startActivity()并return, 我们看下另一个startActivity方法, 代码如下:

private int startActivity(final ActivityRecord r, ActivityRecord sourceRecord,IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,int startFlags, boolean doResume, ActivityOptions options, TaskRecord inTask,ActivityRecord[] outActivity, boolean restrictedBgActivity) {int result = START_CANCELED;final ActivityStack startedActivityStack;try {mService.mWindowManager.deferSurfaceLayout();result = startActivityUnchecked(r, sourceRecord, voiceSession, voiceInteractor,startFlags, doResume, options, inTask, outActivity, restrictedBgActivity);} finally {final ActivityStack currentStack = r.getActivityStack();startedActivityStack = currentStack != null ? currentStack : mTargetStack;if (ActivityManager.isStartResultSuccessful(result)) {if (startedActivityStack != null) {// If there is no state change (e.g. a resumed activity is reparented to// top of another display) to trigger a visibility/configuration checking,// we have to update the configuration for changing to different display.final ActivityRecord currentTop =startedActivityStack.topRunningActivityLocked();if (currentTop != null && currentTop.shouldUpdateConfigForDisplayChanged()) {mRootActivityContainer.ensureVisibilityAndConfig(currentTop, currentTop.getDisplayId(),true /* markFrozenIfConfigChanged */, false /* deferResume */);}}} else {// If we are not able to proceed, disassociate the activity from the task.// Leaving an activity in an incomplete state can lead to issues, such as// performing operations without a window container.final ActivityStack stack = mStartActivity.getActivityStack();if (stack != null) {stack.finishActivityLocked(mStartActivity, RESULT_CANCELED,null /* intentResultData */, "startActivity", true /* oomAdj */);}// Stack should also be detached from display and be removed if it's empty.if (startedActivityStack != null && startedActivityStack.isAttached()&& startedActivityStack.numActivities() == 0&& !startedActivityStack.isActivityTypeHome()) {startedActivityStack.remove();}}mService.mWindowManager.continueSurfaceLayout();}postStartActivityProcessing(r, result, startedActivityStack);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, boolean restrictedBgActivity) {// 初始化mInTask:TaskRecord 记录Task的信息, setInitialState(r, options, inTask, doResume, startFlags, sourceRecord, voiceSession,voiceInteractor, restrictedBgActivity);......//    mTargetStack: ActivityStack栈信息.mTargetStack.startActivityLocked(mStartActivity, topFocused, newTask, mKeepCurTransition,mOptions);if (mDoResume) {final ActivityRecord topTaskActivity =mStartActivity.getTaskRecord().topRunningActivityLocked();if (!mTargetStack.isFocusable()|| (topTaskActivity != null && topTaskActivity.mTaskOverlay&& mStartActivity != topTaskActivity)) {// If the activity is not focusable, we can't resume it, but still would like to// make sure it becomes visible as it starts (this will also trigger entry// animation). An example of this are PIP activities.// Also, we don't want to resume activities in a task that currently has an overlay// as the starting activity just needs to be in the visible paused state until the// over is removed.mTargetStack.ensureActivitiesVisibleLocked(mStartActivity, 0, !PRESERVE_WINDOWS);// Go ahead and tell window manager to execute app transition for this activity// since the app transition will not be triggered through the resume channel.mTargetStack.getDisplay().mDisplayContent.executeAppTransition();} 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()&& !mRootActivityContainer.isTopDisplayFocusedStack(mTargetStack)) {mTargetStack.moveToFront("startActivityUnchecked");}mRootActivityContainer.resumeFocusedStacksTopActivities(mTargetStack, mStartActivity, mOptions);}} else if (mStartActivity != null) {mSupervisor.mRecentTasks.add(mStartActivity.getTaskRecord());}mRootActivityContainer.updateUserStack(mStartActivity.mUserId, mTargetStack);mSupervisor.handleNonResizableTaskIfNeeded(mStartActivity.getTaskRecord(),preferredWindowingMode, mPreferredDisplayId, mTargetStack);return START_SUCCESS;
}

mTargetStack: ActivityStack栈信息.

TaskRecord: 记录Task的信息.

mRootActivityContainer: RootActivityContainer

mSupervisor: ActivityStackSupervisor

7: RootActivityContainer

代码参考: https://www.androidos.net.cn/android/10.0.0_r6/xref/frameworks/base/services/core/java/com/android/server/wm/RootActivityContainer.java

mRootActivityContainer.resumeFocusedStacksTopActivities(mTargetStack, mStartActivity, mOptions);

   boolean resumeFocusedStacksTopActivities() {return resumeFocusedStacksTopActivities(null, null, null);}boolean resumeFocusedStacksTopActivities(ActivityStack targetStack, ActivityRecord target, ActivityOptions targetOptions) {if (!mStackSupervisor.readyToResume()) {return false;}boolean result = false;if (targetStack != null && (targetStack.isTopStackOnDisplay()|| getTopDisplayFocusedStack() == targetStack)) {result = targetStack.resumeTopActivityUncheckedLocked(target, targetOptions);}for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {boolean resumedOnDisplay = false;final ActivityDisplay display = mActivityDisplays.get(displayNdx);for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) {final ActivityStack stack = display.getChildAt(stackNdx);final ActivityRecord topRunningActivity = stack.topRunningActivityLocked();if (!stack.isFocusableAndVisible() || topRunningActivity == null) {continue;}if (stack == targetStack) {// Simply update the result for targetStack because the targetStack had// already resumed in above. We don't want to resume it again, especially in// some cases, it would cause a second launch failure if app process was dead.resumedOnDisplay |= result;continue;}if (display.isTopStack(stack) && topRunningActivity.isState(RESUMED)) {// Kick off any lingering app transitions form the MoveTaskToFront operation,// but only consider the top task and stack on that display.stack.executeAppTransition(targetOptions);} else {resumedOnDisplay |= topRunningActivity.makeActiveIfNeeded(target);}}if (!resumedOnDisplay) {// In cases when there are no valid activities (e.g. device just booted or launcher// crashed) it's possible that nothing was resumed on a display. Requesting resume// of top activity in focused stack explicitly will make sure that at least home// activity is started and resumed, and no recursion occurs.final ActivityStack focusedStack = display.getFocusedStack();if (focusedStack != null) {focusedStack.resumeTopActivityUncheckedLocked(target, targetOptions);}}}return result;}

接着我们再看下resumeTopActivityUncheckedLocked方法

8: ActivityStack

代码参考: https://www.androidos.net.cn/android/10.0.0_r6/xref/frameworks/base/services/core/java/com/android/server/wm/ActivityStack.java

 @GuardedBy("mService")boolean resumeTopActivityUncheckedLocked(ActivityRecord prev, ActivityOptions options) {if (mInResumeTopActivity) {// Don't even start recursing.return false;}boolean result = false;try {// Protect against recursion.mInResumeTopActivity = true;result = resumeTopActivityInnerLocked(prev, options);// When resuming the top activity, it may be necessary to pause the top activity (for// example, returning to the lock screen. We suppress the normal pause logic in// {@link #resumeTopActivityUncheckedLocked}, since the top activity is resumed at the// end. We call the {@link ActivityStackSupervisor#checkReadyForSleepLocked} again here// to ensure any necessary pause logic occurs. In the case where the Activity will be// shown regardless of the lock screen, the call to// {@link ActivityStackSupervisor#checkReadyForSleepLocked} is skipped.final ActivityRecord next = topRunningActivityLocked(true /* focusableOnly */);if (next == null || !next.canTurnScreenOn()) {checkReadyForSleep();}} finally {mInResumeTopActivity = false;}return result;}

resumeTopActivityInnerLocked方法:

	 @GuardedBy("mService")private boolean resumeTopActivityInnerLocked(ActivityRecord prev, ActivityOptions options) {if (!mService.isBooting() && !mService.isBooted()) {// Not ready yet!return false;}// Find the next top-most activity to resume in this stack that is not finishing and is// focusable. If it is not focusable, we will fall into the case below to resume the// top activity in the next focusable task.ActivityRecord next = topRunningActivityLocked(true /* focusableOnly */);......if (next.attachedToProcess()) {if (DEBUG_SWITCH) Slog.v(TAG_SWITCH, "Resume running: " + next+ " stopped=" + next.stopped + " visible=" + next.visible);......}else {// Whoops, need to restart this activity!if (!next.hasBeenLaunched) {next.hasBeenLaunched = true;} else {if (SHOW_APP_STARTING_PREVIEW) {next.showStartingWindow(null /* prev */, false /* newTask */,false /* taskSwich */);}if (DEBUG_SWITCH) Slog.v(TAG_SWITCH, "Restarting: " + next);}if (DEBUG_STATES) Slog.d(TAG_STATES, "resumeTopActivityLocked: Restarting " + next);mStackSupervisor.startSpecificActivityLocked(next, true, true);}return true;}

next.attachedToProcess(): true已经附加到进程,恢复页面并更新栈; 否则就需要 (重新) 启动目标 Activity.

此时我们看到调用的是:mStackSupervisor.startSpecificActivityLocked方法.

9: ActivityStackSupervisor

参考代码: https://www.androidos.net.cn/android/10.0.0_r6/xref/frameworks/base/services/core/java/com/android/server/wm/ActivityStackSupervisor.java

startSpecificActivityLocked方法如下:

   void startSpecificActivityLocked(ActivityRecord r, boolean andResume, boolean checkConfig) {// Is this activity's application already running?final WindowProcessController wpc =mService.getProcessController(r.processName, r.info.applicationInfo.uid);boolean knownToBeDead = false;if (wpc != null && wpc.hasThread()) {try {realStartActivityLocked(r, wpc, 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.knownToBeDead = true;}// Suppress transition until the new activity becomes ready, otherwise the keyguard can// appear for a short amount of time before the new process with the new activity had the// ability to set its showWhenLocked flags.if (getKeyguardController().isKeyguardLocked()) {r.notifyUnknownVisibilityLaunched();}try {if (Trace.isTagEnabled(TRACE_TAG_ACTIVITY_MANAGER)) {Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "dispatchingStartProcess:"+ r.processName);}// Post message to start process to avoid possible deadlock of calling into AMS with the// ATMS lock held.final Message msg = PooledLambda.obtainMessage(ActivityManagerInternal::startProcess, mService.mAmInternal, r.processName,r.info.applicationInfo, knownToBeDead, "activity", r.intent.getComponent());mService.mH.sendMessage(msg);} finally {Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);}}

可以看到先根据processName和uid判断应用进程是否存在, 如果存在调用realStartActivityLocked.

否则mService.mH.sendMessage(msg);

10 :ActivityManagerInternal

代码参考: https://www.androidos.net.cn/android/10.0.0_r6/xref/frameworks/base/core/java/android/app/ActivityManagerInternal.java

startProcess方法如下:

/** Starts a given process. */public abstract void startProcess(String processName, ApplicationInfo info,boolean knownToBeDead, String hostingType, ComponentName hostingName);

11: ActivityManagerService

ActivityManagerInternal实现者是AMS,

代码参考: https://www.androidos.net.cn/android/10.0.0_r6/xref/frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java

 @Overridepublic void startProcess(String processName, ApplicationInfo info,boolean knownToBeDead, String hostingType, ComponentName hostingName) {try {if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "startProcess:"+ processName);}synchronized (ActivityManagerService.this) {startProcessLocked(processName, info, knownToBeDead, 0 /* intentFlags */,new HostingRecord(hostingType, hostingName),false /* allowWhileBooting */, false /* isolated */,true /* keepIfLarge */);}} finally {Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);}}

查看startProcessLocked:

  @GuardedBy("this")final ProcessRecord startProcessLocked(String processName,ApplicationInfo info, boolean knownToBeDead, int intentFlags,HostingRecord hostingRecord, boolean allowWhileBooting,boolean isolated, boolean keepIfLarge) {return mProcessList.startProcessLocked(processName, info, knownToBeDead, intentFlags,hostingRecord, allowWhileBooting, isolated, 0 /* isolatedUid */, keepIfLarge,null /* ABI override */, null /* entryPoint */, null /* entryPointArgs */,null /* crashHandler */);}

12: ProcessList

代码参考: https://www.androidos.net.cn/android/10.0.0_r6/xref/frameworks/base/services/core/java/com/android/server/am/ProcessList.java

startProcessLocked(ProcessRecord app, HostingRecord hostingRecord,boolean disableHiddenApiChecks, boolean mountExtStorageFull,String abiOverride) 如下:

  @GuardedBy("mService")boolean startProcessLocked(ProcessRecord app, HostingRecord hostingRecord,boolean disableHiddenApiChecks, boolean mountExtStorageFull,String abiOverride) {if (app.pendingStart) {return true;}......return startProcessLocked(hostingRecord, entryPoint, app, uid, gids,runtimeFlags, mountExternal, seInfo, requiredAbi, instructionSet, invokeWith,startTime);...}

看下最终的startProcessLocked方法:

 @GuardedBy("mService")
boolean startProcessLocked(HostingRecord hostingRecord,String entryPoint,ProcessRecord app, int uid, int[] gids, int runtimeFlags, int mountExternal,String seInfo, String requiredAbi, String instructionSet, String invokeWith,long startTime) {app.pendingStart = true;app.killedByAm = false;app.removed = false;app.killed = false;if (app.startSeq != 0) {Slog.wtf(TAG, "startProcessLocked processName:" + app.processName+ " with non-zero startSeq:" + app.startSeq);}if (app.pid != 0) {Slog.wtf(TAG, "startProcessLocked processName:" + app.processName+ " with non-zero pid:" + app.pid);}final long startSeq = app.startSeq = ++mProcStartSeqCounter;app.setStartParams(uid, hostingRecord, seInfo, startTime);app.setUsingWrapper(invokeWith != null|| SystemProperties.get("wrap." + app.processName) != null);mPendingStarts.put(startSeq, app);if (mService.mConstants.FLAG_PROCESS_START_ASYNC) {if (DEBUG_PROCESSES) Slog.i(TAG_PROCESSES,"Posting procStart msg for " + app.toShortString());mService.mProcStartHandler.post(() -> {try {final Process.ProcessStartResult startResult = startProcess(app.hostingRecord,entryPoint, app, app.startUid, gids, runtimeFlags, mountExternal,app.seInfo, requiredAbi, instructionSet, invokeWith, app.startTime);synchronized (mService) {handleProcessStartedLocked(app, startResult, startSeq);}} catch (RuntimeException e) {synchronized (mService) {Slog.e(ActivityManagerService.TAG, "Failure starting process "+ app.processName, e);mPendingStarts.remove(startSeq);app.pendingStart = false;mService.forceStopPackageLocked(app.info.packageName,UserHandle.getAppId(app.uid),false, false, true, false, false, app.userId, "start failure");}}});return true;} else {try {final Process.ProcessStartResult startResult = startProcess(hostingRecord,entryPoint, app,uid, gids, runtimeFlags, mountExternal, seInfo, requiredAbi, instructionSet,invokeWith, startTime);handleProcessStartedLocked(app, startResult.pid, startResult.usingWrapper,startSeq, false);} catch (RuntimeException e) {Slog.e(ActivityManagerService.TAG, "Failure starting process "+ app.processName, e);app.pendingStart = false;mService.forceStopPackageLocked(app.info.packageName, UserHandle.getAppId(app.uid),false, false, true, false, false, app.userId, "start failure");}return app.pid > 0;}}

查看startProcess方法:

 private Process.ProcessStartResult startProcess(HostingRecord hostingRecord, String entryPoint,ProcessRecord app, int uid, int[] gids, int runtimeFlags, int mountExternal,String seInfo, String requiredAbi, String instructionSet, String invokeWith,long startTime) {try {Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "Start proc: " +app.processName);checkSlow(startTime, "startProcess: asking zygote to start proc");final Process.ProcessStartResult startResult;if (hostingRecord.usesWebviewZygote()) {startResult = startWebView(entryPoint,app.processName, uid, uid, gids, runtimeFlags, mountExternal,app.info.targetSdkVersion, seInfo, requiredAbi, instructionSet,app.info.dataDir, null, app.info.packageName,new String[] {PROC_START_SEQ_IDENT + app.startSeq});} else if (hostingRecord.usesAppZygote()) {final AppZygote appZygote = createAppZygoteForProcessIfNeeded(app);startResult = appZygote.getProcess().start(entryPoint,app.processName, uid, uid, gids, runtimeFlags, mountExternal,app.info.targetSdkVersion, seInfo, requiredAbi, instructionSet,app.info.dataDir, null, app.info.packageName,/*useUsapPool=*/ false,new String[] {PROC_START_SEQ_IDENT + app.startSeq});} else {startResult = Process.start(entryPoint,app.processName, uid, uid, gids, runtimeFlags, mountExternal,app.info.targetSdkVersion, seInfo, requiredAbi, instructionSet,app.info.dataDir, invokeWith, app.info.packageName,new String[] {PROC_START_SEQ_IDENT + app.startSeq});}checkSlow(startTime, "startProcess: returned from zygote!");return startResult;} finally {Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);}}

可以看到分为三种情况:

  1. usesWebviewZygote时调用startWebView
  2. usesAppZygote时创建appZygote,调用appZygote.getProcess().start
  3. 否则直接调用Process.start

我们看下Process类

13: Process

代码参考:https://www.androidos.net.cn/android/10.0.0_r6/xref/frameworks/base/core/java/android/os/Process.java

   public static ProcessStartResult start(@NonNull final String processClass,@Nullable final String niceName,int uid, int gid, @Nullable int[] gids,int runtimeFlags,int mountExternal,int targetSdkVersion,@Nullable String seInfo,@NonNull String abi,@Nullable String instructionSet,@Nullable String appDataDir,@Nullable String invokeWith,@Nullable String packageName,@Nullable String[] zygoteArgs) {return ZYGOTE_PROCESS.start(processClass, niceName, uid, gid, gids,runtimeFlags, mountExternal, targetSdkVersion, seInfo,abi, instructionSet, appDataDir, invokeWith, packageName,/*useUsapPool=*/ true, zygoteArgs);}

其中ZYGOTE_PROCESS就是ZygoteProcess.

 /*** State associated with the zygote process.* @hide*/public static final ZygoteProcess ZYGOTE_PROCESS = new ZygoteProcess();

14: ZygoteProcess

代码参考: https://www.androidos.net.cn/android/10.0.0_r6/xref/frameworks/base/core/java/android/os/ZygoteProcess.java

 public final Process.ProcessStartResult start(@NonNull final String processClass,final String niceName,int uid, int gid, @Nullable int[] gids,int runtimeFlags, int mountExternal,int targetSdkVersion,@Nullable String seInfo,@NonNull String abi,@Nullable String instructionSet,@Nullable String appDataDir,@Nullable String invokeWith,@Nullable String packageName,boolean useUsapPool,@Nullable String[] zygoteArgs) {// TODO (chriswailes): Is there a better place to check this value?if (fetchUsapPoolEnabledPropWithMinInterval()) {informZygotesOfUsapPoolStatus();}try {return startViaZygote(processClass, niceName, uid, gid, gids,runtimeFlags, mountExternal, targetSdkVersion, seInfo,abi, instructionSet, appDataDir, invokeWith, /*startChildZygote=*/ false,packageName, useUsapPool, zygoteArgs);} catch (ZygoteStartFailedEx ex) {Log.e(LOG_TAG,"Starting VM process through Zygote failed");throw new RuntimeException("Starting VM process through Zygote failed", ex);}}

startViaZygote:

private Process.ProcessStartResult startViaZygote(@NonNull final String processClass,@Nullable final String niceName,final int uid, final int gid,@Nullable final int[] gids,int runtimeFlags, int mountExternal,int targetSdkVersion,@Nullable String seInfo,@NonNull String abi,@Nullable String instructionSet,@Nullable String appDataDir,@Nullable String invokeWith,boolean startChildZygote,@Nullable String packageName,boolean useUsapPool,@Nullable String[] extraArgs)throws ZygoteStartFailedEx {ArrayList<String> argsForZygote = new ArrayList<>();// --runtime-args, --setuid=, --setgid=,// and --setgroups= must go firstargsForZygote.add("--runtime-args");argsForZygote.add("--setuid=" + uid);argsForZygote.add("--setgid=" + gid);argsForZygote.add("--runtime-flags=" + runtimeFlags);if (mountExternal == Zygote.MOUNT_EXTERNAL_DEFAULT) {argsForZygote.add("--mount-external-default");} else if (mountExternal == Zygote.MOUNT_EXTERNAL_READ) {argsForZygote.add("--mount-external-read");} else if (mountExternal == Zygote.MOUNT_EXTERNAL_WRITE) {argsForZygote.add("--mount-external-write");} else if (mountExternal == Zygote.MOUNT_EXTERNAL_FULL) {argsForZygote.add("--mount-external-full");} else if (mountExternal == Zygote.MOUNT_EXTERNAL_INSTALLER) {argsForZygote.add("--mount-external-installer");} else if (mountExternal == Zygote.MOUNT_EXTERNAL_LEGACY) {argsForZygote.add("--mount-external-legacy");}argsForZygote.add("--target-sdk-version=" + targetSdkVersion);// --setgroups is a comma-separated listif (gids != null && gids.length > 0) {StringBuilder sb = new StringBuilder();sb.append("--setgroups=");int sz = gids.length;for (int i = 0; i < sz; i++) {if (i != 0) {sb.append(',');}sb.append(gids[i]);}argsForZygote.add(sb.toString());}if (niceName != null) {argsForZygote.add("--nice-name=" + niceName);}if (seInfo != null) {argsForZygote.add("--seinfo=" + seInfo);}if (instructionSet != null) {argsForZygote.add("--instruction-set=" + instructionSet);}if (appDataDir != null) {argsForZygote.add("--app-data-dir=" + appDataDir);}if (invokeWith != null) {argsForZygote.add("--invoke-with");argsForZygote.add(invokeWith);}if (startChildZygote) {argsForZygote.add("--start-child-zygote");}if (packageName != null) {argsForZygote.add("--package-name=" + packageName);}argsForZygote.add(processClass);if (extraArgs != null) {Collections.addAll(argsForZygote, extraArgs);}synchronized(mLock) {// The USAP pool can not be used if the application will not use the systems graphics// driver.  If that driver is requested use the Zygote application start path.return zygoteSendArgsAndGetResult(openZygoteSocketIfNeeded(abi),useUsapPool,argsForZygote);}}

继续查看zygoteSendArgsAndGetResult:

@GuardedBy("mLock")private Process.ProcessStartResult zygoteSendArgsAndGetResult(ZygoteState zygoteState, boolean useUsapPool, @NonNull ArrayList<String> args)throws ZygoteStartFailedEx {// Throw early if any of the arguments are malformed. This means we can// avoid writing a partial response to the zygote.for (String arg : args) {// Making two indexOf calls here is faster than running a manually fused loop due// to the fact that indexOf is a optimized intrinsic.if (arg.indexOf('\n') >= 0) {throw new ZygoteStartFailedEx("Embedded newlines not allowed");} else if (arg.indexOf('\r') >= 0) {throw new ZygoteStartFailedEx("Embedded carriage returns not allowed");}}/** See com.android.internal.os.ZygoteArguments.parseArgs()* Presently the wire format to the zygote process is:* a) a count of arguments (argc, in essence)* b) a number of newline-separated argument strings equal to count** After the zygote process reads these it will write the pid of* the child or -1 on failure, followed by boolean to* indicate whether a wrapper process was used.*/String msgStr = args.size() + "\n" + String.join("\n", args) + "\n";if (useUsapPool && mUsapPoolEnabled && canAttemptUsap(args)) {try {return attemptUsapSendArgsAndGetResult(zygoteState, msgStr);} catch (IOException ex) {// If there was an IOException using the USAP pool we will log the error and// attempt to start the process through the Zygote.Log.e(LOG_TAG, "IO Exception while communicating with USAP pool - "+ ex.getMessage());}}return attemptZygoteSendArgsAndGetResult(zygoteState, msgStr);}

attemptUsapSendArgsAndGetResult:

private Process.ProcessStartResult attemptUsapSendArgsAndGetResult(ZygoteState zygoteState, String msgStr)throws ZygoteStartFailedEx, IOException {try (LocalSocket usapSessionSocket = zygoteState.getUsapSessionSocket()) {final BufferedWriter usapWriter =new BufferedWriter(new OutputStreamWriter(usapSessionSocket.getOutputStream()),Zygote.SOCKET_BUFFER_SIZE);final DataInputStream usapReader =new DataInputStream(usapSessionSocket.getInputStream());usapWriter.write(msgStr);usapWriter.flush();Process.ProcessStartResult result = new Process.ProcessStartResult();result.pid = usapReader.readInt();// USAPs can't be used to spawn processes that need wrappers.result.usingWrapper = false;if (result.pid >= 0) {return result;} else {throw new ZygoteStartFailedEx("USAP specialization failed");}}}

可以看到zygoteState.getUsapSessionSocket 获取到Socket, 而是ZygoteState是 ZygoteProcess的内部类.

openZygoteSocketIfNeeded获取到ZygoteState.

 @GuardedBy("mLock")private ZygoteState openZygoteSocketIfNeeded(String abi) throws ZygoteStartFailedEx {try {attemptConnectionToPrimaryZygote();if (primaryZygoteState.matches(abi)) {return primaryZygoteState;}if (mZygoteSecondarySocketAddress != null) {// The primary zygote didn't match. Try the secondary.attemptConnectionToSecondaryZygote();if (secondaryZygoteState.matches(abi)) {return secondaryZygoteState;}}} catch (IOException ioe) {throw new ZygoteStartFailedEx("Error connecting to zygote", ioe);}throw new ZygoteStartFailedEx("Unsupported zygote ABI: " + abi);}

ZygoteState连接:

当主zygote为空或者closed,发起connect()操作

 @GuardedBy("mLock")private void attemptConnectionToPrimaryZygote() throws IOException {if (primaryZygoteState == null || primaryZygoteState.isClosed()) {primaryZygoteState =ZygoteState.connect(mZygoteSocketAddress, mUsapPoolSocketAddress);maybeSetApiBlacklistExemptions(primaryZygoteState, false);maybeSetHiddenApiAccessLogSampleRate(primaryZygoteState);maybeSetHiddenApiAccessStatslogSampleRate(primaryZygoteState);}}

相关文章:

Android activity 启动流程

Android activity 启动流程 本文主要记录下acitivty的启动流程. 1: Activity 我们都知道启动activity调用方法: startActivity(Intent intent)startActivity(Intent intent, Nullable Bundle options)startActivityForResult(RequiresPermission Intent intent, int reques…...

使用 Go 语言实现 WebSocket的核心逻辑

文章目录 WebSocket 简介时序图核心逻辑Client 结构与功能创建新客户端消息读取逻辑 (ReadPump)发送消息逻辑 (Send)客户端管理器 (ClientManager)WebSocket 处理器处理心跳与长连接 总结 本文将基于 Go 语言&#xff0c;通过使用 gorilla/websocket 库来实现一个简单的聊天应用…...

Linux下的杀毒软件介绍

Linux下的杀毒软件介绍 一、Linux杀毒软件的基本概念和作用二、Linux杀毒软件的选择三、Linux杀毒软件推荐四、Linux杀毒软件对应用进程的影响五、结论在当今数字化和网络化的环境中,保护计算机系统的安全至关重要。尽管Linux操作系统因其开源、稳定且相对安全的特性而较少受到…...

JSONP详解

JSONP&#xff08;JSON with Padding&#xff09;是一种非官方的协议&#xff0c;它允许在服务器端集成Script tags返回至客户端&#xff0c;通过JavaScript callback的形式实现跨域访问。以下是对JSONP的详细解释&#xff1a; 一、JSONP的背景与原理 背景&#xff1a; 由于浏…...

Leetcode—1115. 交替打印 FooBar【中等】(多线程)

2024每日刷题&#xff08;180&#xff09; Leetcode—1115. 交替打印 FooBar C实现代码 class FooBar { private:int n;sem_t fooSem;sem_t barSem;public:FooBar(int n) {this->n n;sem_init(&fooSem, 0, 1);sem_init(&barSem, 0, 0);}~FooBar() {sem_destroy(&…...

Visual Studio Code基础:使用debugpy调试python程序

相关阅读 VS codehttps://blog.csdn.net/weixin_45791458/category_12658212.html?spm1001.2014.3001.5482 一、安装调试器插件 在VS code中可以很轻松地调试Python程序&#xff0c;首先需要安装Python调试器插件&#xff0c;如图1所示。 图1 安装调试器插件 Python Debugge…...

超全!一文详解大型语言模型的11种微调方法

导读&#xff1a;大型预训练模型是一种在大规模语料库上预先训练的深度学习模型&#xff0c;它们可以通过在大量无标注数据上进行训练来学习通用语言表示&#xff0c;并在各种下游任务中进行微调和迁移。随着模型参数规模的扩大&#xff0c;微调和推理阶段的资源消耗也在增加。…...

C 主要函数解析

1、fseek 函数 int fseek(FILE *stream, long offset, int fromwhere); 第一个参数stream为文件指针 第二个参数offset为偏移量&#xff0c;正数表示正向偏移&#xff0c;负数表示负向偏移 第三个参数origin设定从文件的哪里开始偏移,可能取值为&#xff1a;SEEK_CUR、 SEE…...

vue3学习:数字时钟遇到的两个问题

在前端开发学习中&#xff0c;用JavaScript脚本写个数字时钟是很常见的案例&#xff0c;也没什么难度。今天有时间&#xff0c;于是就用Vue的方式来实现这个功能。原本以为是件非常容易的事&#xff0c;没想到却卡在两个问题上&#xff0c;一个问题通过别人的博文已经找到答案&…...

吴恩达深度学习笔记:卷积神经网络(Foundations of Convolutional Neural Networks)3.7-3.8

目录 第四门课 卷积神经网络&#xff08;Convolutional Neural Networks&#xff09;第三周 目标检测&#xff08;Object detection&#xff09;3.7 非极大值抑制&#xff08;Non-max suppression&#xff09;3.8 Anchor Boxes 第四门课 卷积神经网络&#xff08;Convolutional…...

【Linux】最基本的字符设备驱动

前面我们介绍到怎么编译出内核模块.ko文件&#xff0c;然后还加载了这个驱动模块。但是&#xff0c;那个驱动代码还不完善&#xff0c;驱动写好后怎么在应用层使用也没有介绍。 字符设备抽象 Linux内核中将字符设备抽象成一个具体的数据结构&#xff08;struct cdev&#xff…...

利用 Llama 3.1模型 + Dify开源LLM应用开发平台,在你的Windows环境中搭建一套AI工作流

文章目录 1. 什么是Ollama&#xff1f;2. 什么是Dify&#xff1f;3. 下载Ollama4. 安装Ollama5. Ollama Model library模型库6. 本地部署Llama 3.1模型7. 安装Docker Desktop8. 使用Docker-Compose部署Dify9. 注册Dify账号10. 集成本地部署的 Llama 3.1模型11. 集成智谱AI大模型…...

Docker常用命令分享二

docker的用户组管理过程&#xff1a; 1、sudo : 可以让普通用户临时获得root用户的权限&#xff0c;来新建docker用户组 2、普通用户并没有使用sudo的权限 3、先要让root用户把testing用户加入到sudoers的授权文件中 4、sudoers的文件居然是只读的&#xff0c;先解决这个问…...

【一步步开发AI运动小程序】二十、AI运动小程序如何适配相机全屏模式?

引言 受小程序camera组件预览和抽帧图像不一致的特性影响&#xff0c;一直未全功能支持全屏模式&#xff0c;详见本系列文件第四节小程序如何抽帧&#xff1b;随着插件在云上赛事、健身锻炼、AI体测、AR互动场景的深入应用&#xff0c;各开发者迫切的希望能在全屏模式下应用&am…...

[Java基础] 运算符

[Java基础] 基本数据类型 [Java基础] Java HashMap 的数据结构和底层原理 目录 算术运算符 比较运算符 逻辑运算符 位运算符 赋值运算符 其他运算符 常见面试题 Java语言支持哪些类型的运算符&#xff1f; 请解释逻辑运算符&&和&的区别? 请解释条件运…...

[001-02-018].第05节:数据类型及类型转换

我的后端学习大纲 我的Java学习大纲 1、数据类型介绍&#xff1a; 1.0.计算机存储单位&#xff1a; 1.1.基本数据类型介绍&#xff1a; a.整型&#xff1a;byte、short、int、long 1.整型包括&#xff1a;byte、short、int、long&#xff0c;可如下图方式类比记忆&#xff1…...

Netty基础

Netty基础 一级目录I/O请求基础知识Netty如何实现自己的I/O模型 网络框架的选型 Netty整体架构Netty逻辑处理架构网络通信层事件调度层服务编排层 组件关系梳理Netty源码结构 netty是目前最流行的一款高性能java网络编程框架&#xff0c;广泛使用于中间件、直播、社交、游戏等领…...

602,好友申请二:谁有最多的好友

好友申请二&#xff1a;谁有最多的好友 实现 with tmp as (selectrequester_id idfrom RequestAcceptedunion allselectaccepter_id idfrom RequestAccepted )selectid,count(*) num from tmp group by id order by num desc limit 1;...

【Matlab算法MATLAB实现的音频信号时频分析与可视化(附MATLAB完整代码)

MATLAB实现的音频信号时频分析与可视化 前言正文:时频分析实现原理代码实现代码运行结果图及说明结果图:结果说明:总结前言 音频信号的时频分析是信号处理领域中的一个重要研究方向。它允许我们同时观察信号在时间和频率域的特性,为音频处理、语音识别、音乐分析等应用提供…...

界面耻辱纪念堂--可视元素03

更多的迹象表明&#xff0c;关于在程序里使用新的动态界面元素&#xff0c;微软的态度是不确定的&#xff0c;其中一个是仅仅需要对比一下Office97 里的“Coolbars”和“标准工具条”。Coolbar 按钮直到用户指针通过的时候才成为按钮&#xff08;否则是平的&#xff09;。 工具…...

2021-03-15 iview一些问题

1.iview 在使用tree组件时&#xff0c;发现没有set类的方法&#xff0c;只有get&#xff0c;那么要改变tree值&#xff0c;只能遍历treeData&#xff0c;递归修改treeData的checked&#xff0c;发现无法更改&#xff0c;原因在于check模式下&#xff0c;子元素的勾选状态跟父节…...

ffmpeg(四):滤镜命令

FFmpeg 的滤镜命令是用于音视频处理中的强大工具&#xff0c;可以完成剪裁、缩放、加水印、调色、合成、旋转、模糊、叠加字幕等复杂的操作。其核心语法格式一般如下&#xff1a; ffmpeg -i input.mp4 -vf "滤镜参数" output.mp4或者带音频滤镜&#xff1a; ffmpeg…...

Linux-07 ubuntu 的 chrome 启动不了

文章目录 问题原因解决步骤一、卸载旧版chrome二、重新安装chorme三、启动不了&#xff0c;报错如下四、启动不了&#xff0c;解决如下 总结 问题原因 在应用中可以看到chrome&#xff0c;但是打不开(说明&#xff1a;原来的ubuntu系统出问题了&#xff0c;这个是备用的硬盘&a…...

C++中string流知识详解和示例

一、概览与类体系 C 提供三种基于内存字符串的流&#xff0c;定义在 <sstream> 中&#xff1a; std::istringstream&#xff1a;输入流&#xff0c;从已有字符串中读取并解析。std::ostringstream&#xff1a;输出流&#xff0c;向内部缓冲区写入内容&#xff0c;最终取…...

【服务器压力测试】本地PC电脑作为服务器运行时出现卡顿和资源紧张(Windows/Linux)

要让本地PC电脑作为服务器运行时出现卡顿和资源紧张的情况&#xff0c;可以通过以下几种方式模拟或触发&#xff1a; 1. 增加CPU负载 运行大量计算密集型任务&#xff0c;例如&#xff1a; 使用多线程循环执行复杂计算&#xff08;如数学运算、加密解密等&#xff09;。运行图…...

EtherNet/IP转DeviceNet协议网关详解

一&#xff0c;设备主要功能 疆鸿智能JH-DVN-EIP本产品是自主研发的一款EtherNet/IP从站功能的通讯网关。该产品主要功能是连接DeviceNet总线和EtherNet/IP网络&#xff0c;本网关连接到EtherNet/IP总线中做为从站使用&#xff0c;连接到DeviceNet总线中做为从站使用。 在自动…...

Golang——6、指针和结构体

指针和结构体 1、指针1.1、指针地址和指针类型1.2、指针取值1.3、new和make 2、结构体2.1、type关键字的使用2.2、结构体的定义和初始化2.3、结构体方法和接收者2.4、给任意类型添加方法2.5、结构体的匿名字段2.6、嵌套结构体2.7、嵌套匿名结构体2.8、结构体的继承 3、结构体与…...

C# winform教程(二)----checkbox

一、作用 提供一个用户选择或者不选的状态&#xff0c;这是一个可以多选的控件。 二、属性 其实功能大差不差&#xff0c;除了特殊的几个外&#xff0c;与button基本相同&#xff0c;所有说几个独有的 checkbox属性 名称内容含义appearance控件外观可以变成按钮形状checkali…...

2025-05-08-deepseek本地化部署

title: 2025-05-08-deepseek 本地化部署 tags: 深度学习 程序开发 2025-05-08-deepseek 本地化部署 参考博客 本地部署 DeepSeek&#xff1a;小白也能轻松搞定&#xff01; 如何给本地部署的 DeepSeek 投喂数据&#xff0c;让他更懂你 [实验目的]&#xff1a;理解系统架构与原…...

JavaScript 标签加载

目录 JavaScript 标签加载script 标签的 async 和 defer 属性&#xff0c;分别代表什么&#xff0c;有什么区别1. 普通 script 标签2. async 属性3. defer 属性4. type"module"5. 各种加载方式的对比6. 使用建议 JavaScript 标签加载 script 标签的 async 和 defer …...