Android系统启动过程分析

主要流程

在这里插入图片描述

init进程启动过程

init进程是Android系统中用户空间的第一个进程。进程号为1。


第一步:启动电源

当电源按下,引导芯片代码开始从预定义的地方(固化在ROM)开始执行。加载引导程序到RAM,然后执行。

第二步:执行引导程序(Boot Loader)

通常在运行Android系统之前会先执行Boot Loader引导程序,它不属于Android系统,常见的引导程序有:redboot、uboot、qi bootloader等等。或者自行开发引导程序,它是针对特定主板和芯片的,OEM制造厂商或者运营商在加锁的时候就对这个引导程序做修改,比如魅族就是修改了引导程序,所以刷不了机。

第三步:内核

当内核完成系统设置后,在系统文件中寻找init.rc文件,并启动init进程

第四步:执行init进程

system/core/init/init.cpp

int main(int argc, char** argv) {
......
if (is_first_stage) {
boot_clock::time_point start_time = boot_clock::now();
umask(0);
//创建和挂载启动所需要的文件目录
mount("tmpfs", "/dev", "tmpfs", MS_NOSUID, "mode=0755");
mkdir("/dev/pts", 0755);
mkdir("/dev/socket", 0755);
mount("devpts", "/dev/pts", "devpts", 0, NULL);
#define MAKE_STR(x) __STRING(x)
mount("proc", "/proc", "proc", 0, "hidepid=2,gid=" MAKE_STR(AID_READPROC));
chmod("/proc/cmdline", 0440);
gid_t groups[] = { AID_READPROC };
setgroups(arraysize(groups), groups);
mount("sysfs", "/sys", "sysfs", 0, NULL);
mount("selinuxfs", "/sys/fs/selinux", "selinuxfs", 0, NULL);
mknod("/dev/kmsg", S_IFCHR | 0600, makedev(1, 11));
mknod("/dev/random", S_IFCHR | 0666, makedev(1, 8));
mknod("/dev/urandom", S_IFCHR | 0666, makedev(1, 9));
......
}
......
//对属性服务进行初始化
property_init();
......

epoll_fd = epoll_create1(EPOLL_CLOEXEC);
if (epoll_fd == -1) {
PLOG(ERROR) << "epoll_create1 failed";
exit(1);
}
//用于设置子进程信号处理函数,如果子进程异常退出,init进程会调用该函
//数中设置的信号处理函数进行处理。
signal_handler_init();
......
//启动属性服务
start_property_service();
......
if (bootscript.empty()) {
//解析init.rc配置文件
parser.ParseConfig("/init.rc");
parser.set_is_system_etc_init_loaded(
parser.ParseConfig("/system/etc/init"));
parser.set_is_vendor_etc_init_loaded(
parser.ParseConfig("/vendor/etc/init"));
parser.set_is_odm_etc_init_loaded(parser.ParseConfig("/odm/etc/init"));
}
......
while (true) {
......
if (!(waiting_for_prop || ServiceManager::GetInstance().IsWaitingForExec())) {
am.ExecuteOneCommand();
}
if (!(waiting_for_prop || ServiceManager::GetInstance().IsWaitingForExec())) {
//重启死去的服务
restart_processes();
}
......
}
return 0;}

从上述代码中可以看出,init进程主要做了三件事:

  • 从上述代码中可以看出,init进程主要做了三件事:
    启动挂载了tmpfs、devpts、proc、sysfs和selinuxfs共5种文件系统,这些是系统运行时目录,也就是说只有在系统运行的时候才会存在。

  • 初始化和启动属性服务。
    属性服务类似Windows平台上的注册表管理器,注册表中以key-value的形式来记录用户、软件的一些使用信息。即使系统或软件重启,也能够根据之前注册表中的记录,进行相应的初始化工作。

  • 解析init.rc文件,创建Zygote进程
    该文件的路径为:system/core/rootdir/init.rc。它是一个非常重要的配置文件,是由Android初始化语言编写的脚本。Android8.0对该文件进行来拆分,每个服务对应一个rc文件,启动Zygote的脚本在init.zygoteXX.rc中定义,代表多少位处理器。这里以64位为例。

system/core/rootdir/init.zygote64.rc

service zygote /system/bin/app_process64 -Xzygote /system/bin --zygote --start-system-server
class main
priority -20
user root
group root readproc
socket zygote stream 660 root system
onrestart write /sys/android_power/request_state wake
onrestart write /sys/power/state on
onrestart restart audioserver
onrestart restart cameraserver
onrestart restart media
onrestart restart netd
onrestart restart wificond
writepid /dev/cpuset/foreground/tasks

这里我们只分析zygote进程的创建,所以只贴出了init.rc文件中的Serivice类型的语句,省略了其他语句;这是由Android初始化语言编写的。它的格式如下:
service [ ]* //名字 执行程序路径 传递参数
< option > //修饰词,影响什么时候启动,如何启动service。
< option >

上面的代码的含义就是:通知init进程创建名为zygote的进程,这个进程的执行路径为:/system/bin/app/_process64,其后面的代码是传递给app_process64的参数。class main指的是zygoteclassnamemain

init.rc中的Service类型的语句有相应的类来进行解析,Service语句采用ServiceParser来进行解析,该实现代码在system/core/init/service.app中,代码在这不再贴出,大概的解析过程就是:根据参数创建出Service对象,然后根据选项域中的内容填充Service对象,最后将该对象加入vector类型的Service链表中。(就是把上面传参要启动服务存在Vector链表中)

继续看 在init.rc中有如下代码:

......
on nonencrypted
class_start main
class_start late_start
......

class_start是Android初始化语言中的Command类型的语句,对应的函数为do_class_start,含义就是启动classname为main的Service。,从上述代码我们知道zygote的classname为main

do_class_start函数是在system/core/bin/builtins.cpp中,代码如下:

static int do_class_start(const std::vector<std::string>& args) {
ServiceManager::GetInstance().
ForEachServiceInClass(args[1], [] (Service* s) { s->StartIfNotDisabled(); });
return 0;}

ForEachServiceInClass函数会遍历Service链表,找到classnamemain的Zygote,并执行**StartIfNotDisabled ()**函数。

StartIfNotDisabled ()函数的代码是在system/core/init/service.cpp中定义的,代码如下:

bool Service::StartIfNotDisabled() {
if (!(flags_ & SVC_DISABLED)) {
return Start();
} else {
flags_ |= SVC_DISABLED_START;
}
return true;}

如果Service没有在其对应的rc文件中设置disabled选项,就会执行Start()函数,Zygote对应的文件没有设置disabled选项

Start() 函数:system/core/init/service.cpp

bool Service::Start() {
flags_ &= (~(SVC_DISABLED|SVC_RESTARTING|SVC_RESET|SVC_RESTART|SVC_DISABLED_START));
//如果已运行,直接返回
if (flags_ & SVC_RUNNING) {
return false;
}
......
struct stat sb;
//判断需要启动的Service对应的执行文件是否存在,存在才会执行。
if (stat(args_[0].c_str(), &sb) == -1) {
PLOG(ERROR) << "cannot find '" << args_[0] << "', disabling '" << name_ << "'";
flags_ |= SVC_DISABLED;
return false;
}
......
pid_t pid = -1;
if (namespace_flags_) {
pid = clone(nullptr, nullptr, namespace_flags_ | SIGCHLD, nullptr);
} else {
//如果没有启动,调用fork函数创建子进程
pid = fork();
}

if (pid == 0) {
umask(077);
......
//调用execve函数,子进程就会被启动
if (execve(strs[0], (char**) &strs[0], (char**) ENV) < 0) {
PLOG(ERROR) << "cannot execve('" << strs[0] << "')";
}
_exit(127);
}
......
return true;}

调用execve 函数,子进程就会被启动,就是执行对应的main函数Zygote的执行路径为:system/bin/app_process64,对应的文件为app_main.cpp

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

int main(int argc, char* const argv[]){
......
while (i < argc) {
const char* arg = argv[i++];
if (strcmp(arg, "--zygote") == 0) {
zygote = true;
niceName = ZYGOTE_NICE_NAME;
} else if (strcmp(arg, "--start-system-server") == 0) {
startSystemServer = true;
} else if (strcmp(arg, "--application") == 0) {
application = true;
} else if (strncmp(arg, "--nice-name=", 12) == 0) {
niceName.setTo(arg + 12);
} else if (strncmp(arg, "--", 2) != 0) {
className.setTo(arg);
break;
} else {
--i;
break;
}
}
......
if (zygote) {
runtime.start("com.android.internal.os.ZygoteInit", args, zygote);
} else if (className) {
runtime.start("com.android.internal.os.RuntimeInit", args, zygote);
} else {
fprintf(stderr, "Error: no class name or --zygote supplied.\n");
app_usage();
LOG_ALWAYS_FATAL("app_process: no class name or --zygote supplied.");
}}

调用runtime.start函数就启动了Zygote进程。

以上总结:

  1. 手机按下电源后,加载引导程序到内存中。
  2. 执行引导程序
  3. 启动内核,设置缓存、被保护存储器、计划列表,加载驱动,查找/system/core/bin中init程序文件。
  4. 启动init程序,挂载/sys /dev /proc等等目录,加载和解析init.rc脚本。
  5. 在加载init.rc脚本的时候,启动app_process进程。
  6. 在app_process进程中,根据init.zygote64.rc脚本配置的参数,启动zygote进程

Zygote进程

Android系统中,DVM和ART、应用程序进程以及运行系统的关键服务SystemServer进程都是有Zygote进程创建的,我们也称它为孵化器。它通过 fock(复制进程) 的形式来创建应用程序进程和SystemServer进程,由于Zygote进程在启动的时候会创建DVM或者ART,因此通过fock而创建的应用程序进程和SystemServer进程可以在内部获取一个DVM或者ART的实例副本(也就是说一个Android应用程序对应这一个DVM或者ART)。

Zygote进程都是通过fock自身来创建子进程的,这样Zygote进程以及子进程都会进入app_main.cpp的main函数,所以在上述代码中先区分来当前运行在哪个进程中。运行Zygote进程就会执行AndroidRuntime.start函数。

frameworks/base/core/jni/AndroidRuntime.cpp代码如下:

void AndroidRuntime::start(const char* className, const Vector<String8>& options, bool zygote){
......
JniInvocation jni_invocation;
jni_invocation.Init(NULL);
JNIEnv* env;
//启动Java虚拟机
if (startVm(&mJavaVM, &env, zygote) != 0) {
return;
}
onVmCreated(env);
//为JVM注册JNI方法
if (startReg(env) < 0) {
ALOGE("Unable to register all android natives\n");
return;
}
......
//从app_main传过来的参数classname值为:“com.android.internal.os.ZygoteInit”
classNameStr = env->NewStringUTF(className);
assert(classNameStr != NULL);
env->SetObjectArrayElement(strArray, 0, classNameStr);

for (size_t i = 0; i < options.size(); ++i) {
jstring optionsStr = env->NewStringUTF(options.itemAt(i).string());
assert(optionsStr != NULL);
env->SetObjectArrayElement(strArray, i + 1, optionsStr);
}
//将classname的“.”替换成“/”
char* slashClassName = toSlashClassName(className);
//找到ZygoteInit
jclass startClass = env->FindClass(slashClassName);
if (startClass == NULL) {
ALOGE("JavaVM unable to locate class '%s'\n", slashClassName);
} else {
//找到ZygoteInit的main函数
jmethodID startMeth = env->GetStaticMethodID(startClass, "main",
"([Ljava/lang/String;)V");
if (startMeth == NULL) {
ALOGE("JavaVM unable to find main() in '%s'\n", className);
} else {
//通过JNI调用ZygoteInit的main函数
env->CallStaticVoidMethod(startClass, startMeth, strArray);

#if 0
if (env->ExceptionCheck())
threadExitUncaughtException(env);#endif
}
}
......}

上述代码最后会调用ZygoteInit的main方法,该方法是由Java语言编写的,当前的运行逻辑在Native中,这就需要通过JNI来调用Java。这样Zygote就从Native层近入了Java框架层。

/frameworks/base/core/java/com/android/internal/os/ZygoteInit.java的main方法代码如下:

public static void main(String argv[]) {
......
try {
......
//创建一个Server端的socket,socketName = “zygote”
//在这个socket上会等待AMS的请求
zygoteServer.registerServerSocket(socketName);
if (!enableLazyPreload) {
bootTimingsTraceLog.traceBegin("ZygotePreload");
EventLog.writeEvent(LOG_BOOT_PROGRESS_PRELOAD_START,
SystemClock.uptimeMillis());
//预加载类和资源
preload(bootTimingsTraceLog);
EventLog.writeEvent(LOG_BOOT_PROGRESS_PRELOAD_END,
SystemClock.uptimeMillis());
bootTimingsTraceLog.traceEnd(); // ZygotePreload
} else {
Zygote.resetNicePriority();
}
//启动SystemServer进程
if (startSystemServer) {
startSystemServer(abiList, socketName, zygoteServer);
}
//等待AMS请求,里面是一个while(true)循环
zygoteServer.runSelectLoop(abiList);
zygoteServer.closeServerSocket();
}......}

该方法中主要做了4件事:

1.创建一个Server端的socket(LocalServerSocket)。
2.预加载类和资源。
3.启动SystemServer进程。
4.等待AMS请求创建新的应用程序进程。


SystemServer进程

SystemServer进程主要用于创建系统服务,AMS、WMS、PMS都是由它来创建的。从上面得知通过调用ZygoteInit的startSystemServer 方法来启动SystemServer进程,下面就看一下该方法的代码:

private static boolean startSystemServer(String abiList, String socketName, ZygoteServer zygoteServer)
throws Zygote.MethodAndArgsCaller, RuntimeException {
......
//创建args数组,该数组用来保存启动SystemServer的启动参数
String args[] = {
"--setuid=1000",
"--setgid=1000",
"--setgroups=1001,1002,1003,1004,1005,1006,1007,1008,1009,1010,1018,1021,1023,1032,3001,3002,3003,3006,3007,3009,3010",
"--capabilities=" + capabilities + "," + capabilities,
"--nice-name=system_server",
"--runtime-args",
"com.android.server.SystemServer",
};
ZygoteConnection.Arguments parsedArgs = null;
int pid;
try {
parsedArgs = new ZygoteConnection.Arguments(args);
ZygoteConnection.applyDebuggerSystemProperty(parsedArgs);
ZygoteConnection.applyInvokeWithSystemProperty(parsedArgs);
//创建一个子进程,也就是SystemServer进程
pid = Zygote.forkSystemServer(
parsedArgs.uid, parsedArgs.gid,
parsedArgs.gids,
parsedArgs.debugFlags,
null,
parsedArgs.permittedCapabilities,
parsedArgs.effectiveCapabilities);
} catch (IllegalArgumentException ex) {
throw new RuntimeException(ex);
}
//当前代码运行在子进程中,也就是SystemServer进程
if (pid == 0) {
if (hasSecondZygote(abiList)) {
waitForSecondaryZygote(socketName);
}
//SystemServer进程复制了Zygote进程的地址空间,因此也会得到Zygote创建的Socket。
//该Socket对SystemServer进程没有用,所以要关闭。
zygoteServer.closeServerSocket();
//处理SystemServer进程
handleSystemServerProcess(parsedArgs);
}

return true;}

从上面代码可以看出,SystemServer进程的用户id和用户组id被设置为1000,并且拥有1001~1010、1018、1021、1032、3001~3010的权限;进程名字为system_server;启动的类名为:com.android.server.SystemServer。

创建出SystemServer进程之后就会在该进程调用ZygoteInit.handleSystemServerProcess 方法,下面看一下该方法的代码:

private static void handleSystemServerProcess(
ZygoteConnection.Arguments parsedArgs)
throws Zygote.MethodAndArgsCaller {
......
if (parsedArgs.invokeWith != null) {
......
} else {
ClassLoader cl = null;
if (systemServerClasspath != null) {
//创建PathClassLoader
cl = createPathClassLoader(systemServerClasspath, parsedArgs.targetSdkVersion);
Thread.currentThread().setContextClassLoader(cl);
}
//调用zygoteInit方法
ZygoteInit.zygoteInit(parsedArgs.targetSdkVersion, parsedArgs.remainingArgs, cl);
}}

public static final void zygoteInit(int targetSdkVersion, String[] argv,
ClassLoader classLoader) throws Zygote.MethodAndArgsCaller {
if (RuntimeInit.DEBUG) {
Slog.d(RuntimeInit.TAG, "RuntimeInit: Starting application from zygote");
}
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ZygoteInit");
RuntimeInit.redirectLogStreams();
RuntimeInit.commonInit();
//启动Binder线程池
ZygoteInit.nativeZygoteInit();
//执行SystemServer的main方法
RuntimeInit.applicationInit(targetSdkVersion, argv, classLoader);}

zygoteInit方法中创建了Binder线程池,这样SystemServer进程就可以使用Binder于其他进程进行通信了。

创建Binder线程池后会调用RuntimeInit.applicationInit方法,下面就接着看下该方法的代码:
/frameworks/base/core/java/com/android/internal/os/RuntimeInit.java

protected static void applicationInit(int targetSdkVersion, String[] argv, ClassLoader classLoader)
throws Zygote.MethodAndArgsCaller {
......
// Remaining arguments are passed to the start class's static main
invokeStaticMain(args.startClass, args.startArgs, classLoader);}

private static void invokeStaticMain(String className, String[] argv, ClassLoader classLoader)
throws Zygote.MethodAndArgsCaller {
Class<?> cl;
try {
//通过反射得到SystemServer类
cl = Class.forName(className, true, classLoader);
}......
Method m;
try {
//找到SystemServer的main方法
m = cl.getMethod("main", new Class[] { String[].class });
}......
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 Zygote.MethodAndArgsCaller(m, argv);}

通过反射得到SystemServer,className的值就是上面提到的com.android.server.SystemServer,然后再找到main方法,最后将main方法传入Zygote .MethodAndArgsCaller异常中并抛出该异常。捕获该异常的代码在ZygoteInit.main方法中,该main方法会调用SystemServer的main方法。

那为什么不直接在invokeStaticMain方法中调用呢?而是在异常捕获中调用?

原因是异常处理会清楚所有设置过程中需要的堆栈帧,让SystemServer的main方法看起来像是SystemServer进程的入口。(在Zygote启动SystemServer进程之后,SystemServer进程做了很多准备工作,这些工作都是在main方法调用之前做的。)

/frameworks/base/core/java/com/android/internal/os/ZygoteInit.java的main方法代码

public static void main(String argv[]) {
......
try {
......
//捕获MethodAndArgsCaller异常
} catch (Zygote.MethodAndArgsCaller caller) {
caller.run();
} catch (Throwable ex) {
Log.e(TAG, "System zygote died with exception", ex);
zygoteServer.closeServerSocket();
throw ex;
}}

此处的mMethod就是SystemServer的main方法。

这样就进入了SystemServer的main方法:

/frameworks/base/services/java/com/android/server/SystemServer.java

public static void main(String[] args) {
new SystemServer().run();}

private void run() {
try {
......
//创建Looper
Looper.prepareMainLooper();
//加载动态库“libandroid_servers.so”
System.loadLibrary("android_servers");
performPendingShutdown();
//创建系统的上下文Context
createSystemContext();
// 创建SystemServiceManager
mSystemServiceManager = new SystemServiceManager(mSystemContext);
mSystemServiceManager.setRuntimeRestarted(mRuntimeRestart);
LocalServices.addService(SystemServiceManager.class, mSystemServiceManager);
SystemServerInitThreadPool.get();
} finally {
traceEnd(); // InitBeforeStartServices
}
// Start services.
try {
traceBeginAndSlog("StartServices");
//启动引导服务
startBootstrapServices();
//启动核心服务
startCoreServices();
//启动其他服务
startOtherServices();
SystemServerInitThreadPool.shutdown();
}......}

通过上面可以看出系统服务分为三种:
1.引导服务。
2.核心服务。
3.其他服务。

private void startBootstrapServices() {
......
Installer installer = mSystemServiceManager.startService(Installer.class);
......
mSystemServiceManager.startService(DeviceIdentifiersPolicyService.class);
......
//需要注意的是 ActivityManagerService.Lifecycle实现了SystemService
mActivityManagerService = mSystemServiceManager.startService(
ActivityManagerService.Lifecycle.class).getService();
......
mPackageManagerService = PackageManagerService.main(mSystemContext, installer,
mFactoryTestMode != FactoryTest.FACTORY_TEST_OFF, mOnlyCore);
......
//在该方法中会把ActivityManagerService添加到ServiceManager中
mActivityManagerService.setSystemProcess();
......}private void startCoreServices() {
......
mSystemServiceManager.startService(DropBoxManagerService.class);
traceEnd();
......
mSystemServiceManager.startService(BatteryService.class);
......}private void startOtherServices() {
......
mSystemServiceManager.startService(KeyChainSystemService.class);
......
mSystemServiceManager.startService(TelecomLoaderService.class);
......}

部分服务表:

在这里插入图片描述
在这里插入图片描述

我们从上面看出相关的服务一种是通过SystemServiceManager的startService方法来启动的;另一种是直接调用服务的main方法,比如:PackageManagerService。下面分别看一下这两种方式的代码实现:

/frameworks/base/services/core/java/com/android/server/SystemServiceManager.java

public class SystemServiceManager {
......
private final ArrayList<SystemService> mServices = new ArrayList<SystemService>();
......
//startService方法有几个重载,最终都会调用该方法
public void startService(@NonNull final SystemService service) {
//注册服务
mServices.add(service);
// Start it.
long time = System.currentTimeMillis();
try {
//调用服务自身的onStart方法
service.onStart();
}......
}}public abstract class SystemService {
......}

先将服务添加到mService中,它是一个存储SystemService类型的ArrayList,这样就完成来该服务的注册工作。然后调用服务自身的onStart()方法来启动服务。由SystemServiceManager创建并启动的服务,都是继承了SystemService,但是并没有实现IBinder,所以是不可以进行Binder通信的,也就是由它管理的服务是用于进程内部通信的。

/frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java

public class PackageManagerService extends IPackageManager.Stub
implements PackageSender {
......
public static PackageManagerService main(Context context, Installer installer,
boolean factoryTest, boolean onlyCore) {
PackageManagerServiceCompilerMapping.checkProperties();
PackageManagerService m = new PackageManagerService(context, installer,
factoryTest, onlyCore);
m.enableSystemUserPackages();
//注册
ServiceManager.addService("package", m);
return m;
}
......}

创建对象后添加到ServiceManager中,它是用来管理系统中各种Service的,由它管理的服务都实现了IBinder,所以在ServiceManager中注册的服务是用于进程间通信的:用于系统C/S架构中的Binder通信机制。客户端要使用某个Service,需要先到ServiceManager中查询Service的信息,然后根据该信息与Service所在的Server进程建立通信,这样客户端就可以使用Service了。

从上面的代码我们看出SystemServer进程主要做了如下几件事情:

  • 启动Binder线程池,这样就可以与其他进程进行通信了。
  • 创建SystemServiceManager,用于对系统其他服务进行创建、启动和声明周期管理。
  • 启动各种系统服务

SystemServer进程是Zygote进程fork的第一个进程。其中WindowManagerService、ActivityManagerService、PackageManagerService等重要的可以binder通信的服务都运行在这个SystemServer进程。


整体总结:

  1. 手机按下电源后,加载引导程序到内存中。(通过bootloder加载芯片的引导程序)
  2. 执行引导程序
  3. 启动内核,设置缓存、被保护存储器、计划列表,加载驱动,查找/system/core/bin中init程序文件。
  4. 启动init程序,挂载/sys /dev /proc等等目录,加载和解析init.rc脚本。
  5. 在加载init.rc脚本的时候,启动app_process进程。
  6. 在app_process进程中,根据init.zygote64.rc脚本配置的参数,启动zygote进程
  7. 创建Java虚拟机并为Java虚拟机注册JNI方法
  8. AndroidRuntime通过JNI的方式调用 ZygoteInit的main函数进入Zygote的Java框架层
  9. 通过registerServerSocket 方法创建服务端Socket,并通过runSelectLoop 方法等待AMS的请求来创建新的应用程序进程。
  10. 启动SystemServer进程。
  11. 启动Binder线程池,这样就可以与其他进程进行通信了。
  12. 创建SystemServiceManager,用于对系统其他服务进行创建、启动和声明周期管理。
  13. 启动各种系统服务。
发布了51 篇原创文章 · 获赞 78 · 访问量 4万+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章