引言

前面系列文章介绍了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 进程启动详解(六)相关推荐

  1. PackageManagerService启动详解(五)之Android包信息体和解析器(中)

        PKMS启动详解(五)之Android包信息体和包解析器(中) Android PackageManagerService系列博客目录: PKMS启动详解系列博客概要 PKMS启动详解(一)之 ...

  2. Android 进阶——系统启动之SystemServer创建并启动Installer服务(八)

    文章大纲 引言 一.Intsaller系统服务概述 二.com.android.server.SystemService概述 三.Intsaller系统服务的启动 1.com.android.serv ...

  3. 我的Android进阶之旅------Android利用Sensor(传感器)实现水平仪功能的小例

    这里介绍的水平仪,指的是比较传统的气泡水平仪,在一个透明圆盘内充满液体,液体中留有一个气泡,当一端翘起时,该气泡就会浮向翘起的一端.    利用方向传感器返回的第一个参数,实现了一个指南针小应用.   ...

  4. 我的Android进阶之旅------Android利用温度传感器实现带动画效果的电子温度计

    要想实现带动画效果的电子温度计,需要以下几个知识点: 1.温度传感器相关知识. 2.ScaleAnimation动画相关知识,来进行水印刻度的缩放效果. 3.android:layout_weight ...

  5. android服务进阶,我的Android进阶之旅------Android服务的生命周期回调方法

    先引用一段官网上的文字 ======================================================================================== ...

  6. Android中AMS工作原理,Android AMS启动详解

    启动 在Android系统启动流程中中我们提到过,AMS是在system_service中启动的, //frameworks/base/services/java/corri/android/serv ...

  7. Android 10.0 Activity启动详解(二)

    Android 10.0 Activity启动详解(一) 我们在上一篇博客中已经介绍了关于Activity的一些基础概念.这一篇博客我们以Android 10.0的代码来分析Activity的启动流程 ...

  8. Android Studio CPU profiler性能分析工具介绍和使用详解

    Android Studio CPU profiler性能分析工具介绍和使用详解 CPU profiler介绍 Android Studio CPU 性能剖析器可实时检查应用的 CPU 使用率和线程活 ...

  9. Android四大组件之——Activity的生命周期(图文详解)

        转载请在文章开头处注明本博客网址:http://www.cnblogs.com/JohnTsai       联系方式:JohnTsai.Work@gmail.com       [Andro ...

最新文章

  1. 清华大学大数据研究中心2020年RONG奖学金答辩会成功举办
  2. C++ string线程不安全
  3. Kettle使用_28 转换之数值范围与字符串操作
  4. WebLogic中文博客
  5. MySQL分组函数的使用特点
  6. 5.12 QR分解的阻尼倒数法和正则化方法区别
  7. 2008r2配置 iis mysql php_Windows 2008 R2服务器配置文档iis+php+mysql
  8. 基于JAVA+Servlet+JSP+MYSQL的图书销售管理系统
  9. 微信小程序,对个人开发者开放之亲体验
  10. MySQL 5.5 使用 Event定期自动维护/执行Procedure
  11. 网络流媒体(七)———PTSP
  12. Ubuntu16.04编译roc-rk3328-cc
  13. 送货记账软件网络版怎么用
  14. 什么数据可以成为“数据资产”?数据资产化又该如何实现?
  15. win10系统c盘C:Users后面的用户名怎么更改
  16. 嵌入式笔试面试问题总结
  17. 链表之Reverselist
  18. Chrome 制作绿色便携版
  19. 如何使用ssh连接windows?
  20. Linux mysql数据库修改密码

热门文章

  1. mysql优化总结(四)
  2. docker 安装 onlyoffice
  3. 数字孪生能源系统,打造低碳时代“透视”眼
  4. Windows 平台部署前后端分离项目
  5. 增长率相关速算法原理推导
  6. 5G NR LDPC码(1)—— LDPC码设计原理
  7. VMware:继续使用 Adob​​e Flash 管理您的 vCenter
  8. Android中Wi-Fi扫描、连接和信息(一)
  9. Word文件怎么打印
  10. 2018年第二季度DDoS攻击报告