Android系统启动流程 源码解析
Android系统启动流程
本文链接:https://blog.csdn.net/feather_wch/article/details/132518105
有道云脑图:https://note.youdao.com/s/GZ9d8vzO
1、整体流程
- Boot Room
- BootLoader
- idle
- kthread
- init
- init
- ServiceManager
- zygote
- zygote
- SystemServer
- app
一、init
1、kernel/common/init/main.c
kernel_init();
->try_to_run_init_process("/bin/init");->run_init_process(filename)->kernel_execve(filename, xxx, xxx);->// 会执行init
2、andorid.mk->android.bp编译
// init/android.bpcc_binary {name: "init_second_stage",stem: "init",static_libs: ["libinit"],srcs: ["main.cpp"], // main.cpp// ...
}
3、init是用户空间鼻祖
- 属于C、C++ Framework
1.1 启动源码
FirstStageMain()->挂载文件系统->重定向输入输出
SetupSelinux()->初始化Selinux
SecondStageMain()->初始化属性->设置Selinux->监听子进程终止信号->epoll_ctl 注册监听SIGCHILD 避免僵尸进程->启动属性服务,将sokcet,注册到epoll中->匹配命令和函数->解析init.rc->构造解析器,对应于rc文件里面的service、on、import->parser.ParseConfig:解析rc文件->解析Dir->解析File->按照二进制格式,解析:servicemanager、zygote-> 循环执行脚本,epoll循环等待-> 执行脚本
// frameworks/core/core/init/main.cpp
int main(int argc, char** argv) {// 会反复进入// 2、第二次进来,根据参数执行if (argc > 1) {if (!strcmp(argv[1], "subcontext")) {const BuiltinFunctionMap& function_map = GetBuiltinFunctionMap();// 5、内部while+poll,第四阶段return SubcontextMain(argc, argv, &function_map);}// 3、selinux:第二阶段if (!strcmp(argv[1], "selinux_setup")) {return SetupSelinux(argv);}// 4、第三阶段if (!strcmp(argv[1], "second_stage")) {return SecondStageMain(argc, argv);}}// 1、首次进来:第一阶段return FirstStageMain(argc, argv);
}// 第一阶段:
int FirstStageMain(int argc, char** argv) {if (REBOOT_BOOTLOADER_ON_PANIC) {InstallRebootSignalHandlers();}CHECKCALL(clearenv());CHECKCALL(setenv("PATH", _PATH_DEFPATH, 1));
// 1、挂载文件系统CHECKCALL(mount("tmpfs", "/dev", "tmpfs", MS_NOSUID, "mode=0755"));CHECKCALL(mount("devpts", "/dev/pts", "devpts", 0, NULL));CHECKCALL(mount("sysfs", "/sys", "sysfs", 0, NULL));CHECKCALL(mount("selinuxfs", "/sys/fs/selinux", "selinuxfs", 0, NULL));CHECKCALL(mknod("/dev/random", S_IFCHR | 0666, makedev(1, 8)));CHECKCALL(mknod("/dev/urandom", S_IFCHR | 0666, makedev(1, 9)));// null也是作为管道文件进行处理CHECKCALL(mknod("/dev/null", S_IFCHR | 0666, makedev(1, 3)));CHECKCALL(mkdir("/mnt/vendor", 0755));CHECKCALL(mkdir("/mnt/product", 0755));// 2、重定向输入输出:stdio重定向Dev Null,作为管道文件,输入输出SetStdioToDevNull(argv);// 日志输出初始化InitKernelLogging(argv);
// 3、设置selinux安全策略:重新进入main.cppconst char* path = "/system/bin/init";const char* args[] = {path, "selinux_setup", nullptr};execv(path, const_cast<char**>(args)); // 执行return 1;
}
// Selinux:设置SeLinux安全策略,Android最小权限原则,selinux控制
int SetupSelinux(char** argv) {// 设置selinuxSelinuxSetupKernelLogging();// selinux初始化SelinuxInitialize();// 重新进入第二阶段const char* path = "/system/bin/init";const char* args[] = {path, "second_stage", nullptr};execv(path, const_cast<char**>(args));return 1;
}// frameworks/core/core/init.coo
int SecondStageMain(int argc, char** argv) {// Set init and its forked children's oom_adj.if (auto result = WriteFile("/proc/1/oom_score_adj", StringPrintf("%d", DEFAULT_OOM_SCORE_ADJUST));!result.ok()) {LOG(ERROR) << "Unable to write " << DEFAULT_OOM_SCORE_ADJUST<< " to /proc/1/oom_score_adj: " << result.error();}// 1. 初始化属性:加载属性PropertyInit();// Mount extra filesystems required during second stage initMountExtraFilesystems();// 2. Selinux相关SelinuxSetupKernelLogging();SelabelInitialize();SelinuxRestoreContext();Epoll epoll; // ===============================================================================> epollif (auto result = epoll.Open(); !result.ok()) {PLOG(FATAL) << result.error();}
// 3. 监听子进程中止信号:避免僵尸进程 ==============================================================> 僵尸进程InstallSignalFdHandler(&epoll);InstallInitNotifier(&epoll);
// 4. 启动属性服务StartPropertyService(&property_fd);SetUsbController();// 5. 匹配命令和函数的关系:mount等命令都对应于函数const BuiltinFunctionMap& function_map = GetBuiltinFunctionMap();Action::set_function_map(&function_map);// 6. 解析 init.rcLoadBootScripts(am, sm);// 7. while循环解析脚本:启动zygote、执行重启while (true) {// 解析start等命令am.ExecuteOneCommand();// epoll_wait循环等待 ===================================================================> Handler中Looper.loopauto pending_functions = epoll.Wait(epoll_timeout);}return 0;
}// >>>>>>>>>>>>>>>>>>>>>>>>>>
//在linux当中,父进程是通过捕捉SIGCHLD信号来得知子进程运行结束的情况,
//SIGCHLD信号会在子进程终止的时候发出
//函数的作用就是,接收到SIGCHLD信号时触发HandleSignalFd进行信号处理
// 这样可以在当子进程发出信号后能够及时的将它销毁,避免僵尸进程的存在
static void InstallSignalFdHandler(Epoll* epoll) {const struct sigaction act { .sa_handler = SIG_DFL, .sa_flags = SA_NOCLDSTOP };sigaction(SIGCHLD, &act, nullptr);sigset_t mask;sigemptyset(&mask);sigaddset(&mask, SIGCHLD); //SIGCHLDsignal_fd = signalfd(-1, &mask, SFD_CLOEXEC);// 注册,epoll注册handler。 ===========================> epoll_ctl EPOLL_CTL_ADDif (auto result = epoll->RegisterHandler(signal_fd, HandleSignalFd); !result.ok()) {LOG(FATAL) << result.error();}
}// 注册,epoll注册handler。 ===========================> epoll_ctl EPOLL_CTL_ADD
Result<void> Epoll::RegisterHandler(int fd, std::function<void()> handler, uint32_t events) {epoll_event ev;ev.events = events;ev.data.ptr = reinterpret_cast<void*>(&it->second);if (epoll_ctl(epoll_fd_, EPOLL_CTL_ADD, fd, &ev) == -1) {epoll_handlers_.erase(fd);return result;}return {};
}// >>>>>>>>>>>>>>>>>>> 启动属性服务,注册到epoll中
void StartPropertyService(int* epoll_socket) {InitPropertySet("ro.property_service.version", "2");int sockets[2];if (socketpair(AF_UNIX, SOCK_SEQPACKET | SOCK_CLOEXEC, 0, sockets) != 0) {PLOG(FATAL) << "Failed to socketpair() between property_service and init";}*epoll_socket = from_init_socket = sockets[0];init_socket = sockets[1];StartSendingMessages();if (auto result = CreateSocket(PROP_SERVICE_NAME, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, false, 0666, 0, 0, {});result.ok()) {property_set_fd = *result;}listen(property_set_fd, 8);auto new_thread = std::thread{PropertyServiceThread};property_service_thread.swap(new_thread);
}
init.rc解析
// 解析init.rc
static void LoadBootScripts(ActionManager& action_manager, ServiceList& service_list) {// 1、创建对应解析器 service、on、importParser parser = CreateParser(action_manager, service_list);// 2、解析init.rc init.rc里面有zygote等等std::string bootscript = GetProperty("ro.boot.init_rc", "");if (bootscript.empty()) {parser.ParseConfig("/system/etc/init/hw/init.rc");if (!parser.ParseConfig("/system/etc/init")) {late_import_paths.emplace_back("/system/etc/init");}parser.ParseConfig("/system_ext/etc/init");if (!parser.ParseConfig("/product/etc/init")) {late_import_paths.emplace_back("/product/etc/init");}if (!parser.ParseConfig("/odm/etc/init")) {late_import_paths.emplace_back("/odm/etc/init");}if (!parser.ParseConfig("/vendor/etc/init")) {late_import_paths.emplace_back("/vendor/etc/init");}} else {parser.ParseConfig(bootscript);}
}
// 根据rc文件的内容,创建对应解析器
Parser CreateParser(ActionManager& action_manager, ServiceList& service_list) {Parser parser;parser.AddSectionParser("service", std::make_unique<ServiceParser>(&service_list, GetSubcontext(), std::nullopt));parser.AddSectionParser("on", std::make_unique<ActionParser>(&action_manager, GetSubcontext()));parser.AddSectionParser("import", std::make_unique<ImportParser>(&parser));return parser;
}// frameworks/core/core/init/parse.cpp
// 解析
bool Parser::ParseConfig(const std::string& path) {if (is_dir(path.c_str())) {return ParseConfigDir(path); // 目录 -> ParseConfigFile}return ParseConfigFile(path); // 文件
}
bool Parser::ParseConfigFile(const std::string& path) {// 解析数据ParseData(path, &config_contents.value());return true;
}// 根据二进制格式要求,解析数据
void Parser::ParseData(const std::string& filename, std::string* data) {for (;;) {switch (next_token(&state)) {case T_EOF:// ...return;case T_NEWLINE: {// ...break;}case T_TEXT:args.emplace_back(state.text);break;}}
}
1.2 init.rc文件
import /system/etc/init/hw/init.${ro.zygote}.rcon init# Mount binderfsmkdir /dev/binderfsmount binder binder /dev/binderfs stats=globalchmod 0755 /dev/binderfssymlink /dev/binderfs/binder /dev/bindersymlink /dev/binderfs/hwbinder /dev/hwbindersymlink /dev/binderfs/vndbinder /dev/vndbinder# Start essential services.start servicemanagerstart hwservicemanagerstart vndservicemanageron late-init# Now we can start zygote for devices with file based encryptiontrigger zygote-starton zygote-start && property:ro.crypto.state=unencrypted# A/B update verifier that marks a successful boot.exec_start update_verifier_nonencryptedstart statsdstart netdstart zygotestart zygote_secondary
二、ServiceManager
ServiceManager启动流程:见Binder部分。
main()
->1.初始化Binder驱动,加载"/dev/binder"
->2.实例化ServiceManager,并将自己作为第一个服务,进行添加注册
->3.将自己设置为IPCThreadState的contextobject,也就是设置服务端的BBinder对象
->4.利用Looper,也就是底层epoll处理事务,设置BinderCallback监听(epoll_ctl),无限等待
->5.while() // Binder驱动遇到事件,会回调handleEvent
三、Zygote
3.1 rc解析
// init.zygote64.rc
# 启动一个服务
service zygote /system/bin/app_process64 -Xzygote /system/bin --zygote --start-system-serverclass mainpriority -20user rootgroup root readproc reserved_disksocket zygote stream 660 root systemsocket usap_pool_primary stream 660 root system# zygote被关闭就会重启Android操作系统 onrestart exec_background - system system -- /system/bin/vdc volume abort_fuseonrestart write /sys/power/state ononrestart restart audioserveronrestart restart cameraserveronrestart restart mediaonrestart restart netdonrestart restart wificondwritepid /dev/cpuset/foreground/tasks
// frameworks/base/cmds/app_process/Android.bp
cc_binary {name: "app_process",srcs: ["app_main.cpp"] // 指明代码为app_main.cpp
}
3.2 启动:app_main.cpp
AppRuntime
class AppRuntime : public AndroidRuntime
{
public:AppRuntime(char* argBlockStart, const size_t argBlockLength): AndroidRuntime(argBlockStart, argBlockLength), mClass(NULL){}void setClassNameAndArgs(const String8& className, int argc, char * const *argv) {mClassName = className;for (int i = 0; i < argc; ++i) {mArgs.add(String8(argv[i]));}}virtual void onVmCreated(JNIEnv* env){if (mClassName.isEmpty()) {return; // Zygote. Nothing to do here.}char* slashClassName = toSlashClassName(mClassName.string());mClass = env->FindClass(slashClassName);if (mClass == NULL) {ALOGE("ERROR: could not find class '%s'\n", mClassName.string());}free(slashClassName);mClass = reinterpret_cast<jclass>(env->NewGlobalRef(mClass));}virtual void onStarted(){sp<ProcessState> proc = ProcessState::self();ALOGV("App process: starting thread pool.\n");proc->startThreadPool();AndroidRuntime* ar = AndroidRuntime::getRuntime();ar->callMain(mClassName, mClass, mArgs);IPCThreadState::self()->stopProcess();hardware::IPCThreadState::self()->stopProcess();}// nativeZygoteInit调用时,被JNI中转到这里。virtual void onZygoteInit(){sp<ProcessState> proc = ProcessState::self(); // 1、初始化ProcessState(初始化Binder驱动)ALOGV("App process: starting thread pool.\n");proc->startThreadPool(); // 2、创建Binder线程池,底层一路到IPCThreadState::self()->joinThreadPool();}virtual void onExit(int code){if (mClassName.isEmpty()) {// if zygoteIPCThreadState::self()->stopProcess();hardware::IPCThreadState::self()->stopProcess();}AndroidRuntime::onExit(code);}String8 mClassName;Vector<String8> mArgs;jclass mClass;
};
main
1、app_main.cpp 启动Zygote或者正常app流程 ->目录frameworks\base\cmds\app_process\
// 核心:根据init.zygote64.rc里面配置的参数,--zygote和--start-system-server,启动zygote,systemserver作为参数传入,以后启动
int main(int argc, char* const argv[])
{/**========================* 1、根据rc配置,解析出需要启动zygote,并且在zygote启动后,*========================*/AppRuntime runtime(argv[0], computeArgBlockSize(argc, argv));// 解析传入的参数 -Xzygote /system/bin --zygote --start-system-serverint i;for (i = 0; i < argc; i++) {// 参数解析runtime.addOption(strdup(argv[i]));}// 判断是zygote还是appbool zygote = false;bool startSystemServer = false; // 用于存入参数bool application = false; // appString8 niceName;String8 className;++i; // Skip unused "parent dir" argument.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;}}Vector<String8> args;if (!className.isEmpty()) {// 非zygote模式,存入application启动相关参数args.add(application ? String8("application") : String8("tool"));runtime.setClassNameAndArgs(className, argc - i, argv + i);} else {// zygote模式,将systemserver和其他剩余参数,都一次性放入if (startSystemServer) {args.add(String8("start-system-server"));}String8 abiFlag("--abi-list=");abiFlag.append(prop);args.add(abiFlag);// In zygote mode, pass all remaining arguments to the zygote// main() method.for (; i < argc; ++i) {args.add(String8(argv[i]));}}/**===============================================* 2、启动zygote(systemserver等内容作为参数传入)*=====================================================*/// 启动zygote的java层调用if (zygote) {zygote 为true 表示正在启动的进程为zygote进程//由此可知app_main.cpp在zygote启动其他进程的时候都会通过main()方法//这里启动的是zygote进程调用runtime start()方法 传入参数runtime.start("com.android.internal.os.ZygoteInit", args, zygote);} else if (className) { //这个地方是用于启动app的 // application = true时,会解析。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.");}
}
2、AndroidRuntime.cpp ->目录frameworks\base\core\jni\
void AndroidRuntime::start(const char* className, const Vector<String8>& options, bool zygote)
{// 创建虚拟机if (startVm(&mJavaVM, &env, zygote, primary_zygote) != 0) { // heapsize默认16MB,也在这里面设置return;} // 注册JNIif (startReg(env) < 0) {return;}// 反射调用main方法(进入ZygoteInit.java的main()方法) ==============================> JNI反射char* slashClassName = toSlashClassName(className != NULL ? className : "");jclass startClass = env->FindClass(slashClassName);jmethodID startMeth = env->GetStaticMethodID(startClass, "main",
"([Ljava/lang/String;)V");env->CallStaticVoidMethod(startClass, startMeth, strArray);
}
JNI注册
AndroidRuntime.cpp中注册了所有JNI
/*static*/ int AndroidRuntime::startReg(JNIEnv* env)
{if (register_jni_procs(gRegJNI, NELEM(gRegJNI), env) < 0) {env->PopLocalFrame(NULL);return -1;}
}// 循环执行数组里面的mProc()方法
static int register_jni_procs(const RegJNIRec array[], size_t count, JNIEnv* env)
{for (size_t i = 0; i < count; i++) {if (array[i].mProc(env) < 0) {return -1;}}return 0;
}#define REG_JNI(name) { name, #name }
struct RegJNIRec {int (*mProc)(JNIEnv*); // mProc函数指针 ============================================> Kotlin函数引用const char* mName;
};// 数组存放了所有JNI对应关系static const RegJNIRec gRegJNI[] = {REG_JNI(register_com_android_internal_os_RuntimeInit), //REG_JNI是RegJNIRec结构体的别名REG_JNI(register_com_android_internal_os_ZygoteInit_nativeZygoteInit),...
}// REG_JNI是将函数名,作为函数指针mProc的内容,后续直接mProc(env)进行调用
int register_com_android_internal_os_ZygoteInit_nativeZygoteInit(JNIEnv* env)
{// JNI动态注册,绑定nativeZygoteInit Java方法 和 底层方法 com_android_internal_os_ZygoteInit_nativeZygoteInitconst JNINativeMethod methods[] = {{ "nativeZygoteInit", "()V",(void*) com_android_internal_os_ZygoteInit_nativeZygoteInit },};return jniRegisterNativeMethods(env, "com/android/internal/os/ZygoteInit",methods, NELEM(methods));
}
static void com_android_internal_os_ZygoteInit_nativeZygoteInit(JNIEnv* env, jobject clazz)
{//gCurRuntime是app_main.cpp中的class AppRuntime : public AndroidRuntime,实现了onZygoteInit()方法gCurRuntime->onZygoteInit();
}
3.3 ZygoteInit
3、ZygoteInit.java ->目录frameworks\base\cor\java\com\android\internal\os\
public static void main(String argv[]) {//step1 重要的函数 preloadpreload(bootTimingsTraceLog);//Step2 重要函数 创建socket服务zygoteServer = new ZygoteServer(isPrimaryZygote); if (startSystemServer) {//Step3 重要函数 Zygote Fork出的第一个进程 system_serverRunnable r = forkSystemServer(abiList, zygoteSocketName, zygoteServer);r.run(); // r: ZygoteInit.zygoteInitreturn;}//Step4 重要函数 循环等待fork出其他的应用进程,比如Launcher,比如appcaller = zygoteServer.runSelectLoop(abiList);//执行返回的Runnable对象,进入子进程caller.run();}
preload
static void preload(TimingsTraceLog bootTimingsTraceLog) {preloadClasses();// 加载系统类preloadResources();// 加载系统资源nativePreloadAppProcessHALs();maybePreloadGraphicsDriver();preloadSharedLibraries();// 加载一些共享so库,其实就三个:android、compiler_rt、jnigraphicspreloadTextResources();// 加载字体资源WebViewFactory.prepareWebViewInZygote();// 加载webview相关资源warmUpJcaProviders();// 初始化JCA安全相关的参数}
预加载资源
1、preloadClasses()
"/system/etc/preloaded-classes"
// 会预加载上述文件中的类
// 例如:SurfaceView、TextView、View等等
2、preloadResources()
// 预加载如 com.android.internal.R.xxx 资源
"frameworks/base/core/res/values/arrays.xml"...
ZygoteServer
创建
// 创建ZygoteServer的本质就是创建LocalServerSocket
static void main(){zygoteServer = new ZygoteServer(isPrimaryZygote);
}
// ZygoteServer.java frameworks\base\core\java\com\android\internal\os\ZygoteServer.javaZygoteServer(boolean isPrimaryZygote) {// 初始化,创建SocketmZygoteSocket = Zygote.createManagedSocketFromInitSocket(Zygote.PRIMARY_SOCKET_NAME);}static LocalServerSocket createManagedSocketFromInitSocket(String socketName) {return new LocalServerSocket(fd);// =======================================================================>LocalServerSocket}
创建连接
AMS会创建连接 ==========> AMS
处理事务
1、ZygoteServer源码
- poll机制,多路复用
- 处理客户端连接请求
- 处理数据处理请求:processOneCommand
Runnable runSelectLoop(String abiList) {//mZygoteSocket 是socket通信中的服务端,即zygote进程。保存到socketFDs[0]socketFDs.add(mZygoteSocket.getFileDescriptor());while (true) {// Step 1:Poll机制,IO多路复用。没有事件到来就阻塞// 1. 每次循环,都重新创建需要监听的pollFds。并且关注事件:POLLINStructPollfd[] pollFDs;for (FileDescriptor socketFD : socketFDs) {pollFDs[pollIndex].events = (short) POLLIN; // 关注事件的到来}// 2. Poll机制:处理轮询状态,当pollFds有事件到来则往下执行,否则阻塞在这里pollReturnValue = Os.poll(pollFDs, pollTimeoutMs); // ===================================================================================>Pollwhile (--pollIndex >= 0) {// Step 2:处理 客户端发出连接请求if (pollIndex == 0) { // 意味着有客户端连接请求ZygoteConnection newPeer = acceptCommandPeer(abiList);// 则创建ZygoteConnection对象,并添加到socketFDs。peers.add(newPeer); // 加入到peers和socketFDs,下一次也开始监听socketFDs.add(newPeer.getFileDescriptor());}// Step 3:处理 数据处理请求else if (pollIndex < usapPoolEventFDIndex) {//pollIndex>0,则代表通过socket接收来自对端的数据,并执行相应操作ZygoteConnection connection = peers.get(pollIndex);///重要细节///// 进行进程的处理:创建进程final Runnable command = connection.processOneCommand(this);// TODO (chriswailes): Is this extra check necessary?if (mIsForkChild) {return command; // child} else {socketFDs.remove(pollIndex);//处理完则从socketFDs中移除该文件描述符}}}}}// 接受客户端连接private ZygoteConnection acceptCommandPeer(String abiList) {// 用LocalSocket建立连接return createNewConnection(mZygoteSocket.accept(), abiList);}protected ZygoteConnection createNewConnection(LocalSocket socket, String abiList){return new ZygoteConnection(socket, abiList);}// 初始化输入输出流 BIOZygoteConnection(LocalSocket socket, String abiList) throws IOException { // =================================>Java BIOmSocket = socket;this.abiList = abiList;mSocketOutStream = new DataOutputStream(socket.getOutputStream());mSocketReader =new BufferedReader( // ============================> 装饰者模式new InputStreamReader(socket.getInputStream()), Zygote.SOCKET_BUFFER_SIZE);mSocket.setSoTimeout(CONNECTION_TIMEOUT_MILLIS);peer = mSocket.getPeerCredentials();isEof = false;}
2、ZygoteServer和select、poll、epoll
- select <= Andorid 5.0,使用复杂,最大1024FD
- poll >= Android 6.0,取消FD数量限制
3、为什么使用poll,不使用epoll?
- fork进程频率远不如Handler的Looper,epoll在低并发场景下并没有特别优势
- epoll还需要维护事件队列,没有必要
poll
Title
死磕epoll
四、SystemServer
1、是什么
- 是独立进程
- 管理服务(90+)
4.0 forkSystemServer
forkSystemServer()
->nativeFork()->fork()
->ZygoteInit.zygoteInit()->Runtime.commonInit // 初始化运行时环境->设置异常处理器KillApplicationHandler->ZygoteInit.nativeZygoteInit // JNI注册时,可以发现通过JNI转到了AppRuntime的onZygoteInit回调->构造ProcessSate::self()->open_driver->初始化Binder驱动->设置最大线程数15->mmap 1024-8kb->通过IPCThreadState启动Binder线程池->main()
1、fork出SystemServer
ZygoteInit.java ->目录frameworks\base\core\java\com\android\internal\os\
private static Runnable forkSystemServer(String abiList, String socketName,ZygoteServer zygoteServer) {int pid = Zygote.forkSystemServer(parsedArgs.mUid, parsedArgs.mGid,parsedArgs.mGids,parsedArgs.mRuntimeFlags,null,parsedArgs.mPermittedCapabilities,parsedArgs.mEffectiveCapabilities);// 子进程继续处理if (pid == 0) { if (hasSecondZygote(abiList)) {waitForSecondaryZygote(socketName);}zygoteServer.closeServerSocket();return handleSystemServerProcess(parsedArgs);}// 父进程什么也不做return null;
}
2、Zygote.java frameworks\base\core\java\com\android\internal\os\Zygote.java
static int forkSystemServer(int uid, int gid, int[] gids, int runtimeFlags, int[][] rlimits, long permittedCapabilities, long effectiveCapabilities) {int pid = nativeForkSystemServer(uid, gid, gids, runtimeFlags, rlimits,permittedCapabilities, effectiveCapabilities);return pid;}// 核心作用:调用底层fork()private static native int nativeForkSystemServer(int uid, int gid, int[] gids, int runtimeFlags,int[][] rlimits, long permittedCapabilities, long effectiveCapabilities);
3、ZygoteInit.java frameworks\base\core\java\com\android\internal\os\
private static Runnable handleSystemServerProcess(ZygoteArguments parsedArgs) // pathclassloaderClassLoader cl = createPathClassLoader(systemServerClasspath, parsedArgs.mTargetSdkVersion); // ===============================> PathClassLoaderreturn ZygoteInit.zygoteInit(parsedArgs.mTargetSdkVersion,parsedArgs.mDisabledCompatChanges,parsedArgs.mRemainingArgs, cl);
}// 双亲委派,用BootClassLoader构造PathClassLoader,传入库路径:"java.library.path"static ClassLoader createPathClassLoader(String classPath, int targetSdkVersion) {String libraryPath = System.getProperty("java.library.path");ClassLoader parent = ClassLoader.getSystemClassLoader().getParent(); // We use the boot class loader, that's what the runtime expects at AOT.return ClassLoaderFactory.createClassLoader(classPath, libraryPath, libraryPath,parent, targetSdkVersion, true /* isNamespaceShared */, null /* classLoaderName */);}
4、ZygoteInit.java frameworks\base\core\java\com\android\internal\os\
public static final Runnable zygoteInit(xxx) {//1. 初始化运行环境RuntimeInit.commonInit();//初始化运行环境 //2. 启动Binder ,方法在 androidRuntime.cpp中注册 ZygoteInit.nativeZygoteInit();//3. 通过反射创建程序入口函数的 Method 对象,并返回 Runnable 对象// ActivityThread.mainreturn RuntimeInit.applicationInit(targetSdkVersion, disabledCompatChanges, argv, classLoader);}protected static Runnable applicationInit(xxx, ClassLoader classLoader) {// startClass: 如果AMS通过socket传递过来的是 ActivityThreadreturn findStaticMain(args.startClass, args.startArgs, classLoader);}
4.1 commonInit
framworks\base\core\java\com\android\internal\os\
protected static final void commonInit() {if (DEBUG) Slog.d(TAG, "Entered RuntimeInit!");//1、设置异常处理器:
// 1. 设置未捕获异常的预处理器(pre-handler)为 LoggingHandler,用于处理未捕获的异常日志。
// 2. 设置默认的未捕获异常处理器为 KillApplicationHandler,用于处理应用程序崩溃并终止应用。 =========================> ANR KillApplicationHandlerLoggingHandler loggingHandler = new LoggingHandler();RuntimeHooks.setUncaughtExceptionPreHandler(loggingHandler);Thread.setDefaultUncaughtExceptionHandler(new KillApplicationHandler(loggingHandler));//2、设置日志管理器LogManager.getLogManager().reset();new AndroidConfig();//3、关联套接字标记和流量统计:NetworkManagementSocketTagger.install();
// 省略initialized = true;}
4.2 main()
SystemServer.java
public static void main(String[] args) {new SystemServer().run(); // 构造SystemServer执行run方法}private void run() {/**=====================================* 1、startService之前的准备工作*=======================================*/TimingsTraceAndSlog t = new TimingsTraceAndSlog();try {// Binder线程池 线程数量 -> 31BinderInternal.setMaxThreads(sMaxBinderThreads);// 线程优先级android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_FOREGROUND);android.os.Process.setCanSelfBackground(false);// STEP 1: 主线程Looper =================================================> HandlerLooper.prepareMainLooper();// load android_servers.so // 服务相关System.loadLibrary("android_servers");// STEP 2: 创建SystemServer的Context,用于黑白屏的启动createSystemContext();// STEP 3: 构造SystemServiceManager并且添加到本地服务中。系统服务(SystemServer)的管理者mSystemServiceManager = new SystemServiceManager(mSystemContext);// 添加到LocalServicesLocalServices.addService(SystemServiceManager.class, mSystemServiceManager);// 启动SystemServer内部线程池:mService = ConcurrentUtils.newFixedThreadPoolSystemServerInitThreadPool.start(); // // JVMTI相关if (Build.IS_DEBUGGABLE) {// Property is of the form "library_path=parameters".String jvmtiAgent = SystemProperties.get("persist.sys.dalvik.jvmtiagent");int equalIndex = jvmtiAgent.indexOf('=');String libraryPath = jvmtiAgent.substring(0, equalIndex);String parameterList =jvmtiAgent.substring(equalIndex + 1, jvmtiAgent.length());Debug.attachJvmtiAgent(libraryPath, parameterList, null);}} finally {t.traceEnd(); // InitBeforeStartServices}/**=====================================* 2、startService*=======================================*/// STEP 4: 启动各类服务startBootstrapServices(t);startCoreServices(t);startOtherServices(t);// STEP 5: loop 无限循环Looper.loop();}
createSystemContext()
// SystemServer.java :attach(创建Context和Application执行onCreate)、设置主题private void createSystemContext() {// 1、attach,参数一 system:trueActivityThread activityThread = ActivityThread.systemMain();mSystemContext = activityThread.getSystemContext();// 2、设置主题mSystemContext.setTheme(DEFAULT_SYSTEM_THEME);final Context systemUiContext = activityThread.getSystemUiContext();systemUiContext.setTheme(DEFAULT_SYSTEM_THEME);}
// ActivityThread.java-attachpublic static ActivityThread systemMain() {ActivityThread thread = new ActivityThread();thread.attach(true, 0); // system:true startSeq:0return thread;}
// ActivityThread.java: context和application+onCreateprivate void attach(boolean system, long startSeq) {mSystemThread = system;if (system) {mInstrumentation = new Instrumentation();mInstrumentation.basicInit(this);// 1、创建系统的ContextImplContextImpl context = ContextImpl.createAppContext(this, getSystemContext().mPackageInfo);// 2、构造Applicaiton,执行onCreatemInitialApplication = context.mPackageInfo.makeApplication(true, null);mInitialApplication.onCreate();}}
BinderInternal.setMaxThreads
BinderInternal.setMaxThreads(sMaxBinderThreads);
// 增加SystemServer的binder线程池中线程数量,15---->31
// JNI转给ProcessState
status_t ProcessState::setThreadPoolMaxThreadCount(size_t maxThreads) {status_t result = NO_ERROR;// ioctlif (ioctl(mDriverFD, BINDER_SET_MAX_THREADS, &maxThreads) != -1) {mMaxThreads = maxThreads;}return result;
}
LocalServices
LocalServices
// ArrayMap key=服务.class, value=服务对象 ====================================================> ArrayMapprivate static final ArrayMap<Class<?>, Object> sLocalServiceObjects = new ArrayMap<Class<?>, Object>();public static <T> void addService(Class<T> type, T service) {synchronized (sLocalServiceObjects) { // =================================>synchronizedif (sLocalServiceObjects.containsKey(type)) {throw new IllegalStateException("Overriding service registration");}sLocalServiceObjects.put(type, service);}}
4.3 startServices
private void startBootstrapServices(@NonNull TimingsTraceAndSlog t) {t.traceBegin("startBootstrapServices");// WatchDog SystemServer可能crash =========================> crash、anrt.traceBegin("StartWatchdog");final Watchdog watchdog = Watchdog.getInstance();watchdog.start();// 等待安装完成,安装好后创建/data/user目录(需要先启动)t.traceBegin("StartInstaller");Installer installer = mSystemServiceManager.startService(Installer.class);t.traceEnd();// ATMS// MASt.traceBegin("StartActivityManager");ActivityTaskManagerService atm = mSystemServiceManager.startService(ActivityTaskManagerService.Lifecycle.class).getService(); //启动atmsmActivityManagerService = ActivityManagerService.Lifecycle.startService(mSystemServiceManager, atm); // 启动AMSmActivityManagerService.setSystemServiceManager(mSystemServiceManager);mActivityManagerService.setInstaller(installer);mWindowManagerGlobalLock = atm.getGlobalLock();t.traceEnd();// PwerManagert.traceBegin("StartPowerManager");mPowerManagerService = mSystemServiceManager.startService(PowerManagerService.class);t.traceEnd();// 需要reboot是提供recoery服务t.traceBegin("StartRecoverySystemService");mSystemServiceManager.startService(RecoverySystemService.Lifecycle.class);t.traceEnd();// LED服务,灯服务t.traceBegin("StartLightsService");mSystemServiceManager.startService(LightsService.class);t.traceEnd();// Display服务,用于提供display metricst.traceBegin("StartDisplayManager");mDisplayManagerService = mSystemServiceManager.startService(DisplayManagerService.class);t.traceEnd();// PKMSt.traceBegin("StartPackageManagerService");try {Watchdog.getInstance().pauseWatchingCurrentThread("packagemanagermain");mPackageManagerService = PackageManagerService.main(mSystemContext, installer,mFactoryTestMode != FactoryTest.FACTORY_TEST_OFF, mOnlyCore);} finally {Watchdog.getInstance().resumeWatchingCurrentThread("packagemanagermain");}// User管理t.traceBegin("StartUserManagerService");mSystemServiceManager.startService(UserManagerService.LifeCycle.class);t.traceEnd();// 没有加密,otadexoptif (!mOnlyCore) {boolean disableOtaDexopt = SystemProperties.getBoolean("config.disable_otadexopt",false);if (!disableOtaDexopt) {t.traceBegin("StartOtaDexOptService");try {Watchdog.getInstance().pauseWatchingCurrentThread("moveab");OtaDexoptService.main(mSystemContext, mPackageManagerService);} catch (Throwable e) {reportWtf("starting OtaDexOptService", e);} finally {Watchdog.getInstance().resumeWatchingCurrentThread("moveab");t.traceEnd();}}}// 核心内容,为app进程安排系统进程的以便后期监控t.traceBegin("SetSystemProcess");mActivityManagerService.setSystemProcess();t.traceEnd();t.traceEnd(); // startBootstrapServices}
- 不需要Binder通信的,比如电源服务,可以直接继承SystemService
SystemServiceManager.startService
// SystemServiceManager.javapublic SystemService startService(String className) {// Class.forNamefinal Class<SystemService> serviceClass = loadClassFromLoader(className, this.getClass().getClassLoader());return startService(serviceClass);}public <T extends SystemService> T startService(Class<T> serviceClass) {// 反射构造final String name = serviceClass.getName();final T service;Constructor<T> constructor = serviceClass.getConstructor(Context.class);service = constructor.newInstance(mContext);startService(service);return service; }private final ArrayList<SystemService> mServices = new ArrayList<SystemService>();public void startService(@NonNull final SystemService service) {
// 1、添加到ArrayList中mServices.add(service);
// 2、执行SystemService接口的onStart()方法long time = SystemClock.elapsedRealtime();try {service.onStart();} catch (RuntimeException ex) {throw new RuntimeException("Failed to start service " + service.getClass().getName()+ ": onStart threw an exception", ex);}warnIfTooLong(SystemClock.elapsedRealtime() - time, service, "onStart");}// xxxService: 如BatteryService.javapublic void onStart() {mBinderService = new BinderService();// 是SystemService接口,发布到ServiceManager中publishBinderService("battery", mBinderService);mBatteryPropertiesRegistrar = new BatteryPropertiesRegistrar();publishBinderService("batteryproperties", mBatteryPropertiesRegistrar);publishLocalService(BatteryManagerInternal.class, new LocalService());}
// ServiceManager.addService()protected final void publishBinderService(String name, IBinder service,boolean allowIsolated, int dumpPriority) {ServiceManager.addService(name, service, allowIsolated, dumpPriority);}
答疑
1、挂载是什么意思?
- U盘插入,会绑定到一个文件,才能看到
2、先分配进程还是先分配虚拟机?
- 先分配进程
- 再分配虚拟机:虚拟机是一段代码,并且实现了内存管理
3、孤儿进程是什么?
- 父进程先退出
- 子进程还没有退出
- 会托孤给init进程(任何一个进程都必须有父进程)
4、僵尸进程
- 子进程先退出
- 父进程还没有退出
- 子进程必须等待父进程捕获子进程的退出状态,子进程会变成僵尸进程(只保留一部分退出信息,等着父进程查询)
5、子进程和父进程的区别
- 除了文件锁以外,其他锁都会被继承 ========================>mmkv
6、Zygote的native为什么要启动虚拟机?
- 要跑Java代码就需要JVM
- 需要做预处理工作,不然都交给App进程去做,太慢了。
- 所有进程时Zygote fork出来的,因此JVM、预加载的东西都会被fork出来。
- 如果zygote是多线程的,fork出来的东西没什么用。
7、车载中需要增加一个系统服务帮助其他模块之间通信,需要增加fdbus
- 增加系统服务
- 修改init
- 修改selinux
自我检测
- nativeZygoteInit是谁调用的?
- nativeZygoteInit的作用是什么?
- IPCThreadState的作用是什么?
相关文章:
Android系统启动流程 源码解析
Android系统启动流程 本文链接:https://blog.csdn.net/feather_wch/article/details/132518105 有道云脑图:https://note.youdao.com/s/GZ9d8vzO 1、整体流程 Boot RoomBootLoaderidle kthreadinit init ServiceManagerzygote zygote SystemServerap…...
【头歌】构建哈夫曼树及编码
构建哈夫曼树及编码 第1关:构建哈夫曼树 任务描述 本关任务:构建哈夫曼树,从键盘读入字符个数n及这n个字符出现的频率即权值,构造带权路径最短的最优二叉树(哈夫曼树)。 相关知识 哈夫曼树的定义 设二叉树具有n个带权值的叶子结点{w1,w2,...,wn},从根结点到每个叶…...
创建本地镜像
通过前面文章的阅读,读者已经了解到所谓的容器实际上是在父镜像的基础上创建了一个可读写的文件层级,所有的修改操作都在这个文件层级上进行,而父镜像并未受影响,如果读者需要根据这种修改创建一个新的本地镜像,有两种…...
网络编程套接字(2): 简单的UDP网络程序
文章目录 网络编程套接字(2): 简单的UDP网络程序3. 简单的UDP网络程序3.1 服务端创建(1) 创建套接字(2) 绑定端口号(3) sockaddr_in结构体(4) 数据的接收与发送接收发送 3.2 客户端创建3.3 代码编写(1) v1_简单发送消息(2) v2_小写转大写(3) v3_模拟命令行解释器(4) v4_多线程版…...
Android Mvvm设计模式的详解与实战教程
一、介绍 在开发设计模式中,模式经历了多次迭代,从MVC到MVP,再到如今的MVVM。发现的过程其实很简单,就是为了项目更好的管理。 设计模式严格来说属于软件工程的范畴,但是如今在各大面试中或者开发中,设计模…...
软考A计划-系统集成项目管理工程师-小抄手册(共25章节)-下
点击跳转专栏>Unity3D特效百例点击跳转专栏>案例项目实战源码点击跳转专栏>游戏脚本-辅助自动化点击跳转专栏>Android控件全解手册点击跳转专栏>Scratch编程案例点击跳转>软考全系列点击跳转>蓝桥系列 👉关于作者 专注于Android/Unity和各种游…...
渗透测试是什么?怎么做?
渗透测试报告 一、什么是渗透测试? 渗透测试是可以帮助用户对目前自己的网络、系统、应用的缺陷有相对直观的认识和了解。渗透测试尽可能地以黑客视角对用户网络安全性进行检查,对目标网络、系统和应用的安全性作深入的探测,发现系统最脆弱的…...
【软件安装】Python安装详细教程(附安装包)
软件简介 Python由荷兰数学和计算机科学研究学会的Guido van Rossum 于1990 年代初设计,作为一门叫做ABC语言的替代品。Python提供了高效的高级数据结构,还能简单有效地面向对象编程。Python语法和动态类型,以及解释型语言的本质,…...
微信小程序的form表单提交
获取input有两种方法: 第一:bindsubmit方法 注意: 1.使用form里面定义bindsubmit事件 2.bindsubmit事件需要配合button里面定义的formType“submit” 操作 3.设置input的name值来获取对应的数据 <form bindsubmit"formSubmit"…...
WOFOST模型与PCSE模型应用
实现作物产量的准确估算对于农田生态系统响应全球变化、可持续发展、科学粮食政策制定、粮食安全维护都至关重要。传统的经验模型、光能利用率模型等估产模型原理简单,数据容易获取,但是作物生长发育非常复杂,中间涉及众多生理生化过程&#…...
5-W806-RC522-SPI
main.c #include <stdio.h> #include "wm_hal.h" #include "rc522.h"int main(void) {SystemClock_Config(CPU_CLK_160M);printf("enter main\r\n");HAL_Init();RC522_Init();PcdReset();M500PcdConfigISOType ( A );//设置工作方式IC_te…...
Python实现自动登录+获取数据
前言 Dy这个东西想必大家都用过,而且还经常刷,今天就来用代码,获取它的视频数据 环境使用 Python 3.8 Pycharm 模块使用 requests selenium json re 一. 数据来源分析 1. 明确需求 明确采集网站以及数据内容 网址: https://www.dy.co…...
yolov8热力图可视化
安装pytorch_grad_cam pip install grad-cam自动化生成不同层的bash脚本 # 循环10次,将i的值从0到9 for i in $(seq 0 13) doecho "Running iteration $i";python yolov8_heatmap.py $i; done热力图生成python代码 import warnings warnings.filterwarn…...
【SpringBoot】第一篇:redis使用
背景: 本文是教初学者如何正确使用和接入redis。 一、引入依赖 <!--redis--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><depen…...
Springboot profile多环境配置
1. 前言 profile用于多环境的激活和配置,用来切换生产,测试,本地等多套不通环境的配置。如果每次去更改配置就非常麻烦,profile就是用来切换多环境配置的。 2. 配置方法 三种方式。 2.1 多profile文件方式 在resource目录下新…...
(1)进程与线程区别
1.什么是线程、进程 进程:操作系统资源分配的基本单位线程:处理器任务调度和执行的基本单位。 一个进程至少有一个线程,线程是进程的一部分,所以线程也被称为轻权进程或者轻量级进程。 2.并行与并发 一个基本的事实前提&#x…...
学习JAVA打卡第四十天
对象的字符串表示 在此类中我们讲过,所有的类都默认是java.lang包中object类的子类或间接子类。 Object类有一个public String toString()方法,一个对象通过调用该方法可以获得该对象的字符串表示。一个对象调用toString法(&…...
【跟小嘉学 Rust 编程】十四、关于 Cargo 和 Crates.io
系列文章目录 【跟小嘉学 Rust 编程】一、Rust 编程基础 【跟小嘉学 Rust 编程】二、Rust 包管理工具使用 【跟小嘉学 Rust 编程】三、Rust 的基本程序概念 【跟小嘉学 Rust 编程】四、理解 Rust 的所有权概念 【跟小嘉学 Rust 编程】五、使用结构体关联结构化数据 【跟小嘉学…...
防关联指纹浏览器:高效地管理你的Facebook账户
Facebook,作为全球最受欢迎社交平台的第一名已经成为我们日常和工作中不可或缺的一部分了。不管是用于日常分享、媒体营销、还是店铺运营,Facebook都占据着重要的位置。多个Facebook账户的优势非常明显,然而,当你需要同时管理他们…...
前端学习记录~2023.8.15~JavaScript重难点实例精讲~第7章 ES6(1)
第 7 章 ES6 前言7.1 let关键字和const关键字7.1.1 let关键字(1)let关键字的特性(2)使用let关键字的好处 7.1.2 const关键字(1)const关键字的特性 7.2 解构赋值7.2.1 数组的解构赋值(1ÿ…...
WebSocket详解以及应用
😜作 者:是江迪呀✒️本文关键词:websocket、网络、长连接、前端☀️每日 一言:任何一个你不喜欢而又离不开的地方,任何一种你不喜欢而又无法摆脱的生活,都是监狱! 一、前言 我们在…...
如何评估开源项目的活跃度和可持续性?
🌷🍁 博主猫头虎 带您 Go to New World.✨🍁 🦄 博客首页——猫头虎的博客🎐 🐳《面试题大全专栏》 文章图文并茂🦕生动形象🦖简单易学!欢迎大家来踩踩~🌺 &a…...
远程Linux/ubuntu服务器后台不间断运行py文件/sh脚本
通常我们在生产环境中运行一些项目时需要将程序不间断的运行在服务器上,并且将日志文件打印到某个文件中,直到程序运行结束,下面介绍了在Linux服务器上不间断运行py文件的方式,以及如何保存相应的日志信息。 对于 .py 文件&#x…...
记录一个诡异的bug
将对接oa跳转到会议转写的项目oa/meetingtranslate项目发布到天宫,结果跳转到successPage后报错 这一看就是successPage接口名没对上啊,查了一下代码,没问题啊。 小心起见,我就把successPage的方法请求方式从Post改为Get和POST都…...
Xamarin.Android中的Fragment
目录 1、Activity中使用Fragment2、Fragment与Activity通信3、Fragment与其他的Fragment通信 1、Activity中使用Fragment 一般而言,会在activity中添加一个加载fragment的方法。通过点击菜单的按钮,加载不同的fragment。其样子一般是这样的:…...
portainer初体验
官方文档 安装 docker 这里采用的的是国内汉化的一个镜像,版本号2.16.2。 地址 docker run -d --restartalways --name"portainer" -p 9000:9000 -v /var/run/docker.sock:/var/run/docker.sock 6053537/portainer-ce体验 访问9000端口。 尝试&#x…...
4G数传方案(合宙cat1模块)
一. 合宙Cat1简介 合宙 Air724 模组推出的低功耗,超小体积,高性能嵌入式 4G Cat1 核心版,标准的 2.54 排针、最小成本的进项 2G、4G Cat4 切换;主要功能如下: 实际测试工作环境为-35℃-75℃; 支持 5-12V 供电或者 3.7…...
ElasticSearch - 海量数据索引拆分的一些思考
文章目录 困难解决方案初始方案及存在的问题segment merge引入预排序 拆分方案设计考量点如何去除冗余数据按什么维度拆分,拆多少个最终的索引拆分模型演进历程整体迁移流程全量迁移流程流量回放比对验证异步转同步多索引联查优化效果 总结与思考参考 困难 索引数据…...
【SA8295P 源码分析】83 - SA8295P HQNX + Android 完整源代码下载方法介绍
【SA8295P 源码分析】83 - SA8295P HQNX + Android 完整源代码下载方法介绍 一、高通官网 Chipcode 下载步骤介绍1.1 高通Chipcode 下载步骤1.2 高通 ReleaseNote 下载方法二、高通 HQX 代码介绍2.1 完整的 HQX 代码结构:sa8295p-hqx-4-2-4-0_hlos_dev_qnx.tar.gz2.2 sa8295p-…...
【设计模式--原型模式(Prototype Pattern)
一、什么是原型模式 原型模式(Prototype Pattern)是一种创建型设计模式,它的主要目的是通过复制现有对象来创建新的对象,而无需显式地使用构造函数或工厂方法。这种模式允许我们创建一个可定制的原型对象,然后通过复制…...
网站正在升级建设中代码/小程序排名优化
所有您想了解的有关K2 blackpearl以及流程驱动应用程序知识尽在于此书Wrox Press, 2009 (点击窗口右边网站链接即刻订购)K2 blackpearl 正在改变人们使用软件的方式 — 帮助全球各地从事各种行业的企业和机构精简业务、提高工作效率和节约时间。K2 blackpearl 是一款商务人士、…...
做网站多大上行速度/自媒体平台注册入口官网
.caret 在Bootstrap中的作用,写出一个“下拉箭头”,如图: 其具体css样式为: .caret { display: inline-block; width: 0; height: 0; margin-left: 2px; vertical-align: middle; border-top: 4px dashed; border-top: 4p…...
asp.net4.5网站开发/百度关键词排名价格
全国2016年10月高等教育自学考试计算机应用基础代码:00018一、单项选择题1.微型计算机属于A.第1代计算机B.第2代计算机C.第3代计算机D.第4代计算机2.在以下打印机中,打印速度最快的是A.针式打印机B.激光打印机C.喷墨打印机D.3D打印机3.二进制数110110111…...
自己做网站需要钱吗/微信公众号软文怎么写
2019独角兽企业重金招聘Python工程师标准>>> elinks setsebool 转载于:https://my.oschina.net/u/138995/blog/224332...
专业做网站套餐/深圳百度总部
下面为统计前的大概格式: ########################################### 下面为统计后处理的结果:(所有数据均做作假或者隐藏处理,防止追究责任) ################################## #下面是相关代码,仅…...
哪些网站是用php开发的/网站产品推广
1.使用unity中的Standard Assets包中的控制器 这个资源在资源商店中搜Standard Assets就可以下载并导入,在其Characters文件夹中有第一人称和第三人称的控制器,在unity资源面板中找到其预制件拖入层次视图即可。 涉及到的人物控制的内容很全并且也有源…...