1 handler消息机制
ThreadLocal 线程中的存储类
设置泛型 重写initialValue()方法 返回数据
在发送消息的时候 message中有个 target 属性代表当前的 handler对象
为什么looper死循环没有堵塞线程
队列中没有消息时,会在loop的loop.next()中的nativePollOnce()方法中堵塞,此时主线程释放CPU资源,直到有消息到达,通过往pipe管道写数据来唤醒主线程,采用的是epoll机制是一种IO多路复用机制
1.消息的阻塞在MessageQueue的next方法,阻塞调用的方法是nativePollOnce(ptr, nextPollTimeoutMillis),nextPollTimeoutMillis是阻塞时间,为-1时候,一直阻塞,直到被唤醒,为0不需要阻塞,大于0为阻塞的时间,时间到了,唤醒线程。
2.消息的唤醒在MessageQueue的enqueueMessage方法,唤醒的方法是nativeWake(),底层是epoll机制,通过往pipe管道写端写入数据来唤醒主线程工作,当线程唤醒nativePollOnce就不会阻塞,继续往后执行,取消息,以及分发消息
sndMessageDelayed或者postDelayed怎么实现了
向队列中插入msg的时候,会根据时间排序
为什么messageQueue在looper的私有构造中初始化
一个线程绑定一个looper->在私有构造中初始化可以保证 队列也是唯一的 ->thread对应一个looper对应一个队列
handler如果实现线程切换
在主线程中开启的looper.loop,所有消息会回到主线程
子线程中创建looper
Looper.prepare();
post和postDelayed的区别
1 都是通过sendmessage发送消息
2 post传入的时间是0
2 postDelayed 传入的时间是需要等待的时间
looper的终止方法 quit和quitSafely
1quit 是讲所有的消息清空
2quitSafely是将延迟消息清空,非延迟消息继续处理
消息屏障
msgQ中有普通消息(同步消息),屏障消息(同步屏障),异步消息
屏障消息:在队列中插入一个屏障,拦截之后的所有普通消息,但是拦截不了异步消息
没有target,通过判断target为空判断是不是屏障消息
可以插入多个消息屏障,也有时间戳,按时间排序,只会影响后面的消息
插入之后会返回token,移除屏障也是根据这个token
每个线程最多一个looper
一个线程只有一个msgQ
多个 handler 通过 msg.target来区别2个handler的消息
初始化一个Message Message.obtain()
handlerThread
new HandlerThread的looper和activity中的looper不是同一个 HandlerThread自带一个looper HandlerThread的getlooper可以获得HandlerThread中的looper对象
Message.obtain();
这个方法不会重复创建Message实例对象,而是直接从Message池中获取一个Message的新的实例对象,避免重复创建多个实现对象。
流程
app启动-->在activityThread中的main()-->创建全局唯一的 loop对象 存放在 ThreadLocal中-->loop的私有构造方法中初始化 MessageQuery 对象(不是队列是一个msg单链表)
looper.loop()调用之后-->开启循环从消息队列中获取消息--> 从msg.target中获取handler调用dispatchMessage(msg)-->重写handleMessage
BlockingQueue堵塞队列
2 Binder机制
是一种跨进程通信的一种方式 从应用层来说它是客户端和服务端进行通信的媒介
进程通信的基石.采用aidl的IPC通信.他是基于service实现一种线程间通信的机制,它的本质是c/s架构,需要一个服务端一个客户端.
aidl中有4个对象 1 IInterface专门用来负责接口的调度,
2 Stub用来负责通信的响应和发送给service短数据,
3 Proxy负责两个进程通信的包装算是间接调用Stub的包装类
4 service是服务端处理数据的关键类
AIDL(*)
每个进程都有自己的一块独立内存 aidl就是两个进程的桥梁使两个进程可以进行数据的传输
服务端:创建通信的数据实体->同包名下创建两个aidl文件(1表明实体类,2接口)->创建service->在service中初始化一个binder->清单文件注册service
客户端:初始化要传递的数据->启动服务进行数据传递
原理
1 activity和service通信
同进程中 直接继承binder然后在activity中对service通信
不同进程用AIDL让系统生成一个binder 在activity中对远端的service进行操作
2 activity向远端调用的时候 当前线程会被挂起 方法处理完成才会唤醒
新总结
首先
binder是基于mmap实现了IPC传输数据,binder保存binderProxy,bpBinder,bBinder等各种binder实体,以及对binder驱动操作的processState的封装
在加上binder驱动内部的结构体,命令处理,整体贯穿 java 和native层,涉及用户态 内核态,是一个相对庞大 繁琐的一个机制
\
binder通信4方参与,binder驱动层,client端,service端和serviceManager
client标示应用程序进程
service端标示系统服务,可能运行在system_service进程
servicManager是binder进程通信的上下文管理者,提供service的服务注册和client的服务获取
有init进程通过解析init.rc文件 创建ServiceManager->在ServiceManager的main方法中调用binder_open打开binder驱动->向binder发送指令,申请成为管理者->binder_loop开始轮询处理发送过来的消息,没有消息就休眠
binder驱动层 他们之间不能直接通信,需要借助与binder驱动层进行交互
\
binder是安卓中一种高效 方便 安全的进程间通信
高效是binder只需要一次数据拷贝,把一块物理内存同时映射到内核和目标进程的用户空间,虚拟内存指向物理内存
真是拷贝2次,有一次是最大8K数据,存放头信息(相当于请求头)
方便是只用起来简单直接,client端使用service端的提供的服务只需要传service的一个描述即可,就可以获得service提供的服务接口
安全是指binder验证每个检查的uid,可靠的身份信息是IPC机制本身在内核中添加的
\
跨进程通信
1 client发送一个请求service信息的binder请求到binderDriver中
2 serviceManager 发现BinderDiriver中有自己的请求,将请求的service的数据返回给client
3 client获取的信息就是该service的代理,调用代理的方法将请求发送binderDriver中
4 此时service的binder线程池循环发现有自己的请求,然后用impl处理这个请求最后返回
mmap 内存映射 a向b发消息(一次最大数据1M-8K)
a发送消息->通过调用系统 copy_fron_user将数据复制到内核缓存区->b进程和数据存在映射关系
怎么理解页框和页
页框是指一筐时间的物理内存,页是指程序的一块内存数据单元,内存数据一定是储存在实际的物理内存上
页框和页一样大,都是内核对内存的分块单位,一个页框可以映射给多个页,可就是说一块实际的物理存储空间可以映射给多个进程的多个虚拟内存空间,也就是mmap机制依赖的基础规则
1页是4K
binder流程
client通过ServiceManager或ams获取到远程binder实体,一般会用proxy做一次封装,被封装的远程binder实体是一个binderProxy
bpBinder和BinderProxy是一个东西,一个native层 一个java层,bpBinder内部持有一个binder句柄值handle
procesState是远程单利负责打开binder驱动及mmap,IPCTheadState为线程单利,负责与binder驱动进行具体的命令通信
binder驱动到service的流程是:service通过IPCThreadState接收到client的请求,层层向上,最后回调stub
动脑
binder驱动 主要函数
init() 创建binder的引用队列
native层
client->调用服务service->注册服务serviceManager
\
serviewManager 启动
是binder的大管家->轮询处理客服端发过来的任务->创建defaultServiceManager(创建过程open()打开binder驱动,利用mmap()映射内核)->获取 servieMandger
getContextObject获取BpBinder对象
do_add_service()注册服务-根据服务名来查询匹配的服务->释放服务当查询到已存在同名的服务,则先清理该服务信息,在将当前的服务加入到服务列表svclist
do_find_serviece()查询服务->遍历当服务已存在list中则返回相应的服务,没有返回null
framework层
初始化 在安卓系统开机中,孵化器过程会有一个虚拟机注册过程->调用androidRunTime.startReg()来完成jni方法的注册->注册binderInternal,建立了native和fragmework相互调用的桥梁->注册binderProxy
\
开启新的进程
android:process=":remote"
3 AMS服务(ActivityManagerService)
- ActivityManagerService的作用,什么时候初始化?
主要负责系统中的四大组件的启动,切换,调度及应用进程的管理和调度工作
在systemServer进程启动的时候初始化ams
- ActivityThread和ApplicationThread,以及关系和区别
ActivityThread: 在安卓中代表安卓的主线程并不是一个thread类,activityThread类是安卓进程的初始类,main函数是这个App进程的入口
ApplicationThread: 是ActivityThread的内部类,通过H的handler发送启动activity消息->handlerLaunchActivity接收了消息->让preformLaunchActivity处理,最终启动activity
- Instrumentation是什么,和ActivityThread是什么关系
1 ams与activityThread之间诸如activity的创建,暂停等交互工作都是由Instrumentation操作的
2 每个activity都持有一个Instrumentation的引用,整个进程只有一个Instrumentation.当调用startActivityForResult()实际上还是调用了mInstrumentation.execStartActivity()
- ActivityManagerService和zygote进程通信是如何实现的
1 launcher进程请求ams,ams发送创建应用进程请求,zygote进程接受请求并fork应用进程
2 通过findStaticMain()找到activityThread类的main()方法并执行,进程就这样启动了
- ActivityManager、ActivityManagerService、ActivityManagerNative的关系
ActivityManager: 是对activity管理,运行时功能管理和运行时数据结构的封装,进程,应用程序,服务,任务信息等
ActivityManagerService: 是与系统所有正在运行中的activity进行交互,对系统所有运行中的activity相关信息进行管理和维护
ActivityManagerNative:是个抽象类,真正发挥左右的是他的子类activityManagerService,在API26已经过时了,现在对activity的管理都使用activityManager
- AMS和WMS的关系
1 在安卓体系架构中都属于同一层次
2 ams统一调度所有程序的activity,wms控制素有Window的显示与隐藏以及要现实的位置
AMS的作用
1 统一调度所有应用程序的activity的生命周期
2 启动或杀死应用进程
3 注册BroadcastReceiver,并接收和分发Broadcast
4 启动并发布ContentProvider
5 调度task
6 处理应用程序的crash
7 查询仕通当前运行状态
\
AMS注册
systemServer服务进程创建ams->调用open() mmap()初始化->想binder驱动发送指令->binder唤醒 睡眠的service_manager(服务进程)->服务端向binder驱动中注册服务->binder告诉服务端添加完成->服务端注册完成,binder发送指令告诉客户端ams服务创建完成
\
安卓启动流程
1 开启电源以及系统启动
加载引导 BootLoader到ram然后执行
2 引导程序 BootLoader
是在android操作系统开始运行前的一个小程序,它的左右是吧系统os拉起来运行
3 Linux内核启动
当内核启动时,设置缓冲 存储器 计划列表 加载驱动.当内核完成系统设置时,它首先在系统文件中寻找 init.rc文件,并启动 init 进程
4 init进程
初始化和启动属性服务 并且启动 zygote 进程
5 zygote 进程启动
创建java 虚拟机并为 java虚拟机注册 JNI 方法,创建服务端 socket,fork()进程,启动 systemServer进程
6 system_Server 进程启动
启动 Binder线程池和 systemServiceManager 并且启动各种系统服务 ams pms
7 launcher 启动
被systemServer 进程启动的AMS会启动 launcher,launcher启动后会将已安装的应用的快捷图标显示在界面上
system_server 为什么要在 Zygote 中启动,而不是由 init 直接启动呢
Zygote提前加载资源,这样fork()时基于copy_on_write机制->创建其他进程就会直接用这些资源,而不用重新加载
为什么要专门使用 Zygote 进程去孵化应用进程,而不是让 system_server 去孵化呢?
system_server中会初始化AMS PMS等服务->对一个应用程序来说不需要->system_server中有很多线程,fork()进程对多线程不友好->仅会将发起调用的线程拷贝到子进程
应用进程启动
1 AMS 发送启动应用撤销进程的请求
AMS如果想要启动应用程序进程,就需要向 zygote进程发送创建应用程序进程的请求,AMS 会通过调用startProcessLocked方法 向 zygote进程发送请求
2 zygote 接受请求并创建应用程序进程
app启动流程
1 launcher进程(activityManagerProxy) 采用binder IPC向system_server发起startActivity请求
2 system_server进程(ams)->zygote进程 发送创建进程的请求
3 zygote进程 fork新app进程
4 app进程(activityManagerPrixy)通知 system_service进程(ams)
5 ams 中的 AppliocationThreadProxy->创建 applicationThread->创建activityThread->handler.launchActivity->activity.oncreate开始执行生命周期
startActivity()
1 context.startactivity->launcherApps.startMainactivity热启动,通过binder通信->冷启动 通过仪表(mInstrumentation)启动activity,他会调用ams的startactivity,是一次跨进程通信
2 当ams验证activity的合法性 通过applicationThread回调我们的进程,也是一次跨进程通信->applicationThread就是一个binder,通过handler发送消息完成activity的启动
service启动
contextWraper 通过startService->contextimpl 通过startServiceCommon->lActivityManager 通过startService->AMS ->applicationThraed是一个binder->sendMessage发送 handle消息->处理消息
注册广播
registerReceiver->调用contextWrapper的registerReceiver->Contextimpl中的registerReceiverInternal()和registerReceiver()->AMS
发送和接受广播
contextWrapper的sendBroadcast()->contextimpl中的registerReceiverInternal()和registerReceiver()-AMS->broadcastQueue->ApplicationThread中performReceiver()->ReceiverDispatcher- >BroadcastReceive
\
4 PMS服务(packageManagerService)
安卓的核心服务之一,浮在应用程序的安装 卸载 信息查询等
pms启动流程
第一阶段: 扫描并解析xml,将其中的信息保存到特定的数据构造中
第二阶段: 扫描系统文件,在system中创建framework目录 ->扫描 system/app,system/priv-app 等一些目录的文件
第三阶段: 扫描data/app和data/app-private目录下的文件
第四阶段: 扫描完成->SDK版本不同会更新apk的授权,初始化用户定义的默认app等
第五阶段: 创建packageInstallerService是用于管理安装会话的服务
最后吧 pms注册到serviceManager
->1 启动Installer服务
2 获取设备是否加密,手机密码->加密了只解析core应用
3 调用pms.main初始化pms对象
4 如果没有加密操作它,管理A/B
5 执行updatePackageslfNeeded,完成dex优化
6 执行 performFstrimNeeded 完成磁盘维护
7 调用systemReady 准备就绪
apk扫描
Pms的构造方法中开启扫描->收集apk->解析apk
pms构造方法的核心任务
1 在data/system中创建一些文件用于存储应用信息
2 在systemConfig中读取系统配置来初始化
3 指定 /data目录下一些文件夹
4 读取packages.xml数据存到setting中
5 扫描指定文件目录下的apk包,解析内部的.xml文件
6 将扫描出来的信息写入到 packages.xml中
apk安装过程
吧apk信息通过io流写到PackageInstaller.session->吧apk信息交给pms处理->进行apk的copy操作
复制apk到data/app目录,解压安装包->资源管理apk里的资源文件->解析清单文件,在data/data目录下创建对应的应用数据目录->对dex文件进行有,保存在dalvick-cache目录->将清单文件中的四大组件信息注册到PMS中
动态权限申请
1 activity抵用requestPermissions进行动态权限申请
2 通过隐士意图激活界面,让用户选择是否授权
3 经过pms吧信息传递给PermissionManagerService处理
4 PermissionManagerService吧处理信息回调给pms->pms让packageManager的settting记录下相关授权信息
APK打包流程
1 首先 .aidl文件通过 aidl工具转换成编译器能处理的Java接口
2 资源文件通过aapt处理为 resources.arsc,并生成R.Java文件以保证源码编写时可以方便访问资源
3 通过Java compiler编译R.JAVA ,Java文件->编译成.class
4 将.class通过dex工具转换为.dex
5 通过apkBuilder将.dex打包成apk文件
6 签名工具对apk文件进行签名
7使用zipalign工具进行对其处理
签名原理
数字摘要
通过hash(散列算法)计算后得到一个固定长度的二进制,就是摘要
hash不可逆,唯一性,固定长度->常用的hash算法 MD5,SHA-1,SHA-2
签名过程
1 计算摘要->通过hash算法提取出原始数据的摘要
2 计算签名->私钥使用非对称加密算法对摘要加密,加密后的数据就是签名信息
3 写入签名->将签名信息写入原始数据的签名区块内
校验过程
1 提取摘要->用相同的hash算法从接收的数据中提取出摘要
2 解密签名->使用发送方的公钥对数字签名进行解密,解密出原始摘要
3 比较摘要->如果解密后的数据和提取的摘要一致就通过,不一致就是被第三方篡改不通过
Multidex的局限
dex很大的话,安卓.dex文件到data分区可能会ANR
可能无法在4.0之前的设备上运行
会请求非常打的内存分配
某些类可能必须要放到主dex中->通过gradle显示的把类放到主dex中,可以设置dex文件的大小
流程
扫码工程文件得到main-dex-list->进行拆分,将主,从dex的class文件分开
用dx工具对主,从dex的class文件分别打包成.dex文件,放到apk的目录
所有Application、ContentProvider以及所有export的Activity、Service、Receiver的间接依赖集都必须放在主dex,还需要找到入口类引用的其他类
应用启动只加载了主dex,其他dex需要我们在应用启动后进行动态加载安装
从apk中取出所有的dex,反射依次安装加载dex,放到baseDexClassLoader的 Element数组中
wms
window
一个抽象类,具体实现phoneWindow->创建window需要windowManager->window具体实现位于wms中
windowManager
应用与window之间管理接口,管理窗口顺序
wms
管理窗口的创建,更新和删除,显示顺序,运行在system_server,通过IPC调用和他通信
5 插桩
APP组成
1 AndroidManifest.xml->如果APP是一本书,那么这个文件就是他的封面和目录,加载了APP的名称 权限生命 包含的一系列信息
2 classes.dex->由项目源码生成的.class文件经过转换生成安卓系统可识别的.dex文件,安卓系统的字节码和标准jvm中的字节码是有区别的,所以APP中引入的第三方jar会被包含在.dex中
3 resources.arsc->包含编译后的二进制资源文件,在res文件夹下方一个文件,app会自动生成对应的id保存在R文件中,系统会根据ID寻找对应的资源路径,resources.arsc就是来记录id和资源文件位置的对应关系
4 res->未编译的资源文件
5 asserts->额外建立的资源文件,res目录下文件会在R文件中 生成对应的ID, assets没有对应id 通过AssetManager类的接口来获取
6 libs->存放ndk编出来的so库
7 MERA-INF->保存APP的签名和检验信息
AspectJ
implementation 'org.aspectj:aspectjrt:1.9.4'
id 'android-aspectjx'
classpath 'com.hujiang.aspectjx:gradle-android-plugin-aspectjx:2.0.0'
添加aspectjx的配置
hujiang对AspectJ的二次封装,暂时最高只支持gradle3.6.1而已->使用3.5.2可以用(classpath "com.android.tools.build:gradle:3.5.2")
\
接入AspectJx进行切面编码
对接入点进行拦截
Before :之前执行 After:之后执行 Around:之前之后分别执行
两种类型
call:插入在函数体里面 execution:插入在函数体外面
插桩技术分为两种
1 APT生成Java代码
在编译开始的时候生成代码
隔离了复杂的内部实现,减少了手工重复的工作量
2 AOP用于操作字节码
在生成dex文件之前直接修改.class文件
aop
1代码性能监控
1 定义切入点->applicationOnCreate,activity中on开头的生命周期方法
2 判断是否是onCreate,通过变量判断是否是第一次执行->如果是就统计时间
3定义一个存储信息的管理类->统一输出
2代码修改
埋点
setImageBitmap 修改bitmap大小
onclick 防止多次点击
监控OKHTTP的每一次请求
hook实现方法->字节码插桩 动态代理
6 EventBus
原理(观察者模式)
注册->缓存中查找->1拿到直接返回 2没有拿到,把当前类的订阅方法添加到缓存->添加到map集合中
发送->post->拿到当前线程->获取发送消息的类型(基本数据类型 对象)->根据类型获取订阅者和订阅方法->通过设置的 threadMode判断线程->通过反射直接调用订阅者的订阅方法完成通信
取消注册->从缓存的map集合中吧数据去掉
判读是否在主线程发送
eventbus发送的时候会初始化 MainThreadSupport对象,保存主线程的loop,在发送消息的时候会获取当前线程的loop和主线程的loop进行比较
在主线程执行
主线程发送主线程执行 main在主线程执行就在当前线程执行.
子线程发送主线程执行 在子线程发送,会有一个mainThreadPosted将信息的对象添加到一个队列中,是handler的子类用handler将信息发送到主线程执行
在子线程执行
子线程发送子线程执行 为BACKGROUND时,如果不在主线程发送,直接执行
主线程发送子线程执行 在主线程发送 创建backgroundPosted是Runnable的子类调用run方法从而在子线程中执行
7 Hermes跨进程
github.com/Xiaofei-it/… git地址
HermesEventBus
3步骤
1服务端注册
2客户端连接
3通信
8 数据库
greendao 核心分析
ORM框架 描述对象和数据库直接映射的元数据,将程序中的对象自动持久化到关系数据库中
SQLiteOpenHelper(创建数据库)->SQliteDatabase(创建表)->DaoMaster(对SQliteDatabase进行再次封装)->DaoSession(获取Dao对象管理者)
9 组件化
模块化 将项目中的共享的部分抽离出来,形成独立的Module
组件化 是模块化的一次变种. 核心是可以转化可以是library 也可以是apk
插件化 按业务划分成不同的插件,单位是apk,实现apk的动态加载,动态更新
arouter 路由原理
注解处理器->拿到注解-> 生成一张映射表(map<path,class>)
1 module依赖了 arouter-API和arouter-compiler ,在编译期arouter-complier通过扫描项目中的注解来生成对应的class文件
2 初始化 init方法 通过ClassUtils.getFileNameByPackageName扫描出前缀是alibaba.routes的类->放到set集合里面方便下次拿class
3 使用route直接loginactivity->在module的build目录下生成对应的class文件,这个文件实现了RouteRoot接口->吧对应的loginactivity信息通过RouteMete存到了map中
4 如果设置了重定向 会替换掉设置的url
5 跳转
1 在初始化节点吧所有的信息储存起来
2 初始化interceptorServiceImpl示例
3 加载路由的时候 先从routes中取->如果没有则去groupsindex中拿group信息->在拿最右的RouteMeta信息->将数学封装到postCard中->判断是什么类型
4 跳转navigation,两种形式->通过传递的class或者path,class去interceptorsIndex中找对应的iprovder,paht去groupsIndex中找,最终保存在providers中
\
映射表中包含 分组( map<组名,group>) 路由表(group<path,class>). 先从分组中的组名获取路由表group,在通过注解的path找到对应的class
\
注解和方法说明
实现DegradeService 全局降级策略
没有找到该路由地址会走的回调
实现SerializationService 添加全局序列化方式
在使用withObject 传递数据的时候会使用该SerializationService
实现PathReplaceService 重定向URL 跳转
\
源码文件目录说明
router-annotion 主要是定义注解的,用来存放注解文件
route注解 注明跳转的path
Modules注解 注明总共有多少个module
Module注解 注明当前moudle的名字
Modules,Module 注解主要是为了解决支持多 module 使用的。
router-compiler 主要是用来处理注解的,自动帮我们生成代码
1 根据@module和@modules注解,生成相应的 RouterInit文件
2 扫描 @Route 注解,并根据moudleName生成相应的java文件
扫描所有的Route注解,添加到 targetinfos list当中->调用generateCode生成相应的文件
router-api 是对外的 api,用来处理跳转的。
stub 这个是存放一些空的 java 文件,提前占坑。不会打包进 jar。
初始化router
调用init()->判断是否初始化过->调用routerinit方法初始化->在routerinit中调用map方法初始化->调用Router.getinstance().add()方法完成初始化
实现IProvider()接口会被保存到map中,通过string可以获取到这个类
\
源码解析(*)
init->1 通过hasInit变量判断是否初始化->_arouter.init()返回true设置给hasInit->_ARouter是真正的类, ARouter是对外暴露的API 装饰者模式
2 读取生成类的类名集合 debug模式下会更新版本号否则 缓存中读取->判断类型,通过反射实例化对象->如果没有开启debug模式 调试的时候不会更改版本号
3 通过反射获取项目里面带有com.alibaba.android.arouter.routes的所有的文件名,放到集合中,新版本通过插件来完成->反射实例化->放到集合中
4 不同的module中不能存在相同分组,就是第一段/, 会在对应的module中生成不同的iRoutGroup,会有一个被覆盖
5 集合中key是classname value是RouteMeta对象->里面包含了类的信息
6 RouteType是一个枚举用来标示注解类的路由类型,目前只有activity和fragment 也预留了 Service和Boardcast->目前用的是PROVIDER(2,"com.alibaba.android.arouter.facade.template.IProvider"),
_ARouter.afterInit()
初始化后调用->拦截器服务->在子线程调用拦截器并初始化
navigation路由跳转
调用_arouter的build()->PathReplaceService->继承IProvider的接口,预留给用户实现路径对台变更的功能->build()最后返回一个RouteMeta的子类Postcard
调用Postcard中的navigetion(),最总也调用_arouter中的navigetion
通过path寻找对应的RouteMeta->递归从内存中找,从注册的分组中找->如果有拦截器(在子线程中执行,因为可能会有耗时,里面维护了线程池)->没有拦截器就跳转
拦截器在子线程运行,有个统计拦截器的计数器
\
10 glide
用的双链表 运用httpurlConnection加载
先with(),再load(),最后into()
bitmapPool图片复用池
相当一个容器,防止重复申请内存的过程,使用的Lru算法
4.4以上获取图片大小
bitmap.getAllocationByteCount()
Glide的线程池
newDiskCacheExecutor 用于执行本地缓存任务的线程池
核心线程数1 最大线程数1 无序的堵塞队列 先进先出
newAnimationExecutor 用于动画
根据CPU获取最大线程数
newSourceExecutor 和 newUnlimitedSourceExecutor 用于执行网络请求
newSourceExecutor根据CPU核数最少4个
newUnlimitedSourceExecutor 核心线程数比较多
好处和区别
链式调用,监听生命周期,解耦
如何实现一个图片加载框架
解析路径: 图片来源多样,格式也不同,需要规范
缓存: 为了减少计算,和不必要的资源浪费
查找或者下载文件: 本地直接用,网络图先下载
解码转码: 数据流转成bitmap,对bitmap做一些裁剪
缓存: 得到bitmap之后,缓存方便下次使用
显示: 显示最后的结果
缓存
1 活动资源 图片资源正在使用,图片会被放到活动缓存(运用引用计数的方法 判断是否正在使用)采用的弱引用map
2 内存缓存 最近被加载,但是现在没有使用会放到内存缓存(如果内存不够超出最大值.就会把最早加入的移除掉)
LruCache算法采用的是最少使用策略.掉用trimToSize()判断缓存是否已满.满了就删除最少使用的数据
3 资源类型 图片存到磁盘中,过程可能修改图片参数(图片保存控件的大小)
4 原始数据 图片原始数据在磁盘中的缓存
活动资源中的图片->通过生命周期移除->会添加到内存缓存中
初始化 (**)
1 glide单利
Glide.with框架的入口->多个with(context,activity)-> glide.get()双重校验锁->如果glide创建过直接返回->如果没有创建通过i初始化 GliderBuilder对象,他是glider的构造器,构造一些参数和配置->initializeGlide() 真正初始化glide的方法
2 glideModule配置加载
glide4 通过@GlideModule注解并且继承AppGliderModule ->重写applyOptions方法 方法里面有个GlideBuilder->设置缓冲位置,缓冲大小,网络加载模块等->在initializeGlider()中通过解析器将用户设置的glideModule加载进去
3 glideBuilder构建glide单例
通过一系列的工具创建,请求池 本地缓存线程池 内存缓存 磁盘缓存,构建engine数据加载引擎->最后将engine注入glide
4 构建glide
赋值glideBuilder 注入工具->新建注册器(new Registry())->构建解码器和编码器->注册各个类型对应的解码器和编码器->新建图片显示目标对象工厂,并构建Glide上下文->最主要的是为glide初始化了模型转码器,解码器,转码器,编码器并对各种类型进行注册
5 glide构建成功
通过getRequestManagerRetriever()获得检索器,为每个请求页面创建一个RequestManager
6 生命周期管理
通过RequestManager(检索器)的get方法->先断言activity是否存在->如果存在就获取activity的fragmentManager->就是在当前页面添加一个无UI的fragment通过activityFragmentLifecycle来动态监听页面的生命周期--->主线程用的activityFragmentLifecycle-->子线程用的ApplicationLifecycle不对生命周期绑定
1 load()方法支持多种数据源
2 创建一个requestBuilder用来构建一个request请求工具
3 RequestBuilder.into()启动资源加载
生成请求参数,及显示目标->根据imageview设置的缩放类型配置
构建请求,并启动数据加载->创建request请求->将request添加到requestManager中进行维护->递归构建多个请求->请求开始会启动目标图片请求->请求失败会启动错误图片请求->如果都成功会返回一个singleRequst的单利
之后通过requestManager,request构建一个请求序列,并通过监听生命周期来动态管理requset的开启,暂停,恢复,销毁等
request缓存到imageview的tag中,如果需要自己存数据需要给tag设置一个key,比如 setTag(key,tag)
4 开启资源加载任务
1 创建资源索引key
生成一个可以需要资源本身,宽高,参数等,只有这样一样才会认为是一个相同的图片.否则重新执行加载
2 活动缓存或者内存读取
资源正在使用,直接用已有的. 没有在使用查看时候在内存中
3 通过key获取已经存在的加载任务->如果没有获取到就新建加载任务->新建解码任务,真正执行数据加载和解码的类->缓存加载任务->开启解码任务
Glide将每一个请求都封装为一个解码任务DecodeJob,并扔到线程池中,以此来开启任务的异步加载。
4 解码任务
尝试从处理过的内地资源加载图片->尝试从未处理过的原始本地资源加载->尝试从远程加载图片
上面👆🏻3步骤对应3个解码器 ResourceCacheGenerator->DataCacheGenerator->SourceGenerator
先判断缓存中是否有,如果没有在进行解码和转码
有多个解码器和 转码器
URL的解码器 把流传给对应的解码器->经过对流的包装->生成一个bitmap->根据view的大小和缩放类型进行裁剪->最后设置给imageview
本地id的解码器 将URI转换resid->先是从v7方法获取->如果捕获到异常 通过context生成->生成Drawable->设置给imageview
2个队列 1个准备队列 1个运行队列
\
写入 和读取顺序
写入 弱引用->lru->磁盘
读取 lru->弱引用->磁盘
\
11 RXJAVA
观察者模式
一对多的依赖关系,一个对象改变,则依赖于他的对象会得到通知并自动更新
被观察者(Observable)
5种被观察者 可以相互转换
observable 被观察者
flowable 看成observable的实现 只是他支持背压
single 只能发错一个暑假或一个错误
completable 只有onComplete和onError
Maybe 没有onNext 需要onSuccess发射数据
观察者(observer)--当前线程运行
onSubscribe() 订阅观察者的时候被调用
onNext 发送该事件时 观察者会调用onNext
onError 发送该事件之后 其他事件不会继续发送
onComplete 发送改事件之后 其他事件不会继续发送
订阅(subscribe)
背压
出现原因
上游发射的数据速度,大于下游处理数据的速度,没来得及处理的数据不会丢失,而是会存放在异步池中,数据一直得不到处理积累过多会内存溢出
解决思路
观察者主动去被观察者拉取数据,被观察者等在发送数据的通知
背压策略
MISSING 通过create创建Flowable相当于没有指定背压策略,不会对onNext发射的数据做缓存或丢弃,需要下游通过背压操作符
ERROR 放入异步缓存池中的数据超限 会抛出异常
BUFFER
DROP 缓存池数据超限(最大128),会丢掉上游发射的数据
LATEST 和DROP策略一样,不同的是会将最后一条数据放到缓冲池
生命周期
github.com/trello/RxLi… (3.0需要androidx的支持,可以使用低版本2.2.0)
主要操作 继承RxAppCompatActivity
设置被观察者.compose(this.bindUntilEvent(ActivityEvent.PAUSE))
12 OKHTTP
7层模型
应用层-> 应用层-> 应用层(为应用程序提供服务) 表示层(数据喝水转化加密) 会话层(建立 管理和维护)
传输层-> 传输层-> 传输层(建立 管理和维护端到端的链接)
网络层-> 网络层-> 网络层(ip地址及路由选择)
网络接口层-> 数据链路层 物理层->数据链路层(通过介质访问和链路管理) 物理层(物理层)
tcp/ip 4层 tcp/ip5层 osi 7层
拦截器
自带拦截器 通过责任链一个一个的执行
RetryAndFollowUpInterceptor 重定向
BridgeInterceptor 桥接
CacheInterceptor 缓存
ConnectInterveptor 连接
CallServerInterceptor 发送数据
应用拦截器(不可以重定向) new OkHttpClient().newBuilder().addInterceptor()
\
网络拦截器 new OkHttpClient().newBuilder().addNetworkInterceptor();
拦截url
class DDDd implements Interceptor { @Override public Response intercept(Chain chain) throws IOException { Request request = chain.request(); HttpUrl url = request.url(); HttpUrl parse = HttpUrl.parse(""); HttpUrl newFullUrl = url .newBuilder() .scheme(parse.scheme()) .host(parse.host()) .port(parse.port()) .build(); Request.Builder builder = request.newBuilder(); Request.Builder url1 = builder.url(newFullUrl); Response proceed = chain.proceed(url1.build()); return proceed; } }
请求流程
创建okhttpclient(创建者模式)->分发器,拦截器,连接池,线程池->newCall() 创建request(创建者模式,封装请求参数get/post,body,url封装成httpURL对象)-->同步和异步请求->添加一个拦截器的集合(handle拦截器,连接拦截器,重试拦截器)->response(返回参数 拦截器)
复用池
存放之前请求过的链接,下次请求的时候可以复用,有一个线程池->可以从请求队列中存和取线程->循环将超时的请求从队列中清除掉
原理
主要实现异步和同步的网络操作,创建不同call对象,由于任务很多会维护一个线程池来处理,会创建3种队列,分别是正在执行的同步,异步队列和准备队列.执行任务采用先进先出原则,处理每个任务都会交给后面的拦截器负责,有请求准备拦截器,缓存拦截器,网络连接拦截器.每个拦截器组成了一个责任链的形式最后返回response信息.
底层是通过java的socket发送http请求和响应,OKHTTP实现了连接池,即对于同一主机的多个请求,可以共用一个socket连接而不是每次发送完http请求就关闭.读写操作使用的okio库进行了一层封装
okhttpClient: 通信客户端,用来同意管理发起请求与解析相应
call: 一个接口
Request: 封装请求的信息包括,url header 请求体
interceptor:拦截器
注意一下OKhttp缓存机制
\
\
13 retrofit
创建注解->创建call发起请求->request的请求参数封装
retrofit源码分析
1通过工厂模式配置各种参数,通过带动态代理和注解实现网络请求.
2首先解析注解,传参,将它们封装成我们所熟悉的request。然后通过具体的返回值类型 new一个OkHttpCall
3这个OkHttpCall算是OkHttp的包装类,用它跟OkHttp对接
4有不同的工厂,rxjava java8 guava,比如rxjava可以根据回调的返回值生产具体的callAdapter
5之后OKHTTP进行请求,等待返回值
6retrofit默认用responseBody来接受服务器返回的数据,如果想要转换实体,需要在创建的时候addConvertFactory设置一个解析器.
7最后通过声明的observeOn线程回调给上层
解析
retrofit类构建用到的7大成员变量
serviceMethodCache: 是map key是http请求方法 value是即系网络请求接口后的对象,缓存网络请求信息的对象
callFactory: 用于生产okhttpClient
baseUrl: 网络请求基地址
converterFactories: 数据转换工厂,吧Reponse转换成Java对象
callAdapterFactories: 网络请求适配器工厂集合,吧call对象转成其他类型,比如rxjava
callbackExecutor: 用于执行回调,处理异步网络请求
validateEagerly: 标示位,标示是否立即解析接口中方法
构建Builder类表示retrofit是构造者模式
platform是个单利,获取当前是否安卓平台->返回Android对象->Android对象中defaultCallbackExecutor()来返回一个默认的回调方法执行器,用于从子线程切换到主线程
baseUrl() 将string类型的URL封装成httpUrl类型的url->判断最后是否以斜杠结尾否则抛异常
将当前对象加到converterFactories数据转换工厂集合中->gson数据解析
build()->首先判断baseURL是否为空->创建OkHttpClient()对象并赋值给callFactory->初始化回调器就是 Android对象->复制网络请求适配器添加平台模式->最后传给new Retrofit()中进行对象创建
retrofit.create(),使用的是代理模式,获取具体平台->反射获取我们传递对象的方法->遍历接口中的方法->最后调用loadServiceMethod
loadServiceMethod->有一个ServiceMethod,它是我们定义的接口方法Call通过动态代理封装转化而来的,一个ServiceMethod对应一个接口方法Call
同步请求->call.execute()最后实现是通过OKhttp来哦实现->创建call(),解析request,实际上是调用了OkHttpClient中的RealCall,后面的工作就转交给Okhttp
动态代理: 动态指的是运行期间,代理指的是实现了某一个接口的具体类,实现了接口的方法,称之为代理
在我们调用实现类的方法时,会调用InvocationHandler的回调方法invoke()
整体流程
我们在调用 我们定义接口的 方法时,会调用 InvocationHandler 的 invoke方法
然后执行 loadServiceMethod方法并返回一个 HttpServiceMethod 对象并调用它的 invoke方法
然后执行 OkHttpCall的 enqueue方法
本质执行的是 okhttp3.Call 的 enqueue方法
当然这期间会解析方法上的注解,方法的参数注解,拼成 okhttp3.Call 需要的 okhttp3.Request 对象
然后通过 Converter 来解析返回的响应数据,并回调 CallBack 接口
retrofit.create()
1 使用的是代理模式,获取具体平台->反射获取我们传递对象的方法->遍历接口中的方法->最后调用loadServiceMethod
2 通过ServiceMethod.parseAnnotations(this, method);方法创建一个 ServiceMethod 对象,用map缓存起来
3 HttpServiceMethod.invoke()创建一个call对象,是okHttpCall,返回一个默认的callAdapted
OkHttpCall.enqueue()
请求的关键,以及怎么使用okHttp进行请求的,如何解析response,如何回调
1 声明一个okhttp3.Call对象,用来进行网络请求
2 给OKhttp3.call进行辅助
3 调用OKhttp3.call的enqueue方法,进行真正的网络请求
注解参数解析
1 通过 RequestFactory 解析注解,然后返回 RequestFactory 对象,把 RequestFactory 对象往 HttpServiceMethod 里面传递
2 遍历api接口上定义的方法注解,然后解析注解
3 最终用requestFactory的值来构建okhttp3.Request的对象
4 addConverterFactory(GsonConverterFactory.create()) 解析数据
使用rxjava做网络请求
addCallAdapterFactory(RxJava2CallAdapterFactory.create())
\
\
\
\
1. 通过解析网络请求接口的注解配置网络请求参数;
2. 通过动态代理生成网络请求对象;
3. 通过网络请求适配器将网络请求对象进行平台适配;
4. 通过网络请求执行器发送网络请求;
5. 通过数据转换器解析服务器返回的数据;
6. 通过回调执行器切换线程(从子线程切回主线程);
7. 通过主线程处理返回的结果。
\
\
拦截器执行原理:www.jianshu.com/p/e0f324fd9…
重试和重定向拦截器:www.jianshu.com/p/40636d32c…
基础拦截器:www.jianshu.com/p/fab2d74de…
缓存拦截器:www.jianshu.com/p/44fad764c…
连接拦截器:www.jianshu.com/p/a3a774fdf…
写入拦截器:www.jianshu.com/p/aa77af625…
\
网络配置层: 利用Builder模式配置各种参数
header拼接层: 负责吧用户构造的请求转换成发送给服务器的请求,吧服务器返回的给用户相应
连接层: 实现了网络拦截器,内部拦截器,安全认证,链接池等,没有发起真正链接,只做了连接器的一些参数
数据响应层: 从服务器读取响应的数据
请求与响应
流程: dispatch而不对从request queue里取出请求->根据是否已经存缓存->从缓存或者服务器获取请求的数据->分为同步和异步两种->同步调用 call.exectute()直接返回当前请求的response->异步调用call.enqueue将请求(AsyncCall)添加到请求队列->通过callback获取服务器返回的结果
1 请求的封装
由OKhttp发出,真正的请求都被封装在了接口call的实现类realCall中->在realCall的构造器中传入了OKhttpClient,request,websocket并创建了重试和重定向拦截器
2 请求的发送(RealCall.Java)
同步请求: 有一个synchronized锁->直接执行,并返回请求结果
异步请求: 多了一个callback的回调,创建一个AsyncCall并将自己加入处理队列中->AsyncCall是一个Runable->Dispatcher会调度ExecutorService来执行这些Runable
同步和异步都是Dispatcher在处理,异步多了一个线程调度
3 请求的调度器Dispatcher
维护了3个ArrayDeque()队列,准备运行的异步请求,正在运行的异步请求,正在运行的同步请求
同步请求直接加到队列
异步请求 正在运行的异步不得超过64,同一个host下的异步不得超过5个,否则就会放到准备中的队列
4 请求的处理
创建一个list, 先通过OKhttpClient获取用户自定义的拦截加到即合理->在添加重试.重定向,缓存,等一些拦截器,采用责任链模式,上一级执行完通知下一级
拦截器
RetryAndFollowUpInterceptor:负责重试和重定向。
维护一个管理类,初始化一个socket链接,重试最多20次
BridgeInterceptor:创建连接桥,负责把用户构造的请求转换为发送给服务器的请求,把服务器返回的响应转换为对用户友好的响应。转换的过程就是添加一些服务端需要的header信息
CacheInterceptor:负责读取缓存以及更新缓存。
ConnectInterceptor:负责与服务器建立连接。
CallServerInterceptor:负责从服务器读取响应的数据。
连接机制
创建连接: 查找是否有完整的连接可用->连接池中是否有可用的链接->没有就自己创建一个加入连接池
会创建出来一个RealConnection对象调用connect方法建立连接
连接池:
频繁的进行建立sokcet和断开浪费资源和时间,http的keepalive连接可以解决->支持每个连接的最大连接数是5,默认生命5分钟(保持存活时间)->连接池(ConnectionPool)对连接进行回收和管理
连接池会维护一个线程池->线程池只能运行一个线程(用来请求无用连接)
socket基于tcp->只需要在发送socket数据的时候添加http头部就变成http报文了
使用SynchronousQueue 队列一次只能有一个
分发器
最多64个请求,同一个域名5个,线程池,3个队列
异步请求
不超过最大限制,添加到正在执行队列,同时提交给线程池,否则加入等待队列->有任务执行完了从执行队列移除,遍历等待队列移动到执行队列
\
缓存
缓存策略
强制缓存: 服务端返回了缓存的过期时间
对比缓存: 需要每次请求让服务端进行比较,是否还可以继续用
缓存管理
缓存机制是基于DiskLruCach
\
13热修复
类加载原理
PathClassLoader 只能加载已经安装到安卓系统中的apk文件是安卓默认的类加载器
DexClassLoader 可以夹杂任意目录下的dex/jar/apk/zip文件,
都是将相关参数初始化一个DexPathList, DexPathList的构造方法就是将传递过来的补丁文件放到一个数组中->所有的.dex文件都在这个Element[] dexElement 数组中
查找一个名为name的class类->DexClassLoader通过DexPathList对象获取class->对之前创建好的数组进行遍历->有就返回没有就null
当一个类出现bug->将类打成一个补丁文件->封装成数组的对象->将对象放到之原有数组的最前端->当DexClassLoader去加载类时->优先把前面的加载出来->由于双亲委托加载机制的特点,之前有bug的类不会加载
\
内存泄漏检测的核心原理弱引用和队列实现(WeakReference和ReferenceQueue)
将需要回收activity做一个标记key包装到弱引用中->给弱引用设置队列->在一个set中添加相应的key记录->自动触发GC遍历队列中的对象->和set中需要删除的做对比->队列中没有set中标记的对象就是内存泄漏了
在一个activity执行完onDestroy之后,将它放入弱引用中,弱引用和队列关联,查看队列有没有该对象->没有执行GC,->还是没有就是内存泄漏了->用HAHA开源库去分析dump之后的heap内存
RefWatcher是leakCanary的核心类,用来检查对象释放发生内存泄漏
为什么新版本不需要初始化了
LeakCanary 2.0利用了ContentProvider无需显示初始化的特性来实现了自动注册
activityThread->handleBindApplication->初始化application->installContentProvides()初始化ContentProvides->ContentProvides.oncreate() ->application.oncreate()
LeakCanary有个继承ContentProvides的类->在里面初始化->LeakCanary的一个工程中在清单文件注册了一个provider->APP进程启动的时候,会自动注册provider并执行其中的onCreate方法->注册监听application的ActivityLifecycleCallback
流程
RefWatcher.watch->install->activityDestroyedWatch 和 fragmentDestroyedWatch
activityLifecycleCallbacks->activity销毁会执行 onActivityDestroyed->registerActivityLifecycleCallbacks注册回调->由activityDestroyedWatch来负责监听activity生命周期
流程解析
1 RefWatcher.watch() 创建了一个KeyedWeakReference(弱引用)用于观察对象
2 在后台线程中,检查是否被清除,并且是否没有触发GC
3 如果引用没有被清除,会把堆栈信息保存在文件 .hprof文件里面
4 heepAnalyzerService开启在独立的进程中,并且HeepAnalyzer使用了HAHA开源库解析了制定时空的堆栈文件 heep dump
5 从heap dump中HeepAnalyzer(分析仪)根据一个独特的引用key找到了KeyedWeakReference
6 HeepAnalyzer确定了是否有泄漏,技术GC Roots的最短强引用路径,创建导致泄漏的链式引用
7 结果传回到app进程中的DisplayLeakServie,泄漏通知展示
\
反射
获取宿主和插件的dexElement
反射获取 DexPathList和BaseDexClassLoader->在通过宿主classLoader获取宿主的dexElement->插件dexElement需要自定义类加载器,通过路径加载
合并宿主的dexElement和插件的dexElement
将合并后的dexElement赋值到宿主的dexElement
就可以通过反射在宿主里面获取插件里面的类
启动插件里面的activity
通过两次hook
使用本地的activity替换插件没有注册的activity->让ams去验证->延迟成功在吧假的activity替换成真的activity
1 通过反射获取系统ActivityManager->获取IActivityManager
2 通过动态代理替换系统的c->执行IActivityManager的每个方法时都会走回调->在回调里面判断startActivity()->从中把真的intent 替换成假的intent,并吧真存到假的intent里面(intent.putExtra)
3 ams检查完->activityThread会用handler发消息来启动activity->从handler.msg中获取假的intent->从假的intent中取出真的->并替换
加载插件的资源文件
在插件里面创建一个AssetManager->专门来加载插件的资源文件
反射AssetManager->通过addAssetPath方法->把插件apk包的路径传进去->返回一个Resources
16Gradle
构建流程
初始化阶段->在初始化阶段 Gradle会决定那些项目要参与构建,并且为每个项目模块创建一个与之对应的project实例
配置阶段->配置工程中每个项目的模块,并执行包含其中的配置脚本
任务执行->执行每个参与构建过程的Gradle task
打包提速
提升最新的版本
开启离线模式->所有的编译操作会走本地缓存,缩短编译时间
配置AS的最大堆内存
删除不必要的module或者合并->稳定底层base打包成aar
去除项目中的无用资源
第三方库优化
编译的时间分析
./gradlew assembleDebug --profile 生成诊断文件 包含以下部分
summary->查看初始化 启动时间,setting,loading projects
configuration->查看各个module花费的时间
dependency Resolution->gradle在对各个task进行依赖关系解析时所花费的时间
task Execution->执行各个gradle task所花费的时间
面向对象 元编程
调用类中是否有此方法,如果没有->执行MetaClass(),如果没有->执行methodMissing(),如果没有->执行invokeMethod(),如果没有异常