Android 进阶——系统启动之Android进程造物者Zygote 进程启动详解(六)
引言
前面系列文章介绍了Android系统的第一个用户进程——init进程由解析init.rc脚本启动,完成属性系统的初始化等工作后紧接着启动Android系统上的造物者——Zygote进程,这篇文章接着分析。
写时拷贝(Copy On Write):Linux 进程基本上都是通过fork 创建的,fork出来的子进程除了内核中一些核心数据结构和父进程不同之外,其余大部分的内存空间都是和父进程共享的,当且仅当子进程需要去改写这些共享内存时,内核才会为子进程分配一个新的页面,并将老页面的数据复制一份到新页面中。
以后更多精选系列文章以高度完整的系列形式发布在公众号,真正的形成一套完整的技术栈,欢迎关注,目前第一个系列是关于Android系统启动系列的文章,大概有二十几篇干货:
一、Zygote进程概述
Zygote 中文翻译受精卵,是由Android系统第一个用户进程init进程启动起来的,在Android 系统中,所有的应用程序进程包括用于运行系统关键核心服务的SystemServer进程都是由Zygote 进程fork 而来的,就像受精卵一样具有孵化的功能,因此又被它称之为进程孵化器。即Zygote 通过 fork自身(复制自身)方式创建System进程和应用程序进程的。而且Zygote 进程在启动时会自主在内部创建一个VM实例,而通过fork 而得到的所有子进程自然继承Zygote的空间,换言之,System进程和应用进程内部也持有一个VM实例的拷贝,这样进程才可以通过这个VM实例运行Java 语言开发的程序组件。
fork 函数执行成功后,会有两次返回,其中一次是在当前进程中返回,另一次是在新创建出来的子进程中返回(当返回值等于0时)。
二、Zygote进程的启动
1、Init进程解析init.rc脚本触发Zygote进程的启动
1.1、init.rc 脚本触发zygote 以service 形式启动
Zygote进程是通过aosp/system/core/rootdir/init.rc
脚本启动的,如下所示部分内容
import /init.environ.rc
import /init.usb.rc
import /init.${ro.hardware}.rc
import /init.usb.configfs.rc
import /init.${ro.zygote}.rc
....
在第五行init.${ro.zygote}.rc
可以看到有一个全局系统属性名为ro.zygote(我设备得到的值zygote64_32)
adb shell getprop | grep ro.zygote
其实这是一个init.rc 的动态引入的语法,则代表引入的.rc文件名为 init.zygote64_32.rc,内容如下:
service zygote /system/bin/app_process32 -Xzygote /system/bin --zygote --start-system-server --socket-name=zygoteclass mainsocket zygote stream 660 root systemonrestart write /sys/android_power/request_state wakeonrestart write /sys/power/state ononrestart restart audioserveronrestart restart cameraserveronrestart restart mediaonrestart restart netdwritepid /dev/cpuset/foreground/tasksservice zygote_secondary /system/bin/app_process64 -Xzygote /system/bin --zygote --socket-name=zygote_secondaryclass mainsocket zygote_secondary stream 660 root systemonrestart restart zygotewritepid /dev/cpuset/foreground/tasks
由上得知,定义了两个Zygote服务:zygote和zygote_secondary,分别对应不同的可执行文件,前者是app_process32,后者是app_process64。但它们进程的可执行文件都是app_process,对应的源码在/frameworks/base/cmds/app_process/app_main.cpp。
1.2、解析 init.zygote64_32.rc 中的service zygote /system/bin/app_process32 -Xzygote /system/bin --zygote --start-system-server --socket-name=zygote 指令
当解析到第一行的命令时,遇到service
指令时就会相应地调用system/core/init/service.cpp # Service::start函数来执行启动工作,对应的含义依次为zygote进程所对应的应用程序文件为/system/bin/app_process32
,就紧接着跟着进程启动需要传递的参数,--start-system-server
表示Zygote进程启动完毕后需要马上将System进程启动。对应的app_process 启动命令格式:
app_process [虚拟机参数] 运行目录 参数 [Java类]
名称 | 说明 |
---|---|
虚拟机参数 | 以“-”开头,用于指定指定Android虚拟机时的参数 |
运行目录 | 程序的运行目录,一般是/system/bin |
参数 | 以“–”开头,比如–zygote 表示要启动的zygote进程,–application则表示以普通进程的方式执行Java代码 |
Java类 | 要执行的Java类,即执行这个类的静态方法main,需要注意传递参数“–zygote” 时不会执行这个类而是执行ZygoteInit这个类的main方法 |
1.3、调用system/core/init/service.cpp # Service::start 函数创建zygote进程和/system/core/init/util.cpp # create_socket函数创建名为zygote 的socket
bool Service::Start() {...pid_t pid = fork();if (pid == 0) {umask(077);for (const auto& ei : envvars_) {add_environment(ei.name.c_str(), ei.value.c_str());}//遍历为Zygote进程配置的socket列表,并创建对应的socket并把返回的fd发布到系统中for (const auto& si : sockets_) {int socket_type = ((si.type == "stream" ? SOCK_STREAM :(si.type == "dgram" ? SOCK_DGRAM :SOCK_SEQPACKET)));const char* socketcon =!si.socketcon.empty() ? si.socketcon.c_str() : scon.c_str();int s = create_socket(si.name.c_str(), socket_type, si.perm,si.uid, si.gid, socketcon);if (s >= 0) {PublishSocket(si.name, s);}}...std::vector<std::string> expanded_args;std::vector<char*> strs;expanded_args.resize(args_.size());strs.push_back(const_cast<char*>(args_[0].c_str()));for (std::size_t i = 1; i < args_.size(); ++i) {if (!expand_props(args_[i], &expanded_args[i])) {ERROR("%s: cannot expand '%s'/n", args_[0].c_str(), args_[i].c_str());_exit(127);}strs.push_back(const_cast<char*>(expanded_args[i].c_str()));}strs.push_back(nullptr);//str[0] 值等于/system/bin/app_process 通过shell 加载该文件if (execve(strs[0], (char**) &strs[0], (char**) ENV) < 0) {ERROR("cannot execve('%s'): %s/n", strs[0], strerror(errno));}_exit(127);}if (pid < 0) {ERROR("failed to start '%s'/n", name_.c_str());pid_ = 0;return false;}...return true;
}
第二行就表示在Zygote启动过程中需要再内部创建一个名为“zygote”的socket,用于执行与System 进程进行通信的。
/** create_socket - creates a Unix domain socket in ANDROID_SOCKET_DIR* ("/dev/socket") as dictated in init.rc. This socket is inherited by the* daemon. We communicate the file descriptor's value via the environment* variable ANDROID_SOCKET_ENV_PREFIX<name> ("ANDROID_SOCKET_foo").*/
int create_socket(const char *name, int type, mode_t perm, uid_t uid,gid_t gid, const char *socketcon)
{struct sockaddr_un addr;int fd, ret, savederrno;char *filecon;if (socketcon) {if (setsockcreatecon(socketcon) == -1) {ERROR("setsockcreatecon(/"%s/") failed: %s/n", socketcon, strerror(errno));return -1;}}//调用内核的socket函数创建并返回对应的fd,PF_UNIX表示创建用于本地进程间通信的类型socket,比如说AMS 在请求与Zygote进程通信时创建新的应用进程前,需要先打开下面绑定的设备文件来创建一个连接到Zygote 进程的客户端socketfd = socket(PF_UNIX, type, 0);if (fd < 0) {ERROR("Failed to open socket '%s': %s/n", name, strerror(errno));return -1;}if (socketcon)setsockcreatecon(NULL);memset(&addr, 0 , sizeof(addr));addr.sun_family = AF_UNIX;//ANDROID_SOCKET_DIR 是一个宏 值为/dev/socketsnprintf(addr.sun_path, sizeof(addr.sun_path), ANDROID_SOCKET_DIR"/%s",name);ret = unlink(addr.sun_path);if (ret != 0 && errno != ENOENT) {ERROR("Failed to unlink old socket '%s': %s/n", name, strerror(errno));goto out_close;}filecon = NULL;if (sehandle) {ret = selabel_lookup(sehandle, &filecon, addr.sun_path, S_IFSOCK);if (ret == 0)setfscreatecon(filecon);}//bind 函数的作用就是将上面创建socket的返回的fd 与socket地址addr 绑定起来得到一个设备文件——dev/socket/zygoteret = bind(fd, (struct sockaddr *) &addr, sizeof (addr));savederrno = errno;setfscreatecon(NULL);freecon(filecon);if (ret) {ERROR("Failed to bind socket '%s': %s/n", name, strerror(savederrno));goto out_unlink;}//配置权限ret = lchown(addr.sun_path, uid, gid);if (ret) {ERROR("Failed to lchown socket '%s': %s/n", addr.sun_path, strerror(errno));goto out_unlink;}ret = fchmodat(AT_FDCWD, addr.sun_path, perm, AT_SYMLINK_NOFOLLOW);if (ret) {ERROR("Failed to fchmodat socket '%s': %s/n", addr.sun_path, strerror(errno));goto out_unlink;}INFO("Created socket '%s' with mode '%o', user '%d', group '%d'/n",addr.sun_path, perm, uid, gid);return fd;out_unlink:unlink(addr.sun_path);
out_close:close(fd);return -1;
}
socket 创建完毕之后还需要通过/system/core/init/service.cpp#PublishSocket 函数发布到系统中
void Service::PublishSocket(const std::string& name, int fd) const {std::string key = StringPrintf(ANDROID_SOCKET_ENV_PREFIX "%s", name.c_str());std::string val = StringPrintf("%d", fd);add_environment(key.c_str(), val.c_str());/* make sure we don't close-on-exec */fcntl(fd, F_SETFD, 0);
}
2、Zygote进程加载system/bin/app_process文件完成启动
从上面我们可以知道Zygote进程在启动过程中会加载system/bin/app_process
程序文件,app_process 除了能启动Zygote进程,还可以使用它来执行Java类中的某个main方法。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zo0nLkyn-1638888067002)(assets/image-20210715154633309.png)]
2.1、执行/frameworks/base/cmds/app_process/app_main.cpp # main 函数
app_process/app_main.cpp的main函数主要作用就是解析传入的启动参数,完成以下工作。
2.1.1、创建AppRuntime 对象
在app_main.cpp文件里定义了AppRuntime 类(AppRuntime继承自AndroidRuntime类),主要用于创建和初始化虚拟机以及启动Java线程,AppRuntime 在一个进程中有且只有一个实例对象并保存在全局变量gCurRuntime指针中
\frameworks\base\core\jni\AndroidRuntime.cpp
AndroidRuntime::AndroidRuntime(char* argBlockStart, const size_t argBlockLength) :mExitWithoutCleanup(false),mArgBlockStart(argBlockStart),mArgBlockLength(argBlockLength)
{SkGraphics::Init();//初始化skia 图形引擎// Pre-allocate enough space to hold a fair number of options. 预分配空间来存放传入的虚拟机参数mOptions.setCapacity(20);assert(gCurRuntime == NULL); // one per process 确保只能被初始化一次gCurRuntime = this;
}
2.1.2、给AppRuntime 配置参数和设置进程名称(即将当前进程名称设置为zygote)
2.1.3、解析启动参数
解析参数后niceName的值为zygote,startSystemServer的值为true,zygote为true。
2.1.4、AppRuntime #start函数执行ZygoteInit或者RuntimeInit类的main方法
如果启动的不是zygote进程则执行RuntimeInit类
int main(int argc, char* const argv[])
{...//创建runtime局部对象,Zygote 进程就是需要依靠它来继续完成启动的,如果启动的是Zygote进程那么ZygoteInit类中会进入等到Socket事件的循环,不会被销毁;而执行的是一个java类则会在执行完毕后被销毁。AppRuntime runtime(argv[0], computeArgBlockSize(argc, argv));argc--;argv++;// Parse runtime arguments. Stop at first unrecognized option.bool zygote = false;bool startSystemServer = false;bool application = false;String8 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);}...}Vector<String8> args;if (!className.isEmpty()) {args.add(application ? String8("application") : String8("tool"));runtime.setClassNameAndArgs(className, argc - i, argv + i);} else {// We're in zygote mode.maybeCreateDalvikCache();if (startSystemServer) {args.add(String8("start-system-server"));}String8 abiFlag("--abi-list=");args.add(abiFlag);// In zygote mode, pass all remaining arguments to the zygote main() method.for (; i < argc; ++i) {args.add(String8(argv[i]));}}if (!niceName.isEmpty()) {runtime.setArgv0(niceName.string());set_process_name(niceName.string());//设置进程名称}if (zygote) {runtime.start("com.android.internal.os.ZygoteInit", args, zygote);} else if (className) {runtime.start("com.android.internal.os.RuntimeInit", args, zygote);}...
}
2.2、frameworks/base/core/jni/AndroidRuntime.cpp # start
Zygote进程在运行Java代码前还需要初始化整个Java运行环境。
2.2.1、设置环境变量ANDROID_ROOT的值为/system
2.2.2、进行JNI 接口初始化并启动虚拟机并触发onVmCreated 函数回调
实际上是触发AppRuntime#onVmCreated 回调。
2.2.3、startReg注册系统JNI函数
通过调用register_jni_procs函数将全局数组gRegJNI的本地JNI函数注册到虚拟机中。
2.2.4、JNI调用 com.android.internal.os.ZygoteInit
的main函数
/** Start the Android runtime. This involves starting the virtual machine* and calling the "static void main(String[] args)" method in the class* named by "className".** Passes the main function two arguments, the class name and the specified* options string.*/
void AndroidRuntime::start(const char* className, const Vector<String8>& options, bool zygote)
{//标志着Android系统启动,因为以后的应用进程都是由zygotefork出来的,所以不会再执行start函数了,如果系统反复出现这个日志且id都是zygote则说明Zygote进程在不断地被重启。ALOGD(">>>>>> START %s uid %d <<<<<<\n",className != NULL ? className : "(unknown)", getuid());//定义变量直接初始化,其字面值是"start-system-server"的副本(不包括最后的那个空字符)static const String8 startSystemServer("start-system-server");/** 'startSystemServer == true' means runtime is obsolete and not run from* init.rc anymore, so we print out the boot start event here.*/for (size_t i = 0; i < options.size(); ++i) {if (options[i] == startSystemServer) {/* track our progress through the boot sequence */const int LOG_BOOT_PROGRESS_START = 3000;}}//环境变量ANDROID_ROOT的设置,即环境变量ANDROID_ROOT的值被设置成了/systemconst char* rootDir = getenv("ANDROID_ROOT");if (rootDir == NULL) {rootDir = "/system";if (!hasDir("/system")) {return;}setenv("ANDROID_ROOT", rootDir, 1);}/* start the virtual machine *///通过jni_invocation.Init(NULL)完成jni接口的初始化,并启动虚拟机的代码,后续再分析细节JniInvocation jni_invocation;jni_invocation.Init(NULL);JNIEnv* env;if (startVm(&mJavaVM, &env, zygote) != 0) {return;}onVmCreated(env);/** Register android functions.注册系统JNI函数*/if (startReg(env) < 0) {return;}/** We want to call main() with a String array with arguments in it.* At present we have two arguments, the class name and an option string.* Create an array to hold them.*/jclass stringClass;jobjectArray strArray;jstring classNameStr;stringClass = env->FindClass("java/lang/String");assert(stringClass != NULL);strArray = env->NewObjectArray(options.size() + 1, stringClass, NULL);assert(strArray != NULL);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);}/** Start VM. This thread becomes the main thread of the VM, and will* not return until the VM exits.*/char* slashClassName = toSlashClassName(className);jclass startClass = env->FindClass(slashClassName);if (startClass == NULL) {ALOGE("JavaVM unable to locate class '%s'/n", slashClassName);/* keep going */} else {jmethodID startMeth = env->GetStaticMethodID(startClass, "main","([Ljava/lang/String;)V");if (startMeth == NULL) {ALOGE("JavaVM unable to find main() in '%s'/n", className);/* keep going */} else {//jni调用传入的className 即 com.android.internal.os.ZygoteInit的main函数env->CallStaticVoidMethod(startClass, startMeth, strArray);#if 0if (env->ExceptionCheck())threadExitUncaughtException(env);
#endif}}free(slashClassName);ALOGD("Shutting down VM/n");if (mJavaVM->DetachCurrentThread() != JNI_OK)ALOGW("Warning: unable to detach main thread/n");if (mJavaVM->DestroyJavaVM() != 0)ALOGW("Warning: VM did not shut down cleanly/n");
}
2.3、JNI调用/frameworks/base/core/java/com/android/internal/os/ZygoteInit.java #main 方法
public static void main(String argv[]) {...boolean isFirstBooting = false;ZygoteHooks.startZygoteNoThreadCreation();try {boolean startSystemServer = false;String socketName = "zygote";...for (int i = 1; i < argv.length; i++) {if ("start-system-server".equals(argv[i])) {startSystemServer = true;} else if (argv[i].startsWith(ABI_LIST_ARG)) {abiList = argv[i].substring(ABI_LIST_ARG.length());} else if (argv[i].startsWith(SOCKET_NAME_ARG)) {socketName = argv[i].substring(SOCKET_NAME_ARG.length());} else {throw new RuntimeException("Unknown command line argument: " + argv[i]);}}registerZygoteSocket(socketName);if(!isFirstBooting && !isBox){if (startSystemServer) {startSystemServer(abiList, socketName);}}preload();...runSelectLoop(abiList);closeServerSocket();} catch (MethodAndArgsCaller caller) {caller.run();} catch (Throwable ex) {Log.e(TAG, "Zygote died with exception", ex);closeServerSocket();throw ex;}}
2.3.1、registerZygoteSocket(socketName) 创建一个Server端socket用于等待AMS 请求Zygote进程创建新的应用进程
private static void registerZygoteSocket(String socketName) {if (sServerSocket == null) {int fileDesc;final String fullSocketName = ANDROID_SOCKET_PREFIX + socketName;try {//通过系统环境变量得到fd的值并转换为fdString env = System.getenv(fullSocketName);fileDesc = Integer.parseInt(env);} catch (RuntimeException ex) {throw new RuntimeException(fullSocketName + " unset or invalid", ex);}try {FileDescriptor fd = new FileDescriptor();fd.setInt$(fileDesc);sServerSocket = new LocalServerSocket(fd);} catch (IOException ex) {throw new RuntimeException("Error binding to local socket '" + fileDesc + "'", ex);}}}
2.3.2、调用preload方法预加载系统资源
包括系统预加载类、Framework资源和OpenGL资源,当应用被fork创建出来后,应用进程内自然就包含了这些资源,无需应用再次自己加载。
2.3.3、startSystemServer(abiList, socketName) fork 创建System进程(SystemServer进程)
/*** Prepare the arguments and fork for the system server process.*/private static boolean startSystemServer(String abiList, String socketName)throws MethodAndArgsCaller, RuntimeException {/* Hardcoded command line to start the system server *///System进程启动的参数,可以看到System进程的用户ID和用户组ID都是1000,同时还有其他用户组的权限String args[] = {"--setuid=1000","--setgid=1000","--setgroups=1001,1002,1003,1004,1005,1006,1007,1008,1009,1010,1018,1021,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);/* Request to fork the system server process *///fork 创建System进程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) {if (hasSecondZygote(abiList)) {if(isBox){waitForSecondaryZygote(socketName);}Log.d(TAG,"--------call waitForSecondaryZygote,skip this---,abiList= "+abiList);}// 处理System进程启动事宜handleSystemServerProcess(parsedArgs);}return true;}
2.3.4、执行runSelectLoop(abiList) 方法等待处理AMS 请求Zygote 创建新应用进程
/*** Runs the zygote process's select loop. Accepts new connections as* they happen, and reads commands from connections one spawn-request's* worth at a time.** @throws MethodAndArgsCaller in a child process when a main() should* be executed.*/private static void runSelectLoop(String abiList) throws MethodAndArgsCaller {ArrayList<FileDescriptor> fds = new ArrayList<FileDescriptor>();ArrayList<ZygoteConnection> peers = new ArrayList<ZygoteConnection>();fds.add(sServerSocket.getFileDescriptor());peers.add(null);while (true) {StructPollfd[] pollFds = new StructPollfd[fds.size()];for (int i = 0; i < pollFds.length; ++i) {pollFds[i] = new StructPollfd();pollFds[i].fd = fds.get(i);pollFds[i].events = (short) POLLIN;}try {Os.poll(pollFds, -1);} catch (ErrnoException ex) {throw new RuntimeException("poll failed", ex);}for (int i = pollFds.length - 1; i >= 0; --i) {if ((pollFds[i].revents & POLLIN) == 0) {continue;}if (i == 0) {ZygoteConnection newPeer = acceptCommandPeer(abiList);peers.add(newPeer);fds.add(newPeer.getFileDesciptor());} else {boolean done = peers.get(i).runOnce();if (done) {peers.remove(i);fds.remove(i);}}}}}
简而言之,/frameworks/base/cmds/app_process/app_main.cpp # main 解析init.rc 脚本传入的参数,触发AppRuntime #start函数进而执行ZygoteInit#main 方法。
2.4、fork 创建SystemServer进程
Zygote进程在fork 创建SystemServer进程后返回到SystemServer进程继续进行相关System进程启动工作,至此Zygote 进程启动完毕,Zygote进程在初始化时就会创建Android虚拟机、注册JNI函数、预加载系统资源文件和Java 类。所有应用进程都共享和继承这些资源,Zygote进程启动工作完毕后,也会变成守护进程,负责处理启动App 应用程序的请求,预知后事如何请参见下文。
三、Zygote启动应用进程
预知后事如何请参见下文,不过得先启动AMS服务。
Android 进阶——系统启动之Android进程造物者Zygote 进程启动详解(六)相关推荐
- PackageManagerService启动详解(五)之Android包信息体和解析器(中)
PKMS启动详解(五)之Android包信息体和包解析器(中) Android PackageManagerService系列博客目录: PKMS启动详解系列博客概要 PKMS启动详解(一)之 ...
- Android 进阶——系统启动之SystemServer创建并启动Installer服务(八)
文章大纲 引言 一.Intsaller系统服务概述 二.com.android.server.SystemService概述 三.Intsaller系统服务的启动 1.com.android.serv ...
- 我的Android进阶之旅------Android利用Sensor(传感器)实现水平仪功能的小例
这里介绍的水平仪,指的是比较传统的气泡水平仪,在一个透明圆盘内充满液体,液体中留有一个气泡,当一端翘起时,该气泡就会浮向翘起的一端. 利用方向传感器返回的第一个参数,实现了一个指南针小应用. ...
- 我的Android进阶之旅------Android利用温度传感器实现带动画效果的电子温度计
要想实现带动画效果的电子温度计,需要以下几个知识点: 1.温度传感器相关知识. 2.ScaleAnimation动画相关知识,来进行水印刻度的缩放效果. 3.android:layout_weight ...
- android服务进阶,我的Android进阶之旅------Android服务的生命周期回调方法
先引用一段官网上的文字 ======================================================================================== ...
- Android中AMS工作原理,Android AMS启动详解
启动 在Android系统启动流程中中我们提到过,AMS是在system_service中启动的, //frameworks/base/services/java/corri/android/serv ...
- Android 10.0 Activity启动详解(二)
Android 10.0 Activity启动详解(一) 我们在上一篇博客中已经介绍了关于Activity的一些基础概念.这一篇博客我们以Android 10.0的代码来分析Activity的启动流程 ...
- Android Studio CPU profiler性能分析工具介绍和使用详解
Android Studio CPU profiler性能分析工具介绍和使用详解 CPU profiler介绍 Android Studio CPU 性能剖析器可实时检查应用的 CPU 使用率和线程活 ...
- Android四大组件之——Activity的生命周期(图文详解)
转载请在文章开头处注明本博客网址:http://www.cnblogs.com/JohnTsai 联系方式:JohnTsai.Work@gmail.com [Andro ...
最新文章
- 清华大学大数据研究中心2020年RONG奖学金答辩会成功举办
- C++ string线程不安全
- Kettle使用_28 转换之数值范围与字符串操作
- WebLogic中文博客
- MySQL分组函数的使用特点
- 5.12 QR分解的阻尼倒数法和正则化方法区别
- 2008r2配置 iis mysql php_Windows 2008 R2服务器配置文档iis+php+mysql
- 基于JAVA+Servlet+JSP+MYSQL的图书销售管理系统
- 微信小程序,对个人开发者开放之亲体验
- MySQL 5.5 使用 Event定期自动维护/执行Procedure
- 网络流媒体(七)———PTSP
- Ubuntu16.04编译roc-rk3328-cc
- 送货记账软件网络版怎么用
- 什么数据可以成为“数据资产”?数据资产化又该如何实现?
- win10系统c盘C:Users后面的用户名怎么更改
- 嵌入式笔试面试问题总结
- 链表之Reverselist
- Chrome 制作绿色便携版
- 如何使用ssh连接windows?
- Linux mysql数据库修改密码