frameworks 之FallbackHome

news2024/11/15 3:49:50

frameworks 之FallbackHome

  • FallbackHome 启动
  • 启动 Activity 流程
  • 创建进程
  • ActivityThrad 与 AMS
  • 启动真正的 Launcher

mActivityManagerService 创建后会启动 FallbackHome 再启动桌面程序。因为此时还没解锁,桌面又涉及很多其他应用程序相关,所以要等待用户解锁后才能启动桌面程序,这就是为啥屏幕解锁后只有壁纸 等待一下才看到桌面。

涉及到的类如下

  • frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
  • frameworks/base/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
  • frameworks/base/services/core/java/com/android/server/wm/RootWindowContainer.java
  • frameworks/base/services/core/java/com/android/server/wm/ActivityStartController.java
  • frameworks/base/services/core/java/com/android/server/wm/ActivityStarter.java
  • frameworks/base/services/core/java/com/android/server/wm/Task.java
  • frameworks/base/services/core/java/com/android/server/wm/TaskFragment.java
  • frameworks/base/services/core/java/com/android/server/am/ProcessList.java
  • frameworks/base/core/java/android/os/Process.java
  • frameworks/base/core/java/android/os/ZygoteProcess.java
  • frameworks/base/core/java/android/app/ActivityThread.java
  • frameworks/base/services/core/java/com/android/server/wm/ClientLifecycleManager.java
  • packages/apps/Settings/src/com/android/settings/FallbackHome.java

FallbackHome 启动

systemService 启动 activityMangerService 后 会在 systemReady 方法中 启动对应的 fallbackHome , 其中mAtmInternal 为 ActivityTaskManagerService

public void systemReady(final Runnable goingCallback, @NonNull TimingsTraceAndSlog t) {
			...
			// Enable home activity for system user, so that the system can always boot. We don't
            // do this when the system user is not setup since the setup wizard should be the one
            // to handle home activity in this case.
            if (UserManager.isSplitSystemUser() &&
                    Settings.Secure.getInt(mContext.getContentResolver(),
                         Settings.Secure.USER_SETUP_COMPLETE, 0) != 0
                    || SystemProperties.getBoolean(SYSTEM_USER_HOME_NEEDED, false)) {
                t.traceBegin("enableHomeActivity");
                ComponentName cName = new ComponentName(mContext, SystemUserHomeActivity.class);
                try {
                    AppGlobals.getPackageManager().setComponentEnabledSetting(cName,
                            PackageManager.COMPONENT_ENABLED_STATE_ENABLED, 0,
                            UserHandle.USER_SYSTEM);
                } catch (RemoteException e) {
                    throw e.rethrowAsRuntimeException();
                }
                t.traceEnd();
            }
			// 启动
            if (bootingSystemUser) {
                t.traceBegin("startHomeOnAllDisplays");
                mAtmInternal.startHomeOnAllDisplays(currentUserId, "systemReady");
                t.traceEnd();
            }
}

startHomeOnAllDisplays 里面又直接调用了 RootWindowContainer 遍历每个屏幕 启动对应的 startHomeOnDisplay 方法 ,接着又调用 startHomeOnDisplay 方法。观察该方法 我们可以发现里面的 startHomeOnTaskDisplayArea 方法 大概就是启动对应的 fallbackhome。

// frameworks/base/services/core/java/com/android/server/wm/RootWindowContainer.java
	boolean startHomeOnAllDisplays(int userId, String reason) {
        boolean homeStarted = false;
        for (int i = getChildCount() - 1; i >= 0; i--) {
            final int displayId = getChildAt(i).mDisplayId;
            homeStarted |= startHomeOnDisplay(userId, reason, displayId);
        }
        return homeStarted;
    }
    // 启动
	boolean startHomeOnDisplay(int userId, String reason, int displayId, boolean allowInstrumenting,
            boolean fromHomeKey) {
        // Fallback to top focused display or default display if the displayId is invalid.
        if (displayId == INVALID_DISPLAY) {
            final Task rootTask = getTopDisplayFocusedRootTask();
            displayId = rootTask != null ? rootTask.getDisplayId() : DEFAULT_DISPLAY;
        }

        final DisplayContent display = getDisplayContent(displayId);
        // 其中的 startHomeOnTaskDisplayArea 启动对应的 fallbackHome  
        return display.reduceOnAllTaskDisplayAreas((taskDisplayArea, result) ->
                        result | startHomeOnTaskDisplayArea(userId, reason, taskDisplayArea,
                                allowInstrumenting, fromHomeKey),
                false /* initValue */);
    }

进入 startHomeOnTaskDisplayArea 可以看到 先获取对应的 homeIntent 因为还没解锁 所以获取不到 Launcher 要是解锁后 因为Launcher优先级比较高 就会变为获取到 Launcher 其中 intent 为 public static final String CATEGORY_HOME = “android.intent.category.HOME”;
找到 对应的 intent 后 进入 startHomeActivity

boolean startHomeOnTaskDisplayArea(int userId, String reason, TaskDisplayArea taskDisplayArea,
            boolean allowInstrumenting, boolean fromHomeKey) {
        ...
        // 获取 intent 
		ActivityInfo aInfo = null;
        if (taskDisplayArea == getDefaultTaskDisplayArea()) {
            homeIntent = mService.getHomeIntent();
            aInfo = resolveHomeActivity(userId, homeIntent);
        } else if (shouldPlaceSecondaryHomeOnDisplayArea(taskDisplayArea)) {
            Pair<ActivityInfo, Intent> info = resolveSecondaryHomeActivity(userId, taskDisplayArea);
            aInfo = info.first;
            homeIntent = info.second;
        }
		...
		// 启动 activity 
		// Update the reason for ANR debugging to verify if the user activity is the one that
        // actually launched.
        
        final String myReason = reason + ":" + userId + ":" + UserHandle.getUserId(
                aInfo.applicationInfo.uid) + ":" + taskDisplayArea.getDisplayId();
        mService.getActivityStartController().startHomeActivity(homeIntent, aInfo, myReason,
                taskDisplayArea);
}

进入后 会看到 obtainStarter 进入对应的 设置对应的 参数后 ,执行 execute 启动actiity

// frameworks/base/services/core/java/com/android/server/wm/ActivityStartController.java
void startHomeActivity(Intent intent, ActivityInfo aInfo, String reason,
            TaskDisplayArea taskDisplayArea) {
    ...
    // 进入启动的流程,设置对应的 参数后 ,执行 execute 启动actiity 
	mLastHomeActivityStartResult = obtainStarter(intent, "startHomeActivity: " + reason)
                .setOutActivity(tmpOutRecord)
                .setCallingUid(0)
                .setActivityInfo(aInfo)
                .setActivityOptions(options.toBundle())
                .execute();
}

进入 ActivityStarter , 执行对应的 execute 方法。 execute方法前面是拼接参数。最终执行启动是 executeRequest

// frameworks/base/services/core/java/com/android/server/wm/ActivityStarter.java
int execute() {
	...
	res = executeRequest(mRequest);
}

启动 Activity 流程

进入 流程 IApplicationThread 作为 对应 应用的 ActivityThread 和应用通信的 Ibinder 还没创建 所以为空。 创建对应的 ActivityRecord 后执行 startActivityUnchecked注意传入该 onResume 参数为 true 调用该 方法后,又会调用 厘米的 startActivityInner

...
// frameworks/base/services/core/java/com/android/server/wm/ActivityStarter.java
private int executeRequest(Request request) {
	final ActivityRecord r = new ActivityRecord.Builder(mService)
	... 
	// 执行启动,注意传入该 onResume 参数为 true
	mLastStartActivityResult = startActivityUnchecked(r, sourceRecord, voiceSession,
                request.voiceInteractor, startFlags, true /* doResume */, checkedOptions,
                inTask, inTaskFragment, restrictedBgActivity, intentGrants);
}

private int startActivityUnchecked(final ActivityRecord r, ActivityRecord sourceRecord,
            IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
            int startFlags, boolean doResume, ActivityOptions options, Task inTask,
            TaskFragment inTaskFragment, boolean restrictedBgActivity,
            NeededUriGrants intentGrants) {
			mService.deferWindowLayout();
            Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "startActivityInner");
            // 启动
            result = startActivityInner(r, sourceRecord, voiceSession, voiceInteractor,
                    startFlags, doResume, options, inTask, inTaskFragment, restrictedBgActivity,
                    intentGrants);
            startResultSuccessful = ActivityManager.isStartResultSuccessful(result);
}

因为 前面传入的 onResume 为 true ,所以进入该方法后,会执行 resumeFocusedTasksTopActivities

int startActivityInner(final ActivityRecord r, ActivityRecord sourceRecord,
            IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
            int startFlags, boolean doResume, ActivityOptions options, Task inTask,
            TaskFragment inTaskFragment, boolean restrictedBgActivity,
            NeededUriGrants intentGrants) {
            ...
            // 为 true 进入该方法 最终调用 resumeFocusedTasksTopActivities
			if (mDoResume) {
            	...
                mRootWindowContainer.resumeFocusedTasksTopActivities(
                        mTargetRootTask, mStartActivity, mOptions, mTransientLaunch);
            }
        }
}

接着里面调用 task 的 resumeTopActivityUncheckedLocked

// frameworks/base/services/core/java/com/android/server/wm/RootWindowContainer.java
boolean resumeFocusedTasksTopActivities(
            Task targetRootTask, ActivityRecord target, ActivityOptions targetOptions,
            boolean deferPause) {
        ...
        boolean result = false;
        if (targetRootTask != null && (targetRootTask.isTopRootTaskInDisplayArea()
                || getTopDisplayFocusedRootTask() == targetRootTask)) {
            result = targetRootTask.resumeTopActivityUncheckedLocked(target, targetOptions,
                    deferPause);
        }

}

调用 resumeTopActivityInnerLocked, 调用后 里面又会继续调用 resumeTopActivity 方法。

// frameworks/base/services/core/java/com/android/server/wm/Task.java
boolean resumeTopActivityUncheckedLocked(ActivityRecord prev, ActivityOptions options,
            boolean deferPause) {
            ... 
			if (isLeafTask()) {
                if (isFocusableAndVisible()) {
                    someActivityResumed = resumeTopActivityInnerLocked(prev, options, deferPause);
                }
            } 
}

private boolean resumeTopActivityInnerLocked(ActivityRecord prev, ActivityOptions options,
            boolean deferPause) {
        ... 
		final TaskFragment topFragment = topActivity.getTaskFragment();
        resumed[0] = topFragment.resumeTopActivity(prev, options, deferPause);
}

resumeTop 因为 还没创建对应的进程 所以 next.attachedToProcess() 为 false , 进入 else 判断, 会执行 startSpecificActivity

// frameworks/base/services/core/java/com/android/server/wm/TaskFragment.java
final boolean resumeTopActivity(ActivityRecord prev, ActivityOptions options,
            boolean deferPause) {
	if (next.attachedToProcess()) {
		...
	} else {
		// 创建对应的进程
		mTaskSupervisor.startSpecificActivity(next, true, true);
	}
}

执行 ActivityTaskSupervisor 里面 判断是否进程存在,不存在 执行调用 startProcessAsync 创建进程,存在则调用 realStartActivityLocked

// frameworks/base/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
void startSpecificActivity(ActivityRecord r, boolean andResume, boolean checkConfig) {
		//1.如果wpc不为null且hasThread表示应用Activity所属进程存在,直接realStartActivityLocked启动Activity
		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;
        }
		// 创建进程
		mService.startProcessAsync(r, knownToBeDead, isTop, isTop ? "top-activity" : "activity");
}

创建进程

PooledLambda.obtainMessage() 获取一个 Message,并为其指定 Callback,message 在判断到有 callback 参数时候 会执行 run 方法。 即执行 mService.mAmInternal 对象的 ActivityManagerInternal::startProcess 函数,函数入参为 r.processName、r.info.applicationInfo 等5个参数。由此可知,ActivityManagerInternal 的 startProcess() 方法是请求启动应用进程的起点.

// frameworks/base/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
void startProcessAsync(ActivityRecord activity, boolean knownToBeDead, boolean isTop,
            String hostingType) {
	final Message m = PooledLambda.obtainMessage(ActivityManagerInternal::startProcess,
                    mAmInternal, activity.processName, activity.info.applicationInfo, knownToBeDead,
                    isTop, hostingType, activity.intent.getComponent());
    mH.sendMessage(m);
}

在这里插入图片描述
我们之前说过 mAmInternal 的实现类为 ActivityManagerService, 查看 startProcess 方法。里面调用 startProcessLocked 方法。里面在调用 startProcessLocked

// frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
public void startProcess(String processName, ApplicationInfo info, boolean knownToBeDead,
                boolean isTop, String hostingType, ComponentName hostingName) {
    ...
    // 启动进程 isolated 传入参数为 false 
	startProcessLocked(processName, info, knownToBeDead, 0 /* intentFlags */,
                            new HostingRecord(hostingType, hostingName, isTop),
                            ZYGOTE_POLICY_FLAG_LATENCY_SENSITIVE, false /* allowWhileBooting */,
                            false /* isolated */);
}

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

接着调用 ProcessList 里面的 他会先判断 app 即 ProcessRecord 是否为空。如果为空 创建对应的对象,然后调用 startProcessLocked 接着创建,, 里面又会接着调用 startProcessLocked

// frameworks/base/services/core/java/com/android/server/am/ProcessList.java
ProcessRecord startProcessLocked(String processName, ApplicationInfo info,
            boolean knownToBeDead, int intentFlags, HostingRecord hostingRecord,
            int zygotePolicyFlags, boolean allowWhileBooting, boolean isolated, int isolatedUid,
            String abiOverride, String entryPoint, String[] entryPointArgs, Runnable crashHandler) {
		if (app == null) {
            checkSlow(startTime, "startProcess: creating new process record");
            app = newProcessRecordLocked(info, processName, isolated, isolatedUid, hostingRecord);
            ...
        } else {
			...
		}
		// 创建进程
		final boolean success =
                startProcessLocked(app, hostingRecord, zygotePolicyFlags, abiOverride);
}

	boolean startProcessLocked(ProcessRecord app, HostingRecord hostingRecord,
            int zygotePolicyFlags, String abiOverride) {
        return startProcessLocked(app, hostingRecord, zygotePolicyFlags,
                false /* disableHiddenApiChecks */, false /* disableTestApiChecks */,
                abiOverride);
    }

startProcessLocked 里面又对 app 属性进行赋值,最终在调用 startProcessLocked, 里面接着调用 startProcess 方法 , 其中 entryPoint 为 android.app.ActivityThread。 这就是 应用程序 启动会 走 ActivityThread 入口

boolean startProcessLocked(ProcessRecord app, HostingRecord hostingRecord,
            int zygotePolicyFlags, boolean disableHiddenApiChecks, boolean disableTestApiChecks,
            String abiOverride) {
            // Start the process.  It will either succeed and return a result containing
            // the PID of the new process, or else throw a RuntimeException.
            // 创建进程时候,入口
            final String entryPoint = "android.app.ActivityThread";
	return startProcessLocked(hostingRecord, entryPoint, app, uid, gids,
                    runtimeFlags, zygotePolicyFlags, mountExternal, seInfo, requiredAbi,
                    instructionSet, invokeWith, startTime);
}

boolean startProcessLocked(HostingRecord hostingRecord, String entryPoint, ProcessRecord app,
            int uid, int[] gids, int runtimeFlags, int zygotePolicyFlags, int mountExternal,
            String seInfo, String requiredAbi, String instructionSet, String invokeWith,
            long startTime) {
            	// 创建进程
				final Process.ProcessStartResult startResult = startProcess(hostingRecord,
                        entryPoint, app,
                        uid, gids, runtimeFlags, zygotePolicyFlags, mountExternal, seInfo,
                        requiredAbi, instructionSet, invokeWith, startTime);
                handleProcessStartedLocked(app, startResult.pid, startResult.usingWrapper,
                        startSeq, false);
}

startProcess 方法 会对各种情况做判断,启动 fallbackHome 是走 Process.start

private Process.ProcessStartResult startProcess(HostingRecord hostingRecord, String entryPoint,
            ProcessRecord app, int uid, int[] gids, int runtimeFlags, int zygotePolicyFlags,
            int mountExternal, String seInfo, String requiredAbi, String instructionSet,
            String invokeWith, long startTime) {
            // 启动webview的
			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,
                        app.getDisabledCompatChanges(),
                        new String[]{PROC_START_SEQ_IDENT + app.getStartSeq()});
            } else if (hostingRecord.usesAppZygote()) {
                final AppZygote appZygote = createAppZygoteForProcessIfNeeded(app);

                // We can't isolate app data and storage data as parent zygote already did that.
                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,
                        /*zygotePolicyFlags=*/ ZYGOTE_POLICY_FLAG_EMPTY, isTopApp,
                        app.getDisabledCompatChanges(), pkgDataInfoMap, allowlistedAppDataInfoMap,
                        false, false,
                        new String[]{PROC_START_SEQ_IDENT + app.getStartSeq()});
            } else {
            	// 应用启动
                regularZygote = true;
                startResult = Process.start(entryPoint,
                        app.processName, uid, uid, gids, runtimeFlags, mountExternal,
                        app.info.targetSdkVersion, seInfo, requiredAbi, instructionSet,
                        app.info.dataDir, invokeWith, app.info.packageName, zygotePolicyFlags,
                        isTopApp, app.getDisabledCompatChanges(), pkgDataInfoMap,
                        allowlistedAppDataInfoMap, bindMountAppsData, bindMountAppStorageDirs,
                        new String[]{PROC_START_SEQ_IDENT + app.getStartSeq()});
            }
}

而progress start方法 里面实际又是调用 ZygoteProcess 的 start 方法 。 zygoteProcess 里面又调用 startViaZygote 方法。 该方法里面会对 创建的一些参数 进行初始化。其中 之前传入的 entryPoint 作为参数 放到最后。根据之前的文章 知道会解析 – 的参数,把没有的参数 放到remaind 参数里面 反射调用 启动main 方法

// frameworks/base/core/java/android/os/ZygoteProcess.java
 public final Process.ProcessStartResult start(@NonNull final String processClass,....{
	....
	return startViaZygote(processClass, niceName, uid, gid, gids,
                    runtimeFlags, mountExternal, targetSdkVersion, seInfo,
                    abi, instructionSet, appDataDir, invokeWith, /*startChildZygote=*/ false,
                    packageName, zygotePolicyFlags, isTopApp, disabledCompatChanges,
                    pkgDataInfoMap, allowlistedDataInfoList, bindMountAppsData,
                    bindMountAppStorageDirs, zygoteArgs);
}

private Process.ProcessStartResult startViaZygote(@NonNull final String processClass ...){
		..
		// 初始化参数
		ArrayList<String> argsForZygote = new ArrayList<>();

        // --runtime-args, --setuid=, --setgid=,
        // and --setgroups= must go first
        argsForZygote.add("--runtime-args");
        argsForZygote.add("--setuid=" + uid);
        argsForZygote.add("--setgid=" + gid);
		...
		// 最后要反射的参数
		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),
                                              zygotePolicyFlags,
                                              argsForZygote);
        }
}

zygoteSendArgsAndGetResult 接着调用 attemptZygoteSendArgsAndGetResult ,将参数拼接为 string 字符串, 该方法通过 socket 将消息 发送给 zygote 创建进程。

private Process.ProcessStartResult zygoteSendArgsAndGetResult(
            ZygoteState zygoteState, int zygotePolicyFlags, @NonNull ArrayList<String> args)
            throws ZygoteStartFailedEx {
		...
		if (shouldAttemptUsapLaunch(zygotePolicyFlags, 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);
}
// 发送对应 socket
private Process.ProcessStartResult attemptZygoteSendArgsAndGetResult(
            ZygoteState zygoteState, String msgStr) throws ZygoteStartFailedEx {
        try {
            final BufferedWriter zygoteWriter = zygoteState.mZygoteOutputWriter;
            final DataInputStream zygoteInputStream = zygoteState.mZygoteInputStream;

            zygoteWriter.write(msgStr);
            zygoteWriter.flush();

            // Always read the entire result from the input stream to avoid leaving
            // bytes in the stream for future process starts to accidentally stumble
            // upon.
            Process.ProcessStartResult result = new Process.ProcessStartResult();
            // 读取返回创建的pid
            result.pid = zygoteInputStream.readInt();
            result.usingWrapper = zygoteInputStream.readBoolean();

            if (result.pid < 0) {
                throw new ZygoteStartFailedEx("fork() failed");
            }

            return result;
        } catch (IOException ex) {
            zygoteState.close();
            Log.e(LOG_TAG, "IO Exception while communicating with Zygote - "
                    + ex.toString());
            throw new ZygoteStartFailedEx(ex);
        }
    }

发送 socket 对应的 参数 zygoteWriter 是在 ZygoteState 初始化的时候, 传入 。
在这里插入图片描述
而该参数又是通过 connect 方法的时候 。创建 LocalSocket。 创建socket。并建立连接。 创建该 socket 传入的地址 为 LocalSocketAddress zygoteSocketAddress 参数。该参数又是在 ZygoteProcess 初始化的时候 创建 对应字符串 为 zygote
在这里插入图片描述

	// frameworks/base/core/java/com/android/internal/os/Zygote.java
	/**
     * @hide for internal use only.
     */
    public static final String PRIMARY_SOCKET_NAME = "zygote";

    /**
     * @hide for internal use only.
     */
    public static final String SECONDARY_SOCKET_NAME = "zygote_secondary";
    
// frameworks/base/core/java/android/os/ZygoteProcess.java
public ZygoteProcess() {
        mZygoteSocketAddress =
                new LocalSocketAddress(Zygote.PRIMARY_SOCKET_NAME,
                                       LocalSocketAddress.Namespace.RESERVED);
        mZygoteSecondarySocketAddress =
                new LocalSocketAddress(Zygote.SECONDARY_SOCKET_NAME,
                                       LocalSocketAddress.Namespace.RESERVED);

        mUsapPoolSocketAddress =
                new LocalSocketAddress(Zygote.USAP_POOL_PRIMARY_SOCKET_NAME,
                                       LocalSocketAddress.Namespace.RESERVED);
        mUsapPoolSecondarySocketAddress =
                new LocalSocketAddress(Zygote.USAP_POOL_SECONDARY_SOCKET_NAME,
                                       LocalSocketAddress.Namespace.RESERVED);

        // This constructor is used to create the primary and secondary Zygotes, which can support
        // Unspecialized App Process Pools.
        mUsapPoolSupported = true;
    }

这样就把消息 发给了 zygote 那边。之前的文章 ZygoteInit 执行 main 函数后,会执行 zygoteServer.runSelectLoop(abiList); 方法 里面为 死循环 一直等待新消息 唤醒。收到消息后 执行 processCommand

					if (pollIndex == 0) {
                        // Zygote server socket
                        ZygoteConnection newPeer = acceptCommandPeer(abiList);
                        peers.add(newPeer);
                        socketFDs.add(newPeer.getFileDescriptor());
                    } else if (pollIndex < usapPoolEventFDIndex) {
                        // Session socket accepted from the Zygote server socket

                        try {
                            ZygoteConnection connection = peers.get(pollIndex);
                            boolean multipleForksOK = !isUsapPoolEnabled()
                                    && ZygoteHooks.isIndefiniteThreadSuspensionSafe();
                            final Runnable command =
                                    connection.processCommand(this, multipleForksOK);
					}

processCommand 方法方法里面 又是我们熟悉的 fork 进程 Zygote.forkAndSpecialize
fork 出进程后 调用 handleChildProc 方法处理进程 。处理应用程序进程:将 ActivityThread 的 main() 方法封装到 Runnable 中
在这里插入图片描述
因为 非zygote 进程,所以 执行 ZygoteInit.zygoteInit 。 说明:ZygoteInit.nativeZygoteInit() 启动了 Binder 线程池,应用程序进程此后可以通过 Binder 实现跨进程通讯。RuntimeInit.applicationInit 里面通过调用 findStaticMain 基于反射创建 ActivityThread并返回 runable。 最终会返回给 ZygoteInit 的 main() 方法执行其 run() 方法

private Runnable handleChildProc(ZygoteArguments parsedArgs,
            FileDescriptor pipeFd, boolean isZygote) {
			...
			if (!isZygote) {
                return ZygoteInit.zygoteInit(parsedArgs.mTargetSdkVersion,
                        parsedArgs.mDisabledCompatChanges,
                        parsedArgs.mRemainingArgs, null /* classLoader */);
            } else {
                return ZygoteInit.childZygoteInit(
                        parsedArgs.mRemainingArgs  /* classLoader */);
            }
}

public static Runnable zygoteInit(int targetSdkVersion, long[] disabledCompatChanges,
            String[] argv, ClassLoader classLoader) {
        if (RuntimeInit.DEBUG) {
            Slog.d(RuntimeInit.TAG, "RuntimeInit: Starting application from zygote");
        }

        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ZygoteInit");
        RuntimeInit.redirectLogStreams();

        RuntimeInit.commonInit();
        // //启动 Binder 线程池
        ZygoteInit.nativeZygoteInit();
        return RuntimeInit.applicationInit(targetSdkVersion, disabledCompatChanges, argv,
                classLoader);
    }

在这里插入图片描述

在这里插入图片描述
这样就调用到 ActivityThrad main 方法。

ActivityThrad 与 AMS

进入 ActivityThrad 方法,里面包含对 Looper 的创建。创建 ActivityThread 因为非系统进程 所以 attach 传入为 false 。 loop 开启 循环

public static void main(String[] args) {
	// looper跟当前线程创建
	Looper.prepareMainLooper();
	// 创建 ActivityThread 
	ActivityThread thread = new ActivityThread();
	... 
    thread.attach(false, startSeq);
	if (sMainThreadHandler == null) {
		//创建主线程 H 类:return mH, mH 在定义时就被初始化:final H mH = new H()
		sMainThreadHandler = thread.getHandler();
	}
	...
	//开启消息循环
	Looper.loop();
}

查看 attach 方法 因为传入为 false 。所以第一个分支。其中 mgr 为 AMS 的binder对象。这样调用 attachApplication 将当前 app 进程的 ibinder对象传给AMS 这样 AMS 就可以调用我们进程的方法

// frameworks/base/core/java/android/app/ActivityThread.java
private void attach(boolean system, long startSeq) {
	sCurrentActivityThread = this;
	mSystemThread = system;
	if (!system) { //system  = false
		...
		//mAppThread 为 ApplicationThread 类型,在定义时就被初始化
		RuntimeInit.setApplicationObject(mAppThread.asBinder());
		//获取 AMS
		final IActivityManager mgr = ActivityManager.getService();
		try {
			mgr.attachApplication(mAppThread, startSeq);
		}
		...
	}
	...
}

IActivityManger 也是通过 ServiceManger 获取, IActivityManger 的实现 为 ActivityManagerService

// frameworks/base/core/java/android/app/ActivityManager.java
// 获取单例
private static IActivityTaskManager getTaskService() {
        return ActivityTaskManager.getService();
    }

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

查看 ActivityManagerService 里面的 attachApplication 方法,获取了调用的pid 后,接着又调用了 attachApplicationLocked 方法。里面通过 bindApplication 调用 Applicaiton的 onCreate 方法。getCommonServicesLocked() 方法中将 WMS、DMS、IMS、PMS 等服务的 name 和 binder 添加到 mAppBindArgs(类型:ArrayMap<String, IBinder>)中,并将其返回。这样我们在Activity 中就可以通过context 获取。 最后在调用 mAtmInternal.attachApplication 该方法会调用获取对应 入口activity。

	@Override
    public final void attachApplication(IApplicationThread thread, long startSeq) {
        if (thread == null) {
            throw new SecurityException("Invalid application interface");
        }
        synchronized (this) {
            int callingPid = Binder.getCallingPid();
            final int callingUid = Binder.getCallingUid();
            final long origId = Binder.clearCallingIdentity();
            attachApplicationLocked(thread, callingPid, callingUid, startSeq);
            Binder.restoreCallingIdentity(origId);
        }
    }

private boolean attachApplicationLocked(@NonNull IApplicationThread thread,
            int pid, int callingUid, long startSeq) {
         // 绑定AIDL监听
		try {
            AppDeathRecipient adr = new AppDeathRecipient(
                    app, pid, thread);
            thread.asBinder().linkToDeath(adr, 0);
            app.setDeathRecipient(adr);
        } catch (RemoteException e) {
            app.resetPackageList(mProcessStats);
            mProcessList.startProcessLocked(app,
                    new HostingRecord("link fail", processName),
                    ZYGOTE_POLICY_FLAG_EMPTY);
            return false;
        }
        ...
        // 走else 的 bindApplication 调用 Applicaiton的 onCreate 方法。
		if (app.isolatedEntryPoint != null) {
			...
		} else if (instr2 != null) {
			thread.bindApplication(processName, appInfo, providers, instr2.mClass, profilerInfo, instr2.mArguments,
					instr2.mWatcher, instr2.mUiAutomationConnection, testMode, mBinderTransactionTrackingEnabled, 
					enableTrackAllocation, isRestrictedBackupMode || !normalMode, app.isPersistent(),
					new Configuration(app.getWindowProcessController().getConfiguration()), app.compat, 
					getCommonServicesLocked(app.isolated), mCoreSettingsObserver.getCoreSettingsLocked(),
					buildSerial, autofillOptions, contentCaptureOptions, app.mDisabledCompatChanges);
		} else {
			//第 4 个参数为 ComponentName 类型,影响 Instrumentation 的创建方式
			thread.bindApplication(processName, appInfo, providers, null, profilerInfo, null, null, null, testMode,
					mBinderTransactionTrackingEnabled, enableTrackAllocation, isRestrictedBackupMode || !normalMode, 
					app.isPersistent(), new Configuration(app.getWindowProcessController().getConfiguration()),
					app.compat, getCommonServicesLocked(app.isolated), mCoreSettingsObserver.getCoreSettingsLocked(),
					buildSerial, autofillOptions, contentCaptureOptions, app.mDisabledCompatChanges);
            }
		...
		app.makeActive(thread, mProcessStats);
		// See if the top visible activity is waiting to run in this process...
		// 启动对应的launch Activity
        if (normalMode) {
            try {
                didSomething = mAtmInternal.attachApplication(app.getWindowProcessController());
            } 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 {
                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;
            }
        }
		
}

在这里插入图片描述

查看 ActivityThrad 里面的 BindApplication 方法。通过将传回来的服务,放到 ServiceManger 里面。 在发送 消息 到 BIND_APPLICATION


public final void bindApplication(String processName, ApplicationInfo appInfo, List<ProviderInfo> providers, 
		ComponentName instrumentationName, ProfilerInfo profilerInfo, Bundle instrumentationArgs,
		IInstrumentationWatcher instrumentationWatcher, IUiAutomationConnection instrumentationUiConnection, 
		int debugMode, boolean enableBinderTracking, boolean trackAllocation, boolean isRestrictedBackupMode, 
		boolean persistent, Configuration config, CompatibilityInfo compatInfo, Map services, Bundle coreSettings,
		String buildSerial, AutofillOptions autofillOptions, ContentCaptureOptions contentCaptureOptions, long[] disabledCompatChanges) {
	if (services != null) {
		...
		// sCache.putAll(services), sCache 为 Map<String, IBinder> 类型
		ServiceManager.initServiceCache(services);
	}
	//relaunchAllActivities(false)
	setCoreSettings(coreSettings);
	AppBindData data = new AppBindData();
    ...
    data.instrumentationName = instrumentationName;
	...
    //ActivityThread 的内部类 H 类处理消息,将业务从 Binder 线程切换至主线程
	sendMessage(H.BIND_APPLICATION, data);

收到消息后 执行了 handleBindApplication 方法。对JVM和堆栈进行设置。设置 UI线程为重要线程。对时间 语言 等进行设置。 **创建 Instrumentation (控制activity的生命周期)**并 通过 createAppContext 创建上下文。最后调用 mInstrumentation.callApplicationOnCreate(app); 执行 Application 的 onCreate 。

private void handleBindApplication(AppBindData data) {
		// 将当前UI线程设置为最重要进程
		// Register the UI Thread as a sensitive thread to the runtime.
        VMRuntime.registerSensitiveThread();
        // In the case the stack depth property exists, pass it down to the runtime.
        // 设置当前调试的跟踪栈深度
        String property = SystemProperties.get("debug.allocTracker.stackDepth");
        if (property.length() != 0) {
            VMDebug.setAllocTrackerStackDepth(Integer.parseInt(property));
        }
        if (data.trackAllocation) {
            DdmVmInternal.setRecentAllocationsTrackingEnabled(true);
        }
	...
	final InstrumentationInfo ii;
	...
	//创建 Application 的 Context
	final ContextImpl appContext = ContextImpl.createAppContext(this, data.info);
	...
	//ii 是否为 null,取决于 data.instrumentationName 是否为 null
	if (ii != null) {
		...
		//创建 Instrumentation 的 Context, 
		final ContextImpl instrContext = ContextImpl.createAppContext(this, pi, appContext.getOpPackageName());
		try {
			final ClassLoader cl = instrContext.getClassLoader();
			mInstrumentation = (Instrumentation) cl.loadClass(data.instrumentationName.getClassName()).newInstance();
		}
		...
		//注入 ActivityThread、Context、ComponentName 等
		mInstrumentation.init(this, instrContext, appContext, component, data.instrumentationWatcher, data.instrumentationUiAutomationConnection);
		...
	} else {
		mInstrumentation = new Instrumentation();
		//注入 ActivityThread
		mInstrumentation.basicInit(this);
	}
	...
	Application app;
	...
	try {
		//创建 Application 对象
		app = data.info.makeApplication(data.restrictedBackupMode, null);
		...
		mInitialApplication = app;
		...
		try {
			mInstrumentation.onCreate(data.instrumentationArgs);
		}
		...
		try {
			//调用 Application 的 onCreate() 方法
			mInstrumentation.callApplicationOnCreate(app);
		}
		...
	}
	...
}

回到之前的 mAtmInternal.attachApplication 方法。该方法会启动对应的 activity,该方法具体实现为 ActivityTaskManagerService 。执行

		// frameworks/base/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
		@HotPath(caller = HotPath.PROCESS_CHANGE)
        @Override
        public boolean attachApplication(WindowProcessController wpc) throws RemoteException {
            synchronized (mGlobalLockWithoutBoost) {
                if (Trace.isTagEnabled(TRACE_TAG_WINDOW_MANAGER)) {
                    Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "attachApplication:" + wpc.mName);
                }
                try {
                    return mRootWindowContainer.attachApplication(wpc);
                } finally {
                    Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
                }
            }
        }

attachApplication 方法又会调用 RootWindowContainerstartActivityForAttachedApplicationIfNeeded方法。继而调用 realStartActivityLocked

// frameworks/base/services/core/java/com/android/server/wm/RootWindowContainer.java
boolean attachApplication(WindowProcessController app) throws RemoteException {
				final PooledFunction c = PooledLambda.obtainFunction(
                        RootWindowContainer::startActivityForAttachedApplicationIfNeeded, this,
                        PooledLambda.__(ActivityRecord.class), app,
                        rootTask.topRunningActivity());
                rootTask.forAllActivities(c);
}

private boolean startActivityForAttachedApplicationIfNeeded(ActivityRecord r,
            WindowProcessController app, ActivityRecord top) {
        if (r.finishing || !r.showToCurrentUser() || !r.visibleIgnoringKeyguard || r.app != null
                || app.mUid != r.info.applicationInfo.uid || !app.mName.equals(r.processName)) {
            return false;
        }

        try {
            if (mTaskSupervisor.realStartActivityLocked(r, app,
                    top == r && r.getTask().canBeResumed(r) /*andResume*/,
                    true /*checkConfig*/)) {
                mTmpBoolean = true;
            }
        } catch (RemoteException e) {
            Slog.w(TAG, "Exception in new application when starting activity "
                    + top.intent.getComponent().flattenToShortString(), e);
            mTmpRemoteException = e;
            return true;
        }
        return false;
    }

realStartActivityLocked 里面又会执行 scheduleTransaction 该方法会通过调用 AIDL scheduleTransaction 在 ActivityThread 执行。

boolean realStartActivityLocked(ActivityRecord r, WindowProcessController proc,
            boolean andResume, boolean checkConfig) throws RemoteException {
	...
	mService.getLifecycleManager().scheduleTransaction(clientTransaction);
}

在这里插入图片描述
在这里插入图片描述
在ActivityThread 那边会发送 Handler 消息 ActivityThread.H.EXECUTE_TRANSACTION。该方法会执行 execute 方法,最终执行到 handleRelaunchActivity 方法启动l auncher 方法。这里有小技巧因为里面堆栈较多实现类。所以这里在 handleLaunchActivity 添加堆栈打印。然后启动模拟器 查看堆栈。可以看到 最终是通过 TransactionExecutor.execute -> TransactionExecutor.executeCallbacks -> ActivityTransactionItem.execute -> ActivityRelaunchItem.execute -> ActivityThread.handleRelaunchActivity 启动 FallbackHome 的 主Activity。

public Activity handleLaunchActivity(ActivityClientRecord r,
            PendingTransactionActions pendingActions, Intent customIntent) {
	Slog.v(TAG, "handleLaunchActivity", new NullPointerException());
}

在这里插入图片描述

启动真正的 Launcher

拉起 FallbackHome 界面后,该Activity 位于 packages/apps/Settings/src/com/android/settings/FallbackHome.java,可以通过 过滤 android.intent.category.HOME 查找
activity 的 onCreate 方法 ,注册了解锁的广播监听。收到即调用 maybeFinish 方法。

protected void onCreate(Bundle savedInstanceState) {
		// 设置壁纸
		mWallManager = getSystemService(WallpaperManager.class);
        if (mWallManager == null) {
            Log.w(TAG, "Wallpaper manager isn't ready, can't listen to color changes!");
        } else {
            loadWallpaperColors(flags);
        }
        // 注册解锁监听广播
        registerReceiver(mReceiver, new IntentFilter(Intent.ACTION_USER_UNLOCKED));
        // 开始判断
        maybeFinish();
}

该方法会 每隔 500毫秒 去获取 Intent.CATEGORY_HOME 的 activity 。并判断是否和当前包名一样,如果不一样则代表 已经检测到了 Launch 。结束自己并启动真正的 launch 。(因为解锁后就可以检测到 Launcher 而且优先级比 FallbackHome 高

	private void maybeFinish() {
        if (getSystemService(UserManager.class).isUserUnlocked()) {
        	// 获取对应的 intent 
            final Intent homeIntent = new Intent(Intent.ACTION_MAIN)
                    .addCategory(Intent.CATEGORY_HOME);
            final ResolveInfo homeInfo = getPackageManager().resolveActivity(homeIntent, 0);
            // 判断包名是否一致 ,一致延时500 毫秒 在检测
            if (Objects.equals(getPackageName(), homeInfo.activityInfo.packageName)) {
                Log.d(TAG, "User unlocked but no home; let's hope someone enables one soon?");
                mHandler.sendEmptyMessageDelayed(0, 500);
            } else {
                Log.d(TAG, "User unlocked and real home found; let's go!");
                getSystemService(PowerManager.class).userActivity(
                        SystemClock.uptimeMillis(), false);
                finish();
            }
        }
    }
    // 延时任务检测
    private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            maybeFinish();
        }
    };

通过 PowerManger 的 userActivity 启动, 可以看到 最终 是调用了 IPowerManager , 查找 Context.POWER_SERVICE 看其注册地方。注册了 power ,对应的实现类为 PowerManagerService 。frameworks/base/services/core/java/com/android/server/power/PowerManagerService.java
在这里插入图片描述

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

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

相关文章

项目方案:社会视频资源整合接入汇聚系统解决方案(十)-视频监控汇聚应用案例和解决方案

目录 一、概述 1.1 应用背景 1.2 总体目标 1.3 设计原则 1.4 设计依据 1.5 术语解释 二、需求分析 2.1 政策分析 2.2 业务分析 2.3 系统需求 三、系统总体设计 3.1设计思路 3.2总体架构 3.3联网技术要求 四、视频整合及汇聚接入 4.1设计概述 4.2社会视频资源分…

DevToys-开源免费开发工具箱

个人觉得相较于那些在线的工具箱&#xff0c;这种离线的工具箱客户端更加可信一些。 DevToys 提供了30 个默认工具&#xff1a; 转换器&#xff1a;JSON <> YAML、日期、数字基数......编码器/解码器&#xff1a;HTML、URL、Base64、GZip、JWT、二维码......格式化程序…

【机器学习】随机森林的分类效果及进阶应用

文章目录 一、随机森林概述1.1 Bagging思想1.2 随机森林的定义1.3 随机森林的生成过程投票机制 二、随机森林的性能与优缺点2.1 分类效果的影响因素2.2 优点2.3 缺点 三、随机森林的进阶3.1 缺失值处理3.2 袋外数据&#xff08;OOB&#xff09;OOB计算方法优缺点 3.3 过拟合问题…

Deepin 安装sunix串口卡驱动

折腾了3天&#xff0c;终于搞定&#xff0c;改天上传安装过程&#xff0c;开启用c对串口传感器的编程 这种戴尔拆机卡&#xff0c;芯片用的是sunix&#xff0c;下载sunix 的linux驱动。 串口传感器用的是中盛rs485温湿度串口传感器&#xff0c;加一个rs232 转485接口 串口传感…

安全防御拓扑1

目录 实验的拓扑&#xff1a; 要求&#xff1a; 我搭建的实验拓扑 步骤&#xff1a; 创建vlan&#xff1a; 接口配置&#xff1a; 防火墙&#xff1a; 防火墙配置&#xff1a; 建立安全策略&#xff1a; 防火墙的用户&#xff1a; 办公区的市场部和研发部用户 市场部…

手机删除的文件能恢复吗?删除不等于永别,3个技巧助你找回

安卓手机中的文件&#xff0c;就像是数字世界里的繁星&#xff0c;记录着我们的点点滴滴。然而&#xff0c;有时我们可能会不小心删除了某些重要的文件&#xff0c;让我们感到惋惜和困惑。删除的文件能恢复吗&#xff1f;别担心&#xff0c;删除并不等于永别&#xff0c;我们也…

关于文档理解相关工作的一些总结

过去四年时间&#xff0c;都在处理结构化数据的存储优化相关的工作。最近一段时间在做RAG相关的工作。非结构数据的存储与检索&#xff0c;接触的也越来越多。这篇文章聊聊最近一段时间关于文档理解方面的一些心得。 文档理解 文档理解旨在从非结构化文档中提取信息并将其转化…

DockerSecret+DockerConfig介绍及使用

DockerSecret 查看官网介绍&#xff0c;Secret是daemon API 1.25之后引入的&#xff0c;它运行在swarm上的命令。 生产环境下&#xff0c;为了安全&#xff0c;我们不能把各项目的配置密码写入到配置文件。 我们可以引入docker的secret方式保护密码。 场景&#xff1a; 用…

java数组之——了解十大排序算法(动画版)

详细的冒泡排序和快速排序请查看文章&#xff1a;java数组之冒泡排序、快速排序-CSDN博客https://blog.csdn.net/weixin_44554794/article/details/140361078 一、插入排序 二、希尔排序 三、选择排序 四、堆排序 五、冒泡排序 六、快速排序 七、归并排序 八、计数排序 九、桶…

家庭海外仓怎么拓客:策略落地方法汇总

家庭海外仓因为其高度灵活性和独有的价格优势&#xff0c;还是受到很多跨境卖家欢迎的。不过作为家庭海外仓的经营者&#xff0c;想在这么激烈的竞争下稳定、持续的拿到客户&#xff0c;还是有一定难度的。今天我们就专门来聊一下家庭海外仓的拓客问题。 家庭海外仓在拓客上面临…

【开源】开源数据库工具推荐

Mysql开源工具推荐 dbeaver下载网速太慢了&#xff0c;这么好用的开源工具&#xff0c;可以从镜像站中下载&#xff1a; 下载地址&#xff1a; https://mirrors.nju.edu.cn/github-release/dbeaver/dbeaver/24.1.1/ Redis开源工具推荐 好看好用&#xff0c;UI真是做的很不…

MyBatis where标签内嵌foreach标签查询报错‘缺失右括号‘或‘命令未正确结束‘

MyBatis <where>标签内嵌<foreach>标签查询报错’缺失右括号’或’命令未正确结束’ <where>标签内嵌<foreach>标签 截取一段脱敏xml&#xff0c;写明大概意思 <select id"queryLogByIds" resultMap"BaseResultMap">SELE…

ts使用typeorm实现db创建

1.新建基础架构 ①创建项目文件名, mkdir ‘名称’ ->cd ‘文件名’ -> mkdir ‘src’->npm init mkdir fileName cd fileName mkdir src npm init在当前项目名目录下执行npm init,按照默认执行就会创建package.json. 之后执行 npm i jest/globals casl/ability bcr…

如何快速区分电子原件极性

表贴式电阻电容无极性 1表贴式.二极管 如图所示:有横杠的表示负极&#xff08;竖杠标示&#xff09;&#xff0c;注意一定要查阅数据手册在引脚信息栏一般会有 铝电解电容 手册一般会对正负极有说明 钽电容有极性 发光二极管 芯片 一般规律&#xff1a;1.看丝印朝向正对丝印的…

【C++】静态成员变量和静态成员函数(static)

为了实现多个类公用一个成员变量或函数诞生了static&#xff0c;静态成员变量和静态成员函数。 static成员变量 格式 class A { public:static 静态成员变量类型 静态成员名; } }静态成员变量必须在类外进行初始化&#xff0c;类内初始化会报错 class A { private:st…

氢气传感器TGS2616在氢燃料电池行业的应用

氢燃料电池是一种将氢气和氧气的化学能直接转换成电能的发电装置。其基本原理是电解水的逆反应&#xff0c;把氢和氧分别供给阳极和阴极&#xff0c;氢通过阳极向外扩散和电解质发生反应后&#xff0c;放出电子通过外部的负载到达阴极。 氢燃料电池具有无污染、噪声低、高效率…

excel根据数据批量创建并重命名工作表

需求 根据一列数据&#xff0c;批量创建并重命名工作表 做法 1. 右键该sheet&#xff0c;选择查看代码 2. 输入VBA代码 正向创建 Sub create_sheets_by_col()Dim num% 定义为integer*num Application.WorksheetFunction.CountA(Sheet1.Range("A:A")) num是非空…

unity宏编译版本

在写c程序的时候我们通常可以用不同的宏定义来控制不同版本的编译内容&#xff0c;最近有个需求就是根据需要编译一个完全体验版本&#xff0c;就想到了用vs的那套方法。经过研究发现unity也有类似的控制方法。 注意这里设置完后要点击右下的应用&#xff0c;我起先就没有设置…

Docker 安装ros 使用rviz 等等图形化程序

Docker 安装ros 使用rviz 等等图形化程序 ubuntu 版本与ros 发行版本对应 如何安装其它版本ros 此时考虑使用docker 易于维护 地址&#xff1a; https://hub.docker.com/r/osrf/ros 我主机是 ubuntu22.04 使用这个标签 melodic-desktop-full 1 clone 镜像到本机 docker pu…

云原生简单综合实验

需求&#xff1a;左边服务器搭建一个web服务&#xff0c;名字为www.rhce.com&#xff0c;目录/nfs/rhce为右边服务器共享的/rhce目录 搭建一个dns解析rhce.com域 右边开启autofs服务为左边服务器提供目录 第一步下载相关软件 左边服务器 [rootnode ~]# yum install nginx…