Android的zygote SystemServer的启动,张口就来

复制代码

>adb shell ps | grep -E ‘init|926’

root 1 0 656 372 00000000 0805d546 S /init

root 926 1 685724 43832 ffffffff b76801e0 S zygote

system 1018 926 795924 62720 ffffffff b767fff6 S system_server

u0_a6 1241 926 717704 39252 ffffffff b76819eb S com.android.systemui

u0_a37 1325 926 698280 29024 ffffffff b76819eb S com.android.inputmethod.latin

radio 1349 926 711284 30116 ffffffff b76819eb S com.android.phone

u0_a7 1357 926 720792 41444 ffffffff b76819eb S com.android.launcher

u0_a5 1523 926 703576 26416 ffffffff b76819eb S com.android.providers.calendar

u0_a25 1672 926 693716 21328 ffffffff b76819eb S com.android.musicfx

u0_a17 2040 926 716888 33992 ffffffff b76819eb S android.process.acore

u0_a21 2436 926 716060 23904 ffffffff b76819eb S com.android.calendar

复制代码

init 是 zygote的父进程, 而system_server和其他所有的com.xxx结尾的应用程序都是从zygote fork 而来。本文将图过图表(辅予少量的代码)的方式来描述Zygote,system server 以及android application的启动过程。

废话少说,奉上两张大图开启我们的Zygote之旅。 第一张图是Zygote相关的所有类的结构图,另一张是Zygote启动的流程图。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

按图索骥,我们按照图一中的序号一一分解Zygote的启动过程。

1. App_Process

=================

  • APP_Process: 启动zygote和其他Java程序的应用程序, 代码位于frameworks/base/cmds/app_process/app_main.cpp, 在init.rc 里面指定。

#init.rc

service zygote /system/bin/app_process -Xzygote /system/bin --zygote --start-system-server

代码如下

复制代码

else if (strcmp(arg, “–zygote”) == 0) {

zygote = true;

niceName = “zygote”;

} else if (strcmp(arg, “–start-system-server”) == 0) {

startSystemServer = true;

} else if (strcmp(arg, “–application”) == 0) {

application = true;

} …

if (zygote) {

runtime.start(“com.android.internal.os.ZygoteInit”,

startSystemServer ? “start-system-server” : “”);

} else if (className) {

// Remainder of args get passed to startup class main()

runtime.mClassName = className;

runtime.start(“com.android.internal.os.RuntimeInit”,

application ? “application” : “tool”);

} else {

}

复制代码

可以看到,app_process 里面定义了三种应用程序类型:

  1. Zygote:  com.android.internal.os.ZygoteInit

  2. System Server, 不单独启动,而是由Zygote启动

  3. 其他指定类名的Java 程序,比如说常用的 am. /system/bin/am 其实是一个shell程序,它的真正实现是

exec app_process b a s e / b i n c o m . a n d r o i d . c o m m a n d s . a m . A m " base/bin com.android.commands.am.Am " base/bincom.android.commands.am.Am"@"

这些Java的应用都是通过 AppRuntime.start(className)开始的。从第一张大图可以看出,其实AppRuntime是AndroidRuntime的子类,它主要实现了几个回调函数,而start()方法是实现在AndroidRuntime这个方法类里。什么是AnroidRuntime? 我们接下来马上开始。

需要注意的是Zygote并不是Init启动的第一个程序,从PID可以看出来,在它之前,一下Native实现的重要System Daemon (后台进程)可能先起来,比如 ServiceManager (service的DNS服务).

2. AndroidRuntime

==================

首先,什么是Runtime ?看看Wiki给的几种解释:

我倾向这里指的是后者,看看更进一步的解释:

In computer programming, a runtime library is the API used by a compiler to invoke some of the behaviors of a runtime system. The runtime system implements the execution model and other fundamental behaviors of a programming language. The compiler inserts calls to the runtime library into the executable binary. During execution (run time")) of that computer program, execution of those calls to the runtime library cause communication between the application and theruntime system. This often includes functions for input and output, or for memory management.

归纳起来的意思就是,Runtime 是支撑程序运行的基础库,它是与语言绑定在一起的。比如:

  • C Runtime:就是C standard lib, 也就是我们常说的libc。(有意思的是, Wiki会自动将“C runtime" 重定向到 “C Standard Library”).

  • Java Runtime: 同样,Wiki将其重定向到” Java Virtual Machine", 这里当然包括Java 的支撑类库(.jar).

  • AndroidRuntime:  显而易见,就是为Android应用运行所需的运行时环境。这个环境包括以下东东:

  • Dalvik VM: Android的Java VM, 解释运行Dex格式Java程序。每个进程运行一个虚拟机(什么叫运行虚拟机?说白了,就是一些C代码,不停的去解释Dex格式的二进制码(Bytecode),把它们转成机器码(Machine code),然后执行,当然,现在大多数的Java 虚拟机都支持JIT,也就是说,bytecode可能在运行前就已经被转换成机器码,从而大大提高了性能。过去一个普遍的认识是Java 程序比C,C++等静态编译的语言慢,但随着JIT的介入和发展,这个已经完全是过去时了,JIT的动态性运行允许虚拟机根据运行时环境,优化机器码的生成,在某些情况下,Java甚至可以比C/C++跑得更快,同时又兼具平台无关的特性,这也是为什么Java如今如此流行的原因之一吧)。

  • Android的Java 类库, 大部分来自于 Apache Hamony, 开源的Java API 实现,如 java.lang, java.util, java.net. 但去除了AWT, Swing 等部件。

  • JNI: C和Java互调的接口。

  • Libc: Android也有很多C代码,自然少不了libc,注意的是,Android的libc叫 bionic C.

OK, 那就首先看看AndroidRuntime是怎么搭建起来的吧

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

上图给出了Zygote启动的大概流程,入口是AndroidRuntime.start(), 根据传入参数的不同可以有两种启动方式,一个是 “com.android.internal.os.RuntimeInit”, 另一个是 ”com.android.internal.os.ZygoteInit", 对应RuntimeInit 和 ZygoteInit 两个类, 图中用绿色和粉红色分别表示。这两个类的主要区别在于Java端,可以明显看出,ZygoteInit 相比 RuntimeInit 多做了很多事情,比如说 “preload", “gc” 等等。但是在Native端,他们都做了相同的事, startVM() 和 startReg(), 让我们先从这里开始吧。

从类图中看出,JavaVM 和 JNIEnv 是连结 AndroidRuntim 和 Dalvik VM 之间的唯一两个关卡,它隐藏了Dalvik 里面的实现细节,事实上,他就是两个函数指针结构体,给本地代码提供访问Java资源的接口。JNIEnv则相对于线程,通过JNIEnv的指针最终可以对应到Dalvik VM 内部的Thread 结构体,所有的调用就在这个结构体上下文完成。而JavaVM 对应的是DVMGlobal, 一个进程唯一的结构体,他内部维护了一个线程队列threadList,存放每个Thread 结构体对象, 同时还有各类状态的对象列表,及存放GC的结构体,等等。本文无法深入,只作简单介绍。

  • JavaVM 和 JNIENV

  • 复制代码

struct _JavaVM {

const struct JNIInvokeInterface* functions; //C的函数指针

#if defined(__cplusplus) …

jint GetEnv(void** env, jint version)

{ return functions->GetEnv(this, env, version); }

#endif /*__cplusplus*/

};

struct JNIInvokeInterface {

void* reserved0;

jint (*DestroyJavaVM)(JavaVM*);

jint (*AttachCurrentThread)(JavaVM*, JNIEnv**, void*);

jint (*DetachCurrentThread)(JavaVM*);

jint (*GetEnv)(JavaVM*, void**, jint);

jint (*AttachCurrentThreadAsDaemon)(JavaVM*, JNIEnv**, void*);

};

复制代码

里面最常见的接口就是GetEnv(), 它返回一个JNIEnv对象,对应于每个DVM线程。JNIEnv的定义很长,有兴趣的同学可以到Jni.h 里面找,这里我们只看看这个对象是如何获static jint GetEnv(JavaVM* vm, void** env, jint version) {

  • 复制代码

Thread* self = dvmThreadSelf(); //获取当前线程对象。

if (version < JNI_VERSION_1_1 || version > JNI_VERSION_1_6) {

return JNI_EVERSION;

} //检查版本号,Android 4.3对应 1.6

… *env = (void*) dvmGetThreadJNIEnv(self); //很简单,见最下面一行

dvmChangeStatus(self, THREAD_NATIVE);

return (*env != NULL) ? JNI_OK : JNI_EDETACHED;

}

INLINE JNIEnv* dvmGetThreadJNIEnv(Thread* self) { return self->jniEnv; }

复制代码

很简单嘛,原来就是从当前所在线程的结构体对象里读取即可,这里面好像没JavaVM什么事吗,为什么当参数传入?不知道,也许Google留作将来扩展?但不管怎么,要想调用GetEnv,还是需要JavaVM。将来要写JNI代码的同学可以参考以下的代码看如何获取JavaVM和JniENV.

复制代码

JNIEnv* AndroidRuntime::getJNIEnv()

{

JNIEnv* env;

JavaVM* vm = AndroidRuntime::getJavaVM();

assert(vm != NULL);

if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK)

return NULL;

return env;

}

复制代码

到这里,我们知道JavaVM 和 JNIEnv 是本地(C/C++)代码用来与Java代码进行互调的,那在Java那端一定就是Java虚拟机以及对应的Java应用了。Java虚拟机到底是什么东东,它是如何创建的?答案从AndroidRuntime::startVM() 函数开始。startVM

  • startVM()

  • 复制代码

int AndroidRuntime::startVm(JavaVM** pJavaVM, JNIEnv** pEnv)

{

property_get(“dalvik.vm.checkjni”, propBuf, “”);

… initArgs.version = JNI_VERSION_1_4;

… //创建VM并返回JavaVM 和 JniEnv,pEnv对应于当前线程。

if (JNI_CreateJavaVM(pJavaVM, pEnv, &initArgs) < 0) {

ALOGE(“JNI_CreateJavaVM failed\n”);

goto bail;

}

}

jint JNI_CreateJavaVM(JavaVM** p_vm, JNIEnv** p_env, void* vm_args) {

memset(&gDvm, 0, sizeof(gDvm)); /* 这里才是真正的VM结构体*/

JavaVMExt* pVM = (JavaVMExt*) calloc(1, sizeof(JavaVMExt));

pVM->funcTable = &gInvokeInterface; //初始化函数指针

pVM->envList = NULL;

… gDvmJni.jniVm = (JavaVM*) pVM; //native代码接触的JavaVM原来只是JniVm而已

JNIEnvExt* pEnv = (JNIEnvExt*) dvmCreateJNIEnv(NULL); //创建JNIEnv,因为接下来的虚拟机初始化需要访问C/C++实现

/* 开始初始化. */

gDvm.initializing = true;

std::string status =

dvmStartup(argc, argv.get(), args->ignoreUnrecognized, (JNIEnv*)pEnv);

gDvm.initializing = false;

dvmChangeStatus(NULL, THREAD_NATIVE);

*p_env = (JNIEnv*) pEnv;

*p_vm = (JavaVM*) pVM;

return JNI_OK;

复制代码

复制代码

std::string dvmStartup(int argc, const char* const argv[],

bool ignoreUnrecognized, JNIEnv* pEnv)

{

/*

* 检查输入并准备初始化参数

*/

int cc = processOptions(argc, argv, ignoreUnrecognized);

/* 真正初始化开始,初始化各个内部模块,并创建一系列线程*/

if (!dvmAllocTrackerStartup()) {

return “dvmAllocTrackerStartup failed”;

}

if (!dvmGcStartup()) {

return “dvmGcStartup failed”;

}

if (!dvmThreadStartup()) {

return “dvmThreadStartup failed”;

}

if (!dvmInlineNativeStartup()) {

return “dvmInlineNativeStartup”;

}

if (!dvmRegisterMapStartup()) {

return “dvmRegisterMapStartup failed”;

}

if (!dvmInstanceofStartup()) {

return “dvmInstanceofStartup failed”;

}

if (!dvmClassStartup()) {

return “dvmClassStartup failed”;

}

if (!dvmNativeStartup()) {

return “dvmNativeStartup failed”;

}

if (!dvmInternalNativeStartup()) {

return “dvmInternalNativeStartup failed”;

}

if (!dvmJniStartup()) {

return “dvmJniStartup failed”;

}

if (!dvmProfilingStartup()) {

return “dvmProfilingStartup failed”;

}

if (!dvmInitClass(gDvm.classJavaLangClass)) {

return “couldn’t initialized java.lang.Class”;

}

if (!registerSystemNatives(pEnv)) {

return “couldn’t register system natives”;

}

if (!dvmCreateStockExceptions()) {

return “dvmCreateStockExceptions failed”;

}

if (!dvmPrepMainThread()) {

return “dvmPrepMainThread failed”;

}

if (dvmReferenceTableEntries(&dvmThreadSelf()->internalLocalRefTable) != 0)

{

ALOGW(“Warning: tracked references remain post-initialization”);

dvmDumpReferenceTable(&dvmThreadSelf()->internalLocalRefTable, “MAIN”);

}

if (!dvmDebuggerStartup()) {

return “dvmDebuggerStartup failed”;

}

if (!dvmGcStartupClasses()) {

return “dvmGcStartupClasses failed”;

}

if (gDvm.zygote) {

if (!initZygote()) {

return “initZygote failed”;

}

} else {

if (!dvmInitAfterZygote()) {

return “dvmInitAfterZygote failed”;

}

}

return “”;

}

复制代码

Java虚拟机的启动有太多的细节在这里无法展开,这里我们只需要知道它做了以下一些事情:

1.  从property读取一系列启动参数。

2.  创建和初始化结构体全局对象(每个进程)gDVM,及对应与JavaVM和JNIEnv的内部结构体 JavaVMExt, JNIEnvExt.

3.  初始化java虚拟机,并创建虚拟机线程。“ps -t”,你可以发现每个Android应用都有以下几个线程

复制代码

u0_a46 1284 1281 714900 57896 20 0 0 0 fg ffffffff 00000000 S GC //垃圾回收

u0_a46 1285 1281 714900 57896 20 0 0 0 fg ffffffff 00000000 S Signal Catcher

u0_a46 1286 1281 714900 57896 20 0 0 0 fg ffffffff 00000000 S JDWP //Java 调试

u0_a46 1287 1281 714900 57896 20 0 0 0 fg ffffffff 00000000 S Compiler //JIT

u0_a46 1288 1281 714900 57896 20 0 0 0 fg ffffffff 00000000 S ReferenceQueueD

u0_a46 1289 1281 714900 57896 20 0 0 0 fg ffffffff 00000000 S FinalizerDaemon //Finalizer监护

u0_a46 1290 1281 714900 57896 20 0 0 0 fg ffffffff 00000000 S FinalizerWatchd //

复制代码

4. 注册系统的JNI,Java程序通过这些JNI接口来访问底层的资源。

loadJniLibrary(“javacore”);

loadJniLibrary(“nativehelper”);

5. 为Zygote的启动做最后的准备,包括设置SID/UID, 以及mount 文件系统。

6. 返回JavaVM 给Native代码,这样它就可以向上访问Java的接口。

除了系统的JNI接口(”javacore", “nativehelper”), android framework 还有大量的Native实现,Android将所有这些接口一次性的通过start_reg()来完成,

  • startReg()

复制代码

int AndroidRuntime::startReg(JNIEnv* env){

androidSetCreateThreadFunc((android_create_thread_fn) javaCreateThreadEtc); //创建JVM能访问的线程必须通过特定的接口。

env->PushLocalFrame(200);

if (register_jni_procs(gRegJNI, NELEM(gRegJNI), env) < 0) {

env->PopLocalFrame(NULL);

return -1;

}

env->PopLocalFrame(NULL);

return 0;

}

复制代码

Android native层有两种Thread的创建方式:

复制代码

#threads.cpp

status_t Thread::run(const char* name, int32_t priority, size_t stack)

{

if (mCanCallJava) {

res = createThreadEtc(_threadLoop, this, name, priority, stack, &mThread);

} else {

res = androidCreateRawThreadEtc(_threadLoop,this, name, priority, stack, &mThread);

}

}

复制代码

它们的区别在是是否能够调用Java端函数,普通的thread就是对pthread_create的简单封装。

复制代码

int androidCreateRawThreadEtc(android_thread_func_t entryFunction,

void *userData,

const char* threadName,

int32_t threadPriority,

size_t threadStackSize,

android_thread_id_t *threadId)

{

int result = pthread_create(&thread, &attr,android_pthread_entry)entryFunction, userData);

}

复制代码

而能够访问Java端的thread需要跟JVM进行绑定,下面是具体的实现函数

复制代码

#AndroidRuntime.cpp

int AndroidRuntime::javaCreateThreadEtc(

android_thread_func_t entryFunction,

void* userData,

const char* threadName,

int32_t threadPriority,

size_t threadStackSize,

android_thread_id_t* threadId)

{

args[0] = (void*) entryFunction; //将entryFunc 暂存在args[0]

args[1] = userData;

args[2] = (void*) strdup(threadName);

result =AndroidCreateRawThreadEtc(AndroidRuntime::javaThreadShell, args, threadName, threadPriority, threadStackSize, threadId); //entryFunc变成javaThreadShell.

return result;

}

复制代码

复制代码

int AndroidRuntime::javaThreadShell(void* args) {

void* start = ((void**)args)[0];

void* userData = ((void **)args)[1];

char* name = (char*) ((void **)args)[2]; // we own this storage

JNIEnv* env;

/* 跟 VM 绑定 */

if (javaAttachThread(name, &env) != JNI_OK)

return -1;

/* 运行真正的’entryFunc’ */

result = (*(android_thread_func_t)start)(userData);

/* unhook us */

javaDetachThread();

return result;

}

复制代码

attachVM() 到底做什么事情? 篇幅有限无法展开,这里只需要知道这么几点:

    • 一个进程里有一个Java 虚拟机,Java 虚拟机内部有很多线程,如上面列到的 GC, FinalizeDaemon, 以及用户创建的线程等等.
  • 每个Java线程都维护一个JNIEnvExt对象,里面存放一个指向DVM 内部Thread对象的指针,也就是说,所有从native到Java端的调用,都会引用到这个对象。

  • 所有通过JVM创建的线程都会在VM内部记录在案,但是当前,我们还没有进入Java世界,本地创建的线程VM自然就不知道,因此我们需要通过attach来通知VM来创建相应的内部数据结构。

看看下面代码,你就知道,其实Attach()做的一件重要的事情就是 创建thread和JNIEnvExt.

复制代码

bool dvmAttachCurrentThread(const JavaVMAttachArgs* pArgs, bool isDaemon)

{

Thread* self = NULL;

self = allocThread(gDvm.stackSize);

self->jniEnv = dvmCreateJNIEnv(self);

gDvm.threadList->next = self;

threadObj = dvmAllocObject(gDvm.classJavaLangThread, ALLOC_DEFAULT);

vmThreadObj = dvmAllocObject(gDvm.classJavaLangVMThread, ALLOC_DEFAULT);

self->threadObj = threadObj;

}

复制代码

完了,就开始注册本地的JNI接口函数了- register_jni_procs(), 这个函数其实就是对一个全局数组gRegJni[] 进行遍历调用,这个数组展开可以得到以下的结果

static const RegJNIRec gRegJNI[] = {

{register_android_debug_JNITest},

{register_com_android_internal_os_RuntimeInit}.

}

每个 register_xxx是一个函数指针

int jniRegisterNativeMethods(

C_JNIEnv* env,

const char* className,

const JNINativeMethod* gMethods,

int numMethods);

RegisterNativeMethods 在VM内部到底发生了什么? 同样,这里只需要知道以下几点即可:

gRegJni[]

好了,经过了千辛万苦,Android的运行时环境都已经准备就绪了,让我们再回顾一下AndroidRuntime的初始化都做了哪些工作,

  1. 创建了Dalvik VM.

  2. 获取Native 访问Java的两个接口对象,JavaVM 和 JNIENV。

  3. 注册了一批 (见gRegJni[]) native接口给VM。

这些操作都是相对耗时的工作,如果每个进程都做同样的工作势必会影响到启动速度,这也是为什么我们需要通过Zygote来创建Android 应用,因为通过Linux fork 的 copy_on_write的机制,子进程可以将这些初始化好的内存空间直接映射到自己的进程空间里,不在需要做重复的工作,从而提高了应用启动的速度。

可以是,Android系统只需要基本的运行时环境就够了吗? 答案显然是No。AndriodRuntime 只是提供了语言层面的基础支持,在一个多任务,多用户的图形操作系统上快速的孵化和运行应用程序,我们需要更多。这就是Zygote,这就是为什么在图2中,ZygoteInit会比RuntimeInit做更多的事情。那接下来,让我们真正进入Zygote的世界。

3. ZygoteInit

==============

当VM准备就绪,就可以运行Java代码了,系统也将在此第一次进入Java世界,还记得app_main.cpp里面调到的 Runtime.start()的参数吗, 那就是我们要运行的Java类。Android支持两个类做为起点,一个是‘com.android.internal.os.ZygoteInit’, 另一个是’com.android.internal.os.RuntimeInit’。

此外Runtime_Init 类里还定义了一个ZygoteInit() 静态方法。它在Zygote 创建一个新的应用进程的时候被创建,它和RuntimeInit 类的main() 函数做了以下相同的事情:

  • redirectLogStreams(): 将System.out 和 System.err 输出重定向到Android 的Log系统(定义在 android.util.Log).

  • commonInit(): 初始化了一下系统属性,其中最重要的一点就是设置了一个未捕捉异常的handler,当代码有任何未知异常,就会执行它,调试过Android代码的同学经常看到的"*** FATAL EXCEPTION IN SYSTEM PROCESS" 打印就出自这里:

复制代码

Runtime_init.java

Thread.setDefaultUncaughtExceptionHandler(new UncaughtHandler());

private static class UncaughtHandler implements Thread.UncaughtExceptionHandler {

public void uncaughtException(Thread t, Throwable e) {

try {

// Don’t re-enter – avoid infinite loops if crash-reporting crashes.

if (mCrashing) return;

mCrashing = true;

if (mApplicationObject == null) {

Slog.e(TAG, "*** FATAL EXCEPTION IN SYSTEM PROCESS: " + t.getName(), e);

} else {

Slog.e(TAG, "FATAL EXCEPTION: " + t.getName(), e);

}

ActivityManagerNative.getDefault().handleApplicationCrash(

mApplicationObject, new ApplicationErrorReport.CrashInfo(e));

} catch (Throwable t2) {

} finally {

Process.killProcess(Process.myPid());

System.exit(10);

}

}

}

复制代码

接下来,RuntimeInit::main() 和 RuntimeInit::ZygoteInit() 分别调用里nativeFinishInit() 和 nativeZygoteInit(), 由此开始分道扬镳,RuntimeInit 的nativeFinishInit() 最终会调用到 app_main.cpp 里的 onStarted() 函数,里面调用Java类的main() 函数,然后结束进程退出。

复制代码

virtual void onStarted()

{

sp proc = ProcessState::self();

proc->startThreadPool();

AndroidRuntime* ar = AndroidRuntime::getRuntime();

ar->callMain(mClassName, mClass, mArgC, mArgV);

IPCThreadState::self()->stopProcess();

}

复制代码

而 RuntimeInit::ZygoteInit() 则会调到 app_main.cpp 的 onZygoteInit()

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
img
img
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新

如果你觉得这些内容对你有帮助,可以添加V获取:vip204888 (备注Android)
img

写在最后

最后我想说:对于程序员来说,要学习的知识内容、技术有太多太多,要想不被环境淘汰就只有不断提升自己,从来都是我们去适应环境,而不是环境来适应我们!

这里附上上述的技术体系图相关的几十套腾讯、头条、阿里、美团等公司2021年的面试题,把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节,由于篇幅有限,这里以图片的形式给大家展示一部分。

相信它会给大家带来很多收获:

当程序员容易,当一个优秀的程序员是需要不断学习的,从初级程序员到高级程序员,从初级架构师到资深架构师,或者走向管理,从技术经理到技术总监,每个阶段都需要掌握不同的能力。早早确定自己的职业方向,才能在工作和能力提升中甩开同龄人。

过华为、OPPO等大厂,18年进入阿里一直到现在。**

深知大多数Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
[外链图片转存中…(img-11hIBl4C-1711633970672)]
[外链图片转存中…(img-TaTx8Ano-1711633970673)]
[外链图片转存中…(img-q62NAhAc-1711633970673)]
[外链图片转存中…(img-FlaYOH8r-1711633970674)]
[外链图片转存中…(img-bMJbRMgC-1711633970674)]
[外链图片转存中…(img-6YGU07aX-1711633970675)]
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新

如果你觉得这些内容对你有帮助,可以添加V获取:vip204888 (备注Android)
[外链图片转存中…(img-mCsJBiwx-1711633970675)]

写在最后

最后我想说:对于程序员来说,要学习的知识内容、技术有太多太多,要想不被环境淘汰就只有不断提升自己,从来都是我们去适应环境,而不是环境来适应我们!

这里附上上述的技术体系图相关的几十套腾讯、头条、阿里、美团等公司2021年的面试题,把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节,由于篇幅有限,这里以图片的形式给大家展示一部分。

相信它会给大家带来很多收获:

[外链图片转存中…(img-FfSZIx7c-1711633970675)]

[外链图片转存中…(img-UF9lmUL6-1711633970676)]

当程序员容易,当一个优秀的程序员是需要不断学习的,从初级程序员到高级程序员,从初级架构师到资深架构师,或者走向管理,从技术经理到技术总监,每个阶段都需要掌握不同的能力。早早确定自己的职业方向,才能在工作和能力提升中甩开同龄人。

本文已被CODING开源项目:《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》收录

  • 14
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值