热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

类的加载(上)–_objc_init&read_images

前言上一篇文章主要分析dyld的整个流程以及dyld与_objc_init之间的交互,

前言

上一篇文章主要分析dyld的整个流程以及dyld_objc_init之间的交互,_objc_initdyld注册了回调函数,所以_objc_initdyld中尤为关键,那么我们今天继续往下探讨。

准备工作

  • dyld-852
  • objc4-818.2

_objc_init

void _objc_init(void) { static bool initialized = false; if (initialized) return; initialized = true; // fixme defer initialization until an objc-using image is found? //环境的初始化 environ_init(); //析构函数的绑定 tls_init(); //全局静态c++函数调用,这里只是调用objc自己的。在dyld调用之前,相当于objc的c++构造函数是自己调用的,不是dyld调用的。 static_init(); //runtime相关的两张表的初始化 runtime_init(); //初始化 libobjc 的异常处理系统。 exception_init(); #if __OBJC2__ // //缓存初始化 cache_t::init(); #endif //启动回调机制 _imp_implementationWithBlock_init(); //map_images:管理文件中和动态库中所有的符号 (class Protocol selector category) //load_images:加载执行load方法 //unmap_image:释放类相关资源。 _dyld_objc_notify_register(&map_images, load_images, unmap_image); #if __OBJC2__ didCallDyldNotifyRegister = true; #endif }

  • environ_init:读取影响运行时的环境变量,如果需要还可以打印环境变量帮助 export OBJC_HELP = 1
    tls_init:关于线程key的绑定,比如每个线程数据的析构函数
  • static_init:运行C++静态构造函数。在dyld调用我们的静态构造函数之前,lib会调用_objc_init先调用自己的C++构造函数。
  • runtime_initruntime运行时环境初始化,里面主要是unattachedCategoriesallocatedClasses两张表。
  • exception_init:初始化libobjc库的异常处理系统。
  • cache_t::init缓存条件初始化。
  • _imp_implementationWithBlock_init启动回调机制。通常不会做什么,因为所有的初始化都是惰性的,但是对于某些进程,会迫不及待的加载trampolines dylib
  • _dyld_objc_notify_register: 向dyld的注册回调。

environ_init

void environ_init(void) { …… if (PrintHelp || PrintOptions) { …… for (size_t i = 0; i env, opt->help); if (PrintOptions && *opt->var) _objc_inform("%s is set", opt->env); } } }

由源码可以看出打印的条件是由PrintHelp || PrintOptions进行判断的。

日志输出
跳过PrintHelp || PrintOptions判断,去掉这个判断修改最后的代码成如下那样:

for (size_t i = 0; i env, opt->help); _objc_inform("%s is set", opt->env); }

运行结果:

类的加载(上)--  _objc_init&read_images
运行结果

如果没有objc源码的话,那么我们可以通过终端输出日志。

类的加载(上)--  _objc_init&read_images
终端输出环境

使用命令:

export OBJC_HELP=1

可以看出终端输出环境日志还是比较方便的,那么我们还可以通过xcode进行配置环境变量,操作如下:
Xcode中环境变量配置的位置:选中运行的target–> Edit scheme –>Run–> Arguments –> Environment Variables
在环境变量中有两个配置:OBJC_DISABLE_NONPOINTER_ISAOBJC_PRINT_LOAD_METHODS

类的加载(上)--  _objc_init&read_images
在Xcode中添加环境变量

OBJC_DISABLE_NONPOINTER_ISA
OBJC_DISABLE_NONPOINTER_ISA就是判断是否是优化的指针。YES表示纯指针NO表示优化后的指针就是nonpointer isa

  • Xcode环境变量中不选择OBJC_DISABLE_NONPOINTER_ISA,通过lldb调试如下:
    类的加载(上)--  _objc_init&read_images
    优化过的isa

    isa的最低位是1,表示是优化后的isa,而且高位上也有数据。

  • Xcode环境变量中选择上OBJC_DISABLE_NONPOINTER_ISA,通过lldb调试如下:
    类的加载(上)--  _objc_init&read_images
    纯isa

    isa的最低位是0,表示是纯isa,而且高位上没有数据。

OBJC_PRINT_LOAD_METHODS
环境变量OBJC_PRINT_LOAD_METHODS打印出程序中所有的load方法,在自定义类中添加load方法,配置环境变量OBJC_PRINT_LOAD_METHODS = YES

类的加载(上)--  _objc_init&read_images
打印项目+load方法

XJLPerson类和LGPerson类中添加+load方法,已经在上图打印了出来。通过这样子可以检测哪位写的代码+load方法比较多,+load方法太多会影响程序的启动速度

tls_init

tls_init方法是关于线程key的绑定,比如每个线程数据的析构函数。

void tls_init(void) { #if SUPPORT_DIRECT_THREAD_KEYS //创建线程的缓存池 pthread_key_init_np(TLS_DIRECT_KEY, &_objc_pthread_destroyspecific); #else //析构函数 _objc_pthread_key = tls_create(&_objc_pthread_destroyspecific); #endif }

static_init

运行C++静态构造函数,在dyld调用静态函数之前,libc会调用_objc_init方法先调用自己的C++构造函数,也就是说libobjc会调用自己的全局的C++函数,而且在dyld调用之前!

类的加载(上)--  _objc_init&read_images
验证static_init

调试结果表明确实是libobjc系统库自己调用了内部的C++函数。

runtime_init

runtime运行时环境初始化,里面主要是unattachedCategoriesallocatedClasses两张表,实现代码如下:

void runtime_init(void) { //对两张表进行初始化 objc::unattachedCategories.init(32); objc::allocatedClasses.init(); }

exception_init

初始化libobjc库的异常处理系统,主要是注册异常回调。就像objcdyld中注册回调函数差不多,exception_init回调异常提供开发人员做异常处理

void exception_init(void) { old_terminate = std::set_terminate(&_objc_terminate); }

当程序出现崩溃现象或者用了不符合规则代码的时候,就会进入set_terminate方法,通过_objc_terminate方法来发出异常信息。

** _objc_terminate**

static void (*old_terminate)(void) = nil; static void _objc_terminate(void) { if (PrintExceptions) { _objc_inform("EXCEPTIONS: terminating"); } if (! __cxa_current_exception_type()) { // No current exception. (*old_terminate)(); } else { // There is a current exception. Check if it's an objc exception. @try { __cxa_rethrow(); } @catch (id e) { // It's an objc object. Call Foundation's handler, if any. (*uncaught_handler)((id)e); (*old_terminate)(); } @catch (...) { // It's not an objc object. Continue to C++ terminate. (*old_terminate)(); } } }

通过以上源码的分析,发现没有查询最后报异常处理也会走到_objc_terminate方法,在_objc_terminate方法发现了(*uncaught_handler)((id)e)它会把异常抛出去,全局搜索uncaught_handler

uncaught_handler

objc_uncaught_exception_handler objc_setUncaughtExceptionHandler(objc_uncaught_exception_handler fn) { objc_uncaught_exception_handler result = uncaught_handler; uncaught_handler = fn; return result; }

通过uncaught_handler = fn 可以知道我们可以自己传一个函数的句柄, fn可以是自己定义的函数,然后回调时,可以自己处理异常的信息。

cache_t::init

此方法是缓存条件的初始化。

void cache_t::init() { #if HAVE_TASK_RESTARTABLE_RANGES mach_msg_type_number_t count = 0; kern_return_t kr; while (objc_restartableRanges[count].location) { count++; } //开启缓存 kr = task_restartable_ranges_register(mach_task_self(), objc_restartableRanges, count); if (kr == KERN_SUCCESS) return; _objc_fatal("task_restartable_ranges_register failed (result 0x%x: %s)", kr, mach_error_string(kr)); #endif // HAVE_TASK_RESTARTABLE_RANGES }

_imp_implementationWithBlock_init

启动回调机制。通常不会做什么,因为所有的初始化都是惰性的,但是对于某些进程,会迫不及待的加载 trampolines dylib

void _imp_implementationWithBlock_init(void) { #if TARGET_OS_OSX // Eagerly load libobjc-trampolines.dylib in certain processes. Some // programs (most notably QtWebEngineProcess used by older versions of // embedded Chromium) enable a highly restrictive sandbox profile which // blocks access to that dylib. If anything calls // imp_implementationWithBlock (as AppKit has started doing) then we'll // crash trying to load it. Loading it here sets it up before the sandbox // profile is enabled and blocks it. // // This fixes EA Origin (rdar://problem/50813789) // and Steam (rdar://problem/55286131) if (__progname && (strcmp(__progname, "QtWebEngineProcess") == 0 || strcmp(__progname, "Steam Helper") == 0)) { Trampolines.Initialize(); } #endif }

_dyld_objc_notify_register

dyld注册回调,_dyld_objc_notify_register仅供objc运行时调用并没有方法的实现,其方法的实现在dyld源码中。

// _dyld_objc_notify_register void _dyld_objc_notify_register(_dyld_objc_notify_mapped mapped, _dyld_objc_notify_init init, _dyld_objc_notify_unmapped unmapped) { //主要的方法是registerObjCNotifiers dyld::registerObjCNotifiers(mapped, init, unmapped); }

registerObjCNotifiers

// _dyld_objc_notify_init void registerObjCNotifiers(_dyld_objc_notify_mapped mapped, _dyld_objc_notify_init init, _dyld_objc_notify_unmapped unmapped) { // record functions to call sNotifyObjCMapped = mapped; sNotifyObjCInit = init; sNotifyObjCUnmapped = unmapped; // call 'mapped' function with all images mapped so far try { notifyBatchPartial(dyld_image_state_bound, true, NULL, false, true); } catch (const char* msg) { // ignore request to abort during registration } // call 'init' function on all images already init'ed (below libSystem) for (std::vector::iterator it=sAllImages.begin(); it != sAllImages.end(); it++) { ImageLoader* image = *it; if ( (image->getState() == dyld_image_state_initialized) && image->notifyObjC() ) { dyld3::ScopedTimer timer(DBG_DYLD_TIMING_OBJC_INIT, (uint64_t)image->machHeader(), 0, 0); (*sNotifyObjCInit)(image->getRealPath(), image->machHeader()); } } }

_dyld_objc_notify_register注册了map_imagesload_imagesunmap_image的回调。实现在dyld中,三个参数分别是:

  • map_images:管理文件中和动态库中所有的符号 (classProtocolselectorcategory)。(dyldimage加载到内存中会调用该函数)
  • load_images:加载执行load方法(dyld初始化所有的image文件会调用)。
  • unmap_image:释放类相关资源。

疑问:
在dyld加载过程中map_imagesload_images的调用没有区别,但是在这里传递的参数map_images&(取址)操作。map_images是指针拷贝,load_images值传递map_images需要同步变化,否则有可能发生错乱。而load_images比较简单只是load的调用,不需要同步变化。

补充:
在dyld中全局搜索sNotifyObjCMapped方法,发现如下:

类的加载(上)--  _objc_init&read_images
sNotifyObjCMapped方法

sNotifyObjCMapped调用的地方是在notifyBatchPartial方法中,而notifyBatchPartial方法是在registerObjCNotifiers调用,在objc初始化注册通知时就调用了,所以是调用map_images后调用load_images

map_images分析

进入map_images源码:

void map_images(unsigned count, const char * const paths[], const struct mach_header * const mhdrs[]) { mutex_locker_t lock(runtimeLock); return map_images_nolock(count, paths, mhdrs); }

map_images只是对map_images_nolock的调用。
进入map_images_nolock源码:

void map_images_nolock(unsigned mhCount, const char * const mhPaths[], const struct mach_header * const mhdrs[]) { …… if (hCount > 0) { //类的加载映射 _read_images(hList, hCount, totalClasses, unoptimizedTotalClasses); } …… }

ap_images_nolock的核心逻辑是要找镜像文件是怎么被加载的,也就是对classProtocolselectorcategory等相关的操作。在map_images_nolock中最终发现了_read_images的调用。那么_read_images就是核心的研究对象了!!请往下看。

_read_images

进入_read_images的源码,发现比较多,我们一步步来。
通过读取源码发现_read_images多算的判断都是输出日志,根据输出日志可以知道没段代码做的是什么,那么我们关闭这些判断得到以下明显的代码逻辑结构:

void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses){ ...... // 条件控制进行一次的加载 if (!doneOnce) { ... } ...... //修复编译截断selector混乱问题 //不同的类中有相同方法名的方法,但是他们的地址必须不一样 static size_t UnfixedSelectors; ...... ts.log("IMAGE TIMES: fix up selector references"); //处理错误混乱的类(class) bool hasDyldRoots = dyld_shared_cache_some_image_overridden(); for (EACH_HEADER) { ... } ts.log("IMAGE TIMES: discover classes"); ...... // Fix up remapped classes // Class list and nonlazy class list remain unremapped. // Class refs and super refs are remapped for message dispatching. //修复重映射一些没有被镜像文件加载进来的类 if (!noClassesRemapped()) { ... } ts.log("IMAGE TIMES: remap classes"); ...... //修复一些消息 #if SUPPORT_FIXUP // Fix up old objc_msgSend_fixup call sites for (EACH_HEADER) {......} ts.log("IMAGE TIMES: fix up objc_msgSend_fixup"); ..... #endif // Discover protocols. Fix up protocol refs. //当类中有协议的时候-->readProtocol for (EACH_HEADER) {......} ts.log("IMAGE TIMES: discover protocols"); ...... //修复没有被加载的协议 for (EACH_HEADER) {......} ts.log("IMAGE TIMES: fix up @protocol references"); //对分类的处理 if (didInitialAttachCategories) { for (EACH_HEADER) { load_categories_nolock(hi); } } ts.log("IMAGE TIMES: discover categories"); //类的加载处理 for (EACH_HEADER) { classref_t const *classlist = hi->nlclslist(&count); ...... } ts.log("IMAGE TIMES: realize non-lazy classes"); // Realize newly-resolved future classes, in case CF manipulates them //对没有被处理的类进行优化。 if (resolvedFutureClasses) {......} ts.log("IMAGE TIMES: realize future classes"); ... #undef EACH_HEADER }

通过上面的源码分析,可以知道read_images方法主要做以下事情:

  • 条件控制进行一次加载
  • 修复预编译阶段的@selector的混乱的问题
  • 错误混乱的类处理
  • 修复重映射一些没有被镜像文件加载进来的类
  • 修复一些消息
  • 当类中有协议时:readProtocol
  • 修复没有被加载的协议
  • 分类的处理
  • 类的加载处理
  • 没有被处理的类,优化那些被侵犯的类

既然read_images做了那么多事情,那么就提取重点的在以下进行分析。

只加载一次

if(!doneOnce) { //控制进行只进行一次加载,进来之后修改状态 dOneOnce= YES; launchTime = YES; ... // Preoptimized classes don't go in this table. // 4/3 is NXMapTable's load factor //计算所需class表的大小,负载因子是3/4 int namedClassesSize = (isPreoptimized() ? unoptimizedTotalClasses : totalClasses) * 4 / 3; //创建哈希表 存放所有的类 gdb_objc_realized_classes = NXCreateMapTable(NXStrValueMapPrototype, namedClassesSize); ts.log("IMAGE TIMES: first time tasks"); }

  • 加载一次后控制判断的doneOnce条件等于YES,确保只加载一次,下次来的时候直接到创建表的操作。
  • gdb_objc_realized_classes,表里存放所有的不管是实现的还是没有实现。

修复@selector的混乱

//修复编译截断selector混乱问题 //不同的类中有相同方法名的方法,但是他们的地址必须不一样 static size_t UnfixedSelectors; { mutex_locker_t lock(selLock); for (EACH_HEADER) { if (hi->hasPreoptimizedSelectors()) continue; bool isBundle = hi->isBundle(); //从macho文件中获取方法名列表 SEL *sels = _getObjc2SelectorRefs(hi, &count); UnfixedSelectors += count; for (i = 0; i

不同的下面可以创建相同的方法的,区分这些方法的话必须是让他们都有各自独立方法地址。因为方法是存放在中的,所以即使方法名相同但是存放在的不一样,那么方法地址肯定不一样

错误混乱的类处理

//处理错误混乱的类(class) bool hasDyldRoots = dyld_shared_cache_some_image_overridden(); for (EACH_HEADER) { if (! mustReadClasses(hi, hasDyldRoots)) { // Image is sufficiently optimized that we need not call readClass() continue; } //从mach-o文件中获取class信息 classref_t const *classlist = _getObjc2ClassList(hi, &count); bool headerIsBundle = hi->isBundle(); bool headerIsPreoptimized = hi->hasPreoptimizedClasses(); for (i = 0; i

运行代码,在创建newCls的地方断点,看看cls与newCls的地址是怎样子的

类的加载(上)--  _objc_init&read_images
断点运行代码

看以看出cls地址与newCls的地址是不一样的,在newCls没有被赋值的时候,系统已经给它分配了一个内存地址(脏地址),所以newCls有数据。

让newCls赋值,然后断点查看情况

类的加载(上)--  _objc_init&read_images
让newCls赋值,断点查看

得出结论:read_class是将地址关联起来的。

自定义两个类分别是XJLPersonLGPerson,编译之后查看mach-o文件

类的加载(上)--  _objc_init&read_images
mach-o文件查看

可以看出XJLPerson的地址为:0000000100004780LGPerson的地址为:0000000100004730

在代码中加上拦截代码,看看编译之后的地址是否对应

类的加载(上)--  _objc_init&read_images
加上拦截代码断点调试

macho源码对应起来的,XJLPersonLGPerson的地址跟在mach-o记录的地址一样

readClass
这是绑定clsnewCls关系的核心代码,那么先看它的代码实现逻辑是怎么样子的。

Class readClass(Class cls, bool headerIsBundle, bool headerIsPreoptimized) { // 获取类名 const char *mangledName = cls->nonlazyMangledName(); if (missingWeakSuperclass(cls)) { ... } cls->fixupBackwardDeployingStableSwift(); Class replacing = nil; if (mangledName != nullptr) { ... } if (headerIsPreoptimized && !replacing) {... } else { if (mangledName) { //some Swift generic classes can lazily generate their names //将类名和地址关联起来 addNamedClass(cls, mangledName, replacing); } else { ...} //将关联的类插入到另一张哈希表中 addClassTableEntry(cls); } // for future reference: shared cache never contains MH_BUNDLEs if (headerIsBundle) { ... } return cls; }

  • nonlazyMangledName获取类名
  • rw的赋值和ro的获取并不在readClass里面
  • addNamedClass类名地址关联绑定起来
    addClassTableEntry将关联的类插入哈希表中,这张表中都是初始化过的

nonlazyMangledName获取类名

const char *nonlazyMangledName() const { return bits.safe_ro()->getName(); }

进入safe_ro方法

const class_ro_t *safe_ro() const { class_rw_t *maybe_rw = data(); if (maybe_rw->flags & RW_REALIZED) { // maybe_rw is rw // rw有值 直接从rw中的ro获取 return maybe_rw->ro(); } else // maybe_rw is actually ro // 直接从ro中获取,ro是macho中的数据 return (class_ro_t *)maybe_rw;、 } }

进入addNamedClass,关联地址跟类名

static void addNamedClass(Class cls, const char *name, Class replacing = nil) { runtimeLock.assertLocked(); Class old; if ((old = getClassExceptSomeSwift(name)) && old != replacing) { inform_duplicate(name, old, cls); // getMaybeUnrealizedNonMetaClass uses name lookups. // Classes not found by name lookup must be in the // secondary meta->nonmeta table. addNonMetaClass(cls); } else { //更新gdb_objc_realized_classes表,将key设置为 name value 设置为cls NXMapInsert(gdb_objc_realized_classes, name, cls); } ASSERT(!(cls->data()->flags & RO_META)); // wrong: constructed classes are already realized when they get here // ASSERT(!cls->isRealized()); }

最后更新gdb_objc_realized_classes哈希表,keynamevalue是cls`,这样子类和地址就关联起来了。

进入addClassTableEntry方法—-插入另一张表

static void addClassTableEntry(Class cls, bool addMeta = true) { runtimeLock.assertLocked(); // This class is allowed to be a known class via the shared cache or via // data segments, but it is not allowed to be in the dynamic //table already. // allocatedClasses auto &set = objc::allocatedClasses.get(); ASSERT(set.find(cls) == set.end()); if (!isKnownClass(cls)) set.insert(cls); if (addMeta) //将元类插入哈希表中 addClassTableEntry(cls->ISA(), false); }

  • allocatedClasses_objc_initruntime_init运行时环境初始化,里面主要是unattachedCategoriesallocatedClasses两张表,此时插入allocatedClasses表中。
  • addMeta = true 将元类添加allocatedClasses表中。

注意:rw的赋值和ro的获取并不在readClass里面!!

类的加载

类的加载是比较复杂的,需要单独一个篇章进行分析探索,这里只是简单分析以下类加载的一些流程。

类的加载(上)--  _objc_init&read_images
XJLPerson非懒加载断点情况

注释很明显的提示初始化非懒加载类,什么是非懒加载类?其实就是实现了load方法或者静态的实例方法,图中添加断点地方没有断住,就是因为XJLPerson是懒加载类。现在给XJLPerson添加load方法:

类的加载(上)--  _objc_init&read_images
XJLPerson添加load方法

XJLPerson添加+load方法时候,能够进入断点。

疑点:realizeClassWithoutSwift(cls, nil);方法明显就是类加载的核心点,那么它是怎么样子实现的呢?那就要在下篇文章详细分解了哦。

补充map_image与load_image流程图

类的加载(上)--  _objc_init&read_images
流程图

推荐阅读
  • 图解redis的持久化存储机制RDB和AOF的原理和优缺点
    本文通过图解的方式介绍了redis的持久化存储机制RDB和AOF的原理和优缺点。RDB是将redis内存中的数据保存为快照文件,恢复速度较快但不支持拉链式快照。AOF是将操作日志保存到磁盘,实时存储数据但恢复速度较慢。文章详细分析了两种机制的优缺点,帮助读者更好地理解redis的持久化存储策略。 ... [详细]
  • vue使用
    关键词: ... [详细]
  • 一、Hadoop来历Hadoop的思想来源于Google在做搜索引擎的时候出现一个很大的问题就是这么多网页我如何才能以最快的速度来搜索到,由于这个问题Google发明 ... [详细]
  • 本文介绍了数据库的存储结构及其重要性,强调了关系数据库范例中将逻辑存储与物理存储分开的必要性。通过逻辑结构和物理结构的分离,可以实现对物理存储的重新组织和数据库的迁移,而应用程序不会察觉到任何更改。文章还展示了Oracle数据库的逻辑结构和物理结构,并介绍了表空间的概念和作用。 ... [详细]
  • IhaveconfiguredanactionforaremotenotificationwhenitarrivestomyiOsapp.Iwanttwodiff ... [详细]
  • 本文介绍了Hyperledger Fabric外部链码构建与运行的相关知识,包括在Hyperledger Fabric 2.0版本之前链码构建和运行的困难性,外部构建模式的实现原理以及外部构建和运行API的使用方法。通过本文的介绍,读者可以了解到如何利用外部构建和运行的方式来实现链码的构建和运行,并且不再受限于特定的语言和部署环境。 ... [详细]
  • Oracle分析函数first_value()和last_value()的用法及原理
    本文介绍了Oracle分析函数first_value()和last_value()的用法和原理,以及在查询销售记录日期和部门中的应用。通过示例和解释,详细说明了first_value()和last_value()的功能和不同之处。同时,对于last_value()的结果出现不一样的情况进行了解释,并提供了理解last_value()默认统计范围的方法。该文对于使用Oracle分析函数的开发人员和数据库管理员具有参考价值。 ... [详细]
  • 计算机存储系统的层次结构及其优势
    本文介绍了计算机存储系统的层次结构,包括高速缓存、主存储器和辅助存储器三个层次。通过分层存储数据可以提高程序的执行效率。计算机存储系统的层次结构将各种不同存储容量、存取速度和价格的存储器有机组合成整体,形成可寻址存储空间比主存储器空间大得多的存储整体。由于辅助存储器容量大、价格低,使得整体存储系统的平均价格降低。同时,高速缓存的存取速度可以和CPU的工作速度相匹配,进一步提高程序执行效率。 ... [详细]
  • 本文介绍了计算机网络的定义和通信流程,包括客户端编译文件、二进制转换、三层路由设备等。同时,还介绍了计算机网络中常用的关键词,如MAC地址和IP地址。 ... [详细]
  • 本文介绍了游标的使用方法,并以一个水果供应商数据库为例进行了说明。首先创建了一个名为fruits的表,包含了水果的id、供应商id、名称和价格等字段。然后使用游标查询了水果的名称和价格,并将结果输出。最后对游标进行了关闭操作。通过本文可以了解到游标在数据库操作中的应用。 ... [详细]
  • Android系统源码分析Zygote和SystemServer启动过程详解
    本文详细解析了Android系统源码中Zygote和SystemServer的启动过程。首先介绍了系统framework层启动的内容,帮助理解四大组件的启动和管理过程。接着介绍了AMS、PMS等系统服务的作用和调用方式。然后详细分析了Zygote的启动过程,解释了Zygote在Android启动过程中的决定作用。最后通过时序图展示了整个过程。 ... [详细]
  • 如何实现JDK版本的切换功能,解决开发环境冲突问题
    本文介绍了在开发过程中遇到JDK版本冲突的情况,以及如何通过修改环境变量实现JDK版本的切换功能,解决开发环境冲突的问题。通过合理的切换环境,可以更好地进行项目开发。同时,提醒读者注意不仅限于1.7和1.8版本的转换,还要适应不同项目和个人开发习惯的需求。 ... [详细]
  • 初识java关于JDK、JRE、JVM 了解一下 ... [详细]
  • go channel 缓冲区最大限制_Golang学习笔记之并发.协程(Goroutine)、信道(Channel)
    原文作者:学生黄哲来源:简书Go是并发语言,而不是并行语言。一、并发和并行的区别•并发(concurrency)是指一次处理大量事情的能力 ... [详细]
  • 都会|可能会_###haohaohao###图神经网络之神器——PyTorch Geometric 上手 & 实战
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了###haohaohao###图神经网络之神器——PyTorchGeometric上手&实战相关的知识,希望对你有一定的参考价值。 ... [详细]
author-avatar
木头人2幸福
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有