【Android 14源码分析】Activity启动流程-3
忽然有一天,我想要做一件事:去代码中去验证那些曾经被“灌输”的理论。
– 服装学院的IT男
本篇已收录于Activity短暂的一生系列
欢迎一起学习讨论Android应用开发或者WMS
V:WJB6995
Q:707409815
正文
由于篇幅原因,整个启动流程分为以下3篇进行分析:
Activity启动流程-1
Activity启动流程-2
Activity启动流程-3
本篇介绍阶段三的逻辑,这部分的分析上篇阶段二的触发点是同级的,也就是在阶段一中 TaskFragment::resumeTopActivity 触发的。
进程是怎么创建的不是当前分析的重点,所以快速过一遍流程。
1 阶段三–触发进程创建
执行pause后会执行 ActivityTaskManagerService::startProcessAsync,最后也是通过 ActivityManagerService来触发启动进程的。
看看AMS这块执行进程创建的调用流程
# ActivityTaskManagerServicevoid startProcessAsync(ActivityRecord activity, boolean knownToBeDead, boolean isTop,String hostingType) {try {if (Trace.isTagEnabled(TRACE_TAG_WINDOW_MANAGER)) {Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "dispatchingStartProcess:"+ activity.processName);}// 发送消息,启动进程,调用 ActivityManagerInternal::startProcessfinal Message m = PooledLambda.obtainMessage(ActivityManagerInternal::startProcess,mAmInternal, activity.processName, activity.info.applicationInfo, knownToBeDead,isTop, hostingType, activity.intent.getComponent());mH.sendMessage(m);} finally {Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);}}
这里通过 Handler 来完成,ActivityManagerInternal::startProcess 的实现在 ActivityManagerService 的内部类 LocalService 中。
# ActivityManagerService$LocalServicepublic void startProcess(String processName, ApplicationInfo info, boolean knownToBeDead,boolean isTop, 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, isTop),ZYGOTE_POLICY_FLAG_LATENCY_SENSITIVE, false /* allowWhileBooting */,false /* isolated */);}} finally {Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);}}@GuardedBy("this")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 */,false /* isSdkSandbox */, 0 /* sdkSandboxClientAppUid */,null /* sdkSandboxClientAppPackage */,null /* ABI override */, null /* entryPoint */,null /* entryPointArgs */, null /* crashHandler */);}
流程走到 ProcessList::startProcessLocked 。
# ProcessListboolean startProcessLocked(......) {......mService.mProcStartHandler.post(() -> handleProcessStart(app, entryPoint, gids, runtimeFlags, zygotePolicyFlags, mountExternal,requiredAbi, instructionSet, invokeWith, startSeq)); ...... }private void handleProcessStart(......) {创建一个用于启动进程的 Runnable 对象final Runnable startRunnable = () -> {try { // 调用 startProcess 方法启动进程,并获取启动结果 ProcessStartResultfinal Process.ProcessStartResult startResult = startProcess(app.getHostingRecord(),entryPoint, app, app.getStartUid(), gids, runtimeFlags, zygotePolicyFlags,mountExternal, app.getSeInfo(), requiredAbi, instructionSet, invokeWith,app.getStartTime());// 在锁定 ActivityManagerService 后,处理进程启动结果synchronized (mService) {// 更新应用的状态,如设置PID,更新生命周期状态等handleProcessStartedLocked(app, startResult, startSeq);}} catch (RuntimeException e) {......异常处理}};......}
-
- 通过 ProcessList::startProcess 来启动应用进程
-
- 启动玩之后 ProcessList::handleProcessStartedLocked 会更新应用的状态,如设置PID,更新生命周期状态等
ProcessList::handleProcessStartedLocked 经过几次重载会调用下面的方法
# ProcessListActivityManagerService mService = null;boolean handleProcessStartedLocked(ProcessRecord app, int pid, boolean usingWrapper,long expectedStartSeq, boolean procAttached) {......StringBuilder buf = mStringBuilder;buf.append("Start proc ");buf.append(pid);buf.append(':');buf.append(app.processName);buf.append('/');UserHandle.formatUid(buf, app.getStartUid());if (app.getIsolatedEntryPoint() != null) {buf.append(" [");buf.append(app.getIsolatedEntryPoint());buf.append("]");}buf.append(" for ");buf.append(app.getHostingRecord().getType());if (app.getHostingRecord().getName() != null) {buf.append(" ");buf.append(app.getHostingRecord().getName());}// 输出日志mService.reportUidInfoMessageLocked(TAG, buf.toString(), app.getStartUid());......}
1.1创建进程小结
创建进行逻辑我没深入了解过,所以简单看了下调用逻辑,以 AMS::reportUidInfoMessageLocked 结束的原因是因为其会打印下面的这个创建进程的关键日志:
07-26 19:19:05.477 8737 8782 I ActivityManager: Start proc 19643:com.example.myapplication/u0a198 for next-top-activity {com.example.myapplication/com.example.myapplication.MainActivity}
经常看日志看启动了哪个进程搜的日志就是在这里打印的。
这部分的调用链如下:
ActivityTaskManagerService::startProcessAsyncActivityManagerService$LocalService::startProcessActivityManagerService::startProcessLockedProcessList::startProcessLockedProcessList::handleProcessStartProcessList::startProcess -- 启动进程ProcessList::handleProcessStartedLockedActivityManagerService::reportUidInfoMessageLocked -- 打印日志
2. 阶段三–应用进程创建
进程创建完成后会执行 ActivityThread::main 方法,所以应用端进程创建结束的逻辑从这个方法开始分析。
2.1 应用端处理
# ActivityThread// ApplicationThread 是 AMS 作为 C 端时,与应用进程通信的方式final ApplicationThread mAppThread = new ApplicationThread();public static void main(String[] args) {......// 主线程LooperLooper.prepareMainLooper();......ActivityThread thread = new ActivityThread();// 下一步thread.attach(false, startSeq);// 主线程LooperLooper.loop();}private void attach(boolean system, long startSeq) {......final IActivityManager mgr = ActivityManager.getService();try {//重点 *2. 将mAppThread告知AMS,用于AMS与应用进程通信mgr.attachApplication(mAppThread, startSeq);} catch (RemoteException ex) {throw ex.rethrowFromSystemServer();}......}
应用端的调用链比较简单:
ActivityThread::mainLooper::prepareMainLooperActivityThread::initActivityThread::attachActivityManagerService::attachApplication -- 跨进程Looper::loop
应用进程(电话)创建完毕后,在 main 方法里会执行 attach 就是要将自己的信息告知AMS,毕竟 AMS 是管理模块。
2.2 system_service端处理
system_service 端 ActivityManagerService 知道有进程启动了,这个行为也可能会触发系统组显示逻辑的改变,所以比如也会做响应处理
2.1 调用链
ActivityManagerService::attachApplicationActivityManagerService::attachApplicationLockedActivityThread::bindApplicationActivityTaskManagerService.LocalService::attachApplication WindowContainer::forAllRootTasks --- 省略forAllRootTasks等固定堆栈Task::forAllRootTasksWindowContainer::forAllActivitiesActivityRecord::forAllActivitiesRootWindowContainer.AttachApplicationHelper::testRootWindowContainer.AttachApplicationHelper::testActivityTaskSupervisor::realStartActivityLocked -- 构建LaunchActivityItem
接上一篇知道如果进程启动了 ActivityTaskSupervisor::startSpecificActivity 就会走进去ActivityTaskSupervisor::realStartActivityLocked 。
但是可能会好奇怎么就知道要执行应用 MainActivity 到 onCreate 就一定是在这个方法里呢? 调试方法有很多,比如加 log ,打堆栈,但是对应这个逻辑比较简单的是,需要执行 Activity 启动到 onCreate 的控制在 LaunchActivityItem 中,而 LaunchActivityItem在 framework 的引用除了本身,就只有在 ActivityTaskSupervisor。
2.2 主流程
# ActivityManagerService// 当应用进程调用attachApplication 执行public final void attachApplication(IApplicationThread thread, long startSeq) {if (thread == null) {throw new SecurityException("Invalid application interface");}synchronized (this) {// 获取 应用进程的信息后执行attachApplicationLockedint 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) {// 需要启动应用的进程数据ProcessRecord app;......if (pid != MY_PID && pid >= 0) {synchronized (mPidsSelfLocked) {// 通过mPidsSelfLocked获取app = mPidsSelfLocked.get(pid);}......} ...... // 触发ActivityThread::bindApplication 逻辑if (app.getIsolatedEntryPoint() != null) {......} else if (instr2 != null) {// bindApplication...... } else {// 重点* 1. bindApplicationthread.bindApplication(processName, appInfo,app.sdkSandboxClientAppVolumeUuid, app.sdkSandboxClientAppPackage,providerList, null, profilerInfo, null, null, null, testMode,mBinderTransactionTrackingEnabled, enableTrackAllocation,isRestrictedBackupMode || !normalMode, app.isPersistent(),new Configuration(app.getWindowProcessController().getConfiguration()),app.getCompat(), getCommonServicesLocked(app.isolated),mCoreSettingsObserver.getCoreSettingsLocked(),buildSerial, autofillOptions, contentCaptureOptions,app.getDisabledCompatChanges(), serializedSystemFontMap,app.getStartElapsedTime(), app.getStartUptime());}......if (!mConstants.mEnableWaitForFinishAttachApplication) {// 重点* 2. finishAttachApplicationInner(startSeq, callingUid, pid);} else {app.setPendingFinishAttach(true);}......}
ActivityManagerService::finishAttachApplicationInner 是 U 把原来的逻辑提取新增的方法。
# ActivityManagerServicepublic ActivityTaskManagerInternal mAtmInternal;private void finishAttachApplicationInner(long startSeq, int uid, int pid) {......if (normalMode) {try {// 重点 触发构建 LaunchActivityItem 流程didSomething = mAtmInternal.attachApplication(app.getWindowProcessController());} catch (Exception e) {Slog.wtf(TAG, "Exception thrown launching activities in " + app, e);badApp = true;}}......}
这里是触发 LaunchActivityItem 的流程主线, mAtmInternal是 ATMS 的内部类 LocalService 。
# ActivityTaskManagerService$LocalService@Overridepublic boolean attachApplication(WindowProcessController wpc) throws RemoteException {......return mRootWindowContainer.attachApplication(wpc);......}
流程来到 RootWindowContainer 预感要开始处理窗口显示逻辑。
# RootWindowContainerprivate final AttachApplicationHelper mAttachApplicationHelper = new AttachApplicationHelper();boolean attachApplication(WindowProcessController app) throws RemoteException {try {return mAttachApplicationHelper.process(app);} finally {mAttachApplicationHelper.reset();}}# RootWindowContainer// 实现 Consumer 接口private class AttachApplicationHelper implements Consumer<Task>, Predicate<ActivityRecord> {......boolean process(WindowProcessController app) throws RemoteException {mApp = app;for (int displayNdx = getChildCount() - 1; displayNdx >= 0; --displayNdx) {// 重点* 调用每个容器的 forAllRootTasksgetChildAt(displayNdx).forAllRootTasks(this);......}......}......}
这里看到传递了“this”,所以 AttachApplicationHelper 必然实现了 Consumer 接口, 直接看其 accept 实现即可。
# RootWindowContainer$AttachApplicationHelperprivate class AttachApplicationHelper implements Consumer<Task>, Predicate<ActivityRecord> {......boolean process(WindowProcessController app) throws RemoteException {mApp = app;for (int displayNdx = getChildCount() - 1; displayNdx >= 0; --displayNdx) {// 重点*1. 调用每个容器的 forAllRootTasksgetChildAt(displayNdx).forAllRootTasks(this);......}......}@Overridepublic void accept(Task rootTask) {......if (rootTask.getVisibility(null /* starting */)== TASK_FRAGMENT_VISIBILITY_INVISIBLE) {// 如果Task 不可见则不需要处理return;}// 执行 topRunningActivitymTop = rootTask.topRunningActivity();// 重点*2. 执行accept 让容器下的每个 ActivityRecord 执行 testrootTask.forAllActivities(this);}@Overridepublic boolean test(ActivityRecord r) {// 判断 ActivityRecord 是否满足需要启动条件if (r.finishing || !r.showToCurrentUser() || !r.visibleIgnoringKeyguard|| r.app != null || mApp.mUid != r.info.applicationInfo.uid|| !mApp.mName.equals(r.processName)) {return false;}try {// 重点*3. 执行 realStartActivityLocked 尝试实际启动 Activityif (mTaskSupervisor.realStartActivityLocked(r, mApp,mTop == r && r.getTask().canBeResumed(r) /* andResume */,true /* checkConfig */)) {mHasActivityStarted = true;}} catch (RemoteException e) {......}return false;}}
这部分的逻辑也就是一路执行,比较疑惑的点也许是进入 AttachApplicationHelper::proces 方法后的几个 Lambda 表达式容易绕晕。
简单梳理一下,首先是从 RootWindowContainer 开始执行的,目的就是想要执行到所有 ActivityRecord 然后判断一下它的情况是不是需要启动对应的 Activity 。
-
- AttachApplicationHelper::proces 里的 “getChildAt” 对应的是每个屏幕,也就是 DisplayContent ,然后再遍历其下的每个 RootTask ,让其执行 accept 函数
-
- AttachApplicationHelper::accept 目前是对象是 Task ,准确的说是 RootTask 。如果这个 Task 是可见的,则又开始遍历其下的所有 ActivityRecord ,让其执行 test 函数
-
- AttachApplicationHelper::test 方法是真正干活的地方,所有一堆条件判断这个 ActivityRecord 是否满足
- r.finishing:活动是否正在结束
- !r.showToCurrentUser() :活动是否对当前用户可见
- !r.visibleIgnoringKeyguard :活动是否可见,即使有屏幕保护器
- r.app != null :活动的应用程序是否已经存在
- mApp.mUid != r.info.applicationInfo.uid :应用程序的UID是否与预期的不同
- !mApp.mName.equals(r.processName):应用程序的名称是否与活动的进程名称不同
感觉比较重要的就是前面3个条件,这个 ActivityRecord 是不是需要显示给用户,如果需要则执行 ActivityTaskSupervisor::realStartActivityLocked 试图启动 Activity 。
3. 阶段三总结
阶段三的流程相对来逻辑简单一些,知道个调用链就好,流程目的就是执行 ActivityTaskSupervisor::realStartActivityLocked 。
这部分的堆栈如下图:
加上应用端的调用链,完成调用链如下:
ActivityThread::mainLooper::prepareMainLooperActivityThread::initActivityThread::attachActivityManagerService::attachApplication -- 跨进程ActivityManagerService::attachApplicationLockedActivityThread::bindApplicationActivityManagerService::finishAttachApplicationInnerActivityTaskManagerService$LocalService::attachApplicationRootWindowContainer::attachApplicationRootWindowContainer$AttachApplicationHelper::process -- 开始遍历WindowContainer::forAllRootTasks --- 省略forAllRootTasks等固定堆栈Task::forAllRootTasks -- 1. 遍历所有root TaskRootWindowContainer$AttachApplicationHelper::accept -- 1.1 root Task执行acceptWindowContainer::forAllActivities -- 2. 遍历下面的所有ActivityRecordActivityRecord::forAllActivities RootWindowContainer$AttachApplicationHelper::test -- 2.1 ActivityRecord执行testActivityTaskSupervisor::realStartActivityLocked -- 试图启动ActivityLooper::loop
对应时序图:
大概流程图如下:
这一阶段主要就是应用进程启动后,试图拉起对应的 Activity ,能不能启动的条件就是没有正在 pause 的 Activity 了。
主要逻辑就是触发 ActivityTaskSupervisor::realStartActivityLocked
4. 阶段四–真正启动Activity
阶段四其实就是触发应用端创建 Activity 。
4.1 realStartActivityLocked
这个方法是要真去触发 Activity 启动的,根据前面的流程图,阶段二,三的最终就是想执行到这个方法里面,来触发 Activity 启动。
这个方法的最终目的就是通过事务执行 LaunchActivityItem 和 PauseActivityItem ,也就是会触发应用端 TargetActivity 启动,并执行生命周期到 onCreate 和 onResume 。
# ActivityTaskSupervisor boolean realStartActivityLocked(ActivityRecord r, WindowProcessController proc,boolean andResume, boolean checkConfig) throws RemoteException {// 重点* 1. 判断是否执行完了pause if (!mRootWindowContainer.allPausedActivitiesComplete()) {// While there are activities pausing we skipping starting any new activities until// pauses are complete. NOTE: that we also do this for activities that are starting in// the paused state because they will first be resumed then paused on the client side.// 如果还有Activity没完成pause,则打印日志并returnProtoLog.v(WM_DEBUG_STATES,"realStartActivityLocked: Skipping start of r=%s some activities pausing...",r);return false;}// 重点* 2. 表示ActivityRecord已连接到相应的进程r.setProcess(proc);......// event日志: wm_restart_activity EventLogTags.writeWmRestartActivity(r.mUserId, System.identityHashCode(r),task.mTaskId, r.shortComponentName);......// 重点* 3.1 创建事务,用于Activity启动// Create activity launch transaction.final ClientTransaction clientTransaction = ClientTransaction.obtain(proc.getThread(), r.token);final boolean isTransitionForward = r.isTransitionForward();// 获取Activity所在 TaskFragment Tokenfinal IBinder fragmentToken = r.getTaskFragment().getFragmentToken();// 重点* 3.2 将构建的 LaunchActivityItem 添加到 clientTransaction 中clientTransaction.addCallback(LaunchActivityItem.obtain(new Intent(r.intent),System.identityHashCode(r), r.info,// TODO: Have this take the merged configuration instead of separate global// and override configs.mergedConfiguration.getGlobalConfiguration(),mergedConfiguration.getOverrideConfiguration(), r.compat,r.getFilteredReferrer(r.launchedFromPackage), task.voiceInteractor,proc.getReportedProcState(), r.getSavedState(), r.getPersistentSavedState(),results, newIntents, r.takeOptions(), isTransitionForward,proc.createProfilerInfoIfNeeded(), r.assistToken, activityClientController,r.shareableActivityToken, r.getLaunchedFromBubble(), fragmentToken));// 重点* 3.3 设置预期的最终状态为 ResumeActivityItemfinal ActivityLifecycleItem lifecycleItem;if (andResume) {// Resume逻辑,启动走的这。 表示需要执行到onCreatelifecycleItem = ResumeActivityItem.obtain(isTransitionForward);} else {// Pause 逻辑lifecycleItem = PauseActivityItem.obtain();}clientTransaction.setLifecycleStateRequest(lifecycleItem);// 重点* 3.4 执行事务mService.getLifecycleManager().scheduleTransaction(clientTransaction);......}
根据代码的标注解释:
-
- 根据注释,如果有 Activity 正在 pause 则不允许任何 Activity 启动。换句话说就是想要启动一个 Activity 必须其他的 Activity 需要 pause 的都完成了 pause 流程
-
- 对应上一篇提到的 ActivityRecord::attachedToProcess 方法如果需要返回 true ,则必须在这里执行 setProcess 方法,否则 ActivityRecord 下的 app 变量就是 null
-
- 这里就是开始真正执行启动 Activity 的地方了,是通过事务执行的,分为以下几步
- 3.1 构建一个事务
- 3.2 设置 LaunchActivityItem ,这一步会将 Activity 的生命周期执行到 onCreate
- 3.3 设置 ResumeActivityItem ,这一步会将 Activity 的生命周期执行到 onResume
- 3.4 执行事务
后续逻辑就是在应用端执行 Activity 的创建,以及生命周期处理了,这部分本篇大概看一遍流程,以分析到 Activity 的创建为止。
4.2 创建Activity
这部分的调用链如下:
LaunchActivityItem::executeActivityThread::handleLaunchActivityActivityThread::performLaunchActivityInstrumentation::newActivity -- 创建ActivityActivity::attach -- 处理Window相关Window::initWindow::setWindowManagerInstrumentation::callActivityOnCreate -- onCreate流程Activity::performCreateActivity::onCreate --over
时序图图下:
接下来看 LaunchActivityItem::execute
# LaunchActivityItem public void execute(ClientTransactionHandler client, IBinder token,PendingTransactionActions pendingActions) {Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityStart");ActivityClientRecord r = new ActivityClientRecord(token, mIntent, mIdent, mInfo,mOverrideConfig, mCompatInfo, mReferrer, mVoiceInteractor, mState, mPersistentState,mPendingResults, mPendingNewIntents, mActivityOptions, mIsForward, mProfilerInfo,client, mAssistToken, mShareableActivityToken, mLaunchedFromBubble,mTaskFragmentToken);client.handleLaunchActivity(r, pendingActions, null /* customIntent */);Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);}
这里的 client 是 ClientTransactionHandler 类型, 而 ActivityThread 是 ClientTransactionHandler 子类。
重点是这边创建了 ActivityClientRecord ,第一个参数就是我们要找的token
逻辑来到应用进程 ActivityThread
# ActivityThreadpublic Activity handleLaunchActivity(ActivityClientRecord r,PendingTransactionActions pendingActions, Intent customIntent) {......final Activity a = performLaunchActivity(r, customIntent);......}private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {......Activity activity = null;try {// 重点* 1. 通过Instrumentation 反射创建Activityjava.lang.ClassLoader cl = appContext.getClassLoader();activity = mInstrumentation.newActivity(cl, component.getClassName(), r.intent);......}try {......// 重点* 2. 执行 attach 流程activity.attach(appContext, this, getInstrumentation(), r.token,r.ident, app, r.intent, r.activityInfo, title, r.parent,r.embeddedID, r.lastNonConfigurationInstances, config,r.referrer, r.voiceInteractor, window, r.activityConfigCallback,r.assistToken, r.shareableActivityToken);......if (r.isPersistable()) {mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);} else {重点* 3. onCreate流程mInstrumentation.callActivityOnCreate(activity, r.state);}}......}
这里有3个重点:
-
- Activity 是通过反射创建的。 到这里其实 Activity 启动流程基本分析完了
-
- 执行 Activity::attach 。这里会触发创建 Window
-
- 触发 Activity::onCreate
Activity 已经创建好了,那就剩下 onCreate 了。
# Instrumentationpublic void callActivityOnCreate(Activity activity, Bundle icicle) {prePerformCreate(activity);// onCreate流程activity.performCreate(icicle);postPerformCreate(activity);}# Activityfinal void performCreate(Bundle icicle) {performCreate(icicle, null);}final void performCreate(Bundle icicle, PersistableBundle persistentState) {......if (persistentState != null) {onCreate(icicle, persistentState);} else {// 执行onCreateonCreate(icicle);}......}
写过应用的都知道,默认都是一个参数的onCreate。
至此,Activity 启动流程分析完毕。
在 ActivityTaskSupervisor::realStartActivityLocked 方法看到给事务加了个 ResumeActivityItem , 因为 LaunchActivityItem 只是创建,但是创建完成后,需要执行到对应的生命周期。
正常情况都是希望执行到onResume,所以会设置 ResumeActivityItem 。
4.3 阶段四小结
无论是阶段二还是阶段三触发了 ActivityTaskSupervisor::realStartActivityLocked 方法,并且还满足启动 Activity 条件,则会触发应用端进程 Activity 的创建和生命周期的执行。
相关文章:

【Android 14源码分析】Activity启动流程-3
忽然有一天,我想要做一件事:去代码中去验证那些曾经被“灌输”的理论。 – 服装…...
Javascript客户端时间与服务器时间
在Java代码中使用new Date(),获取的是本机时间; 但是在Javascript 中使用new Date(),获取的却是访问该页面的客户端时间。 这样,就可能会出现一个问题:我的电脑时间比正常时间要快,我访问一个页面&#x…...
系统架构设计师教程 第11章 11.4 边缘计算概述 笔记
11.4 边缘计算概述 ★★☆☆☆ 11.4.1 边缘计算概念 边缘计算将数据的处理、应用程序的运行甚至一些功能服务的实现,由 网络中心下放到网络边缘的节点上。在网络边缘侧的智能网关上就近采集并且处理数据,不需要上传原生数据。 11.4.2 边缘计算的定义 1…...
CSS全解析
文章目录 CSS全解析一、CSS是什么二、基本语法规范三、引入方式(一)内部样式表(二)行内样式表(三)外部样式 四、代码风格(一)样式格式(二)样式大小写…...

一款基于 Java 的可视化 HTTP API 接口快速开发框架,干掉 CRUD,效率爆炸(带私活源码)
平常我们经常需要编写 API,但其实常常只是一些简单的增删改查,写这些代码非常枯燥无趣。 今天给大家带来的是一款基于 Java 的可视化 HTTP API 接口快速开发框架,通过 UI 界面编写接口,无需定义 Controller、Service、Dao 等 Jav…...

CSS3渐变
一、线性渐变 通过background-image: linear-gradient(...)设置线性渐变 语法: linear-gradient(direction,color1,color2, . . ) direction:渐变方向,默认从上到下,可选值: 简单选取: ① to right&…...

Emissive CEO Fabien Barati谈《消失的法老》背后的故事:XR大空间体验的创新与未来
在最近的一次播客访谈中,虚拟现实之声(Voices of VR)的主持人Kent Bye与Emissive公司的联合创始人兼CEO Fabien Barati进行了深入交流。Emissive是全球顶级的VR大空间体验制作商之一,以其沉浸式探险项目如《永恒的巴黎圣母院》和《胡夫地平线》而闻名。以下是这次访谈的核心…...
mysql设置表的某一个字段每天定时清零
推荐学习文档 golang应用级os框架,欢迎stargolang应用级os框架使用案例,欢迎star案例:基于golang开发的一款超有个性的旅游计划app经历golang实战大纲golang优秀开发常用开源库汇总想学习更多golang知识,这里有免费的golang学习笔…...

实例分割、语义分割和 SAM(Segment Anything Model)
实例分割、语义分割和 SAM(Segment Anything Model) 都是图像处理中的重要技术,它们的目标是通过分割图像中的不同对象或区域来帮助识别和分析图像,但它们的工作方式和适用场景各有不同。 1. 语义分割(Semantic Segme…...

深度学习项目----用LSTM模型预测股价(包含LSTM网络简介,代码数据均可下载)
前言 前几天在看论文,打算复现,论文用到了LSTM,故这一篇文章是小编学LSTM模型的学习笔记;LSTM感觉很复杂,但是结合代码构建神经网络,又感觉还行;本次学习的案例数据来源于GitHub,在…...

《精通开关电源设计》笔记一
重点 效率 纹波 环路响应 尺寸,从静态到动态的研究方法,假设开关电源稳态运行,以电感为中心,根据半导体器件(mos管或二极管)分段分析电路的状态,工具有电路原理和能量守恒 影响效率的主要是开关损耗,所以…...
QLoRA代码实战
QLoRA原理参考: BiliBili:4bit量化与QLoRA模型训练 zhihu:QLoRA(Quantized LoRA)详解 下载llama3-8b模型 from modelscope import snapshot_download model_dir snapshot_download(LLM-Research/Meta-Llama-3-8B-In…...
pyqt QGraphicsView 以鼠标为中心进行缩放
注意几个关键点: 1. 初始化 class CustomGraphicsView(QGraphicsView):def __init__(self, parentNone):super(CustomGraphicsView, self).__init__(parent)self.scene QGraphicsScene()self.setScene(self.scene)self.setGeometry(0, 0, 1024, 600)# 以下初始化…...

FPGA-Vivado-IP核-逻辑分析仪(ILA)
ILA IP核 背景介绍 在用FPGA做工程项目时,当Verilog代码写好,我们需要对代码里面的一些关键信号进行上板验证查看。首先,我们可以把需要查看的这些关键信号引出来,接好线通过示波器进行实时监测,但这会用到大量的线材…...

基于webComponents的纯原生前端框架
我本人的个人开发web前端前框架xui,正在开发中,业已完成50%的核心开发工作,并且在开发过程中逐渐完善. 目前框架未采用任何和市面上框架模式,没有打包过程,实现真实的开箱即用。 当然在开发过程中也会发现没有打包工…...
OpenCV-背景建模
文章目录 一、背景建模的目的二、背景建模的方法及原理三、背景建模实现四、总结 OpenCV中的背景建模是一种在计算机视觉中从视频序列中提取出静态背景的技术。以下是对OpenCV背景建模的详细解释: 一、背景建模的目的 背景建模的主要目标是将动态的前景对象与静态的…...
一个简单的摄像头应用程序6
主要改进点: 使用 ThreadPoolExecutor 管理多线程: 使用 concurrent.futures.ThreadPoolExecutor 来管理多线程,这样可以更高效地处理图像。 在 main 函数中创建一个 ThreadPoolExecutor,并在每个循环中提交图像处理任务。 减少…...

Pikachu-目录遍历
目录遍历,跟不安全文件上传下载有差不多; 访问 jarheads.php 、truman.php 都是通过 get 请求,往title 参数传参; 在后台,可以看到 jarheads.php 、truman.php所在目录: /var/www/html/vul/dir/soup 图片…...
用Python实现基于Flask的简单Web应用:从零开始构建个人博客
解锁Python编程的无限可能:《奇妙的Python》带你漫游代码世界 前言 在现代Web开发中,Python因其简洁、易用以及丰富的库生态系统,成为了许多开发者的首选编程语言。Flask作为一个轻量级的Python Web框架,以其简洁和灵活性深受开…...

IDEA的lombok插件不生效了?!!
记录一下,防止找不到解决方案,已经遇到好几次了 前面啰嗦的多,可以直接跳到末尾的解决方法,点击一下 问题现场情况 排查过程 确认引入的依赖正常 —》🆗 idea 是否安装了lombok插件 --》🆗 貌似没有问题…...
R语言AI模型部署方案:精准离线运行详解
R语言AI模型部署方案:精准离线运行详解 一、项目概述 本文将构建一个完整的R语言AI部署解决方案,实现鸢尾花分类模型的训练、保存、离线部署和预测功能。核心特点: 100%离线运行能力自包含环境依赖生产级错误处理跨平台兼容性模型版本管理# 文件结构说明 Iris_AI_Deployme…...
Golang dig框架与GraphQL的完美结合
将 Go 的 Dig 依赖注入框架与 GraphQL 结合使用,可以显著提升应用程序的可维护性、可测试性以及灵活性。 Dig 是一个强大的依赖注入容器,能够帮助开发者更好地管理复杂的依赖关系,而 GraphQL 则是一种用于 API 的查询语言,能够提…...
【android bluetooth 框架分析 04】【bt-framework 层详解 1】【BluetoothProperties介绍】
1. BluetoothProperties介绍 libsysprop/srcs/android/sysprop/BluetoothProperties.sysprop BluetoothProperties.sysprop 是 Android AOSP 中的一种 系统属性定义文件(System Property Definition File),用于声明和管理 Bluetooth 模块相…...

【单片机期末】单片机系统设计
主要内容:系统状态机,系统时基,系统需求分析,系统构建,系统状态流图 一、题目要求 二、绘制系统状态流图 题目:根据上述描述绘制系统状态流图,注明状态转移条件及方向。 三、利用定时器产生时…...
关于 WASM:1. WASM 基础原理
一、WASM 简介 1.1 WebAssembly 是什么? WebAssembly(WASM) 是一种能在现代浏览器中高效运行的二进制指令格式,它不是传统的编程语言,而是一种 低级字节码格式,可由高级语言(如 C、C、Rust&am…...
3403. 从盒子中找出字典序最大的字符串 I
3403. 从盒子中找出字典序最大的字符串 I 题目链接:3403. 从盒子中找出字典序最大的字符串 I 代码如下: class Solution { public:string answerString(string word, int numFriends) {if (numFriends 1) {return word;}string res;for (int i 0;i &…...

成都鼎讯硬核科技!雷达目标与干扰模拟器,以卓越性能制胜电磁频谱战
在现代战争中,电磁频谱已成为继陆、海、空、天之后的 “第五维战场”,雷达作为电磁频谱领域的关键装备,其干扰与抗干扰能力的较量,直接影响着战争的胜负走向。由成都鼎讯科技匠心打造的雷达目标与干扰模拟器,凭借数字射…...
ip子接口配置及删除
配置永久生效的子接口,2个IP 都可以登录你这一台服务器。重启不失效。 永久的 [应用] vi /etc/sysconfig/network-scripts/ifcfg-eth0修改文件内内容 TYPE"Ethernet" BOOTPROTO"none" NAME"eth0" DEVICE"eth0" ONBOOT&q…...

Linux 中如何提取压缩文件 ?
Linux 是一种流行的开源操作系统,它提供了许多工具来管理、压缩和解压缩文件。压缩文件有助于节省存储空间,使数据传输更快。本指南将向您展示如何在 Linux 中提取不同类型的压缩文件。 1. Unpacking ZIP Files ZIP 文件是非常常见的,要在 …...

Selenium常用函数介绍
目录 一,元素定位 1.1 cssSeector 1.2 xpath 二,操作测试对象 三,窗口 3.1 案例 3.2 窗口切换 3.3 窗口大小 3.4 屏幕截图 3.5 关闭窗口 四,弹窗 五,等待 六,导航 七,文件上传 …...