image.png

目录

Android应用启动之从Launcher拉起APP(一)Android应用启动之从Launcher拉起APP(二)Android应用启动之从Launcher拉起APP(三)Android应用启动之从Launcher拉起APP时序图

上文中写到,写到从AMS->ZygoteServer->ActivityThread。继续承接上文。

ActivityThread启动主线程

进入android.app.activethread类后,开始执行main函数:

public static void main(String[] args) {Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");SamplingProfilerIntegration.start();CloseGuard.setEnabled(false);Environment.initForCurrentUser();EventLogger.setReporter(new EventLoggingReporter());final File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());TrustedCertificateStore.setDefaultUserDirectory(configDir);Process.setArgV0("<pre-initialized>");Looper.prepareMainLooper();ActivityThread thread = new ActivityThread();thread.attach(false);if (sMainThreadHandler == null) {sMainThreadHandler = thread.getHandler();}if (false) {Looper.myLooper().setMessageLogging(newLogPrinter(Log.DEBUG, "ActivityThread"));}Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);Looper.loop();throw new RuntimeException("Main thread loop unexpectedly exited");}

函数中会创建消息处理,并执行了ActivityThread的attach函数:

private void attach(boolean system) {sCurrentActivityThread = this;mSystemThread = system;if (!system) {ViewRootImpl.addFirstDrawHandler(new Runnable() {@Overridepublic void run() {ensureJitEnabled();}});android.ddm.DdmHandleAppName.setAppName("<pre-initialized>",UserHandle.myUserId());RuntimeInit.setApplicationObject(mAppThread.asBinder());final IActivityManager mgr = ActivityManager.getService();try {// 执行ActivityManagerServer的attachApplication函数mgr.attachApplication(mAppThread);} catch (RemoteException ex) {throw ex.rethrowFromSystemServer();}……}

ActivityThread通过Binder将ApplicationThread对象传递给ActivityManagerService。
ActivityManagerService的attachApplication函数:

public final void attachApplication(IApplicationThread thread) {synchronized (this) {int callingPid = Binder.getCallingPid();final long origId = Binder.clearCallingIdentity();attachApplicationLocked(thread, callingPid);Binder.restoreCallingIdentity(origId);}

进入到attachApplicationLocked函数:

private final boolean attachApplicationLocked(IApplicationThread thread,int pid) {ProcessRecord app;long startTime = SystemClock.uptimeMillis();if (pid != MY_PID && pid >= 0) {synchronized (mPidsSelfLocked) {app = mPidsSelfLocked.get(pid);}} else {app = null;}if (app == null) {……}// 清除ProcessRecord中的信息,以确保没有不相关进程的信息if (app.thread != null) {handleAppDiedLocked(app, true, true);}if (DEBUG_ALL) Slog.v(TAG, "Binding process pid " + pid + " to record " + app);final String processName = app.processName;try {// 注册AppDeathRecipient,保证意外关闭时候系统能收到信息AppDeathRecipient adr = new AppDeathRecipient(app, pid, thread);thread.asBinder().linkToDeath(adr, 0);app.deathRecipient = adr;} catch (RemoteException e) {……}EventLog.writeEvent(EventLogTags.AM_PROC_BOUND, app.userId, app.pid, app.processName);app.makeActive(thread, mProcessStats);app.curAdj = app.setAdj = app.verifiedAdj = ProcessList.INVALID_ADJ;app.curSchedGroup = app.setSchedGroup = ProcessList.SCHED_GROUP_DEFAULT;app.forcingToImportant = null;updateProcessForegroundLocked(app, false, false);app.hasShownUi = false;app.debugging = false;app.cached = false;app.killedByAm = false;app.killed = false;……// 调用ActivityThread的bindApplication方法checkTime(startTime, "attachApplicationLocked: immediately before bindApplication");mStackSupervisor.mActivityMetricsLogger.notifyBindApplication(app);if (app.instr != null) {thread.bindApplication(processName, appInfo, providers,app.instr.mClass,profilerInfo, app.instr.mArguments,app.instr.mWatcher,app.instr.mUiAutomationConnection, testMode,mBinderTransactionTrackingEnabled, enableTrackAllocation,isRestrictedBackupMode || !normalMode, app.persistent,new Configuration(getGlobalConfiguration()), app.compat,getCommonServicesLocked(app.isolated),mCoreSettingsObserver.getCoreSettingsLocked(),buildSerial);} else {thread.bindApplication(processName, appInfo, providers, null, profilerInfo,null, null, null, testMode,mBinderTransactionTrackingEnabled, enableTrackAllocation,isRestrictedBackupMode || !normalMode, app.persistent,new Configuration(getGlobalConfiguration()), app.compat,getCommonServicesLocked(app.isolated),mCoreSettingsObserver.getCoreSettingsLocked(),buildSerial);}checkTime(startTime, "attachApplicationLocked: immediately after bindApplication");updateLruProcessLocked(app, false, null);checkTime(startTime, "attachApplicationLocked: after updateLruProcessLocked");app.lastRequestedGc = app.lastLowMemory = SystemClock.uptimeMillis();} catch (Exception e) {……}mPersistentStartingProcesses.remove(app);if (DEBUG_PROCESSES && mProcessesOnHold.contains(app)) Slog.v(TAG_PROCESSES,"Attach application locked removing on hold: " + app);mProcessesOnHold.remove(app);boolean badApp = false;boolean didSomething = false;// 执行ActivityStack中的attachApplicationLocked函数if (normalMode) {try {if (mStackSupervisor.attachApplicationLocked(app)) {didSomething = true;}} catch (Exception e) {Slog.wtf(TAG, "Exception thrown launching activities in " + app, e);badApp = true;}}……return true;}

从上文代码知道,ActivityManagerService有执行了ActivityThread的bindApplication函数:

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) {if (services != null) {ServiceManager.initServiceCache(services);}setCoreSettings(coreSettings);AppBindData data = new AppBindData();data.processName = processName;data.appInfo = appInfo;data.providers = providers;data.instrumentationName = instrumentationName;data.instrumentationArgs = instrumentationArgs;data.instrumentationWatcher = instrumentationWatcher;data.instrumentationUiAutomationConnection = instrumentationUiConnection;data.debugMode = debugMode;data.enableBinderTracking = enableBinderTracking;data.trackAllocation = trackAllocation;data.restrictedBackupMode = isRestrictedBackupMode;data.persistent = persistent;data.config = config;data.compatInfo = compatInfo;data.initProfilerInfo = profilerInfo;data.buildSerial = buildSerial;sendMessage(H.BIND_APPLICATION, data);}

bindApplicationhan函数中会像消息处理发送BIND_APPLICATION消息:

case BIND_APPLICATION:Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "bindApplication");AppBindData data = (AppBindData)msg.obj;handleBindApplication(data);Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);break;

最后会执行handleBindApplication函数:

private void handleBindApplication(AppBindData data) {// 设置虚拟机信息VMRuntime.registerSensitiveThread();if (data.trackAllocation) {DdmVmInternal.enableRecentAllocations(true);}……Process.setArgV0(data.processName);android.ddm.DdmHandleAppName.setAppName(data.processName,UserHandle.myUserId());//设置进程运行信息……final ContextImpl appContext = ContextImpl.createAppContext(this, data.info);updateLocaleListFromAppContext(appContext,mResourcesManager.getConfiguration().getLocales());……
.final StrictMode.ThreadPolicy savedPolicy = StrictMode.allowThreadDiskWrites();try {Application app = data.info.makeApplication(data.restrictedBackupMode, null);mInitialApplication = app;if (!data.restrictedBackupMode) {if (!ArrayUtils.isEmpty(data.providers)) {installContentProviders(app, data.providers);mH.sendEmptyMessageDelayed(H.ENABLE_JIT, 10*1000);}}try {mInstrumentation.onCreate(data.instrumentationArgs);}catch (Exception e) {……}try {mInstrumentation.callApplicationOnCreate(app);} catch (Exception e) {……}} finally {StrictMode.setThreadPolicy(savedPolicy);}// 加载字体资源FontsContract.setApplicationContextForResources(appContext);try {final ApplicationInfo info =getPackageManager().getApplicationInfo(data.appInfo.packageName,PackageManager.GET_META_DATA /*flags*/,UserHandle.myUserId());if (info.metaData != null) {final int preloadedFontsResource = info.metaData.getInt(ApplicationInfo.METADATA_PRELOADED_FONTS, 0);if (preloadedFontsResource != 0) {data.info.mResources.preloadFonts(preloadedFontsResource);}}} catch (RemoteException e) {throw e.rethrowFromSystemServer();}}

ActivityManagerServiceh中后续执行了ActivityStackSupervisor类的attachApplicationLocked函数:

boolean attachApplicationLocked(ProcessRecord app) throws RemoteException {final String processName = app.processName;boolean didSomething = false;for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks;for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) {final ActivityStack stack = stacks.get(stackNdx);if (!isFocusedStack(stack)) {continue;}// 获取Activity的描述ActivityRecord hr = stack.topRunningActivityLocked();if (hr != null) {if (hr.app == null && app.uid == hr.info.applicationInfo.uid&& processName.equals(hr.processName)) {try {// 执行启动Activityif (realStartActivityLocked(hr, app, true, true)) {didSomething = true;}} catch (RemoteException e) {Slog.w(TAG, "Exception in new application when starting activity "+ hr.intent.getComponent().flattenToShortString(), e);throw e;}}}}}if (!didSomething) {// 继续执行显示ActivityensureActivitiesVisibleLocked(null, 0, !PRESERVE_WINDOWS);}return didSomething;}

到这里,后面就该ActivityThread调度执行Activity生命周期方法,完成Activity的启动。realStartActivityLocked函数:

final boolean realStartActivityLocked(ActivityRecord r, ProcessRecord app,boolean andResume, boolean checkConfig) throws RemoteException {……app.thread.scheduleLaunchActivity(new Intent(r.intent), r.appToken,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.launchedFromPackage, task.voiceInteractor, app.repProcState, r.icicle,r.persistentState, results, newIntents, !andResume,mService.isNextTransitionForward(), profilerInfo)……return true;}

最后又执行回去ActivityThread的scheduleLaunchActivity函数:

public final void scheduleLaunchActivity(Intent intent, IBinder token, int ident,ActivityInfo info, Configuration curConfig, Configuration overrideConfig,CompatibilityInfo compatInfo, String referrer, IVoiceInteractor voiceInteractor,int procState, Bundle state, PersistableBundle persistentState,List<ResultInfo> pendingResults, List<ReferrerIntent> pendingNewIntents,boolean notResumed, boolean isForward, ProfilerInfo profilerInfo) {updateProcessState(procState, false);ActivityClientRecord r = new ActivityClientRecord();r.token = token;r.ident = ident;r.intent = intent;r.referrer = referrer;r.voiceInteractor = voiceInteractor;r.activityInfo = info;r.compatInfo = compatInfo;r.state = state;r.persistentState = persistentState;r.pendingResults = pendingResults;r.pendingIntents = pendingNewIntents;r.startsNotResumed = notResumed;r.isForward = isForward;r.profilerInfo = profilerInfo;r.overrideConfig = overrideConfig;updatePendingConfiguration(curConfig);sendMessage(H.LAUNCH_ACTIVITY, r);}case LAUNCH_ACTIVITY: {Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStart");final ActivityClientRecord r = (ActivityClientRecord) msg.obj;r.packageInfo = getPackageInfoNoCheck(r.activityInfo.applicationInfo, r.compatInfo);handleLaunchActivity(r, null, "LAUNCH_ACTIVITY");Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);} break;

接续跟踪,执行到了handleLaunchActivity函数:

private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent, String reason) {……Activity a = performLaunchActivity(r, customIntent);if (a != null) {r.createdConfig = new Configuration(mConfiguration);reportSizeConfigurations(r);Bundle oldState = r.state;handleResumeActivity(r.token, false, r.isForward,!r.activity.mFinished && !r.startsNotResumed, r.lastProcessedSeq, reason);if (!r.activity.mFinished && r.startsNotResumed) {performPauseActivityIfNeeded(r, reason);if (r.isPreHoneycomb()) {r.state = oldState;}}} else {……}}

进入performLaunchActivity函数(从函数名字就知道他是用来处理启动Activity的):

private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {ActivityInfo aInfo = r.activityInfo;if (r.packageInfo == null) {r.packageInfo = getPackageInfo(aInfo.applicationInfo, r.compatInfo,Context.CONTEXT_INCLUDE_CODE);}ComponentName component = r.intent.getComponent();if (component == null) {component = r.intent.resolveActivity(mInitialApplication.getPackageManager());r.intent.setComponent(component);}if (r.activityInfo.targetActivity != null) {component = new ComponentName(r.activityInfo.packageName,r.activityInfo.targetActivity);}ContextImpl appContext = createBaseContextForActivity(r);Activity activity = null;try {java.lang.ClassLoader cl = appContext.getClassLoader();// 根据类名字,创建activity对象activity = mInstrumentation.newActivity(cl, component.getClassName(), r.intent);StrictMode.incrementExpectedActivityCount(activity.getClass());r.intent.setExtrasClassLoader(cl);r.intent.prepareToEnterProcess();if (r.state != null) {r.state.setClassLoader(cl);}} catch (Exception e) {if (!mInstrumentation.onException(activity, e)) {throw new RuntimeException("Unable to instantiate activity " + component+ ": " + e.toString(), e);}}try {Application app = r.packageInfo.makeApplication(false, mInstrumentation);if (localLOGV) Slog.v(TAG, "Performing launch of " + r);if (localLOGV) Slog.v(TAG, r + ": app=" + app+ ", appName=" + app.getPackageName()+ ", pkg=" + r.packageInfo.getPackageName()+ ", comp=" + r.intent.getComponent().toShortString()+ ", dir=" + r.packageInfo.getAppDir());if (activity != null) {CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager());Configuration config = new Configuration(mCompatConfiguration);if (r.overrideConfig != null) {config.updateFrom(r.overrideConfig);}if (DEBUG_CONFIGURATION) Slog.v(TAG, "Launching activity "+ r.activityInfo.name + " with config " + config);Window window = null;if (r.mPendingRemoveWindow != null && r.mPreserveWindow) {window = r.mPendingRemoveWindow;r.mPendingRemoveWindow = null;r.mPendingRemoveWindowManager = null;}appContext.setOuterContext(activity);// 执行activity的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.configCallback);if (customIntent != null) {activity.mIntent = customIntent;}r.lastNonConfigurationInstances = null;checkAndBlockForNetworkAccess();activity.mStartedActivity = false;int theme = r.activityInfo.getThemeResource();if (theme != 0) {activity.setTheme(theme);}activity.mCalled = false;// 执行activity的OnCreat函数if (r.isPersistable()) {mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);} else {mInstrumentation.callActivityOnCreate(activity, r.state);}if (!activity.mCalled) {throw new SuperNotCalledException("Activity " + r.intent.getComponent().toShortString() +" did not call through to super.onCreate()");}r.activity = activity;r.stopped = true;if (!r.activity.mFinished) {// 执行activity的onStart函数activity.performStart();r.stopped = false;}if (!r.activity.mFinished) {if (r.isPersistable()) {if (r.state != null || r.persistentState != null) {mInstrumentation.callActivityOnRestoreInstanceState(activity, r.state,r.persistentState);}} else if (r.state != null) {mInstrumentation.callActivityOnRestoreInstanceState(activity, r.state);}}if (!r.activity.mFinished) {activity.mCalled = false;if (r.isPersistable()) {mInstrumentation.callActivityOnPostCreate(activity, r.state,r.persistentState);} else {mInstrumentation.callActivityOnPostCreate(activity, r.state);}if (!activity.mCalled) {throw new SuperNotCalledException("Activity " + r.intent.getComponent().toShortString() +" did not call through to super.onPostCreate()");}}}r.paused = true;mActivities.put(r.token, r);} catch (SuperNotCalledException e) {throw e;} catch (Exception e) {if (!mInstrumentation.onException(activity, e)) {throw new RuntimeException("Unable to start activity " + component+ ": " + e.toString(), e);}}return activity;}

这样就开始Activity的生命周期。

最后看一下handleLaunchActivity中调用的handleResumeActivity函数:

final void handleResumeActivity(IBinder token,boolean clearHide, boolean isForward, boolean reallyResume) {// If we are getting ready to gc after going to the background, well// we are back active so skip it.unscheduleGcIdler();mSomeActivitiesChanged = true;// TODO Push resumeArgs into the activity for considerationActivityClientRecord r = performResumeActivity(token, clearHide);……
}

这样子就app启动完成了。

总结

APP启动大致需要六步骤:
(1)Launcher响应了用户的点击事件,然后通知AMS;
(2)AMS得到Launcher的通知,新建一个Task去准备启动Activity,并且Paused Launcher;
(3)Launcher得到AMS消息,那么就直接挂起,并告诉AMS已经Paused了;
(4)AMS知道了Launcher已经挂起之后,为新的Activity准备启动工作,AMS通过Socket去和Zygote通信,创建进程,然后创建一个线程,新的进程会导入ActivityThread类,这就是每一个应用程序都有一个ActivityThread与之对应的原因;
(5)进程创建好了,通过调用上述的ActivityThread的main方法,这是应用程序的入口,在这里开启消息循环队列,这也是主线程默认绑定Looper的原因;
(6)这时候,App还没有启动完,要永远记住,四大组建的启动都需要AMS去启动,将上述的应用进程信息注册到AMS中,AMS再在堆栈顶部取得要启动的Activity,通过一系列链式调用去完成App启动;

image.png

http://www.taodudu.cc/news/show-6389133.html

相关文章:

  • Android中LaunchMode详解
  • 【Android Framework (六) 】- Launcher
  • 安卓四种launchMode的理解
  • launch4j使用
  • Android 12.0 屏蔽FallbackHome机制去掉android正在启动直接进入默认Launcher功能实现
  • Android launcher 上滑进入allapps和recentUI
  • launcher功能入口(二)
  • launcher功能入口(三)
  • Java的封包
  • 关于数据的封包发送和拆包使用
  • 安卓手机被抓包了怎么办?如何防止?
  • 手机数据包抓包详解
  • 如何在ipad中设置c编译环境,编译c程序
  • 北京两男子开黑客论坛提供教程,涉嫌非法利用信息网络罪被抓
  • 超nei卷!连黑客勒索软件团伙都开始谈客户体验…
  • 5分钟告诉你如何成为一名黑客?从萌新成为大佬,只需掌握这5点(思维、编程语言、网络安全、入侵实操、法律)
  • 如何成为黑客?
  • 中国厉害的黑客组织?别说只知道红客联盟,知道“他们”才厉害
  • 华为公有云认证培训认证体系- HCIA,HCIP ,HCIE
  • HCIA 的认证证书含金量?
  • 考华为HCIE认证的过程?及前景如何?
  • HCIE-Cloud云计算认证考试心得
  • 考取华为HCIA证书需要什么流程,报培训班加考试费大概需要多少钱?
  • 前端js中计时器的使用
  • STC8H8k64U——定时器T0(60s倒计时)
  • Javascript实战——电子钟(时钟、闹钟、计时器、倒计时)
  • js实现一个时分秒计时器
  • 倒计时器和计时器
  • 计时器实现简单方法
  • 6 计时器(三)

Android应用启动之从Launcher拉起APP(三)相关推荐

  1. Android应用启动之从Launcher拉起APP(二)

    image.png 目录 Android应用启动之从Launcher拉起APP(一)Android应用启动之从Launcher拉起APP(二)Android应用启动之从Launcher拉起APP(三) ...

  2. 修改Android默认启动项launcher

    问题背景: 因为目前很多IPTV的厂商+广电的要求,不允许用户自己替换自己的launcher,为了保证利益,强行推广自己的launcher,对播控平台的掌控,于是就必须要求芯片原厂提供turnkey方 ...

  3. Android 12.0 屏蔽FallbackHome机制去掉android正在启动直接进入默认Launcher功能实现

    目录 1.概述 2.屏蔽FallbackHome机制去掉android正在启动直接进入默认Launcher功能实现的核心类

  4. Android系统启动(四) — Launcher 启动过程

    1 Launcher 概述 系统启动的最后一步是启动一个应用程序来显示系统中已经安装的应用程序,这个应用程序就叫做 Launcher.Launcher 在启动过程中会请求 PackageManager ...

  5. 【Android 启动过程】Android 应用启动流程 | Activity 启动流程

    文章目录 一.Android 系统启动流程 二.Activity 启动流程 一.Android 系统启动流程 打开 Android 手机电源键后 , 先运行 BootLoader , 然后使用 Boo ...

  6. android之启动桌面activity

    主页面布局:layout\activity_main.xml <LinearLayout xmlns:android="http://schemas.android.com/apk/r ...

  7. 【Android 安全】DEX 加密 ( Application 替换 | Android 应用启动原理 | ActivityThread 后续分析 | Application 替换位置 )

    文章目录 一.ActivityThread 后续分析 二.ActivityThread 相关源码 三.Application 替换位置 dex 解密时 , 需要将 代理 Application 替换为 ...

  8. android自动创建桌面,Android应用启动后自动创建桌面快捷方式的实现方法

    Android的开发其实是比较灵活的,其实在安装了Android应用程序之后,会在桌面上自动创建快捷方式,接下来爱站技术频道小编将会介绍Android应用启动后自动创建桌面快捷方式的实现方法给大家,有 ...

  9. 突破Android微信微博浏览器限制直接拉起应

    前言 众所周知,微信微博等应用为了防止用户在使用过程当中跳出程序本身,对浏览器里面的自定义协议打开做了限制.这个需求很正常,是个产品经理都能想到. 但作为微博微信这种体量的应用来说,我要多说几句:&q ...

最新文章

  1. OpenResty搭建高性能服务端
  2. GO Negotiation流程分析
  3. 退休失败,64岁Python之父决定加入微软,将开源进行到底!
  4. 那些妖术——树的遍历
  5. Linux之curl命令使用详解—网络故障定位(五)
  6. Swift中文教程(八) 枚举类型
  7. K近邻算法:机器学习萌新必学算法
  8. 极大似然估计_一文读懂矩估计,极大似然估计和贝叶斯估计
  9. zk 02之 Windows安装和使用zookeeper
  10. 研究生怎么看 ,怎么写论文
  11. 什么是 Token 令牌
  12. Java调用外部api接口请求数据(阿里云ISBN查询图书信息为例)
  13. 排行前五的web3风投公司2022年都投了什么
  14. Facebook CTF 2019 Products Manager
  15. k8s中控制器使用详解
  16. decltype的介绍
  17. List的ArrayList类和LInkedlist的Vector类的使用
  18. 索尼大变身:消费电子业务转向医疗设备
  19. Vue.Draggable 文档总结
  20. office 365安装教程

热门文章

  1. 智能领域死伤无数,这家公司为什么能被谷歌亚马逊看重?
  2. 唐骏的八大“职业经理潜规则”辨析
  3. Python3 数字组合
  4. 动力环境监控系统包括哪些设备
  5. jQuery 如何得到 scrollHeight 的值
  6. Henry前端笔记之 宏观世界
  7. 新年开工项目经理要做的几件事
  8. 第一篇 厚黑学 自 序
  9. CEC认证查询怎么查?CEC认证查询方法详解
  10. CUMT2020华为杯