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

Android应用程序进程的启动过程

Android应用程序进程的启动过程

在这里插入图片描述

导语

到这篇文章为止,我们已经简要地了解过了Android系统的启动流程了,其中比较重要的内容有Zygote进程的启动和SystemService以及Launcher的启动,接下来我们将要学习的是Android应用程序的启动过程,这篇文章将会比较简单地介绍这个过程,下面是一张个人总结出来的流程图:
在这里插入图片描述

ActivityManagerService向Zygote服务发送请求

其实在之前Zygote服务启动的过程中,我们已经提到了这个Zygote服务将会创建出一个Socket来用于给ActivityManagerService使用,应用程序进程的启动就需要ActivityManagerService来给Zygote的服务端发送请求。

首先我们介绍ProcessRecord类,这个类是Android系统中用于表示应用程序进程的数据结构。每当应用程序启动时,系统都会为其创建一个ProcessRecord对象来跟踪该进程的状态和信息。

具体来说,在ActivityMangerService中也是通过这个类来启动进程的,来看看这个方法:

   @GuardedBy("mService")boolean startProcessLocked(ProcessRecord app, HostingRecord hostingRecord,int zygotePolicyFlags, boolean disableHiddenApiChecks, boolean disableTestApiChecks,String abiOverride) {if (app.isPendingStart()) {return true;}final long startUptime = SystemClock.uptimeMillis();final long startElapsedTime = SystemClock.elapsedRealtime();if (app.getPid() > 0 && app.getPid() != ActivityManagerService.MY_PID) {checkSlow(startUptime, "startProcess: removing from pids map");mService.removePidLocked(app.getPid(), app);app.setBindMountPending(false);mService.mHandler.removeMessages(PROC_START_TIMEOUT_MSG, app);checkSlow(startUptime, "startProcess: done removing from pids map");app.setPid(0);app.setStartSeq(0);}// Clear any residual death recipient link as the ProcessRecord could be reused.app.unlinkDeathRecipient();app.setDyingPid(0);if (DEBUG_PROCESSES && mService.mProcessesOnHold.contains(app)) Slog.v(TAG_PROCESSES,"startProcessLocked removing on hold: " + app);mService.mProcessesOnHold.remove(app);checkSlow(startUptime, "startProcess: starting to update cpu stats");mService.updateCpuStats();checkSlow(startUptime, "startProcess: done updating cpu stats");try {final int userId = UserHandle.getUserId(app.uid);try {AppGlobals.getPackageManager().checkPackageStartable(app.info.packageName, userId);} catch (RemoteException e) {throw e.rethrowAsRuntimeException();}int uid = app.uid;int[] gids = null;int mountExternal = Zygote.MOUNT_EXTERNAL_NONE;boolean externalStorageAccess = false;if (!app.isolated) {int[] permGids = null;try {checkSlow(startUptime, "startProcess: getting gids from package manager");final IPackageManager pm = AppGlobals.getPackageManager();permGids = pm.getPackageGids(app.info.packageName,MATCH_DIRECT_BOOT_AUTO, app.userId);StorageManagerInternal storageManagerInternal = LocalServices.getService(StorageManagerInternal.class);mountExternal = storageManagerInternal.getExternalStorageMountMode(uid,app.info.packageName);externalStorageAccess = storageManagerInternal.hasExternalStorageAccess(uid,app.info.packageName);if (pm.checkPermission(Manifest.permission.INSTALL_PACKAGES,app.info.packageName, userId)== PackageManager.PERMISSION_GRANTED) {Slog.i(TAG, app.info.packageName + " is exempt from freezer");app.mOptRecord.setFreezeExempt(true);}} catch (RemoteException e) {throw e.rethrowAsRuntimeException();}if (app.processInfo != null && app.processInfo.deniedPermissions != null) {for (int i = app.processInfo.deniedPermissions.size() - 1; i >= 0; i--) {int[] denyGids = mService.mPackageManagerInt.getPermissionGids(app.processInfo.deniedPermissions.valueAt(i), app.userId);if (denyGids != null) {for (int gid : denyGids) {permGids = ArrayUtils.removeInt(permGids, gid);}}}}gids = computeGidsForProcess(mountExternal, uid, permGids, externalStorageAccess);}app.setMountMode(mountExternal);checkSlow(startUptime, "startProcess: building args");if (mService.mAtmInternal.isFactoryTestProcess(app.getWindowProcessController())) {uid = 0;}int runtimeFlags = 0;...........app.setGids(gids);app.setRequiredAbi(requiredAbi);app.setInstructionSet(instructionSet);ApplicationInfo definingAppInfo;if (hostingRecord.getDefiningPackageName() != null) {definingAppInfo = new ApplicationInfo(app.info);definingAppInfo.packageName = hostingRecord.getDefiningPackageName();definingAppInfo.uid = uid;} else {definingAppInfo = app.info;}runtimeFlags |= Zygote.getMemorySafetyRuntimeFlags(definingAppInfo, app.processInfo, instructionSet, mPlatformCompat);if (TextUtils.isEmpty(app.info.seInfoUser)) {Slog.wtf(ActivityManagerService.TAG, "SELinux tag not defined",new IllegalStateException("SELinux tag not defined for "+ app.info.packageName + " (uid " + app.uid + ")"));}final String seInfo = app.info.seInfo+ (TextUtils.isEmpty(app.info.seInfoUser) ? "" : app.info.seInfoUser);final String entryPoint = "android.app.ActivityThread";return startProcessLocked(hostingRecord, entryPoint, app, uid, gids,runtimeFlags, zygotePolicyFlags, mountExternal, seInfo, requiredAbi,instructionSet, invokeWith, startUptime, startElapsedTime);} catch (RuntimeException e) {Slog.e(ActivityManagerService.TAG, "Failure starting process " + app.processName, e);mService.forceStopPackageLocked(app.info.packageName, UserHandle.getAppId(app.uid),false, false, true, false, false, app.userId, "start failure");return false;}}

这里截取了部分代码,其实这个方法里做的主要就是获取到了应用程序的一些信息,比如Gid,Uid等,然后把获取到的信息设置到要启动的App中:

     app.setGids(gids);app.setRequiredAbi(requiredAbi);app.setInstructionSet(instructionSet);

然后最后还需要调用一层重载方法开始正式进行进程的启动:

    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 startUptime, long startElapsedTime) {............try {final Process.ProcessStartResult startResult = startProcess(hostingRecord,entryPoint, app,uid, gids, runtimeFlags, zygotePolicyFlags, mountExternal, seInfo,requiredAbi, instructionSet, invokeWith, startUptime);handleProcessStartedLocked(app, startResult.pid, startResult.usingWrapper,startSeq, false);} catch (RuntimeException e) {Slog.e(ActivityManagerService.TAG, "Failure starting process "+ app.processName, e);app.setPendingStart(false);mService.forceStopPackageLocked(app.info.packageName, UserHandle.getAppId(app.uid),false, false, true, false, false, app.userId, "start failure");}return app.getPid() > 0;}}

这里它又会调用到startProcess和handleProcessStartedLocked方法,startProcessLocked 方法用于启动新的进程,而 handleProcessStartedLocked 方法用于处理进程启动完成后的回调,更新进程状态并通知其他模块有关进程启动完成的事件。这两个方法共同协作以实现进程的启动和管理。

在这里我们主要还是看进程的启动,所以着重来看startProcess方法,这个方法又将调用到Process的start方法:

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

所以接下来看Process的start方法,这个方法又会调转到ZygoteProcess的start方法,并继续跳转到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,int zygotePolicyFlags,boolean isTopApp,@Nullable long[] disabledCompatChanges,@Nullable Map<String, Pair<String, Long>>pkgDataInfoMap,@Nullable Map<String, Pair<String, Long>>allowlistedDataInfoList,boolean bindMountAppsData,boolean bindMountAppStorageDirs,@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_INSTALLER) {argsForZygote.add("--mount-external-installer");} else if (mountExternal == Zygote.MOUNT_EXTERNAL_PASS_THROUGH) {argsForZygote.add("--mount-external-pass-through");} else if (mountExternal == Zygote.MOUNT_EXTERNAL_ANDROID_WRITABLE) {argsForZygote.add("--mount-external-android-writable");}argsForZygote.add("--target-sdk-version=" + targetSdkVersion);// --setgroups is a comma-separated listif (gids != null && gids.length > 0) {final StringBuilder sb = new StringBuilder();sb.append("--setgroups=");final int sz = gids.length;for (int i = 0; i < sz; i++) {if (i != 0) {sb.append(',');}sb.append(gids[i]);}argsForZygote.add(sb.toString());}..........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);}}

这个变量名argsForZygote的数组显然是用作传参用的,这个方法做的主要也就是创建一个字符串列表argsForZygote,并将应用程序的启动参数保存在这个列表中,最后调用zygoteSendArgsAndGetResult方法将其传给zygote执行,这实际上就是达到了AMS向zygote发送请求的目的:

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();Process.ProcessStartResult result = new Process.ProcessStartResult();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);}}

在这个过程中会调用到上面这个方法,将应用参数的启动数据等写入zygote中,实际上就是向zygote发送了请求了。里面出现的ZygoteState类就是用于表示与Zygote的通信状况的。

再回到之前的

zygoteSendArgsAndGetResult(openZygoteSocketIfNeeded(abi),zygotePolicyFlags,argsForZygote);

这里的请求就是通过第一个参数(其实也是方法)的socket传达的,我们来看一看这个方法:

    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);}

实际上就是根据abi参数来建立与Zygote的Socket连接,其实就是根据Zygote的启动脚本类型来建立不同的连接,这里我们就不深入了。

所以到目前为止,ActivityManagerService就成功地读取应用程序的相关信息并将其打包发给Zygote,请求Zygote创建进程了,接下来我们看Zygote是如何响应AMS的请求的。

Zygote服务创建应用程序的进程

在之前的文章中,其实我们已经提到过了,Zygote将在创建完自身的Socket后等待AMS的请求,具体是在ZygoteInit的main方法中:

caller = zygoteServer.runSelectLoop(abiList);

其实这个方法里还是继续跳转,我们直接跳过中间步骤,来到最终调用到的ZygoteConnection方法中:

Runnable processCommand(ZygoteServer zygoteServer, boolean multipleOK) {ZygoteArguments parsedArgs;try (ZygoteCommandBuffer argBuffer = new ZygoteCommandBuffer(mSocket)) {while (true) {try {parsedArgs = ZygoteArguments.getInstance(argBuffer);//1-----1} .........if (parsedArgs.mInvokeWith != null || parsedArgs.mStartChildZygote|| !multipleOK || peer.getUid() != Process.SYSTEM_UID) {// Continue using old code for now. TODO: Handle these cases in the other path.pid = Zygote.forkAndSpecialize(parsedArgs.mUid, parsedArgs.mGid,parsedArgs.mGids, parsedArgs.mRuntimeFlags, rlimits,parsedArgs.mMountExternal, parsedArgs.mSeInfo, parsedArgs.mNiceName,fdsToClose, fdsToIgnore, parsedArgs.mStartChildZygote,parsedArgs.mInstructionSet, parsedArgs.mAppDataDir,parsedArgs.mIsTopApp, parsedArgs.mPkgDataInfoList,parsedArgs.mAllowlistedDataInfoList, parsedArgs.mBindMountAppDataDirs,parsedArgs.mBindMountAppStorageDirs);//2-----2try {if (pid == 0) {// in childzygoteServer.setForkChild();zygoteServer.closeServerSocket();IoUtils.closeQuietly(serverPipeFd);serverPipeFd = null;return handleChildProc(parsedArgs, childPipeFd,parsedArgs.mStartChildZygote);//3--------3} else {IoUtils.closeQuietly(childPipeFd);childPipeFd = null;handleParentProc(pid, serverPipeFd);return null;}} finally {IoUtils.closeQuietly(childPipeFd);IoUtils.closeQuietly(serverPipeFd);}} else {ZygoteHooks.preFork();Runnable result = Zygote.forkSimpleApps(argBuffer,zygoteServer.getZygoteSocketFileDescriptor(),peer.getUid(), Zygote.minChildUid(peer), parsedArgs.mNiceName);if (result == null) {// parent; we finished some number of forks. Result is Boolean.// We already did the equivalent of handleParentProc().ZygoteHooks.postForkCommon();// argBuffer contains a command not understood by forksimpleApps.continue;} else {// child; result is a Runnable.zygoteServer.setForkChild();Zygote.setAppProcessName(parsedArgs, TAG);  // ??? Necessary?return result;}}}}........}

方法很长,我们主要看注释出的三处,第一处的

parsedArgs = ZygoteArguments.getInstance(argBuffer);

方法显然是将我们之间通过AMS发送过来的启动参数给获取出来了。接着注释二处通过zygote将要启动的应用程序的进程就已经fork出来了,到此为止,实际上要启动的进程已经被创建出来了,只不过创建出来后我们还需要将这个进程进行一些处理,所以接下来会调用到注释三处的handleChildProc方法,到了这里之后又会跳转,具体来说会跳转到ZygoteInit.zygoteInit方法,这个方法我们在之前的Zygote的启动中也提到过,它首先会启动进程的Binder进程池,这样这个进程就可以与其他进程进行Binder进程间通信了,然后它会执行具体类的main方法。在这里,他将会启动ActivityThread的main方法。

这个ActivityThread类是Android系统中的核心类之一,它负责管理应用程序的主线程(UI线程)以及应用程序的生命周期和消息处理。每个应用程序在启动时都会创建一个ActivityThread实例,并在该实例的主线程中执行应用程序的主要逻辑。ActivityThread类的主要职责包括:

  1. 应用程序的初始化:在ActivityThread的main()方法中,会创建Application实例,并调用其onCreate()方法来进行应用程序的初始化工作。

  2. 管理Activity的生命周期:ActivityThread负责跟踪和管理应用程序中所有Activity的生命周期。它会接收来自系统的生命周期回调消息,并分发给相应的Activity进行处理,例如调用Activity的onCreate()、onStart()、onResume()等方法。

  3. 处理消息和事件:ActivityThread通过一个消息循环机制,处理来自系统和应用程序的消息和事件。它会接收并分发消息给对应的Handler进行处理,例如处理用户界面事件、处理来自其他组件的消息等。

  4. 启动Activity和Service:ActivityThread负责启动应用程序中的Activity和Service组件。它会接收来自系统的启动请求,并负责创建和启动相应的组件实例。

  5. 处理窗口和界面更新:ActivityThread负责处理窗口和界面的更新操作,包括创建和管理窗口、更新界面布局、处理用户界面事件等。

那么接下来我们就来简单看看它的main方法:

public static void main(String[] args) {Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");// Install selective syscall interceptionAndroidOs.install();CloseGuard.setEnabled(false);Environment.initForCurrentUser();// Make sure TrustedCertificateStore looks in the right place for CA certificatesfinal File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());TrustedCertificateStore.setDefaultUserDirectory(configDir);// Call per-process mainline module initialization.initializeMainlineModules();Process.setArgV0("<pre-initialized>");Looper.prepareMainLooper();// Find the value for {@link #PROC_START_SEQ_IDENT} if provided on the command line.// It will be in the format "seq=114"long startSeq = 0;if (args != null) {for (int i = args.length - 1; i >= 0; --i) {if (args[i] != null && args[i].startsWith(PROC_START_SEQ_IDENT)) {startSeq = Long.parseLong(args[i].substring(PROC_START_SEQ_IDENT.length()));}}}ActivityThread thread = new ActivityThread();thread.attach(false, startSeq);if (sMainThreadHandler == null) {sMainThreadHandler = thread.getHandler();}if (false) {Looper.myLooper().setMessageLogging(newLogPrinter(Log.DEBUG, "ActivityThread"));}// End of event ActivityThreadMain.Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);Looper.loop();throw new RuntimeException("Main thread loop unexpectedly exited");}

可以看到这个main方法中会自动创建出一个消息Looper和一个默认的Handler,然后会自动开启消息队列的循环使它可以处理消息。接下来Activity就将由ActivityThread管理启动了,这就已经完成了应用程序进程的启动了。

总结

最后再来回顾一下上面那张总结图:
在这里插入图片描述
这就是应用程序进程的启动过程。

相关文章:

Android应用程序进程的启动过程

Android应用程序进程的启动过程 导语 到这篇文章为止&#xff0c;我们已经简要地了解过了Android系统的启动流程了&#xff0c;其中比较重要的内容有Zygote进程的启动和SystemService以及Launcher的启动&#xff0c;接下来我们将要学习的是Android应用程序的启动过程&#xff…...

【2】Midjourney注册

随着AI技术的问世&#xff0c;2023年可以说是AI爆炸性成长的一年&#xff0c;近期最广为人知的AI服务除了chatgpt外&#xff0c;就是从去年五月就已经问世的AI绘画工具mid journey了。 ▲几个AI工具也代表了人工智能的热门阶段 只要输入一段文字&#xff0c;AI就会根据语意计算…...

第六十八天学习记录:高等数学:导数(宋浩板书)

导数是微积分中的一个概念&#xff0c;描述了函数在某一个点上的变化率。具体地说&#xff0c;函数 f ( x ) f(x) f(x)在 x a xa xa处的导数为 f ′ ( a ) f(a) f′(a)&#xff0c;表示当 x x x在 a a a处发生微小的变化 Δ x \Delta x Δx时&#xff0c; f ( x ) f(x) f(x)对…...

unreal 5 实现角色拾取功能

要实现角色拾取功能&#xff0c;我们需要实现蓝图接口功能&#xff0c;蓝图接口主要提供的是蓝图和蓝图之间可以通信&#xff0c;接下来&#xff0c;跟着教程&#xff0c;实现一下角色的拾取功能。 首先&#xff0c;我们要实现一个就是可视区的物品在朝向它的时候&#xff0c;会…...

chatgpt赋能python:如何使用Python升序排列一个列表?

如何使用Python升序排列一个列表&#xff1f; 在Python编程中&#xff0c;我们经常需要对列表进行排序。列表排序是一种常见的操作&#xff0c;可以帮助我们对数据进行分析和管理。在这篇文章中&#xff0c;我们将学习如何使用Python对一个列表进行升序排列。 什么是升序排列…...

Lecture 20 Topic Modelling

目录 Topic ModellingA Brief History of Topic ModelsLDAEvaluationConclusion Topic Modelling makeingsense of text English Wikipedia: 6M articlesTwitter: 500M tweets per dayNew York Times: 15M articlesarXiv: 1M articlesWhat can we do if we want to learn somet…...

ThreadPoolExecutor线程池

文章目录 一、ThreadPool线程池状态二、ThreadPoolExecutor构造方法三、Executors3.1 固定大小线程池3.2 带缓冲线程池3.3 单线程线程池 四、ThreadPoolExecutor4.1 execute(Runnable task)方法使用4.2 submit()方法4.3 invokeAll()4.4 invokeAny()4.5 shutdown()4.6 shutdownN…...

chatgpt赋能python:Python实践:如何升级pip

Python实践&#xff1a;如何升级pip Python作为一门高效的脚本语言&#xff0c;被广泛应用于数据分析、人工智能、Web开发等领域。而pip则是Python的包管理工具&#xff0c;是开发Python应用的必备工具。但是pip在使用过程中&#xff0c;有时候会出现版本不兼容或者出现漏洞等…...

【JavaEE进阶】mybatis

目录&#xff1a; 一、Mybatis是什么 三个映射关系如下图&#xff1a; 二、mybatis的使用&#xff08;前置工作简单案例&#xff09; 第一步&#xff1a;导入MAVEN依赖 第二步&#xff1a; 在spring项目当中新建数据源 第三步&#xff1a;新建一个实体类&#xff0c;是和…...

Redis的大key

什么是 redis 的大 key redis 的大 key 不是指存储在 redis 中的某个 key 的大小超过一定的阈值&#xff0c;而是该 key 所对应的 value 过大对于 string 类型来说&#xff0c;一般情况下超过 10KB 则认为是大 key&#xff1b;对于set、zset、hash 等类型来说&#xff0c;一般…...

MMPretrain

title: mmpretrain实战 date: 2023-06-07 16:04:01 tags: [image classification,mmlab] mmpretrain实战 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ccTl9bOl-1686129437336)(null)] 主要讲解了安装,还有使用教程.安装教程直接参考官网.下面讲…...

栈和队列(数据结构刷题)[一]-python

文章目录 前言一、原理介绍二、用栈实现队列1.操作2.思路 三、关于面试考察栈里面的元素在内存中是连续分布的么&#xff1f; 前言 提到栈和队列&#xff0c;大家可能对它们的了解只停留在表面&#xff0c;再深入一点&#xff0c;好像知道又好像不知道的感觉。本文我将从底层实…...

【备战秋招】JAVA集合

集合 前言 一方面&#xff0c; 面向对象语言对事物的体现都是以对象的形式&#xff0c;为了方便对多个对象 的操作&#xff0c;就要 对对象进行存储。 另一方面&#xff0c;使用Array存储对象方面具有一些弊端&#xff0c;而Java 集合就像一种容器&#xff0c;可以动态地把多…...

setState详解

this. setState( [partialState], [callback]) 1.[partialState] :支持部分状态更改 this, setState({ x:100 //不论总共有多少状态&#xff0c;我们只修改了x&#xff0c;其余的状态不动 });callback :在状态更改/视图更新完毕后触发执行&#xff0c;也可以说只要执行了setS…...

Qt5.12.6配置Android Arm开发环境(windows)

1. 安装jdk1.8 2.安装Android Studio 并安装 SDK 与NDK SDK Tools 选择 26.0.3 SDK Platform 选择 Android SDK Platform 26 NDK选择19版本 安卓ARM环境配置成功如下: JDK1.8 , SDK 26 , NDK 19 在安装QT时要选择 ARMv7(32位CPU)与ARM64-v8a(64位CPU) 选择支持android平台…...

七、进程程序替换

文章目录 一、进程程序替换&#xff08;一&#xff09;概念&#xff08;二&#xff09;为什么程序替换&#xff08;三&#xff09;程序替换的原理&#xff08;四&#xff09;如何进行程序替换1. execl2. 引入进程创建——子进程执行程序替换&#xff0c;会不会影响父进程呢? &…...

C++核心编程——详解运算符重载

文章目录&#x1f4ac; 一.运算符重载基础知识①基本概念②运算符重载的规则③运算符重载形式④运算符重载建议 二.常用运算符重载①左移(<<)和右移(>>)运算符重载1️⃣重载后函数参数是什么&#xff1f;2️⃣重载的函数返回类型是什么&#xff1f;3️⃣重载为哪种…...

2023年前端面试汇总-CSS

1. CSS基础 1.1. CSS选择器及其优先级 对于选择器的优先级&#xff1a; 1. 标签选择器、伪元素选择器&#xff1a;1&#xff1b; 2. 类选择器、伪类选择器、属性选择器&#xff1a;10&#xff1b; 3. id 选择器&#xff1a;100&#xff1b; 4. 内联样式&#xff1a;1000&a…...

Java调用Pytorch实现以图搜图(附源码)

Java调用Pytorch实现以图搜图 设计技术栈&#xff1a; 1、ElasticSearch环境&#xff1b; 2、Python运行环境&#xff08;如果事先没有pytorch模型时&#xff0c;可以用python脚本创建模型&#xff09;&#xff1b; 1、运行效果 2、创建模型&#xff08;有则可以跳过&#xf…...

【EasyX】实时时钟

目录 实时时钟1. 绘制静态秒针2. 秒针的转动3. 根据实际时间转动4. 添加时针和分针5. 添加表盘刻度 实时时钟 本博客介绍利用EasyX实现一个实时钟表的小程序&#xff0c;同时学习时间函数的使用。 本文源码可从github获取 1. 绘制静态秒针 第一步定义钟表的中心坐标center&a…...

基于XC7Z100的PCIe采集卡(GMSL FMC采集卡)

GMSL 图像采集卡 特性 ● PCIe Gen2.0 X8 总线&#xff1b; ● 支持V4L2调用&#xff1b; ● 1路CAN接口&#xff1b; ● 6路/12路 GMSL1/2摄像头输入&#xff0c;最高可达8MP&#xff1b; ● 2路可定义相机同步触发输入/输出&#xff1b; 优势 ● 采用PCIe主卡与FMC子…...

Kibana:使用 Kibana 自带数据进行可视化(一)

在今天的练习中&#xff0c;我们将使用 Kibana 自带的数据来进行一些可视化的展示。希望对刚开始使用 Kibana 的用户有所帮助。 前提条件 如果你还没有安装好自己的 Elastic Stack&#xff0c;你可以参考如下的视频来开启 Elastic Stack 并进行下面的练习。你可以开通阿里云检…...

MySQL数据库基础 07

第七章 单行函数 1. 函数的理解1.1 什么是函数1.2 不同DBMS函数的差异1.3 MySQL的内置函数及分类 2. 数值函数2.1 基本函数2.2 角度与弧度互换函数2.3 三角函数2.4 指数与对数2.5 进制间的转换 3. 字符串函数4. 日期和时间函数4.1 获取日期、时间 4.2 日期与时间戳的转换 4.3 获…...

JVM | JVM垃圾回收

JVM | JVM垃圾回收 1、堆空间的基本结构2、内存分配和回收原则2.1、对象优先在 Eden 区分配2.2、大对象直接进入老年代2.3、长期存活的对象将进入老年代2.4、主要进行 gc 的区域2.5、空间分配担保3、死亡对象判断方法3.1、引用计数法3.2、可达性分析算法3.3、引用类型总结3.4、…...

avive零头撸矿

Avive 是一个透明的、自下而上替代自上而下的多元网络&#xff0c;旨在克服当前生态系统的局限性&#xff0c;实现去中心化社会。 aVive&#xff1a;一个基于 SBT 和市场的 deSoc&#xff0c;它使 dapps 能够与分散的位置 oracle 和 SBT 关系进行互操作。您的主权社交网络元宇宙…...

openGauss5.0之学习环境 Docker安装

文章目录 0.前言1. 准备软硬件安装环境1.1 软硬件环境要求1.2 修改操作系统配置1.2.1 关闭操作系统防火墙 1.3 设置字符集参数1.4 设置时区和时间&#xff08;可选&#xff09;关闭swap交换内存1.5 关闭RemoveIPC1.6 关闭HISTORY记录 2. 容器安装2. 1支持的架构和操作系统版本2…...

数据可视化大屏人员停留系统的开发实录(默认加载条件筛选、单击加载、自动刷新加载、异步加载数据)

项目需求 录入进入房间的相关数据&#xff1b;从进入时间开始计时&#xff0c;计算滞留房间的时间&#xff1b;定时刷新数据&#xff0c;超过30分钟的人数&#xff0c;进行红色告警&#xff1b; 实现流程 为了完整地实现上述需求&#xff0c;我们可以按照以下步骤开发&#…...

【Linux】-关于调试器gdb的介绍和使用

作者&#xff1a;小树苗渴望变成参天大树 作者宣言&#xff1a;认真写好每一篇博客 作者gitee:gitee 如 果 你 喜 欢 作 者 的 文 章 &#xff0c;就 给 作 者 点 点 关 注 吧&#xff01; 文章目录 前言一、Linux中的debug和release二、gdb的使用**1.进入调试****2.显示代码*…...

项目开发经验

hadoop 1.namenode中有专门的工作线程池用于处理与datanode的心跳信号 dfs.namenode.handler.count20 * log2(Clust 2.编辑日志存储路径 dfs.namenode.edits.dir 设置与镜像文件存储路径 dfs.namenode分开存放&#xff0c;可以达到提高并发 3.yarn参数调优&#xff0c;单个服…...

STM32——05-按键、时钟控制、中断复位 点亮LED灯

如何点亮一颗LED灯 编程实现点灯 常用的 GPIO HAL 库函数&#xff1a; void HAL_GPIO_Init ( GPIO_TypeDef * GPIOx , GPIO_InitTypeDef * GPIO_Init ); void HAL_GPIO_WritePin ( GPIO_TypeDef * GPIOx , uint16_t GPIO_Pin , GPIO_PinState PinState ); void HAL_GPIO_Togg…...

上海公司注册网/网站排名优化培训课程

转自&#xff1a;http://dev.10086.cn/cmdn/wiki/index.php?doc-view-3717.htmlAndroidSDK提供了一个强大的类Drawable&#xff0c;Drawable这个抽象类到底代表了什么&#xff0c;如何使用&#xff1f;Drawable是个很抽象的概念&#xff0c;通过简单的例子程 序来学习它&#…...

广发证券 网站谁做的/b站推广软件

keka在创建压缩和解压时从不要求文件名。现在keka总是在拖放文件夹/文件进行压缩时要求新的文件名。这个问题是因为更新了有关文件访问的信息&#xff0c;那么该如何解决&#xff0c;恢复到以前那样&#xff1f; Keka文件访问权限解决办法 磁盘访问 为了能够像以前一样集成&…...

个人网站建设 免费/域名大全免费网站

scrapy爬虫成长日记之将抓取内容写入mysql数据库前面小试了一下scrapy抓取博客园的博客(您可在此查看scrapy爬虫成长日记之创建工程-抽取数据-保存为json格式的数据)&#xff0c;但是前面抓取的数据时保存为json格式的文本文件中的。这很显然不满足我们日常的实际应用&#xff…...

云南人才招聘网/网站seo如何优化

几何着色器&#xff0c;也就是根据顶点着色器输出的数据进行处理&#xff0c;比如让他们衍生啥的&#xff0c;反正就是处理 关于几何链接着色器 geometryShader glCreateShader(GL_GEOMETRY_SHADER); glShaderSource(geometryShader, 1, &gShaderCode, NULL); glCompileSh…...

龙岩网站建设哪里比较好/手机怎么建自己的网站

1 java socket的两个timeout 一个是connect timeout&#xff0c;即建立连接的timeout&#xff0c;另外一个是so timeout&#xff0c;是读取数据的timeout。这两个timeout都是因为客户端等不及了。 2 connect timeout 客户端想要和服务器端建立tcp连接&#xff0c;如果连接迟迟没…...

内贸在什么网站做/石家庄限号

支付宝沙箱环境支付demo 下载电脑网站的官方demo&#xff1a; 下载地址&#xff1a;https://docs.open.alipay.com/270/106291/ 解压导入demo项目 3.配置AlipayConfig 3.1 注册蚂蚁金服开发者账号 注册地址&#xff1a;https://open.alipay.com/platform/home.htm &#xff…...