1.Retrofit2.9.0源码解析
2.深度分析Binder线程池的手机启动流程
3.通过transmittable-thread-local源码理解线程池线程本地变量传递的原理
4.ThreadPoolExecutor简介&源码解析
5.JobIntentService源码解析
6.线程池newCachedThreadPool
Retrofit2.9.0源码解析
前言 之前我们探讨了OkHttp的基本原理,这款以高效的线程线程池设计、任务分配与转化以及基于责任链模式的池源程池五大全拦截器而深受开发者喜爱的库,却在引入时需要进行封装,码手以适应主、机线子线程的源码无人区玫瑰有溯源码真吗切换与返回值的转换。面对团队成员的手机偏好,选择Retrofit作为解决方案,线程无疑提升了团队协作的池源程池友好性。接下来,码手我们将深度剖析这个优秀的机线开源框架是如何促进团队合作的。 使用 以下代码摘自Retrofit的源码官方示例,除了线程管理部分,手机其余部分基本相同,线程可以直接在Android Studio项目中运行。池源程池Retrofit的使用方式相对直观,但在此不再赘述,直接进入源码解析。 Retrofit的封装模式在于为OkHttp提供了一层更友好的调用方式,实质上仍依赖OkHttp执行网络请求。正如一把剑,除了锋利的刃之外,剑柄、剑鞘和符咒共同决定了它的使用体验。Retrofit与OkHttp的关系图展示了它们之间的爱恨纠葛。 Retrofit.build()方法详解 在Retrofit构建实例的过程中,以下关键步骤被实现:判断并设置baseUrl。
赋值callFactory,即OkHttp客户端。
若未指定callFactory,则默认使用OkHttpClient。
设置callbackExecutor,用于线程切换。
赋值callAdapterFactories,用于处理网络请求的转换。
其中,callbackExecutor的默认值是Android平台的MainThreadExecutor,确保了执行方法后线程切换至主线程。callAdapterFactories是一个工厂模式的列表,用于创建不同的callAdapter,以处理网络请求的关键步骤(enqueue、execute)。webrtc源码流程分析 在Android平台下,defaultCallbackExecutor被构造为MainThreadExecutor的实例,通过Handler与Looper的关联确保了线程切换。 最后,我们了解了converterFactories的作用,这是负责服务端返回值转换的关键组件。 Retrofit.create()方法解析 在调用Retrofit.create()方法时,动态代理(Proxy.newProxyInstance)发挥关键作用。这个过程类比于N女士委托X律师处理问题,动态代理将实体方法的调用转化为OkHttp请求的执行。 动态代理通过反射机制,实现所有请求的统一处理,简化了接口的使用,同时增强了功能。尽管它可能导致性能损耗,但Retrofit的高效与强大使其成为众多开发者的首选。 代理执行的关键步骤包括:明确动态代理概念。
理解invoke()方法的执行时机。
分析github(代理).contributors方法的执行流程。
通过动态代理,Retrofit实现了对网络请求的封装,简化了开发过程,并提供了灵活的适配性。最终,请求通过OkHttp客户端执行,返回值通过适配器转换为预期格式。 生成Call与执行网络请求 在生成Call后,执行network request的过程由OkHttp客户端负责。在Retrofit的实现中,Call的创建与执行紧密相连,最终通过OkHttp的Call.execute()方法完成网络请求的执行。 结语 撰写源码解析的过程不仅加深了对Retrofit的理解,也揭示了其作为团队协作工具的潜力。通过阅读优秀源码,开发者可以不断提升自我,学习到更深层次的知识与技能。Retrofit以其简洁、高效的设计,为开发者提供了强大的网络请求支持,成为了Android开发中的重要组件。源码的反编译拿源码探索之旅,既是一次技术的修炼,也是对开源精神的致敬。深度分析Binder线程池的启动流程
理论基础Binder
Binder它是Android中的一种进程间通信机制,它主要采用的是CS架构模式。Binder框架中主要涉及到4个角色Client、Server、ServiceManager及Binder驱动,其中Client、Server、ServiceManager运行在用户空间,Binder驱动运行在内核空间。
线程池线程池它是一种用于多线程处理形式,处理过程中将任务添加到队列,然后在创建线程后自动启动这些任务。线程池线程都是后台线程。每个线程都使用默认的堆栈大小,以默认的优先级运行,并处于多线程单元中。
简单的说:线程池就是创建一些线程,它们的集合称为线程池。
Binder线程池启动流程我们知道一个新的app应用程序进程在创建完成之后,它会通过调用RunTimeInit类的静态成员函数zygoteInitNative来进行启动Binder线程池。
Binder线程池启动过程中,主要调用几个关键函数:ZygoteInitNative--->onZygoteInit--->startThreadPool。
下面的源码分析主要是以android5.0版本为例。
ZygoteInitNative源码分析由于ZygoteInitNative函数是java实现的代码,实践上最终调用的是由C++实现的JNI方法。以下代码来源于系统的/frameworks/base/core/jni/androidRuntime.cpp文件中
staticvoidcom_android_internal_os_RuntimeInit_nativeZygoteInit(JNIEnv*env,jobjectclazz){ //gCurRuntime是个全局的变量,后面跟上的是另外实现的方法。gCurRuntime->onZygoteInit();}onZygoteInit源码分析onZygoteInit函数在需要源码的位置:/frameworks/base/cmds/app_process/app_main.cpp文件中。
该函数是个虚函数,并且是一个无返回值和无参数的函数virtualvoidonZygoteInit(){ //Re-enabletracingnowthatwe'renolongerinZygote.atrace_set_tracing_enabled(true);//获取进程的状态信息sp<ProcessState>proc=ProcessState::self();//打印日志信息ALOGV("Appprocess:startingthreadpool.\n");//启动线程池proc->startThreadPool();}startThreadPool源码分析startThreadPool系统实现在\frameworks\native\libs\binder\ProcessState.cpp文件中。
每一个支持Binder进程间通信机制的进程内都有一个唯一的ProcessState对象,当这个ProcessState对象的成员函数StartThreadPool函数被第一次调用的时候,它就会在当前进程中启动一个线程池,并将mThreadPoolStarted这个成员变量设置为true。
//该函数是个无参数,无返回值的函数voidProcessState::startThreadPool(){ AutoMutex_l(mLock);//判断线程池是否启动状态,启动的话就将标志信息设置为true属性。if(!mThreadPoolStarted){ mThreadPoolStarted=true;spawnPooledThread(true);}}总结Binder在android底层中是一个非常重要的机制,我们在实际的代取快递app源码项目调用过程中,我们在app应用程序中只要实现自己的Binder本地对象的时候,跟其他服务一样,只需要将它进行启动起来,并且进行注册到ServerMananger就可以了。至于内部的实现一般是不需要去关心的。
通过transmittable-thread-local源码理解线程池线程本地变量传递的原理
最近几周,我投入了大量的时间和精力,完成了UCloud服务和中间件迁移至阿里云的工作,因此没有空闲时间撰写文章。不过,回忆起很早之前对ThreadLocal源码的分析,其中提到了ThreadLocal存在向预先创建的线程中传递变量的局限性。恰好,我的一位前同事,HSBC的技术大牛,提到了团队引入了transmittable-thread-local(TTL)来解决此问题。借此机会,我深入分析了TTL源码,本文将全面分析ThreadLocal和InheritableThreadLocal的局限性,并深入探讨TTL整套框架的实现。如有对线程池和ThreadLocal不熟悉的读者,建议先阅读相关前置文章,本篇文章行文较为干硬,字数接近5万字,希望读者耐心阅读。
在Java中,没有直接的API允许子线程获取父线程的实例。获取父线程实例通常需要通过静态本地方法Thread#currentThread()。同样,为了在子线程中传递共享变量,也常采用类似的方法。然而,这种方式会导致硬编码问题,限制了方法的复用性和灵活性。为了解决这一问题,线程本地变量Thread Local应运而生,其基本原理是通过线程实例访问ThreadLocal.ThreadLocalMap来实现变量的存储与传递。
ThreadLocal与InheritableThreadLocal之间的区别主要在于控制ThreadLocal.ThreadLocalMap的创建时机和线程实例中对应的属性获取方式。通过分析源码,可以清楚地看到它们之间的联系与区别。对于不熟悉概念的伦敦战场源码读者,可以尝试通过自定义实现来理解其中的原理与关系。
ThreadLocal和InheritableThreadLocal的最大局限性在于无法为预先创建的线程实例传递变量。泛线程池Executor体系、TimerTask和ForkJoinPool等通常会预先创建线程,因此无法在这些场景中使用ThreadLocal和InheritableThreadLocal来传递变量。
TTL提供了更灵活的解决方案,它通过委托机制(代理模式)实现了变量的传递。委托可以基于Micrometer统计任务执行时间并上报至Prometheus,然后通过Grafana进行监控展示。此外,TTL通过字节码增强技术(使用ASM或Javassist等工具)实现了类加载时期替换Runnable、Callable等接口的实现,从而实现了无感知的增强功能。TTL还使用了模板方法模式来实现核心逻辑。
TTL框架的核心类TransmittableThreadLocal继承自InheritableThreadLocal,通过全局静态变量holder来管理所有TransmittableThreadLocal实例。holder实际上是一个InheritableThreadLocal,用于存储所有线程本地变量的映射,实现变量的全局共享。disableIgnoreNullValueSemantics属性的设置可以影响NULL值的处理方式,影响TTL实例的行为。
发射器Transmitter是TransmittableThreadLocal的一个公有静态类,提供传输TransmittableThreadLocal实例和注册当前线程变量至其他线程的功能。通过Transmitter的静态方法,可以实现捕获、重放和复原线程本地变量的功能。
TTL通过TtlRunnable类实现了任务的封装,确保在执行任务时能够捕获和传递线程本地变量。在任务执行前后,通过capture和restore方法捕获和重放变量,实现异步执行时上下文的传递。
启用TTL的Agent模块需要通过Java启动参数添加javaagent来激活字节码增强功能。TTL通过Instrumentation回调激发ClassFileTransformer,实现目标类的字节码增强,从而在执行任务时自动完成上下文的捕捉和传递。
TTL框架提供了一种高效、灵活的方式来解决线程池中线程复用时上下文传递的问题。通过委托机制和字节码增强技术,TTL实现了无入侵地提供线程本地变量传递功能。如果您在业务代码中遇到异步执行时上下文传递的问题,TTL库是一个值得考虑的解决方案。
ThreadPoolExecutor简介&源码解析
线程池是通过池化管理线程的高效工具,尤其在多核CPU时代,利用线程池进行并行处理任务有助于提升服务器性能。ThreadPoolExecutor是线程池的具体实现,它负责线程管理和任务管理,以及处理任务拒绝策略。这个类提供了多种功能,如通过Executors工厂方法配置,执行Runnable和Callable任务,维护任务队列,统计任务完成情况等。
创建线程池需要考虑关键参数,如核心线程数(任务开始执行时立即创建),最大线程数(任务过多时限制新线程生成),线程存活时间,任务队列大小,线程工厂以及拒绝策略。JDK提供了四种拒绝策略,如默认的AbortPolicy,当资源饱和时抛出异常。此外,线程池还提供了beforeExecute和afterExecute钩子函数,用于在任务执行前后执行自定义操作。
当任务提交到线程池时,会经历一系列处理流程,包括任务的执行和线程池状态的管理。例如,如果任务队列和线程池满,会根据拒绝策略处理新任务。使用线程池时,需注意线程池容量与状态的计算,以及线程池工作线程的动态调整。
示例中,自定义线程池并重写钩子函数,创建任务后向线程池提交,可以看到线程池如何根据配置动态调整资源。但要注意,如果任务过多且无法处理,可能会抛出异常。源码分析中,submit方法实际上是调用execute,而execute内部包含Worker类和runWorker方法的逻辑,包括任务的获取和执行。
线程池的容量上限并非Integer.MAX_VALUE,而是由ctl变量的低位决定。 Doug Lea的工具函数简化了ctl的操作,使得计算线程池状态和工作线程数更加便捷。通过深入了解ThreadPoolExecutor,开发者可以更有效地利用线程池提高应用性能。
JobIntentService源码解析
Android 8.0引入了更严格的系统资源管控,包括后台限制规则。
在Android 8.0中,禁止应用在后台运行时创建Service。
若应用在后台运行,将会收到错误提示。
JobIntentService是Android 8.0中新增的类,继承自Service。
该类用于执行加入队列的任务。对于Android 8.0及以上系统,JobIntentService任务将通过JobScheduler.enqueue执行,而8.0以下系统则继续使用Context.startService。
JobIntentService使用便捷,只需调用YourService.enqueueWork(context, new Intent())方法。
相较于JobService,JobIntentService简化了操作,开发者无需关注其生命周期,避免了在后台运行时创建Service导致的crash问题,且通过静态方法即可启动。
源码解析如下:首先记录几个关键变量的含义。
在Android 8.0以上的系统中,执行流程如下。
work的具体逻辑处理在何处?
通过JobService的工作原理,查找onStartJob方法。
最终,处理work的逻辑会流转至AsyncTask中,通过protected abstract void onHandleWork(@NonNull Intent intent)方法实现。
子类需实现jobIntentService处理work,使用线程池的AsyncTask执行,无需考虑主线程阻塞问题。
针对Android 8.0以下系统,流程如下:回到onStartCommand方法。
同样,最终会流转至Asynctask任务执行onHandleWork。
线程池newCachedThreadPool
新线程池newCachedThreadPool的源码揭示了其独特设计和功能。它的核心特点在于动态创建和重用线程,以提高执行短暂异步任务的程序性能。此池允许在先前构造的线程可用时重复使用它们,且最大线程数为Integer.MAX_VALUE,意味着资源使用相对灵活。
在newCachedThreadPool中,线程的存活时间设置为秒,超过此时间未使用的线程将被终止并从池中移除。这一特性有助于避免资源浪费,保持空闲时间足够长的池不会消耗任何资源。此外,新线程池不包含核心线程,其操作基于SynchronousQueue队列,确保线程间高效同步。
使用newCachedThreadPool时,程序执行到大约秒后自动终止,因为线程池已完成所有任务。存活线程在超过秒的闲置后被终止和移除,这体现了其设计原理。
为何newCachedThreadPool选择SynchronousQueue而不是其他线程池通常采用的LinkedBlockQueue?SynchronousQueue是一个特殊的阻塞队列,旨在实现线程间高效同步。它没有内部容量,且插入操作需等待相应的删除操作。此特性使其成为切换设计的理想选择,允许线程在需要时安全地传递信息、事件或任务,尤其适用于需要多线程间同步的应用场景。
SynchronousQueue通过实现Collection和Iterator接口支持所有可选方法,包括支持可选的公平性策略。默认情况下,不保证生产者和使用者线程的FIFO顺序访问,但通过将公平性策略设置为true,可以确保按此顺序授予访问权限。
总之,newCachedThreadPool通过动态线程重用和SynchronousQueue的高效同步机制,提供了一种灵活且高效的处理短暂异步任务的方法。其设计旨在优化资源使用,通过在任务完成后的秒内自动清理资源,保持系统性能高效。
聊聊Android线程优化这件事
在日常开发Android APP的过程中,第三方库和第二方库的使用可以加速功能实现,提升开发效率。然而,这些库也会给线程带来压力,主要表现在线程数的增加、内存消耗与CPU使用率的提升等方面。针对这一问题,本文将分享一个线程优化的整体思路及具体方案。
首先,我们需要对线程进行检测与统计。通过读取伪文件系统内的线程信息,可以实时监控运行过程中的线程变动,统计创建的线程数、可用线程数与正在运行的线程数。理想状态下,可用线程数与运行线程数应接近,但实际观察发现差异明显,因此优化潜力巨大。
为了进一步了解创建线程与线程池的过程,我们采用ASM(字节码操作框架)进行插桩,扫描并记录创建线程池与线程的类名。插桩不仅高效、灵活,且易于维护,对于优化应用程序性能具有重要意义。
接着,提出线程与线程池的优化方案。根据反射、代理、协程与插桩等多种方法的优劣对比,我们选择通过插桩代理来收敛第三方线程池。通过代码设计图、流程图与实施过程的说明,我们可以看到优化的细节与步骤。实施代理后,线程数量减少了条,内存使用降低了M,CPU使用率降低了3%,APP的流畅性提升,每秒平均刷新帧数增加帧,符合预期优化效果。
在优化过程中,我们需注意线程栈大小的设置。默认线程栈大小为1M,通过函数FixStackSize的源码实现,我们可以调整线程栈大小以适应具体应用需求。合理调整线程栈大小,不仅可以优化内存使用,还能提高运行效率。同时,我们还需要关注线程栈大小设置的动态调整,以应对可能出现的线上问题。
通过线程优化,我们不仅减少了线程数量,降低了内存消耗与CPU使用率,还提高了APP的流畅性。为帮助大家全面掌握性能优化知识,特提供相关核心笔记,内容覆盖启动优化、内存优化、UI优化、网络优化、Bitmap优化与压缩优化、多线程并发优化与数据传输效率优化、体积包优化等多方面,助力开发者深入理解并应用性能优化技术。
此外,为了更好地掌握性能优化技术,推荐阅读《Android 性能监控框架》与《Android Framework学习手册》等资料,进一步提升对Android系统架构与性能优化的理解与实践能力。