转自:http://www.cloudchou.com/android/post-361.html

前言

一直想研究Android完整的启动过程,网上看了不少资料,也看了书上的一些说明,对这些观点有些怀疑,于是自己分析了系统启动的完整过程。从内核启动第一个用户程序init开始说起,直到Home应用的启动,每一步都有源代码展示。希望能解除读者对Android系统启动过程中的困惑,若有什么疑问,欢迎留言交流。本研究基于CM10.1源码,读者若能对照源代码查看效果会更好。

1) init启动servicemanager和 zygote两个service

Android底层是Linux内核,和linux类似,内核初始化后启动的第一个用户进程是init,它会解析init.rc脚本,启动init.rc里声明的service,并执行一些action。在init.rc里有启动Andriod空间的一些关键服务,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#…
service servicemanager /system/bin/servicemanagerclass coreuser systemgroup systemcriticalonrestart restart zygoteonrestart restart mediaonrestart restart surfaceflingeronrestart restart drm
service zygote /system/bin/app_process -Xzygote /system/bin --zygote --start-system-serverclass mainsocket zygote stream 660 root systemonrestart write /sys/android_power/request_state wakeonrestart write /sys/power/state ononrestart restart mediaonrestart restart netd
#…

servicemanager负责管理所有的binder service, 这些binder service有native的,也有java的。native的binder service有surfaceflinger,drm,media等,java的binder service就有我们平常熟悉的很多管理服务了,ActivityManagerService,WindowManagerService,BatteryService,PowerManagerService,InputManagerService等等。service manager并不负责这些binder service的创建,native的binder service大多由init启动init.rc里的service时创建并启动,java层的binder service大多由zygote创建并启动的,接下来会详细这些service是如何被启动的。

2) zygote service启动java层的ZygoteInit

zygote服务是java层所有程序进程的父进程,它是Android空间程序的孵化器,Android空间所有程序都是由zygote进程启动的。zygote service对应/system/bin/app_process程序,源代码位于frameworks/base/cmds/app_process/app_main.cpp,启动时的main函数代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
int main(int argc, const char* const argv[])
{//...
/*runtime就是dalvik虚拟机实例,启动Java层应用时,*会fork 一个子进程,复制虚拟机,许多书上将runtime看作一个进程,*然后再启动zygote进程,个人觉得这是错误的 */
AppRuntime runtime;
//...
while (i < argc) {const char* arg = argv[i++];if (!parentDir) {parentDir = arg;/*init.rc启动app_main会设置参数--zygote*/ } else if (strcmp(arg, "--zygote") == 0) {zygote = true;niceName = "zygote"; //进程的名字/*init.rc启动app_main会设置参数--start-system-server,*表示需启动systemserver*/} else if (strcmp(arg, "--start-system-server") == 0) {startSystemServer = true;/*启动应用时会使用--application参数*/             } else if (strcmp(arg, "--application") == 0) {application = true;
/*--nice-name=参数表示要设置的进程名字*/            } else if (strncmp(arg, "--nice-name=", 12) == 0) {niceName = arg + 12;} else {className = arg;break;}
}
/*设置进程名*/
if (niceName && *niceName) {setArgv0(argv0, niceName);set_process_name(niceName);
}
/*设置虚拟机运行环境的父目录*/
runtime.mParentDir = parentDir;
if (zygote) {/*虚拟机里启动com.android.internal.os.ZygoteInit,*并传递参数start-system-server*/runtime.start("com.android.internal.os.ZygoteInit",startSystemServer ? "start-system-server" : "");
} else if (className) {        /*若不是zygote,则启动的第一个类是com.android.internal.os.RuntimeInit,*RumtimeInit初始化后会启动mClassName*/runtime.mClassName = className;runtime.mArgC = argc - i;runtime.mArgV = argv + i;runtime.start("com.android.internal.os.RuntimeInit",application ? "application" : "tool");
} else {fprintf(stderr, "Error: no class name or --zygote supplied.\n");app_usage();LOG_ALWAYS_FATAL("app_process: no class name or --zygote supplied.");return 10;
}
//...
}

通过上述代码可知道zygote service将运行dalvik虚拟机,并在虚拟机里执行com.android.internal.os.ZygoteInit,还给它传递了参数start-system-server

3) ZygoteInit启动SystemServer

ZygoteInit启动时的相关源代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public static void main(String argv[]) {{try {   //...//在某个描述符上监听连接请求,//其它Android空间的程序的启动都是通过连接zygote才孵化出来的registerZygoteSocket();//... if (argv[1].equals("start-system-server")) {//启动SystemServerstartSystemServer();} else if (!argv[1].equals("")) {throw new RuntimeException(argv[0] + USAGE_STRING);}//.../*ZYGOTE_FORK_MODE默认为false,如果为true的话,每收到一个连接请求,*就会建立一个新进程,然后再运行连接请求所要求执行的命令,此时会建立另一个新进程*/if (ZYGOTE_FORK_MODE) {runForkMode();} else {//使用Select poll的方式来建立新进程,收到连接请求后,也会建立进程启动某个程序runSelectLoopMode();}closeServerSocket();
} catch (MethodAndArgsCaller caller) {caller.run();
} catch (RuntimeException ex) {Log.e(TAG, "Zygote died with exception", ex);closeServerSocket();throw ex;
}
}

从上述代码可知道会调用startSystemServer以启动SystemServer,相关源代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
private static boolean startSystemServer()
{/* Hardcoded command line to start the system server */
//启动SystemServer使用的参数
String args[] = {"--setuid=1000","--setgid=1000","--setgroups=1001,1002,1003,1004,1005,1006,1007,1008,1009,1010,1018,1021,3001,3002,3003,3004,3006,3007,3009","--capabilities=130104352,130104352","--runtime-init","--nice-name=system_server",//注意:就是在这里设置要启动的SystemServer包名及类名,故此后续才能启动SystemServer"com.android.server.SystemServer",
};
ZygoteConnection.Arguments parsedArgs = null;
int pid;
try {/*将args参数传给ZygoteConnection进行转化,--形式的参数将全部被接收* 但是要启动的类的类名com.android.server.SystemServer会放在*ZygoteConnection.Arguments的remainingArgs里,后来调用handleSystemServerProcess时会用到*/parsedArgs = new ZygoteConnection.Arguments(args); /*添加额外运行参数*/ZygoteConnection.applyDebuggerSystemProperty(parsedArgs);ZygoteConnection.applyInvokeWithSystemProperty(parsedArgs); /*开启新进程*/pid = Zygote.forkSystemServer(parsedArgs.uid, parsedArgs.gid,parsedArgs.gids,parsedArgs.debugFlags,null,parsedArgs.permittedCapabilities,parsedArgs.effectiveCapabilities);
} catch (IllegalArgumentException ex) {throw new RuntimeException(ex);
}
/* For child process */
if (pid == 0) {/*调用handleSystemServerProcess会执行ZygoteConnection.Arguments的remainingArgs参数*所指定的类,即com.android.server.SystemServer      */handleSystemServerProcess(parsedArgs);
}
}

ZygoteInit的startSystemServer会调用forkSystemServer,然后:

ZygoteInit.forkSystemServer -> Zygote.nativeForkSystemServer-> dalvik_system_Zygote.cpp 里的Dalvik_dalvik_system_Zygote_forkSystemServer-> forkAndSpecializeCommon->fork建立新进程

ZygoteInit的startSystemServer会调用handleSystemServerProcess来真正启动systemserver,相关源代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
private static void handleSystemServerProcess(ZygoteConnection.Arguments parsedArgs)throws ZygoteInit.MethodAndArgsCaller {//... 
if (parsedArgs.niceName != null) {Process.setArgV0(parsedArgs.niceName);
}
//启动systemserver时invokeWith为null
if (parsedArgs.invokeWith != null) {WrapperInit.execApplication(parsedArgs.invokeWith,parsedArgs.niceName, parsedArgs.targetSdkVersion,null, parsedArgs.remainingArgs);
} else {/** 启动systemserver时,parsedArgs.remainingArgs为com.android.server.SystemServer.*/RuntimeInit.zygoteInit(parsedArgs.targetSdkVersion, parsedArgs.remainingArgs);
}
}

然后的流程是

RuntimeInit.zygoteInit-> applicationInit,applicationInit的代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
private static void applicationInit(int targetSdkVersion, String[] argv)
{//...
final Arguments args;
try {//参数转换,系统启动时,argv里有一个参数是com.android.server.SystemServerargs = new Arguments(argv);
} catch (IllegalArgumentException ex) {Slog.e(TAG, ex.getMessage());// let the process exitreturn;
}
//...
//终于在此启动了SystemServer
invokeStaticMain(args.startClass, args.startArgs)
}

4) SystemServer 启动过程

执行com.android.server.SystemServer时,main函数里会调用init1函数,init1函数是一个本地函数,init1的实现放在frameworks/base/services/jni/com_android_server_SystemServer.cpp里,对应的jni函数是android_server_SystemServer_init1,在该函数里会调用system_init,而system_init的实现是在frameworks/base/cmds/system_server/library/system_init.cpp,该函数的实现代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
extern "C" status_t system_init()
{//...
sp<ProcessState> proc(ProcessState::self());
sp<IServiceManager> sm = defaultServiceManager();
ALOGI("ServiceManager: %p\n", sm.get());
sp<GrimReaper> grim = new GrimReaper();
sm->asBinder()->linkToDeath(grim, grim.get(), 0);
char propBuf[PROPERTY_VALUE_MAX];
property_get("system_init.startsurfaceflinger", propBuf, "1");
if (strcmp(propBuf, "1") == 0) {// Start the SurfaceFlingerSurfaceFlinger::instantiate();
}
property_get("system_init.startsensorservice", propBuf, "1");
if (strcmp(propBuf, "1") == 0) {// Start the sensor serviceSensorService::instantiate();
}
// And now start the Android runtime.  We have to do this bit
// of nastiness because the Android runtime initialization requires
// some of the core system services to already be started.
// All other servers should just start the Android runtime at
// the beginning of their processes's main(), before calling
// the init function.
ALOGI("System server: starting Android runtime.\n");
AndroidRuntime* runtime = AndroidRuntime::getRuntime();
ALOGI("System server: starting Android services.\n");
JNIEnv* env = runtime->getJNIEnv();
if (env == NULL) {return UNKNOWN_ERROR;
}
jclass clazz = env->FindClass("com/android/server/SystemServer");
if (clazz == NULL) {return UNKNOWN_ERROR;
}
//反过来调用Java里SystemServer的init2函数
jmethodID methodId = env->GetStaticMethodID(clazz, "init2", "()V");
if (methodId == NULL) {return UNKNOWN_ERROR;
}
env->CallStaticVoidMethod(clazz, methodId);
ALOGI("System server: entering thread pool.\n");
ProcessState::self()->startThreadPool();
IPCThreadState::self()->joinThreadPool();
ALOGI("System server: exiting thread pool.\n");
}

5) 启动Java层的各种binder service

调用SystemServer的init2函数后,会开启新线程android.server.ServerThread,在新线程里会启动各种Java层的binder service,并在service manager里注册,这些Service大多开启了新线程运行,故此都是systemserver的子线程,添加的Service列表如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
ServiceManager.addService("entropy", new EntropyMixer());
ServiceManager.addService(Context.POWER_SERVICE, power);
ServiceManager.addService(Context.DISPLAY_SERVICE, display, true);
ServiceManager.addService("telephony.registry", telephonyRegistry);
ServiceManager.addService(Context.SCHEDULING_POLICY_SERVICE,
ServiceManager.addService(Context.USER_SERVICE,UserManagerService.getInstance());
ServiceManager.addService(Context.ACCOUNT_SERVICE, accountManager);
ServiceManager.addService("battery", battery);
ServiceManager.addService("vibrator", vibrator);
ServiceManager.addService(Context.ALARM_SERVICE, alarm);
ServiceManager.addService(Context.WINDOW_SERVICE, wm);
ServiceManager.addService(Context.INPUT_SERVICE, inputManager);
ServiceManager.addService(BluetoothAdapter.BLUETOOTH_MANAGER_SERVICE, bluetooth);
ServiceManager.addService(Context.INPUT_METHOD_SERVICE, imm);
ServiceManager.addService(Context.ACCESSIBILITY_SERVICE,new AccessibilityManagerService(context));
ServiceManager.addService("mount", mountService);
ServiceManager.addService("lock_settings", lockSettings);
ServiceManager.addService(Context.DEVICE_POLICY_SERVICE, devicePolicy);
ServiceManager.addService(Context.STATUS_BAR_SERVICE, statusBar);
ServiceManager.addService(Context.CLIPBOARD_SERVICE,new ClipboardService(context));
ServiceManager.addService(Context.NETWORKMANAGEMENT_SERVICE, networkManagement);
ServiceManager.addService(Context.TEXT_SERVICES_MANAGER_SERVICE, tsms);
ServiceManager.addService(Context.NETWORK_STATS_SERVICE, networkStats);
ServiceManager.addService(Context.NETWORK_POLICY_SERVICE, networkPolicy);
ServiceManager.addService(Context.WIFI_P2P_SERVICE, wifiP2p);
ServiceManager.addService(Context.WIFI_SERVICE, wifi);
ServiceManager.addService(Context.CONNECTIVITY_SERVICE, connectivity);
ServiceManager.addService(Context.NSD_SERVICE, serviceDiscovery);
ServiceManager.addService(Context.THROTTLE_SERVICE, throttle);
ServiceManager.addService("fm_receiver",new FmReceiverService(context));
ServiceManager.addService("fm_transmitter",new FmTransmitterService(context));
ServiceManager.addService(Context.UPDATE_LOCK_SERVICE,new UpdateLockService(context));
ServiceManager.addService(Context.PROFILE_SERVICE, profile);
ServiceManager.addService(Context.NOTIFICATION_SERVICE, notification);
ServiceManager.addService(DeviceStorageMonitorService.SERVICE,
ServiceManager.addService(Context.LOCATION_SERVICE, location);
ServiceManager.addService(Context.COUNTRY_DETECTOR, countryDetector);
ServiceManager.addService(Context.SEARCH_SERVICE,new SearchManagerService(context));
ServiceManager.addService(Context.DROPBOX_SERVICE,new DropBoxManagerService(context, new File("/data/system/dropbox")));
ServiceManager.addService(Context.WALLPAPER_SERVICE, wallpaper);
ServiceManager.addService(Context.AUDIO_SERVICE, new AudioService(context));
ServiceManager.addService(Context.USB_SERVICE, usb);
ServiceManager.addService(Context.SERIAL_SERVICE, serial);
ServiceManager.addService(Context.BACKUP_SERVICE,new BackupManagerService(context));
ServiceManager.addService(Context.APPWIDGET_SERVICE, appWidget);
ServiceManager.addService("diskstats", new DiskStatsService(context));
ServiceManager.addService("samplingprofiler", new SamplingProfilerService(context));
ServiceManager.addService("commontime_management", commonTimeMgmtService);
ServiceManager.addService(DreamService.DREAM_SERVICE, dreamy);
ServiceManager.addService("assetredirection", new AssetRedirectionManagerService(context));
ServiceManager.addService("pieservice", pieService);

上述并没有看到将ActivityManagerService添加到servicemanager管理,它的添加过程比较特别。在线程android.server.ServerThread里会调用ActivityManagerService.setSystemProcess();setSystemProcess函数的代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public static void setSystemProcess() {//…
ActivityManagerService m = mSelf;
ServiceManager.addService("activity", m, true);
ServiceManager.addService("meminfo", new MemBinder(m));
ServiceManager.addService("gfxinfo", new GraphicsBinder(m));
ServiceManager.addService("dbinfo", new DbBinder(m));
if (MONITOR_CPU_USAGE) {ServiceManager.addService("cpuinfo", new CpuBinder(m));
}
ServiceManager.addService("permission", new PermissionController(m));
ApplicationInfo info =mSelf.mContext.getPackageManager().getApplicationInfo("android", STOCK_PM_FLAGS);
mSystemThread.installSystemApplicationInfo(info);
//…
}

可以看到ActivityManagerService采用了单例模式,并调用ServiceManager.addService(“activity”, m, true);将ActivityManagerService交给servicemanager管理,在ActivityManagerService里还添加了别的binder service,像MemBinder,GraphicsBinder,DbBinder。

最后会调用Looper.loop();进入loop循环,等待和别的程序通信。

6) 启动系统界面

线程android.server.ServerThread里有如下代码:

1
2
3
4
5
6
7
8
ActivityManagerService.self().systemReady(new Runnable() {public void run() {Slog.i(TAG, "Making services ready");if (!headless) startSystemUi(contextF);//...}
}

startSystemUi就是用于启动系统界面的,代码如下:

1
2
3
4
5
6
7
static final void startSystemUi(Context context) {Intent intent = new Intent();intent.setComponent(new ComponentName("com.android.systemui","com.android.systemui.SystemUIService"));//Slog.d(TAG, "Starting service: " + intent);context.startServiceAsUser(intent, UserHandle.OWNER);
}

这样便启动了com.android.systemui应用,该应用将启动PowerUI和RingtonePlayer两个线程。

7) 启动Home 程序

线程android.server.ServerThread里有如下代码:

1
2
3
4
5
6
7
//…
ActivityManagerService.self().systemReady(new Runnable() {public void run() {//…}});
//…

ActivityManagerService.self().systemReady有如下代码:

1
2
3
//…
mMainStack.resumeTopActivityLocked(null);
//…

ActivityStack. resumeTopActivityLocked()有如下代码:

1
resumeTopActivityLocked(prev, null);

resumeTopActivityLocked的实现有如下代码:

1
2
3
4
5
6
7
8
9
10
//…
if (next == null) {// There are no more activities!  Let's just start up the// Launcher...if (mMainStack) {ActivityOptions.abort(options); return mService.startHomeActivityLocked(mCurrentUser);}
}
//…

mService类型是ActivityManagerService,ActivityManagerService. startHomeActivityLocked的实现有如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
boolean startHomeActivityLocked(int userId) {//…
Intent intent = new Intent(mTopAction,mTopData != null ? Uri.parse(mTopData) : null);
intent.setComponent(mTopComponent);
//这里便添加了Intent.CATEGORY_HOME,
//所有的Home应用都会都带有该类型的Activity,只有这样才会被认为是Home应用
if (mFactoryTest != SystemServer.FACTORY_TEST_LOW_LEVEL) {intent.addCategory(Intent.CATEGORY_HOME);
}
ActivityInfo aInfo =resolveActivityInfo(intent, STOCK_PM_FLAGS, userId);
if (aInfo != null) {intent.setComponent(new ComponentName(aInfo.applicationInfo.packageName, aInfo.name));// Don't do this if the home app is currently being// instrumented.aInfo = new ActivityInfo(aInfo);aInfo.applicationInfo = getAppInfoForUser(aInfo.applicationInfo, userId);ProcessRecord app = getProcessRecordLocked(aInfo.processName,aInfo.applicationInfo.uid);if (app == null || app.instrumentationClass == null) {intent.setFlags(intent.getFlags() | Intent.FLAG_ACTIVITY_NEW_TASK);mMainStack.startActivityLocked(null, intent, null, aInfo,null, null, 0, 0, 0, 0, null, false, null);}
}
}

这样先找到使用Intent.CATEGORY_HOME声明的Activity组件,然后再调用mMainStack.startActivityLocked启动该Activity。

system server启动Home程序总结:

android.server.ServerThread->ActivityManagerService.self().systemReady->mMainStack.resumeTopActivityLocked->resumeTopActivityLocked-> mService.startHomeActivityLocked-> intent.addCategory(Intent.CATEGORY_HOME);mMainStack.startActivityLocked

总结

内核初始化好后,运行的第一个用户程序是init,init将启动init.rc里声明的多个service,跟Android空间相关的有servicemanager和zygote,servicemanager负责管理所有的binder service,zygote负责孵化所有Android空间的程序。zygote service对应的程序是app_process,不过加了一些启动参数,所以它会启动Java层的ZygoteInit,在ZygoteInit里会启动SystemServer,SystemServer分为两个阶段:本地的init1和Java层的init2,init2里会启动线程android.server.ServerThread。在android.server.ServerThread线程里会启动Java层的各种binder service,比如ActivityManagerService,PackageManagerService,WindowManagerService。然后调用ActivityManagerService的systemReady方法,在该方法里会启动系统界面以及Home程序。

Android系统启动过程详解相关推荐

  1. Android编译过程详解(三)

    Android编译过程详解(一):http://www.cnblogs.com/mr-raptor/archive/2012/06/07/2540359.html Android编译过程详解(二):h ...

  2. linux系统启动过程详解-开机加电后发生了什么 --linux内核剖析(零)

    本文参考了如下文章 深入理解linux启动过程 mbr (主引导记录(Master Boot Record)) 电脑从开机加电到操作系统main函数之前执行的过程 详解linux系统的启动过程及系统初 ...

  3. IBM AIX 5.3 系统管理 -- 系统启动过程详解

    一. 启动过程 启动过程包含下面的一些步骤: 1.1启动一个系统的初始步骤是上电自检(Power On Self Test,POST).其目的是验证基本硬件是否处于正常的工作状态.同时初始化内存.键盘 ...

  4. Android 启动过程详解

    Android从Linux系统启动有4个步骤: (1) init进程启动 (2) Native服务启动 (3) System Server,Android服务启动 (4) Home启动 总体启动框架图 ...

  5. Linux系统启动过程详解

    目录 linux系统启动详细过程 内核引导 启动init程序进入初始化阶段 init程序的类型 系统初始化 建立终端 用户登录 关机 linux系统启动详细过程 通电 bios初始化 grub2磁盘引 ...

  6. linux 服务 init3,Linux系统启动过程详解之UpStart init(三)

    RHEL6.0 init(UpStart) 1)SysV init的优缺点 SysV init是最早的解决方案,依靠划分不同的运行级别,启动不同的服务集,服务依靠脚本控制,并且是顺序执行的. SysV ...

  7. android 启动过程详解,Activity的启动过程详解(基于Android10.0)

    一.概述 话说Android中有四大组件:Activity.Service.BroadcastReceiver.ContentProvider.咱们最常接触也是用户直接感受到的便是Activity了, ...

  8. linux 服务器进bios设置密码,linux系统启动过程详解及root密码找回

    系统启动流程 BIOS---->MBR:Boot Code---->执行引导程序GRUB(操作系统引导程序)---->加载内核---->执行init--->runleve ...

  9. android 实现定时任务,Android 实现定时任务的过程详解

    在Android开发中,通过以下三种方法定时执行任务: 一.采用Handler与线程的sleep(long)方法(不建议使用,java的实现方式) 二.采用Handler的postDelayed(Ru ...

最新文章

  1. OpenCV 4.5.2 发布
  2. go通过thrift连接hbase_关于thrift协议改进畅想
  3. 报表测试用例设计方法总结
  4. jdbc odbc java mysql数据库连接_Java数据库连接之配置ODBC数据源
  5. 【二十一】插件开发——用于验证码识别的 JMeter 插件(上)
  6. bzoj 1854: [Scoi2010]游戏(并查集)
  7. ZeroC IceGrid demo构建(继承Ice::Application类)
  8. poj 3694 Network (无向图的 割边 lca )
  9. win7 NVMe驱动 SSD固态硬盘 补丁 免重装 DISM++ Windows6.1-KB3087873-v2-x64.msu
  10. 【python黑帽子2】netcat.py编写及使用说明
  11. Python 文本转语音
  12. Socket通讯--数据结构WSADATA
  13. 我的世界启动器怎么更改java_我的世界启动器Java路径怎么设置?
  14. 微信小游戏排行榜:主域和子域
  15. iPhone免越狱Git服务器搭建教程-iSH
  16. 力扣第四十六题(全排列)详解
  17. User management
  18. springMVC实现jsonp的跨域请求
  19. 拥有全国性物流网络却只靠卖书赚微薄利润的李国庆, 最终倒下了
  20. mp3与aac音频格式的比较

热门文章

  1. EGO走进美团——追寻千亿市场背后的技术力量
  2. window8.1中用户的管理员权限的提升方法
  3. linux下源码安装apache服务
  4. System.Web.Caching
  5. CCNP精粹系列之三----OSPF(open short path first)
  6. openeuler学习指南
  7. 程序设计习惯养成计划---一、方法规约设计
  8. html 圆饼画布,html5 canvas画饼
  9. 【机器学习】基于GBDT的数据回归及python实现
  10. 考软件测试初学者眼影,Summer 大讲堂第一期:如何制作出版级的高分辨率图表?...