课程咨询: 400-996-5531 / 投诉建议: 400-111-8989

认识达内从这里开始

认真做教育 专心促就业

重庆java培训班:Android应用程序的进程创建过程
  • 发布:重庆达内
  • 来源:重庆达内
  • 时间:2021-03-23 11:37

我们知道当startActivity的时候,除了创建Activity相关实体,系统会根据需要是否创建进程,此进程就是指的Linux层面的进程实体;后面会说到,其实创建的过程是fork,意为从父进程“copy”出一个新的进程。此过程必定经历启动者、ams、zygote等相互的进程间通信,本文主要梳理ams在此过程的重要环节,涉及虚拟机的继承、Binder线程池、消息循环是怎么建立的等等,而Activity的启动细节则放在其他章节另作探讨。

步骤

step1 Ams发起请求startProcessLocked

ActivityManagerService.java

直接从Ams中向zygote发起请求的入口开始分析,当然真正的开始是Ams收到其他应用的Binder请求,所以应该在onTransact里面才是真正的开始,待会儿会打出trace看。(这儿一个小细节,源码中有很多方法带Locked后缀,说明使用该方法需要加锁,因为其是非线程安全的)。

// Start the process. It will either succeed and return a result containing

// the PID of the new process, or else throw a RuntimeException.

boolean isActivityProcess = (entryPoint == null);

if (entryPoint == null) entryPoint = "android.app.ActivityThread";

Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "Start proc: " +

app.processName);

checkTime(startTime, "startProcess: asking zygote to start proc");

Process.ProcessStartResult startResult = Process.start(entryPoint,

app.processName, uid, uid, gids, debugFlags, mountExternal,

app.info.targetSdkVersion, app.info.seinfo, requiredAbi, instructionSet,

app.info.dataDir, entryPointArgs);

checkTime(startTime, "startProcess: returned from zygote!");

Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);

比较重要的是这一段话,调用Process类的start方法,并指定了入口类为ActivityThread。

system 671 641 2348412 179992 0 0000000000 S Binder:641_1

641-671/system_process W: at android.os.Process.zygoteSendArgsAndGetResult(Process.java:605)

641-671/system_process W: at android.os.Process.startViaZygote(Process.java:738)

641-671/system_process W: at android.os.Process.start(Process.java:521)

641-671/system_process W: at com.android.server.am.ActivityManagerService.startProcessLocked(ActivityManagerService.java:4214)

641-671/system_process W: at com.android.server.am.ActivityManagerService.startProcessLocked(ActivityManagerService.java:4069)

641-671/system_process W: at com.android.server.am.ActivityManagerService.startProcessLocked(ActivityManagerService.java:3909)

641-671/system_process W: at com.android.server.am.ActivityStackSupervisor.startSpecificActivityLocked(ActivityStackSupervisor.java:1441)

641-671/system_process W: at com.android.server.am.ActivityStack.resumeTopActivityInnerLocked(ActivityStack.java:2741)

641-671/system_process W: at com.android.server.am.ActivityStack.resumeTopActivityUncheckedLocked(ActivityStack.java:2213)

641-671/system_process W: at com.android.server.am.ActivityStackSupervisor.resumeFocusedStackTopActivityLocked(ActivityStackSupervisor.java:1859)

641-671/system_process W: at com.android.server.am.ActivityStack.completePauseLocked(ActivityStack.java:1393)

641-671/system_process W: at com.android.server.am.ActivityStack.activityPausedLocked(ActivityStack.java:1237)

641-671/system_process W: at com.android.server.am.ActivityManagerService.activityPaused(ActivityManagerService.java:7393)

641-671/system_process W: at android.app.ActivityManagerNative.onTransact(ActivityManagerNative.java:571)

641-671/system_process W: at com.android.server.am.ActivityManagerService.onTransact(ActivityManagerService.java:3169)

641-671/system_process W: at android.os.Binder.execTransact(Binder.java:565)

从如上的trace可以直接看出,在收到startActivity的请求后,执行了对当前Activity的pause操作,然后通过Process.java这个类对Zygote进程发起请求。注意,通过对线程号的观察可以看到,这一切都在一次binder线程中操作的。

注意Ams与Zygote之间是通过socket连接的,封装了zygoteState类去做通信相关的任务。从下图fd的占用可以明确地看到,zygote进程是不使用binder的。

device:/proc/341/fd # ls -l

ls -l

total 0

lrwx------ 1 root root 64 2021-03-16 17:49 0 -> /dev/null

lrwx------ 1 root root 64 2021-03-16 17:49 1 -> /dev/null

lr-x------ 1 root root 64 2021-03-16 17:49 10 -> /system/framework/core-oj.jar

lr-x------ 1 root root 64 2021-03-16 17:49 11 -> /system/framework/core-libart.jar

lr-x------ 1 root root 64 2021-03-16 17:49 12 -> /system/framework/conscrypt.jar

lr-x------ 1 root root 64 2021-03-16 17:49 13 -> /system/framework/okhttp.jar

lr-x------ 1 root root 64 2021-03-16 17:49 14 -> /system/framework/core-junit.jar

lr-x------ 1 root root 64 2021-03-16 17:49 15 -> /system/framework/bouncycastle.jar

lr-x------ 1 root root 64 2021-03-16 17:49 16 -> /system/framework/ext.jar

lr-x------ 1 root root 64 2021-03-16 17:49 17 -> /system/framework/framework.jar

lr-x------ 1 root root 64 2021-03-16 17:49 18 -> /system/framework/telephony-common.jar

lr-x------ 1 root root 64 2021-03-16 17:49 19 -> /system/framework/voip-common.jar

lrwx------ 1 root root 64 2021-03-16 17:49 2 -> /dev/null

lr-x------ 1 root root 64 2021-03-16 17:49 20 -> /system/framework/ims-common.jar

lr-x------ 1 root root 64 2021-03-16 17:49 21 -> /system/framework/apache-xml.jar

lr-x------ 1 root root 64 2021-03-16 17:49 22 -> /system/framework/org.apache.http.legacy.boot.jar

lrwx------ 1 root root 64 2021-03-16 17:49 23 -> socket:[9842]

lr-x------ 1 root root 64 2021-03-16 17:49 24 -> /system/framework/framework-res.apk

lr-x------ 1 root root 64 2021-03-16 17:49 25 -> /dev/urandom

l-wx------ 1 root root 64 2021-03-16 17:49 5 -> /sys/kernel/debug/tracing/trace_marker

lrwx------ 1 root root 64 2021-03-16 17:49 6 -> /dev/null

lrwx------ 1 root root 64 2021-03-16 17:49 7 -> /dev/null

lrwx------ 1 root root 64 2021-03-16 17:49 8 -> /dev/null

lrwx------ 1 root root 64 2021-03-16 17:49 9 -> socket:[9289]

Ams的调用栈到上面就截止了,接下来进入到Zygote进程中。

step2 Zygote收到请求

猜想Zygote进程初始化完成后,一定有个无限循环在等待其他进程的请求,直接定位到代码。

private static void runSelectLoop(String abiList) throws MethodAndArgsCaller {

ArrayList fds = new ArrayList();

ArrayList peers = new ArrayList();

fds.add(sServerSocket.getFileDescriptor());

peers.add(null);

while (true) {

StructPollfd[] pollFds = new StructPollfd[fds.size()];

for (int i = 0; i < pollFds.length; ++i) {

pollFds[i] = new StructPollfd();

pollFds[i].fd = fds.get(i);

pollFds[i].events = (short) POLLIN;

}

try {

Os.poll(pollFds, -1);

} catch (ErrnoException ex) {

throw new RuntimeException("poll failed", ex);

}

for (int i = pollFds.length - 1; i >= 0; --i) {

if ((pollFds[i].revents & POLLIN) == 0) {

continue;

}

if (i == 0) {

ZygoteConnection newPeer = acceptCommandPeer(abiList);

peers.add(newPeer);

fds.add(newPeer.getFileDesciptor());

} else {

boolean done = peers.get(i).runOnce();

if (done) {

peers.remove(i);

fds.remove(i);

}

}

}

}

}

可以看到,当接收到请求后,调用ZygoteConnection类的runOnce来处理。

boolean runOnce() throws ZygoteInit.MethodAndArgsCaller {

.......省略代码

pid = Zygote.forkAndSpecialize(parsedArgs.uid, parsedArgs.gid, parsedArgs.gids,

parsedArgs.debugFlags, rlimits, parsedArgs.mountExternal, parsedArgs.seInfo,

parsedArgs.niceName, fdsToClose, parsedArgs.instructionSet,

parsedArgs.appDataDir);

......省略代码

try {

/*子进程执行pid==0情况,父进程执行else情况*/

if (pid == 0) {

/*子进程*/

// in child

IoUtils.closeQuietly(serverPipeFd);

serverPipeFd = null;

handleChildProc(parsedArgs, descriptors, childPipeFd, newStderr);

// should never get here, the child is expected to either

// throw ZygoteInit.MethodAndArgsCaller or exec().

return true;

} else {

// in parent...pid of < 0 means failure

IoUtils.closeQuietly(childPipeFd);

childPipeFd = null;

/*Process中的io流监听的pid等信息都是通过下面的代码发出去的*/

return handleParentProc(pid, descriptors, serverPipeFd, parsedArgs);

}

} finally {

IoUtils.closeQuietly(childPipeFd);

IoUtils.closeQuietly(serverPipeFd);

}

}

主要玄机是forkAndSpecialize这个方法,能fork出native的子进程,然后再返回到调用的地方;但是会返回两次,一次是子进程,一次是父进程,这个通过返回值来判断,和linux的fork方法类似;如果是子进程,那么执行handleChildProc,注意下面的注释,意味着不会执行到return的语句,也就是上层的selectloop(因为子进程不用管zygote的接受命令循环了,直接去执行app进程的任务了);而父进程会调用handleParentProc后返回。

子进程的返回有个小技巧,是通过抛一个异常达到的,那么回到了哪里?我们再看一下ZygoteInit.java的main方法。

public static void main(String argv[]) {

// Mark zygote start. This ensures that thread creation will throw

// an error.

...

try {

Trace.traceBegin(Trace.TRACE_TAG_DALVIK, "ZygoteInit");

RuntimeInit.enableDdms();

// Start profiling the zygote initialization.

SamplingProfilerIntegration.start();

registerZygoteSocket(socketName);

preload();

gcAndFinalize();

// Zygote process unmounts root storage spaces.

Zygote.nativeUnmountStorageOnInit();

...

Log.i(TAG, "Accepting command socket connections");

/*这儿调用的runSelectLoop开启等待循环的*/

runSelectLoop(abiList);

closeServerSocket();

} catch (MethodAndArgsCaller caller) {

caller.run();

} catch (Throwable ex) {

Log.e(TAG, "Zygote died with exception", ex);

closeServerSocket();

throw ex;

}

}

是不是看到了熟悉的身影runSelectLoop,所以是Zygote进入java世界后,先进入main方法做了一些准备工作,然后开启selectLoop进行等待循环,而收到子进程抛出的MethodAndArgsCaller异常后会执行caller.run,这是我们能从调用栈中看到子进程生命的开始,之所以这样做,还可以通过抛异常的方式清空之前的调用栈,去除一些fork、设置等,看起来app的生命更清爽。如下图到onCreate的流程。

3199-3199/com.android.myapplication W: at com.android.myapplication.MainActivity.onCreate(MainActivity.java:42)

3199-3199/com.android.myapplication W: at android.app.Activity.performCreate(Activity.java:6709)

3199-3199/com.android.myapplication W: at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1118)

3199-3199/com.android.myapplication W: at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2624)

3199-3199/com.android.myapplication W: at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2732)

3199-3199/com.android.myapplication W: at android.app.ActivityThread.-wrap12(ActivityThread.java)

3199-3199/com.android.myapplication W: at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1483)

3199-3199/com.android.myapplication W: at android.os.Handler.dispatchMessage(Handler.java:102)

3199-3199/com.android.myapplication W: at android.os.Looper.loop(Looper.java:154)

3199-3199/com.android.myapplication W: at android.app.ActivityThread.main(ActivityThread.java:6141)

3199-3199/com.android.myapplication W: at java.lang.reflect.Method.invoke(Native Method)

3199-3199/com.android.myapplication W: at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:912)

3199-3199/com.android.myapplication W: at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:802)

哈哈,扯远了,继续看一下刚才子进程调用的handleChildProc到底做了什么事儿。

step3 handleChildProc -- 进入子进程的世界

.frameworks/base/core/java/com/android/internal/os/ZygoteConnection.java

按照惯例,先看调用栈。

private void handleChildProc(Arguments parsedArgs,

FileDescriptor[] descriptors, FileDescriptor pipeFd, PrintStream ewStderr)

throws ZygoteInit.MethodAndArgsCaller {

closeSocket();

ZygoteInit.closeServerSocket();

if (descriptors != null) {

try {

Os.dup2(descriptors[0], STDIN_FILENO);

Os.dup2(descriptors[1], STDOUT_FILENO);

Os.dup2(descriptors[2], STDERR_FILENO);

for (FileDescriptor fd: descriptors) {

IoUtils.closeQuietly(fd);

}

newStderr = System.err;

} catch (ErrnoException ex) {

Log.e(TAG, "Error reopening stdio", ex);

}

}

......省略代码

if (parsedArgs.runtimeInit) {

if (parsedArgs.invokeWith != null) {

WrapperInit.execApplication(parsedArgs.invokeWith,

parsedArgs.niceName, parsedArgs.targetSdkVersion,

pipeFd, parsedArgs.remainingArgs);

} else {

RuntimeInit.zygoteInit(parsedArgs.targetSdkVersion,

parsedArgs.remainingArgs, null /* classLoader */);

}

} else {

......省略代码

}

}

一开始进来有一些资源回收的工作,比如关闭socket等,因为子进程用不到了,避免占用fd。dup2那三个操作是把原来的标准输入、标准输出和错误输出(fd分别为0、1、2)用参数传来的descriptors代替,实际上传来的就是“/dev/null”,可以随便进入一个app的fd可以看到0、1、2一定是/dev/null。接下来,Ams传来的参数中有“--runtime-init”并且invokeWith的是ActivityThead,然后执行RuntimeInit.zygoteInit这个方法。

step4 RuntimeInit.zygoteInit -- 子进程环境准备

先看代码

public static final void zygoteInit(int targetSdkVersion, String[] argv, ClassLoader classLoader)

throws ZygoteInit.MethodAndArgsCaller {

if (DEBUG) Slog.d(TAG, "RuntimeInit: Starting application from zygote");

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

redirectLogStreams();

/*设置时区、log、http代理等一些信息*/

commonInit();

nativeZygoteInit();

applicationInit(targetSdkVersion, argv, classLoader);

}

commonInit是一些通用的初始化,不用去深究,下面重点看一下nativeZygoteInit(比较重要的方法一般都是放在native去干的)和applicationInit。

nativeZygoteInit

通过一系列继承、重写的技巧,总之最后调用到了app_main.cpp中

.frameworks/base/cmds/app_process/app_main.cpp

virtual void onZygoteInit()

{

sp proc = ProcessState::self();

ALOGV("App process: starting thread pool.\n");

proc->startThreadPool();

}

如果你熟悉native binder进程的写法的话,对这两句话一定不陌生,又看到了熟悉的套路。这两句话在app启动之前帮忙打开了/dev/binder,同时启动了binder线程池。之后app在使用binder时就不用关心binder驱动以及线程池相关的事儿了。

applicationInit

private static void invokeStaticMain(String className, String[] argv, ClassLoader classLoader)

throws ZygoteInit.MethodAndArgsCaller {

Class cl;

try {

cl = Class.forName(className, true, classLoader);

} catch (ClassNotFoundException ex) {

throw new RuntimeException(

"Missing class when invoking static main " + className,

ex);

}

Method m;

try {

m = cl.getMethod("main", new Class[] { String[].class });

} catch (NoSuchMethodException ex) {

throw new RuntimeException(

"Missing static main on " + className, ex);

} catch (SecurityException ex) {

throw new RuntimeException(

"Problem getting static main on " + className, ex);

}

int modifiers = m.getModifiers();

if (! (Modifier.isStatic(modifiers) && Modifier.isPublic(modifiers))) {

throw new RuntimeException(

"Main method is not public and static on " + className);

}

/*

* This throw gets caught in ZygoteInit.main(), which responds

* by invoking the exception's run() method. This arrangement

* clears up all the stack frames that were required in setting

* up the process.

*/

throw new ZygoteInit.MethodAndArgsCaller(m, argv);

}

程序比较简单,主要是通过反射的方式找到ActivityThread的main方法,并且抛出异常把该方法作为参数传递到zygote的main方法中去(前面讲了zygote的main方法除了开启selectLoop还捕获了MethodAndArgsCaller这个异常)。

/**

* Helper exception class which holds a method and arguments and

* can call them. This is used as part of a trampoline to get rid of

* the initial process setup stack frames.

*/

public static class MethodAndArgsCaller extends Exception

implements Runnable {

/** method to call */

private final Method mMethod;

/** argument array */

private final String[] mArgs;

public MethodAndArgsCaller(Method method, String[] args) {

mMethod = method;

mArgs = args;

}

public void run() {

try {

mMethod.invoke(null, new Object[] { mArgs });

} catch (IllegalAccessException ex) {

throw new RuntimeException(ex);

} catch (InvocationTargetException ex) {

Throwable cause = ex.getCause();

if (cause instanceof RuntimeException) {

throw (RuntimeException) cause;

} else if (cause instanceof Error) {

throw (Error) cause;

}

throw new RuntimeException(ex);

}

}

}

MethodAndArgsCaller的run方法很简单,其实就是去执行ActivityThread的main方法。现在我们来捋一下,zygote饶了这么大一圈,就是为了fork出子进程,做一些binder线程池等的准备工作,最后执行ActivityThread的main方法。

通过调用栈来总结一下子进程创建生命的过程

1896-1896/com.android.myapplication W: at com.android.internal.os.RuntimeInit.invokeStaticMain(RuntimeInit.java:237)

1896-1896/com.android.myapplication W: at com.android.internal.os.RuntimeInit.applicationInit(RuntimeInit.java:338)

1896-1896/com.android.myapplication W: at com.android.internal.os.RuntimeInit.zygoteInit(RuntimeInit.java:290)

1896-1896/com.android.myapplication W: at com.android.internal.os.ZygoteConnection.handleChildProc(ZygoteConnection.java:757)

1896-1896/com.android.myapplication W: at com.android.internal.os.ZygoteConnection.runOnce(ZygoteConnection.java:243)

1896-1896/com.android.myapplication W: at com.android.internal.os.ZygoteInit.runSelectLoop(ZygoteInit.java:876)

1896-1896/com.android.myapplication W: at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:798)

注意虽然看pid这是子进程的pid号,但是继承了父进程的调用栈,从handleChildProc开始是子进程,之前是父进程zygote。

step5 ActivityThread -- app的入口

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

这次我们主要看一下ActivityThread的静态main方法,因为整个文件有6000多行,关系到Activity实例的各种生命周期和管理、以及viewroot的创建等等。

public static void main(String[] args) {

...

Looper.prepareMainLooper();

ActivityThread thread = new ActivityThread();

thread.attach(false);

if (sMainThreadHandler == null) {

sMainThreadHandler = thread.getHandler();

}

// End of event ActivityThreadMain.

Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);

Looper.loop();

throw new RuntimeException("Main thread loop unexpectedly exited");

}

主要是调用Looper的静态方法创建消息循环,然后创建ActivityThread实例,最后进入到消息循环中。

总结

通过本文大致理了从Ams开始创建一个app进程的大致流程,如果以执行的进程实体来看,其实主要分三个部分,一个是ams中,一个是zygote父进程中,一个是子进程中。有了这个大致的脉络,再根据调用栈来看,实际还是比较清晰的。ams负责接收binder请求并与zygote联系,zygote父进程收到socket消息后去fork出子进程,子进程负责准备好binder环境和消息循环,然后开始Activity的生命周期。

<  上一篇:重庆java培训:使用闭包模拟实现AMD模块化规范
下一篇:重庆java培训机构:类加载子系统  >
相关推荐
最新资讯
免费试听课程
  • 全部课程
  • IT课程
  • 设计课程
  • 运营课程
Free courses
最新开班时间
  • 北京
  • 上海
  • 广州
  • 深圳
  • 南京
  • 成都
  • 武汉
  • 西安
  • 青岛
  • 天津
  • 杭州
  • 重庆
  • 哈尔滨
  • 济南
  • 沈阳
  • 合肥
  • 郑州
  • 长春
  • 苏州
  • 长沙
  • 昆明
  • 太原
  • 无锡
  • 石家庄
  • 南宁
  • 佛山
  • 珠海
  • 宁波
  • 保定
  • 呼和浩特
  • 洛阳
  • 烟台
  • 运城
  • 潍坊
  • 开课名称
  • 开班时间
  • 抢座
  • 咨询
  • 开课名称
  • 开班时间
  • 抢座
  • 咨询
    • Java全链路开发
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • 云计算全栈开发
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • 网络安全工程师
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • 人工智能工程师
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • 数据分析与商业智能
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • C++物联网工程师
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • 软件测试工程师
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • AI大模型全栈工程师
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • 鸿蒙原生应用开发
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • VFX商业视效设计
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • AGI商业设计变现
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • 新媒体电商运营
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • 云计算全栈开发
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • C++物联网工程师
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • 软件测试工程师
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • AI大模型全栈工程师
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • 鸿蒙原生应用开发
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • VFX商业视效设计
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • 云计算全栈开发
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • 网络安全工程师
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • C++物联网工程师
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • 软件测试工程师
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • AI大模型全栈工程师
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • 鸿蒙原生应用开发
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • VFX商业视效设计
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • Java全链路开发
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • 云计算全栈开发
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • 网络安全工程师
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • C++物联网工程师
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • 软件测试工程师
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • AI大模型全栈工程师
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • 鸿蒙原生应用开发
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • VFX商业视效设计
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • AGI商业设计变现
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • 新媒体电商运营
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • 云计算全栈开发
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • 网络安全工程师
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • C++物联网工程师
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • 软件测试工程师
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • AI大模型全栈工程师
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • 鸿蒙原生应用开发
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • VFX商业视效设计
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • AGI商业设计变现
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • 新媒体电商运营
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • Java全链路开发
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • 云计算全栈开发
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • 网络安全工程师
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • C++物联网工程师
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • 软件测试工程师
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • AI大模型全栈工程师
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • 鸿蒙原生应用开发
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • VFX商业视效设计
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • Java全链路开发
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • 网络安全工程师
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • C++物联网工程师
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • 软件测试工程师
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • AI大模型全栈工程师
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • 鸿蒙原生应用开发
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • VFX商业视效设计
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • 云计算全栈开发
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • 网络安全工程师
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • C++物联网工程师
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • 软件测试工程师
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • AI大模型全栈工程师
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • 鸿蒙原生应用开发
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • C++物联网工程师
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • 软件测试工程师
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • AI大模型全栈工程师
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • 鸿蒙原生应用开发
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • VFX商业视效设计
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • Java全链路开发
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • C++物联网工程师
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • 软件测试工程师
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • AI大模型全栈工程师
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • 鸿蒙原生应用开发
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • VFX商业视效设计
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • Java全链路开发
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • 网络安全工程师
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • 数据分析与商业智能
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • C++物联网工程师
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • 软件测试工程师
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • AI大模型全栈工程师
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • 鸿蒙原生应用开发
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • VFX商业视效设计
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • AGI商业设计变现
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • 新媒体电商运营
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • Java全链路开发
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • 网络安全工程师
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • C++物联网工程师
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • 软件测试工程师
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • AI大模型全栈工程师
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • 鸿蒙原生应用开发
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • VFX商业视效设计
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • Java全链路开发
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • C++物联网工程师
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • 软件测试工程师
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • AI大模型全栈工程师
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • 鸿蒙原生应用开发
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • VFX商业视效设计
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • Java全链路开发
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • 网络安全工程师
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • C++物联网工程师
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • 软件测试工程师
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • AI大模型全栈工程师
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • 鸿蒙原生应用开发
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • VFX商业视效设计
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • 新媒体电商运营
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • Java全链路开发
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • 网络安全工程师
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • C++物联网工程师
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • 软件测试工程师
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • AI大模型全栈工程师
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • 鸿蒙原生应用开发
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • AGI商业设计变现
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • Java全链路开发
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • 网络安全工程师
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • C++物联网工程师
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • 软件测试工程师
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • AI大模型全栈工程师
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • 鸿蒙原生应用开发
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • VFX商业视效设计
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • 云计算全栈开发
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • 网络安全工程师
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • C++物联网工程师
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • 软件测试工程师
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • AI大模型全栈工程师
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • 鸿蒙原生应用开发
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • VFX商业视效设计
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • AGI商业设计变现
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • 新媒体电商运营
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • Java全链路开发
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • C++物联网工程师
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • 软件测试工程师
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • AI大模型全栈工程师
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • 鸿蒙原生应用开发
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • VFX商业视效设计
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • Java全链路开发
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • 网络安全工程师
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • C++物联网工程师
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • 软件测试工程师
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • AI大模型全栈工程师
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • 鸿蒙原生应用开发
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • Java全链路开发
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • 云计算全栈开发
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • C++物联网工程师
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • 软件测试工程师
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • AI大模型全栈工程师
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • 鸿蒙原生应用开发
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • VFX商业视效设计
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • 网络安全工程师
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • C++物联网工程师
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • 软件测试工程师
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • AI大模型全栈工程师
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • 鸿蒙原生应用开发
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • VFX商业视效设计
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • 新媒体电商运营
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • Java全链路开发
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • 网络安全工程师
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • C++物联网工程师
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • 软件测试工程师
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • AI大模型全栈工程师
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • 鸿蒙原生应用开发
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • VFX商业视效设计
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • AGI商业设计变现
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • Java全链路开发
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • C++物联网工程师
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • 软件测试工程师
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • AI大模型全栈工程师
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • 鸿蒙原生应用开发
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • AGI商业设计变现
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • 云计算全栈开发
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • C++物联网工程师
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • 软件测试工程师
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • AI大模型全栈工程师
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • 鸿蒙原生应用开发
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • VFX商业视效设计
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • Java全链路开发
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • 网络安全工程师
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • C++物联网工程师
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • 软件测试工程师
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • AI大模型全栈工程师
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • 鸿蒙原生应用开发
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • AGI商业设计变现
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • 网络安全工程师
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • C++物联网工程师
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • 软件测试工程师
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • AI大模型全栈工程师
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • 鸿蒙原生应用开发
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • VFX商业视效设计
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • 网络安全工程师
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • C++物联网工程师
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • 软件测试工程师
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • AI大模型全栈工程师
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • 鸿蒙原生应用开发
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • VFX商业视效设计
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • Java全链路开发
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • C++物联网工程师
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • 软件测试工程师
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • AI大模型全栈工程师
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • 鸿蒙原生应用开发
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • AGI商业设计变现
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • 网络安全工程师
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • C++物联网工程师
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • 软件测试工程师
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • AI大模型全栈工程师
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • 鸿蒙原生应用开发
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • VFX商业视效设计
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • 网络安全工程师
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • C++物联网工程师
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • 软件测试工程师
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • AI大模型全栈工程师
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • 鸿蒙原生应用开发
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • AGI商业设计变现
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • 网络安全工程师
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • C++物联网工程师
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • 软件测试工程师
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • AI大模型全栈工程师
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • 鸿蒙原生应用开发
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • VFX商业视效设计
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • C++物联网工程师
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • 软件测试工程师
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • AI大模型全栈工程师
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • 鸿蒙原生应用开发
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • VFX商业视效设计
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • 网络安全工程师
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • C++物联网工程师
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • 软件测试工程师
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • AI大模型全栈工程师
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • 鸿蒙原生应用开发
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • VFX商业视效设计
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • C++物联网工程师
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • 软件测试工程师
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • AI大模型全栈工程师
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • 鸿蒙原生应用开发
    • 9月29日
    • 火热抢座中
    • 立即咨询
    • VFX商业视效设计
    • 9月29日
    • 火热抢座中
    • 立即咨询
预约申请试听课
收起