1. Binder跨进程通信
- 进程间通信方式对比
Socket(数据拷贝两次),内存共享(无需拷贝),Binder(数据拷贝一次) - 为什么Intent不能传递大数据?
进程初始化申请的虚拟空间只有1M-8K,映射的物理空间只有这么大
2. Handler
子线程Handler.sendMessage()->MessageQueue.enqueueMessage()
主线程Looper.loop()->MessageQueue.next() -> msg.target.dispatchMessage(msg) -> 主线程handler.handleMessage()
ActivityThread.main() -> Looper.prepareMainLooper() 创建主线程的Looper
1.子线程到主线程通信的原理?线程间内存共享
2.handler内存泄漏的原因?匿名内部类持有外部类对象,GCRoot
sThreadLocal->Looper->MessageQueue->msg->handler->Activity, 解决方案:静态内部类,removeMessages(),Activity弱引用
3.MessageQueue中存储的Message有上限吗?没有上限(app运行依赖MessageQueue, ViewRootImpl.performTraversal() 渲染, 屏幕刷新也是Message);为什么这么设计?生产者消费者设计模式;能不能用阻塞队列代替MessageQueue? 不能,Message不能有上限
4.Handler如何处理发送延迟消息?
Handler.sendMessageAtTime()->MessageQueue.next()->nativePollOnce()->epoll机制
5.使用Message时应该如何创建它?Message.obtain() ,省略了new Message的过程,sPool 复用,享元设计模式(RecyclerView复用机制,AMS中的ActivityRecord复用机制),频繁的创建释放导致内存抖动,内存抖动导致OOM和卡顿;
6.handler没有消息处理是阻塞的还是非阻塞的?为什么不会有ANR产生?
阻塞的,ANR(按键事件5s没响应,Provider10s, Service20s)
7.子线程如何创建handler?
Looper.prepare(), Looper.loop()
8.子线程中维护的Looper,消息队列无消息的时候的处理方案是什么?有什么用?
主线程即UI线程是不能退出的,ActivityThread.main()是不能退出的,界面的刷新,Activity/Fragment的生命周期,Service和广播都是Message
3. 内存性能分析
-
内存抖动:短时间频繁创建对象和销毁对象,会导致APP整体卡顿,甚至OOM的可能,为什么内存抖动会导致OOM?在新生代上创建大量的对象,而新生代的空间比较小,就会腾挪一部分老年代,如果有大对象满足进入老年代,如果老年代空间不够或者没有连续的空间来存储大对象,就会导致OOM
内存实例分析:
内存呈现锯齿状,底部有一排垃圾桶,伴随着频繁GC
Record Java/Kotlin Allocations -> Record, Allocations排序下,点击char[], 查看右下角的Allocation Call Stack代码调用栈, -
内存泄漏:对象无法释放
可达性分析角度,静态变量,线程栈变量,常量池会成为GCRoots的根,如果一个对象在GCRoots的引用链上根可达就不会被回收,如果根不可达就会被回收,如静态变量引用activity,静态是全局的,activity执行了onDestroy,activity也无法释放导致内存泄漏
内存泄漏的核心原理
LeakCanary: 都会捕捉堆转储,Android Profiler和MAT在开发过程中检测,而LeakCanary可以分析线上的内存泄漏,LeakCanary通过hook Android的生命周期来自动检测Activity和Fragment何时被销毁,何时应该被垃圾回收
-
内存溢出OOM和内存实时监控
导致OOM常见原因:
加载大图片,内存泄漏
捕捉OOM:OutOfMemoryError-》Error-》Throwable -
Android Profiler Android性能分析器,内存实时图表,捕捉堆转储Capture heap dump, 导出hprof文件;强制执行垃圾回收force garbage collection, 跟踪内存分配
MAT, 执行MemoryAnalyzer.exe, 使用MAT需要转换hprof文件,sdk/platform-tools下有个hprof-conv.exe,控制台执行hprof-conv 1.hprof 1-conv.hprof, MAT file->Open heap dump 直方图
MAT->merge shortest paths to GC Roots, 排除软弱引用,exclude all phantom/weak/soft references
4. 图片压缩
质量压缩,尺寸压缩,Native压缩
-
质量压缩:Bitmap.compress(Bitmap.CompressFormat.JPEG,quality,fos),JPEG有损压缩,PNG是无损压缩
-
尺寸压缩:Options,属性inJustDecodeBounds,如果该值为true,那么将不返回实际的bitmap,也不给分配内存空间这样就避免内存溢出了,可以查询图片的信息,options.outHeight 图片原始高度和 options.outWidth 图片原始宽度;inSampleSize=2 即取出的缩略图的宽和高都是原始图片的1/2
-
Native压缩
libjpeg-turbo 图片编解码器
5.插件化
插件化的优点:减小安装APK的体积,按需下载模块;动态更新插件;宿主和插件分开编译,提升团队开发效率;解决方法数超过65535问题;
组件化和插件化的区别:组件化是将一个APP分成多个模块,每个模块都是一个组件(module),最终发布的时候将这些组件合并成一个统一的APK;插件化是将整个APP拆分成很多模块,每个模块都是一个APK,最终打包的时候将宿主APK和插件APK分开打包
常用类加载器:BootClassLoader 用于加载系统常用类;PathClassLoader 加载应用程序类;DexClassLoader
自定义一个String类,让他代替系统的String类,能做到吗?不能,双亲委派机制保证系统类的安全
Hook: 在某段SDK源码逻辑执行的过程中,通过代码手段拦截执行该逻辑,加入自己的代码逻辑。
- 插件化架构深入解析
1.插件加载,2.插件化中的组件支持,3.插件化中的资源加载 - 面试题:简述Activity的启动流程
Context.startActivity->ContextImpl.startActivity->Instrumentation启动Activity,它会调用AMS的startActivity方法,这时一个跨进程过程,当AMS校验完Activity的合法性后,会通过ApplicationThread回调到我们的进程,这也是一次跨进程过程,而ApplicationThread就是一个binder,回调逻辑是在binder线程池中完成的,所以需要通过Handler H切换到UI线程,第一个消息是LAUNCH_ACTIVITY,它对应handleLaunchActivity,在这个方法里完成了Activity的创建和启动。 - 面试题:raw目录和assets目录有什么区别
raw: 在XML中是可以访问的,Android会自动为目录中的所有资源生成一个ID
assets: 只能通过AssetManager访问,XML中不能访问;不会生成ID
6.高级UI
滑动类型:1.scroll:手指没有离开屏幕的滑动;2.fling:手指离开屏幕后的惯性滑动
CoordinatorLayout:协调员布局
0.协调员布局是什么效果?原始状态相当于FrameLayout
1.协调员是谁?coordinatorlayout
2.协调谁?coordinatorlayout的子女
3.协调什么内容?behavior决定的,a.可以处理子女的事件分发;b.响应依赖的view的改变,删除;c.响应隶属的view的测量和布局;d.响应coordinatorlayout的嵌套滑动事件,事件由后代触发
4.怎么协调?通过Behavior
自定义View: 构造-》测量-》布局-》绘制-》事件分发-》嵌套滑动-》滑动冲突处理
fling是一个手势-》手势是由事件组成-》action_down ...action_move...action_up
recyclerview adapter里面包含了viewholder的创建-》违背solid的单一职责原则-》开闭原则
getItemCount-》getItemViewType-》onCreateViewHolder-》onBindViewHolder
retrofit + okhttp + kotlin协程 + Moshi
协程:a.用同步的方式写异步的代码; b.一个页面有可能有多个网络请求,多个网络请求同时发出,同步等待结果合并,页面上只会收到一次通知
7.okhttp和retrofit
- 使用
同步execute: 需要创建线程new Thread, 返回结果runOnUiThread返回到主线程刷新Ui;异步enqueue
GET请求,POST请求(表单和RequestBody),文件上传(MultipartBody),自定义拦截器addInterceptor - okhttp最大请求数maxRequests:64; 同一个主机的最大请求数maxRequestsPerHost:5
- okhttp默认支持5个并发KeepAlive,链路默认的存活时间为5分钟
- 优点:load faster and saves bandwidth
- 双任务队列机制:
readyAsyncCalls等待队列,runningAsyncCalls正在执行队列,如果runningAsyncCalls大于最大请求maxRequests(64),readyAsyncCalls里任务继续等待。如果小于最大请求数64,readyAsyncCalls里任务会添加到runningAsyncCalls正在执行队列中,然后交给线程池(线程池没有核心线程数,有任务过来立即执行),循环执行AsyncCall - 拦截器:责任链设计模式
- TCP三次握手和四次挥手
三次握手:客户端发送请求建立连接,服务端收到请求后立即回应,客户端收到回应后打开连接并通知服务端,服务端再次收到消息也打开连接;
四次挥手:客户端数据发送完成告诉服务端申请断开连接,服务端收到断连并回应,客户端继续等待最后数据的传送,服务端业务完成再次发送回应消息并断开连接,客户端收到回应,再次发送一次确认并断开; - Retrofit使用
支持RESTful API设计风格,表述性状态转移
GET请求(@GET, @Query, @QueryMap)
POST请求(@POST)
表单(@FormUrlEncoded, @Field, @FieldMap)
Body(@Body)
文件上传(@Multipart,@Part)
8.glide
-
glide缓存机制
活动缓存:页面加载了5张图片,这5张图片都是活动缓存;内存缓存:LRU算法;磁盘缓存;网络缓存 -
glide怎么实现页面生命周期
Glide.with(context)会传递一个上下文,glide会给这个上下文添加一个的空的fragment,fragment和activity生命周期是同步的
《Android进阶解密》
###Android系统启动过程
init进程启动,
解析init.rc配置文件并启动zygote进程,
init进程初始化和启动属性服务,
zygote进程启动过程:
创建Java虚拟机并为Java虚拟机注册JNI方法,启动SystemServer进程
SystemServer进程被创建后,启动Binder线程池,创建SystemServiceManager启动各种系统服务
Launcher启动过程
Launcher=Android系统的桌面
ActivityStack描述Activity堆栈
SystemServer->AMS->ActivityStackSupervisor->ActivityStack
被SystemServer进程启动的AMS会启动Launcher
Launcher如何显示应用图标?Launcher.onCreate()
###应用程序进程启动过程:
AMS请求Zygote创建新的应用程序进程(Socket通信),Zygote进程通过fork自身创建应用程序进程,这样应用
程序进程就会获得Zygote进程在启动时创建的虚拟机实例,创建了Binder线程池和消息循环
AMS发送启动应用程序进程请求,Zygote接收请求并创建应用程序进程
----> 应用程序进程主线程管理类ActivityThread.main
Binder线程池启动过程 ProcessState#startThreadPool
消息循环创建过程 ActivityThread.main里创建主线程Looper, H类(应用程序进程主线程的消息管理类)
继承自Handler,用于处理主线程的消息循环
###四大组件的工作过程
根Activity的启动过程(指的是应用程序启动的第一个Activity,也可以理解为应用程序的启动过程)
Launcher请求AMS过程: Launcher->Activity->Instrumentation(主要用来监控应用程序和系统的交互)
->IActivityManager(IBinder类型的AMS引用)->AMS
IActivityManager是AIDL工具编译时自动生成的,要实现进程间通信,服务端也就是AMS只需要继承IActivityManager.Stub类
Launcher.startActivitySafely->...->AMS.startActivity
AMS到ApplicationThread的调用过程:
AMS->ActivityStarter->ActivityStackSupervisor->ActivityStack->ApplicationThread
ApplicationThread来与应用程序进程进行Binder通信,ApplicationThread是AMS所在进程(SystemServer进程)和应用程序进程的通信桥梁
ActivityThread启动Activity的过程:
ApplicationThread#scheduleLaunchActivity->H.sendMessage(LAUNCH_ACTIVITY)->
ActivityThread#handleLaunchActivity->ActivityThread.performLaunchActivity->Instrumentation->Activity#onCreate
根Activity启动过程中涉及的进程:Zygote进程,Launcher进程,AMS进程(SystemServer进程),应用程序进程;
普通Activity启动过程涉及几个进程?AMS和应用程序进程
Service的启动过程:
ContextImpl到AMS的调用过程: ContextImpl#startService->通过AMS的代理IActivityManager最终调用的是AMS#startService
ActivityThread启动Service: AMS#startService-->ApplicationThread#scheduleCreateService-->
H.sendMessage(CREATE_SERVICE)->ActivityThread#handleCreateService
Service的绑定过程:
ContextImpl到AMS的调用过程:ContextImpl#bindService->AMS#bindService->ActivityThread#scheduleBindService
->H.sendMessage(BIND_SERVICE)->ActivityThread#handleBindService--->ServiceConnection#onServiceConnected
ProcessRecord用于描述一个应用程序进程,
ActivityRecord用于描述一个Activity,TaskRecord用于描述一个Activity任务栈,
ServiceRecord用于描述一个Service,ConnectionRecord用于描述应用程序进程和Service建立的一次通信
广播的注册,发送和接收过程:
广播的注册过程: 静态注册(由PMS完成注册过程)和动态注册
ContextImpl#registerReceiver->AMS#registerReceiver请求AMS注册广播接收者
广播的发送和接收过程:
ContextImpl#sendBroadcast->AMS#broadcastIntent-->ApplicationThread#scheduleRegisteredReceiver
Content Provider的启动过程:
ContextImpl#getContentResolver
query方法到AMS的调用过程: ContentResolver#query->AMS#getContentProvider
AMS启动Content Provider的过程: ActivityThread#handleBindApplication->ActivityThread#installContentProviders
###理解上下文Context
Context上下文,应用程序环境信息的接口
使用: 启动Activity,访问资源,调用系统级服务,弹出Toast,创建Dialog
一个应用程序进程有多少个Context=Activity和Service的总个数加1
继承关系:Activity->ContextThemeWrapper->ContextWrapper->Context; ContextImpl->Context; Service->ContextWrapper
装饰模式:ContextWrapper是装饰类,它对ContextImpl进行包装
Application Context的创建过程: Instrumentation#newApplication 通过反射创建Application
Application Context的获取过程: 通过getApplicationContext方法来获得Application Context,
ContextWrapper#getApplicationContext->ContextImpl#getApplicationContext->LoadedApk#getApplication
Activity的Context创建过程: ActivityThread#performLaunchActivity->ActivityThread#createBaseContextForActivity
Service的Context创建过程: ActivityThread#handleCreateService-> ContextImpl#createAppContext
###理解ActivityManagerService(AMS) 系统服务
Android7.0的AMS家族:
ActivityManagerNative(AMN): 抽象类,具体功能交给子类AMS处理, AMP是AMN的内部类,它们都实现了IActivityManager接口
ActivityManagerProxy(AMP):AMS的代理类,通过AMN#getDefault获取(将IBinder类型的AMS的引用封装成AMP)
Instrumentation#execStartActivity->Binder进程间通信->AMN#onTransact->AMS#startActivity
AMP是Client端,AMN是Server端,AMP就是AMS在Client端的代理类,AMN实现了Binder类,这样AMP和AMS接可以通过Binder来进行进程间通信
Android8.0的AMS家族:
Instrumentation#execStartActivity->AIDL进程间通信->AMS#startActivity
IActivityManager类是由AIDL工具在编译时自动生成的,服务端AMS只需要继承IActivityManager.Stub类就可以和ActivityManager实现进程间通信了
IActivityManager是AMS在本地的代理
AMS的启动过程: AMS是在SystemServer进程中启动的
SystemServer#main->SystemServer#run 创建SystemServiceManager,它会对系统服务进行创建,启动和生命周期管理 ->
SystemServer#startBootstrapServices
系统服务分为3种:引导服务(AMS, PowerManagerService, PackageManagerService),核心服务和其他服务(WMS)
AMS与应用程序进程:
启动应用程序时AMS会检查这个检查这个应用程序需要的应用程序进程是否存在,如果不存在,AMS会请求Zygote进程创建需要的应用程序进程
Zygote的Java框架层中,会创建一个Server端的Socket,这个Socket用来等待AMS请求Zygote来创建新的应用程序进程
AMS重要的数据结构:
ActivityRecord: 用来描述一个Activity,内部存储了Activity的所有信息
TaskRecord: 用来描述一个Activity任务栈,内部存储了任务栈的所有信息, TaskRecord中包含一个或多个ActivityRecord
ActivityStack: 是一个管理类,用来管理系统所有的Activity, Activity任务栈存储在ActivityStack中,ActivityStack包含一个或多个TaskRecord, 它是TaskRecord的管理者
Activity栈管理:
Launch Mode: Activity的启动方式,无论是哪种启动方式,所启动的Activity都会位于Activity栈的栈顶
standard: 默认模式,每次启动Activity都会创建一个新的Activity实例
singleTop: 如果要启动的Activity已经在栈顶,则不会重新创建Activity,同时该Activity的onNewIntent方法会被调用
如果要启动的Activity不在栈顶,则会重新创建该Activity实例
singleTask:1.如果要启动的Activity已经存在于它想要归属的栈中,那么不会创建该Activity实例,将栈中位于该Activity上的所有Activity出栈,同时onNewIntent方法会被调用
2.如果要启动的Activity不存在它想要归属的栈中,并且该栈存在,则会重新创建该Activity实例
3.如果要启动的Activity想要归属的栈不存在,首页创建一个新栈,然后创建该Activity实例并压入新栈中
singeleInstance:启动Activity时要创建一个新栈,然后创建该Activity实例并压入新栈中,新栈中只会存在这一个Activity实例
Intent的FLAG:
FLAG_ACTIVITY_SINGLE_TOP: singleTop
FLAG_ACTIVITY_NEW_TASK: singleTask
FLAG_ACTIVITY_CLEAR_TOP: 在Launch Mode中没有与此对应的模式,如果要启动的Activity已经存在于栈中,则将所有位于它上面的Activity出栈,singleTask默认具有此标记位的效果
如果Launch Mode和FLAG设定的Activity的启动方式冲突了,则以FLAG设定为准
taskAffinity:
可以在AndroidManifest.xml中设置android:taskAffinity,用来指定Activity希望归属的栈
###理解WindowManager
WindowManager是与WMS关联最紧密的类
Window,WindowManager和WMS:
Window是一个抽象类,具体实现类为PhoneWindow,它对View进行管理,Window通过setWindowManager方法与WindowManager发生关联
WindowManager是一个接口类,继承自接口ViewManager, 实现类为WindowManagerImpl,但是具体的功能都会委托给WindowManagerGlobal来实现(这里用到的是桥接模式),
用来管理Window的(添加,更新和删除操作)
WindowManager会将具体的工作交由WMS处理,WindowManager和WMS通过Binder进行跨进程通信
PhoneWindow是何时创建的?ActivityThread#performLaunchActivity->Activity#attach
Window的属性:定义在WindowManager的内部类LayoutParams中
Window的属性: Type(Window的类型), Flag(Window的标志)和SoftInputMode(软键盘相关模式)
Window的类型:
Application Window(应用程序窗口):Activity就是一个典型的应用程序窗口
Sub Window(子窗口): PopupWindow
System Window(系统窗口): Toast, 输入法窗口,系统音量条窗口
Window的标志:
设置Window的Flag有3中方法: 1.通过Window的addFlags方法
2.通过Window的setFlags方法
3.给LayoutParams设置Flag,并通过WindowManager的addView方法进行添加
软键盘相关模式:如用户登录界面,软键盘窗口可能会盖住输入框下方的按钮,可以在AndroidManifet中为Activity设置android:windowSoftInputMode, 或者Java代码中为Window设置SoftInputMode, getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE)
Window的操作:
ViewRootImpl的职责:View树的根并管理View树;触发View的测量,布局和绘制;输入事件的中转站;管理Surface;负责与WMS进行进程间通信
系统窗口的添加过程:
WindowManagerImpl#addView->ViewRootImpl#setView
ViewRootImpl与WMS通过Binder进程间通信:
本地进程:ViewRootImpl, IWindowSession(Binder对象,Client端代理,服务端是Session)
WMS所在的进程(SystemServer进程):Session, WMS(ViewRootImpl想要和WMS进行通信需要经过Session, 每个应用程序进程对应一个Session,
WMS为这个添加的窗口分配Surface,Surface负责显示界面,WMS会将由它所管理的Surface交由SurfaceFlinger处理,SurfaceFlinger会将这个Surface混合并绘制到屏幕上)
Activity的添加过程:ActivityThread#handleResumeActivity->WindowManagerImpl#addView, addView的第一个参数是DecorView
Window的更新过程:WindowManagerImpl#updateViewLayout->ViewRootImpl#performTraversals->WMS#relayoutWindow,
performTraversals方法中更新了Window视图,又执行Window中的View的工作流程,performTraversals还会分别调用performMeasure,performLayout,performDraw,在它们内部又会调用View的measure,layout和draw方法
###理解WindowManagerService
WMS是WindowManager的管理者
WMS的职责:窗口管理(负责窗口的启动,添加和删除),窗口动画,Surface管理(为每个窗口分配Surface), 输入事件的中转站
WMS的创建过程:SystemServer#startOtherServices->WMS#main(system_server线程)->new WMS(android.display线程)-> PWM.init(android.ui线程), 线程优先级android.ui>android.display>system_server
WMS的重要成员:
mPolicy(WindowManagerPolicy,窗口管理策略的接口类,具体的实现类为PhoneWindowManager)
mSessions, 元素类型Session,主要用于进程间通信,其他的应用程序进程想要和WMS进程进行通信就需要经过Session, 并且每个应用程序进程都会对应一个Session, WMS保存这些Session用来记录所有向WMS提出窗口管理服务的客户端
WindowState 用于保存窗口的信息,在WMS中用来描述一个窗口; DisplayContent 用来描述一块屏幕
WindowToken 窗口令牌,当应用程序想要向WMS申请新创建一个窗口,则需要向WMS出示有效的WindowToken,AppWindowToken是WindowToken的子类,主要描述应用程序的WindowToken结构,应用程序中每个Activity都对应一个AppWindowToken
WindowAnimator 管理窗口的动画以及特效动画
InputManagerService(IMS) 输入系统的管理者,IMS会对触摸事件进行处理
Window的添加过程(WMS处理部分):WMS#addWindow
Window的删除过程: WindowManagerImpl#removeView->ViewRootImpl#dispatchDetachedFromWindow->WMS#removeWindow
###JNI原理
JNI (Java Native Interface, Java本地接口), NDK开发是基于JNI的,使得Java代码和Native代码可以互相访问
场景: 音视频开发,热修复和插件化,逆向开发,系统源码调用
对于Java Framework层来说只需要加载对应的JNI库,接着声明native方法就行了
Native方法注册:
静态注册:
多用于NDK开发,JNIEnv是Native中Java环境的代表,通过JNIEnv*可以在Native中访问Java的代码进行操作
jclass->java.lang.Class, jobject->Object
静态注册就是根据方法名将java方法和JNI函数建立关联
动态注册:
多用与Framework开发
JNINativeMethod用来记录Java的Native方法和JNI层函数的对应关系
JNINativeMethod数组注册JNI_OnLoad是在System.loadLibrary函数后调用的
数据类型的转换:基本数据类型:jint, jfloat等; 引用数据类型:jobject(jclass, jstring, jarray)
方法签名:()V;
(Ljava/lang/Object;Ljava/lang/String)V
JNIEnv#CallStaticVoidMethod 访问Java的静态方法,JNIEnv#CallVoidMethod 访问Java的方法
###Java虚拟机
无论任何语言只要能编译成Class文件,就可以被Java虚拟机识别并执行
类的生命周期:加载,链接(验证,准备和解析),初始化,使用和卸载
运行时数据区域:
Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为不同的数据区域
这些数据区域分别为:
1. 程序计数器
也叫PC寄存器,Java虚拟机的多线程是通过轮流切换并分配处理器执行时间的方式来实现的,在一个确定的时刻只有一个处理器执行一条线程中的指令,为了在线程切换后能恢复到正确的执行位置,每个线程都会有一个独立的程序计数器
程序计数器是线程私有的
2. Java虚拟机栈
平常所说的栈内存(Stack)指的就是Java虚拟机栈,会抛出StackOverflowError和OutOfMemoryError异常
3. Java堆
所有线程共享的,Java堆用来存放对象实例,Java堆存储的对象被垃圾收集器管理
4. 本地方法栈
5. 方法区: 所有线程共享
6. 运行时常量池
Java虚拟机通过栈帧中的对象引用到Java堆中的Java对象的实例信息
垃圾标记算法
Java中的引用:
强引用:垃圾收集器绝不会回收它,Java虚拟机宁愿抛出OutOfMemoryError异常
软引用:SoftReference, 当内存不够时,会回收这些对象的内存, 回收后如果还是没有足够的内存,就会抛出OutOfMemoryError异常
弱引用:WeakReference, 弱引用比软引用具有更短的生命周期,GC一旦发现了具有弱引用的对象,不管内存是否足够,都会回收
虚引用:PhantomReference, 一个虚引用对象,被GC时会收到一个系统通知,和没有任何引用一样,在任何时候都可能被GC回收
引用计数法:
每个对象都有一个引用计数器,当对象被引用了+1,引用失效了-1,值为0则该对象不能被使用,变成垃圾
Java虚拟机没有选择引用计数算法来为垃圾标记,原因是引用计数算法没有解决对象之间相互循环引用的问题
根搜索算法:
选定一些对象为GC Roots,以这些GC Roots的对象作为起始点,如果目标对象到GC Roots是连接着的,我们称该目标对象是可达的,
如果目标对象不可达则目标对象是可以被收回的对象
可以作为GC Roots的对象: Java栈中的引用对象,本地方法栈中JNI引用的对象,方法区运行时常量池引用的对象,方法区静态属性引用的对象,运行中的线程
被标记为不可达的对象会立即被垃圾收集器回收吗?不会,该对象会进入收集状态,重新finalize方法
垃圾收集算法:
标记-清除算法:效率不高,容易产生大量不连续的内存碎片(可能导致没有足够的连续内存分配给较大的对象)
复制算法:新生代中广泛应用
标记-压缩算法:老年代中广泛应用
分代收集算法:
根据生命周期长短将它们分别放到不同的区域,并在不同的区域采用不同的收集算法
Java堆区基于分代的概念,分为新生代和老年代,新生代分为(Eden空间,From Survivor空间和To Survivor空间)
###Dalvik和ART
Dalvik虚拟机(DVM)是Google专门为Android平台开发的虚拟机
JVM: Java类被编译成一个或多个.class文件,并打包成.jar文件, 而后JVM从中获取相应的字节码
DVM:DVM会用dx工具将所有的.class文件转换成一个.dex文件,然后DVM会从该.dex文件读取指令和数据
Android中的每一个应用都运行在一个DVM实例中,每一个DVM实例都运行在一个独立的进程空间中
DVM的GC日志:
在DVM中每次垃圾收集都会将GC日志打印到logcat中
D/dalvikvm: <GC_Reason> <Amount_freed> <Heap_stats> <External_memory_stats> <Pause_time>
GC_CONCURRENT: 当堆开始填充时,并发GC可以释放内存
GC_HPROF_DUMP_HEAP: 当你请求创建HPROF文件来分析堆内存时出现的GC
ART虚拟机:
ART(Android Runtime)虚拟机是Android4.4 发布的,用来替换Dalvik虚拟机
ART的运行时堆的空间划分:Zygote Space,Allocation Space,Image Space,Large Object Space
其中Zygote Space和Image Space是进程间共享的
ART的GC日志:
ART会为那些主动请求的垃圾收集器事件或者认为GC速度慢时才会打印GC日志(慢指的是GC暂停超过5ms或者GC持续时间超过100ms)
I/art: <GC_Reason> <GC_Name> <Objects_freed>(<Size_freed>) AllocSpace Objects,
<Large_objects_freed>(<Large_object_size_freed>) <Heap_stats> LOS objects, <Pause_time>
eg: I/art: Explicit concurrent mark sweep GC freed 104710(7MB) AllocSpace objects, 21(416KB) LOS objects, 33% free, 25MB/38MB, paused 1.230ms total 67.216ms
GC原因是Explicit, 垃圾收集器是CMS收集器,释放对象数量(释放字节数),释放大对象数量(释放大对象字节数),堆的空闲内存百分比,已用内存/堆的总内存,GC暂停时长,GC总时长
DVM和ART如何诞生:
init启动Zygote时会调用app_main.cpp#main函数->AndroidRuntime#start函数->startVm 创建Java虚拟机,startReg为Java虚拟机注册JNI方法
是在Zygote进程中诞生的,这样Zygote进程就持有了DVM或者ART的实例,此后Zygote进程fork自身创建应用程序进程时,应用程序进程也得到了DVM或者ART的实例
###理解ClassLoader
Java中的ClassLoader: 加载Class文件
系统加载器
Bootstrap ClassLoader(引导类加载器): Bootstrap ClassLoader是由C/C++编写的,并不是一个Java类
Extensions ClassLoader(扩展类加载器): Java中的实现类为ExtClassLoader, 用于加载Java的扩展类
Application ClassLoader(应用程序类加载器):Java中的实现类为AppClassLoader
自定义加载器:通过继承java.lang.ClassLoader类的方式来实现自己的类加载器, 复写findClass方法
Android中的ClassLoader: 加载dex文件
系统加载器:
BootClassLoader: Android系统启动时会使用BootClassLoader来预加载常用类,应用程序无法直接调用的
DexClassLoader: 加载dex文件,继承自BaseDexClassLoader
PathClassLoader: Android系统使用PathClassLoader来加载系统类和应用程序类
DexPathList是在BaseDexClassLoader的构造方法中创建的,里面存储了dex相关文件的路径,在ClassLoader执行双亲委派模式的查询流程时会从DexPathList中进行查找
ClassLoader的加载过程就是遵循的双亲委派模式,如果委托流程没有检查到此前加载过传入的类,就调用ClassLoader的findClass方法
2024/8/7
###热修复原理
Instant Run能够显著减少开发人员第二次及以后得构建和部署时间
加载so(动态链接库)主要用到了System类的load和loadLibrary方法,在Java Framework最终调用的都是nativeLoad方法
nativeLoad->JavaVMExt#loadNativeLibrary
###Hook技术
Hook 钩子,用来劫持对象,起到了欺上瞒下的作用,可以实现在应用程序进程更改系统进程的行为
Hook点:被劫持的对象叫做Hook点,用代理对象来替代Hook点,Hook点一般选择容易找到并且不易变化的对象,静态变量和单例就符合这个条件
代理模式也叫委托模式,为其他对象提供一种代理以控制对这个对象的访问称为代理模式
1.抽象主题类(声明真实主题与代理的共同接口方法) 2.真实主题类 3.代理类,持有对真实主题的引用 4.客户端类
代理模式从编码的角度可以分为静态代理和动态代理,而动态代理则是在代码运行时通过反射来动态生成代理类的对象,Java提供了动态的代理接口InvocationHandler,实现该接口需要重写invoke方法,调用Proxy.newProxyInstance来生成动态代理类
Hook startActivity方法:--简单的说找到Hook点,再用代理对象来替换Hook点
Hook Activity的startActivity方法:Hook Activity里的Instrumentation
Hook Context的startActivity方法: Hook ActivityThread里的Instrumentation, Activity#attachBaseContext先于onCreate方法调用
###插件化原理
插件化技术和热修复技术都属于动态加载技术
可执行文件总的来说分为两种,一种是动态链接库so, 另一种是dex相关文件(dex以及包含dex的jar/apk文件)
插件一般指经过处理的APK,so和dex等文件,插件可以被宿主进行加载,有的插件也可以作为APK独立运行,eg: 淘宝客户端分为两大部分,一部分是宿主部分,也就是淘宝主客户端,另一部分是插件部分,如外接的其他应用业务如聚划算,飞猪旅行,也可以是淘宝自身的业务模块如消息和搜索
插件化的优点:应用以及主dex的体积会相应变小,间接地避免了65536限制,第一次加载到内存的只有宿主,当使用到其他插件时才会加载相应插件到内存,这些就减少了内存的使用。
插件化框架:VirtualApk加载耦合插件方面的首选
Activity插件化:
Activity的启动过程
根Activity的启动过程:首先Launcher进程向AMS请求创建根Activity,AMS会判断根Activity所需的应用程序进程是否存在,如果不存在就会请求Zygote进程创建应用程序进程,应用程序进程启动后,AMS会请求应用程序进程创建并启动根Activity
普通Activity的启动过程:在应用程序进程中的Activity向AMS请求创建普通Activity,如果Activity满足AMS的校验,AMS就会请求应用程序进程中的ActivityThread去创建并启动普通Activity
Hook IActivityManager方案实现:
1.注册Activity进行占坑:要启动的是插件TargetActivity
2.使用占坑Activity通过AMS验证(Hook点是IActivityManager)
3.还原插件Activity:用插件TargetActivity来替换占坑的SubActivity(Hook点是Handle的mCallback)
Hook Instrumentation方案实现:在Instrumentation#execStartActivity方法中用占坑SubActivity来通过AMS的验证,在Instrumentation#newActivity方法中还原TargetActivity
资源插件化:Resources会依赖AssetManager来加载资源
方案:1.将插件的资源全部添加到宿主的Resources中(合并资源方案,反射调用宿主的AssetManager#addAssetPath) 2.每个插件都构造出独立的Resources(构建插件资源方案)
###绘制优化
性能优化包括绘制优化,内存优化,电量优化,启动优化,存储优化,流量优化,图片优化和APK优化
绘制原理:
View的绘制流程有3个步骤,分别是measure,layout和draw,它们主要运行在系统的应用架构层,而真正将数据渲染到屏幕上的则是系统Native层的SurfaceFlinger服务来完成的
CPU负责数据计算工作,GPU负责栅格化,渲染,CPU和GPU是通过图形驱动层来进行连接的
FPS(Frames Per Second)要想画面保持在60fps,需要屏幕在1秒内刷新60次,也就是每16.67ms刷新一次,Android系统每隔16ms发出VSYNC信号触发对UI进行渲染
卡顿原因:
布局Layout过于复杂,无法在16ms内完成渲染
同一时间动画执行的次数过多,导致CPU和GPU负载过重
View过渡绘制,导致某些像素在同一帧时间内被绘制多次
在UI线程中做了稍微耗时的操作
GC回收时暂停时间过长或者频繁的GC产生大量的暂停时间
性能分析工具:
开发者选项打开Profile GPU Rendering,屏幕会出现彩色的柱状图,横轴代表时间,纵轴代表某一帧的耗时
Systrace 性能数据采样和分析工具:DDMS中使用Systrace;命令行使用Systrace(Android提供一个Python脚本文件systrace.py,位于Android SDK目录tools/systrace中);代码中使用Systrace(TraceCompat#beginSection, TraceCompat#endSection,TraceCompat类对Trace类进行了封装)
Chrome分析Systrace:
eg: Measure/Layout took a significant time, contributing to jank. Avoid triggering layout during animations. ---View在Measure/Layout时耗费了大量的时间,导致出现了jank(同一帧画了多次),避免在动画播放期间控制布局
eg: 线程在绘制时, 在很长一段时间都没有分配到CPU时间片,因此无法继续进行绘制
Systrace提供一个概览,粗略的检查以便了解大概得情况,更详细的分析如CPU为什么繁忙,某些方法的调用次数,需要借助TraceView
TraceView: Android SDK自带的数据采集和分析工具
查看单次执行耗时的方法,执行次数多的方法
在代码中加入调试语句:在开始监控的地方调用startMethodTracing方法,在需要结束监控的地方调用stopMethodTracing方法,系统会在SD卡中生成trace文件,将trace文件导出并用SDK中的TraceView打开即可
布局优化: 一个界面的测量和绘制时通过递归来完成的,减少布局的层数就会减少测量和绘制的时间
布局优化工具:
Hierarchy View:Android SDK自带的可视化的调试工具,用来检查布局嵌套和绘制的时间
Android Lint:代码扫描工具,这里只关注XML布局检查,通过AS的Analyse-》Inspect Code来配置检查的范围
布局优化方法:
合理运用布局,如果布局复杂,那么合理地利用RelativeLayout来替换LinearLayout会减少很多层布局
include标签:多个布局复用一个相同的布局
Merge标签:Merge标签可以减少多余的层级,Merge标签一般和Include标签搭配使用
用ViewStub来提高加载速度:ViewStub是轻量级的View,不可见的时候不占布局位置,当ViewStub调用inflate方法或者设置可见时,系统会加载ViewStub指定的布局,然后将这个布局添加到ViewStub中
避免GPU过渡绘制:
过渡绘制是指在屏幕上某个像素在同一帧的时间内被绘制多次,从而浪费了GPU和CPU的资源
主要原因:1.在XML布局中,控件有重叠且都有设置背景; 2.View的onDraw在同一区域绘制多次
开发者选项中打开调试GPU过渡绘制选项就可以进入GPU过渡绘制模式
方案:1.移除不需要的background;2.在自定义View的onDraw方法中,用Canvas#clipRect来指定绘制的区域, 防止重叠的组件发生过渡绘制
###内存优化
内存泄漏就是指没有用的对象到GC Roots是可达的(对象被引用),导致GC无法回收该对象
原因:1.开发人员自己编码造成的泄漏(可控的) 2.第三方框架造成的泄漏;3.由Android系统或者第三方ROM造成的泄漏(2和3是不可控的)
内存泄漏的场景:
非静态内部类的静态实例:非静态内部类会持有外部类实例的引用,如果非静态内部类的实例是静态的,就会间接地长期维持着外部类的引用,阻止被系统回收
多线程相关的匿名内部类/非静态内部类:如AsyncTask类,Thread类和实现Runnable接口的类,它们的匿名内部类/非静态内部类如果做耗时操作可能发生内存泄漏
Handler内存泄漏:Handler的Message存储在MessageQueue中,有些Message并不能马上被处理,就会导致Handler无法被回收,如果Handler是非静态的,Handler也会导致引用的Activity或者Service不能被回收
解决方案: 使用静态的Handler内部类,Handler持有的对象要使用弱引用;在Activity的onDestroy方法中移除MessageQueue中的消息,handler#removeCallbacksAndMesssages
未正确使用Context:一个单例对象传入了Activity Context,导致GC无法回收产生内存泄漏,解决方法是使用Application的Context
静态View: 静态View会持有Activity的引用,导致Activity无法被回收,解决方法是在onDestroy方法中将静态View置为null
WebView: WebView都会存在内存泄漏问题,解决方法是为WebView单开一个进程,使用AIDL与应用的主进程进行通信
资源对象未关闭;集合中对象没清理;Bitmap对象;监听器未关闭:自己添加的Listener记得在合适的时候及时移除
Memory Monitor
内存抖动:指在很短的时间内发生了多次内存分配和释放,严重的内存抖动还会导致应用程序卡顿,内存抖动的原因主要是短时间频繁地创建对象(可能在循环里创建对象),现象是产生了锯齿状的抖动图示
Allocation Tracker 跟踪内存分配的情况
Heap Dump: 查看不同的数据类型在内存中的使用情况
Heap Size堆栈分配给当前的应用程序的内存大小, Allocated已分配的内存, Free空闲的内存, %Used当前Heap的使用率,#Objects对象的数量
MAT: Memory Analysis Tool对内存进行详细分析的工具
生成hprof文件
DDMS生成hprof文件:DDMS生成的hprof文件并不是标准的,还需要将它转换为标准的hprof文件,这样才能被MAT识别从而进行分析,可以使用SDK自带的hprof-conv进行转换,它的路径在sdk/platform-tools中
Memory Monitor生成hprof文件
Dump Java Heap按钮生成hprof文件,Export to standard.hprof选项导出标准的hprof文件
LeakCanary: Square公司基于MAT开源了LeakCanary
《Android开发艺术探讨》
###1.Activity的生命周期和启动模式
onPause可以做一些存储数据,停止动画的工作,不能在onPause中做重量级的操作;onDestroy可以做一些回收工作和最终的资源释放
第一次启动:onCreate-》onStart-》onResume
用户打开新的Activity或者切换到桌面的时候:onPause-》onStop,如果新Activity采用了透明主题,那么当前Activity不会回调onStop
再次回到原Activity:onRestart-》onStart-》onResume
当用户按back键回退:onPause-》onStop-》onDestroy
问题1:onStart和onResume,onPause和onStop从描述上来看差不多,对我们来说有什么实质的不同呢? onStart和onStop是从Activity是否可见这个角度来回调的,而onResume和onPause是从Activity是否位于前台这个角度回调的
问题2:假设当前Activity为A,如果这时用户打开一个新Activity B,那么B的onResume和A的onPause哪个先执行呢? 旧Activity先onPause,然后新Activity再启动
异常情况下的生命周期分析:
资源相关的系统配置发生改变导致Activity被杀死并重新创建
系统会调用onSaveInstanceState来保存当前Activity的状态,这个方法的调用时机是在onStop之前,它和onPause没有既定的时序关系;当Activity被重新创建后,系统会调用onRestoreInstanceState,并且把Activity销毁时onSaveInstanceState方法所保存的Bundle对象作为参数同时传递给onRestoreInstanceState和onCreate方法
系统资源不足导致低优先级的Activity被杀死
Activity优先级:前台Activity-》可见但非前台Activity-》后台Activity
如果不想让Activity再屏幕旋转的时候重新创建,就可以给configChanges属性添加orientation
启动模式:
standard:标准模式,当我们用ApplicationContext去启动standard模式的Activity的时候会报错,这时因为standard模式的Activity默认进入启动它的Activity所属的任务栈中,但是由于非Activity类型的Context(如ApplicationContext)并没有所属的任务栈,解决方法是为待启动Activity指定FLAG_ACTIVITY_NEW_TASK标记位,这样启动的时候就会为它创建一个新的任务栈
singleTop:栈顶复用模式
singleTask:栈内复用模式
singleInstance:单实例模式,此模式的Activity只能单独为于一个任务栈中
TaskAffinity标识了一个Activity所需要的任务栈的名字
如何给Activity指定启动模式呢?1.通过AndroidManifest为Activity指定启动模式;2.通过在Intent中设置标志位来为Activity指定启动模式
Activity的Flags:
FLAG_ACTIVITY_NEW_TASK ---singleTask启动模式
FLAG_ACTIVITY_SINGLE_TOP ---singleTop启动模式
FLAG_ACTIVITY_CLEAR_TOP ---同一任务栈所有位于它上面的Activity都要出栈,singleTask启动模式默认就具有此标记位的效果
FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS ---具有这个标记的Activity不会出现在历史Activity的列表中,等同于在XML中指定Activity的属性android:excludeFromRecents="true"
IntentFilter的匹配规则:
显式调用:需要指定被启动对象的组件信息,包括包名和类名
隐式调用:需要Intent能够匹配目标组件的IntentFilter中所设置的过滤信息,IntentFilter中的过滤信息由action,category,data
action的匹配规则:要求Intent中的action存在且必须和过滤规则中的其中一个action相同
category的匹配规则:要求Intent可以没有category,但是如果你一旦有category,不管有几个,每个都要能够和过滤规则中的任何一个category相同
为了我们的activity能够接收隐式调用,必须在intent-filter中指定“android.intent.category.DEFAULT”这个category
data的匹配规则:如果过滤规则中定义了data,那么Intent中必须也要定义可匹配的data
data由两部分组成,mimeType和URI,URI有<scheme><host><port><path>等
###2.IPC机制
IPC(Inter-Process Communication)进程间通信
线程:CPU调度的最小单元,同时线程是一种有限的系统资源;进程:一般指一个执行单元,在PC或移动设备上指一个程序或者一个应用
Android中的多进程模式:给四大组件在AndroidManifest中指定android:process属性,进程名以“:”开头的进程属于当前应用的私有进程,其他应用的组件不可以和它跑在同一个进程中
adb shell ps | grep 包名 ---通过ps命令查看一个包名中当前存在的进程信息
Android会为每个进程都分配一个独立的虚拟机,不同的虚拟机在内存分配上有不同的地址空间,导致在不同的虚拟机中访问同一个类的对象会产生多份副本
多进程会造成的问题:静态成员和单例模式完全失效;线程同步机制完全失效;SharedPreferences的可靠性下降;Application会多次创建
在多进程模式,不同进程的组件的确会拥有独立的虚拟机,Application以及内存空间
IPC基础概念介绍:
Serializable接口:Java提供的序列化接口,这个类实现Serializable接口并声明一个serialVersionUID即可;指定serialVersionUID可以在很大程度上避免反序列过程的失败;transient关键字标记的成员变量不参与序列化过程
Parcelable接口:序列化功能由writeToParcel方法完成,反序列化功能由CREATOR来完成,内容描述功能由describeContents方法来完成;系统中Intent,Bundle,Bitmap实现了Parcelable接口
Binder:Binder是Android中的一个类,实现了IBinder接口,设备驱动是/dev/binder;
从Android Framework角度来说,Binder是ServiceManager连接各种Manager和相应ManagerService的桥梁;从Android应用层来说,Binder是客户端和服务端进行通信的媒介
根据IBookManager.aidl系统为我们生成了IBookManager.java这个类,它继承了IInterface这个接口,这个接口的核心实现是它的内部类Stub(Binder)和Stub的内部代理类Proxy;AIDL的本质是系统为我们提供了一种快速实现Binder的工具
Android中的IPC方式:
Bundle; 文件共享(局限性并发读写问题),
Messenger:底层实现是AIDL
服务端进程:创建一个Service来处理客户端的连接请求,创建一个Handler并通过它来创建一个Messenger对象,然后在Service的onBind中返回这个Messenger对象底层Binder即可
客户端进程:绑定服务端的Service,绑定成功后用服务端的IBinder对象创建一个Messenger,通过这个Messenger就可以向服务端发送消息了,发消息类型为Message对象
AIDL:
Book.aidl
IBookManager.aidl ---AIDL的包结构在服务端和客户端要保持一致
BookManagerServcie --服务端Service,创建一个Binder对象并在onBind中返回它,这个对象继承自IBookManager.Stub并实现了它内部的AIDL方法
BookManagerActivity ---客户端实现,首先绑定远程服务,绑定成功后将服务端返回的Binder对象转换成AIDL接口,然后就可以通过这个接口去调用服务端的远程方法
对象的跨进程传输本质都是反序列化的过程,这就是为什么AIDL中的自定义对象都必须实现Parcelable接口
RemoteCallbackList是系统专门提供的用于删除跨进程listener的接口
客户端调用远程服务的方法,被调用的方法运行在服务端的Binder线程池中,同时客户端线程会被挂起,这个时候如果服务端的方法执行比较耗时,就会导致客户端线程长时间地阻塞在这里,而如果客户端线程是UI线程的话,就会导致客户端的ANR
Binder是可能意外死亡的,这往往是由于服务端进程意外停止了,这时我们需要重新连接服务,有两种方法:
1.给Binder设置DeathRecipient监听,当Binder死亡时,我们会收到binderDied方法的回调,在binderDied方法中我们可以重连远程服务
2.在onServiceDisconnected中重连远程服务
如何在AIDL中使用权限验证?1.在服务端onBind中进行验证;2.在服务端的onTransact方法中进行权限验证
ContentProvider:
ContentProvider是Android中提供的专门用于不同应用间进行数据共享的方式,底层实现同样也是Binder
android:authorities是Content Provider的唯一标识,通过这个属性外部应用就可以访问我们的ContentProvider; 权限分为读权限和写权限,分别对应android:readPermission和android:writePermission
onCreate运行在main线程中,query,update,insert和delete方法运行在Binder线程中
ContentProvider通过Uri来区分外界要访问的数据集合
update,insert和delete方法会引起数据源的改变,我们需要通过ContentResolver#notifyChange方法来通知外界当前ContentProvider中的数据已经发生改变,可以通过ContentResolver#registerContentObserver方法来注册观察者,通过unregisterContentObserver方法来解除观察者
Socket
###3.View的事件体系
MotionEvent:
getX/getY返回的是相对于当前View左上角的x和y坐标,而getRawX/getRawY返回的是相对于屏幕左上角的x和y坐标
TouchSlop: 系统所能识别出的被认为是滑动的最小距离,ViewConfiguration.get(getContext()).getScaledTouchSlop()
VelocityTracker: 用于追踪手指在滑动过程中的速度,在View的onTouchEvent方法中追踪当前单击事件的速度,这里的速度是指一段时间内手机所滑过的像素数,当手指从右往左滑动时,水平方向速度即为负值,速度=(终点位置-起点位置)/时间段
GestureDetector: 手势检测,用于辅助检测用户的单击,滑动,长按,双击等行为
Scroller: 实现View的弹性滑动,需要和View的computeScroll方法配合使用
View的滑动:
使用scrollTo/scrollBy: 如果从左向右滑动,mScrollX为负值,反之为正值;如果从上往下滑动,那么mScrollY为负值,反之为正值;只能将View的内容进行移动,并不能将View本身进行移动
使用动画:主要是操作View的tranlationX和translationY属性
View动画是对View的影像做操作,并不能真正改变View的位置参数,包括宽/高
改变布局参数:即改变LayoutParams
弹性滑动:
使用Scroller:
Scroller到底是如何让View弹性滑动的?startScroll方法里调用了invalidate方法,invalidate方法会导致View重绘,在View的draw方法中又会去调用computeScroll方法
通过动画
使用延迟策略:使用Handler或View的postDelayed方法,线程的sleep方法
View的事件分发机制
对于一个根ViewGroup,点击事件产生后,首先会传递给它,这时它的dispatchTouchEvent就会被调用,如果这个ViewGroup的onInterceptTouchEvent方法返回true
就表示它要拦截当前事件,接着事件就会交给这个ViewGroup处理,即它的onTouchEvent方法就会被调用,如果这个ViewGroup的onInterceptTouchEvent方法返回false,
就表示不拦截当前事件,这时当前事件就会继续传递给它的子元素,接着子元素的dispatchTouchEvent方法就会被调用,如此反复直到事件被最终处理
View的OnTouchListener优先级比onTouchEvent要高,OnClickListener优先级最低
传递顺序:Activity-》Window-》View
如果一个View的onTouchEvent返回false,那么它的父容器的onTouchEvent将会被调用,依此类推,如果所有的元素都不处理这个事件,那么这个事件将会最终传递给Activity处理,即Activity的onTouchEvent方法会被调用
同一个事件序列:down事件开始,中间含有数量不定的move事件,最终以up事件结束
某个View一旦开始处理事件,如果它不消耗ACTION_DOWN事件(onTouchEvent返回了false),那么同一事件序列中的其他事件都不会再交给它处理了,并且事件重新交由它的父元素去处理,即父元素的onTouchEvent会被调用
View没有onInterceptTouchEvent方法
事件分发的源码解析:事件最先传递给当前Activity-》交给附属的Window进行分发-》PhoneWindow将事件直接传递给了DecorView(DecorView继承自FrameLayout且是父View)
-》ViewGroup#dispatchTouchEvent
通过requestDisallowInterceptTouchEvent方法设置FLAG_DISALLOW_INTERCEPT标记位,这个标记位的作用是让ViewGroup不再拦截事件,前提是ViewGroup不拦截ACTION_DOWN事件
View的滑动冲突:
内外两层同时可以滑动,这个时候就会产生滑动冲突
1.外部滑动方向和内部滑动方向不一致:如HorizontalScrollView+ListView 当用户左右滑动时,需要让外部的View拦截点击事件,当用户上下滑动时,需要让内部View拦截点击事件;滑动规则:水平和竖直的滑动距离差
解决方式:
外部拦截法:点击事件先经过父容器的拦截处理,父容器重写onInterceptTouchEvent方法
首先是ACTION_DOWN事件,父容器必须返回false,即不拦截ACTION_DOWN事件(因为一旦父容器拦截了ACTION_DOWN,那么后续的ACTION_MOVE和ACTION_UP事件都会直接交给父容器处理)
ACTION_MOVE事件,这个事件可以根据需要来决定是否拦截,如果父容器需要拦截就返回true,否则返回false
最后ACTION_UP要返回false(如果父容器在ACTION_UP时返回了true, 就会导致子元素无法接收到ACTION_UP事件,这时候子元素的onClick事件就无法触发)
内部拦截法:父元素不拦截任何事件,所有的事件都传递给子元素
子元素的dispatchTouchEvent方法中,ACTION_DOWN里,parent.requestDisallowInterceptTouchEvent(true); ACTION_MOVE里根据父元素是否需要此类事件,调用parent.requestDisallowInterceptTouchEvent(false)
父元素的onInterceptTouchEvent方法中,ACTION_DOWN返回false, ACTION_MOVE和ACTION_UP返回true
为什么父容器不能拦截ACTION_DOWN事件呢?ACTION_DOWN事件不受FLAG_DISALLOW_INTERCEPT标记位的控制,一旦父容器拦截ACTION_DOWN事件,那么所有的事件都无法传递到子元素中去
###4.View的工作原理
ViewRoot对应于ViewRootImpl类,它是连接WindowManager和DecorView的纽带;当Activity对象创建完毕后,会将DecorView添加到Window中,同时会创建ViewRootImpl对象,并将ViewRootImpl对象和DecorView建立关联
View的绘制流程是从ViewRoot的performTraversals方法开始的,进过measure,layout和draw三个过程最终将一个View绘制出来
理解MeasureSpec:
在测量过程中,系统会将View的LayoutParams根据父容器所施加的规则转换成对应的MeasureSpec,然后再根据这个measureSpec来测量出View的宽/高
MeasureSpec代表一个32位int值,高2位代表SpecMode,低30位代表SpecSize,SpecMode是测量模式,SpecSize是指在某种测量模式下的规格大小
SpecMode:
UNSPECIFIED: 父容器不对View有任何限制,要多大给多大,这种情况一般用于系统内部
EXACTLY: 父容器已经检测出View所需要的精确大小,这个时候View的最终大小就是SpecSize所指定的值,对应于LayoutParams中的match_parent和具体的数值者两种模式
AT_MOST:父容器指定一个可用大小即SpecSize,View的大小不能大于这个值,对应于LayoutParams中的wrap_content
MeasureSpec和LayoutParams的对应关系:
对于DecorView,其MeasureSpec由窗口的尺度和其自身的LayoutParams来共同确定;对于普通View,其MeasureSpec由父容器的MeasureSpec和自身的LayoutParams来共同决定,MeasureSpec一旦确定后,onMeasure中就可以确定View的测量宽/高
子元素的MeasureSpec的创建与父容器的MeasureSpec和子元素本身的LayoutParams有关,此外还和View的margin及padding有关
ViewGroup#measureChildWithMargins-》ViewGroup#getChildMeasureSpec --根据父容器的MeasureSpec同时结合View本身的LayoutParams来确定子元素的MeasureSpec
View的工作流程:
measure过程:
View的measure过程:
View#onMeasure-》setMeasuredDimension
View最终的大小是在layout阶段确定的
直接继承View的自定义控件需要重写onMeasure方法并设置wrap_content时的自身大小,否则布局中使用wrap_content就相当于使用match_parent
ViewGroup的measure过程:
除了完成自己的measure过程以外,还会遍历去调用所有子元素的measure方法
ViewGroup#measureChildren
在onMeasure方法中拿到的测量宽/高很可能是不准确的,一个比较好的习惯是在onLayout方法中去获取View的测量宽/高或者最终宽/高
在onCreate,onStart,onResume中无法正确得到某个View的宽高信息,因为View的measure过程和Activity的生命周期方法不是同步执行的,解决方法:
1.Activity/View#onWindowFocusChanged方法
2.view.post(runnable)
3.ViewTreeObserver: 使用OnGlobalLayoutListener接口onGlobalLayout方法里获取
layout过程:
layout方法确定View本身的位置,而onLayout方法则会确定所有子元素的位置
View的getMeasuredWidth和getWidth两个方法有什么区别?在View的默认实现中,View的测量宽高和最终宽高是相等的,只不过测量宽高形成于View的measure过程,而最终宽高形成于View的layout过程
draw过程:
绘制背景,绘制自己,绘制children,绘制装饰
自定义View
分类:继承View重写onDraw方法;继承ViewGroup派生特殊的Layout;继承特定的View;继承特定的ViewGroup
须知:让View支持wrap_content; 如果有必要,让View支持padding;尽量不要在View中使用Handler,没必要;View中如果有线程或者动画,需要即使停止,可以在View#onDetachedFromWindow中停止线程和动画;View带有滑动嵌套情形时,需要处理好滑动冲突;
提供自定义属性:定义attrs.xml,View的构造函数中解析自定义属性
###5.理解RemoteViews
RemoteViews在通知栏上的应用:通知栏由NotificationManager管理
只要提供当前应用的包名和布局文件的资源id即可创建一个RemoteViews对象
关于PendingIntent,它表示的是一种待定的Intent,这个Intent中所包含的意图必须由用户来触发
RemoteViews在桌面小部件的应用:桌面小部件由AppWidgetProvider管理
AppWidgetProvider是Android提供的用于实现桌面小部件的类,其本质是一个广播BroadcastReceiver
桌面小部件在界面上的操作都要通过RemoteViews,不管是小部件的界面初始化还是界面更新都必须依赖它
PendingIntent:
PendingIntent典型应用场景是给RemoteViews添加单击事件
PendingIntent支持三种待定意图:启动Activity,启动Service和发送广播
RemoteViews的内部机制:
RemoteViews的作用是在其他进程中实现并更新View界面
NotificationManager和AppWidgetManager通过Binder分别和SystemServer进程中的NotificationManagerService以及AppWidgetService进程通信
系统并没有通过Binder直接支持View的跨进程访问
应用场景:现在有两个应用,一个应用需要能够更新另个一应用中的某个界面,跨应用更新界面;缺点:它仅支持一些常用的View,对于自定义View它是不支持的
###6.Android的Drawable
Drawable的分类:
BitmapDrawable:表示一张图片,<bitmap>
ShapeDrawable: 通过颜色来构造图形<shape>
LayerDrawable: 表示一种层次化的Drawable集合,XML标签是<layer-list>
StateListDrawable: 表示Drawable集合, 对应<selector>标签, 主要用于设置可单击的View的背景
LevelListDrawable: 同样表示一个Drawable集合,对应于<level-list>标签
TransitionDrawable:实现两个Drawable之间的淡入淡出效果,对应于<transition>标签
InsetDrawable: 可以将其他Drawable内嵌到自己当中,并可以在四周留出一定的间距,对应于<inset>标签
ScaleDrawable: 可以根据自己的等级将指定的Drawable缩放到一定的比例,对应于<scale>标签
ClipDrawable: 可以根据自己当前的等级来裁剪另一个Drawable;等级越大,表示裁剪的区域越小
自定义Drawable:
继承Drawable,重写draw,setAlpha,setColorFilter和getOpacity方法
###7.Android动画深入分析
View动画:
平移动画TranslateAnimation;缩放动画ScaleAnimation;旋转动画RotateAnimation;透明度动画AlphaAnimation
<set>标签表示动画集合,对应AnimationSet类
AnimationUtils#loadAnimation加载xml动画
自定义View动画:继承Animation这个抽象类,重写它的initialize和applyTransformation方法
View动画的特殊使用场景:
LayoutAnimation:给ViewGroup的子元素加上出场效果
Activity的切换效果:overridePendingTransition这个方法必须位于startActivity或者finish的后面,否则动画效果将不起作用
帧动画:顺序播放一组预先定义好的图片,AnimationDrawable来使用帧动画
属性动画:在一个时间间隔内完成对象从一个属性值到另一个属性值的改变
动画类:ValueAnimator,ObjectAnimator和AnimationSet,其中ObjectAnimator继承自ValueAnimator,AnimationSet是动画集合
通过XML来定义,属性动画需要定义在res/animator/目录下
<set>---》AnimationSet;<animator>---》ValueAnimator;<objectAnimator>---》ObjectAnimator
AnimationInflater#loadAnimator
属性动画要求对象的该属性有set方法,如果动画的时候没有传递初始值,那么还要提供get方法
插值器和估值器:
TimeInterpolator时间插值器:LinearInterpolator线性插值器,匀速动画;AccelerateDecelerateInterpolator加速减速插值器,动画两头慢中间快;DecelerateInterpolator减速插值器,动画越来越慢
TypeEvaluator估值器:IntEvaluator,FloatEvaluator,ArgbEvaluator
监听器:AnimatorUpdateListener和AnimatorListener
###8.理解Window和WindowManager
WindowManager是外界访问Window的入口,Window的具体实现位于WindowManagerService中,WindowManager和WindowManagerServie的交互是一个IPC过程
Android中所有的视图都是通过Window来呈现的,不管是Activity,Dialog还是Toast;Window实际是View的直接管理者
Window的类型:应用Window,子Window,系统Window
WindowManager提供的功能:添加View,更新View,删除View
WindowManager操作Window的过程更像是操作Window中的View
Window的内部机制:
Window是一个抽象的概念,每一个Window都对应着一个View和一个ViewRootImpl,Window和View通过ViewRootImpl来建立联系,因此Window并不是实际存在的,它是以View的形式存在
Window的添加过程:
WindowManagerImpl这个工作模式是典型的桥接模式,将所有的操作全部委托给WindowManagerGlobal来实现
WindowManagerImpl#addView-》WindowManagerGlobal#addView-》ViewRootImpl#setView -》IPC -》WMS#addWindow
Window的删除过程:
WindowManagerGlobal#removeView-》ViewRootImpl#doDie-》ViewRootImpl#dispatchDetachedFromWindow -》IPC -》WMS#removeWindow
Window的更新过程:
WindowManagerGlobal#updateViewLayout -》ViewRootImpl#scheduleTraversals -》 IPC -》WMS#relayoutWindow
Window的创建过程:
Activity的Window创建过程:
ActivityThread#performLaunchActivity-》Activity#attach 里创建Activity所属的Window对象并为其设置回调接口 -》PolicyManager#makeNewWindow -》new PhoneWindow
Activity实现了Window的Callback接口,接口方法有onAttachToWindow,onDetachedFromWindow,dispatchTouchEvent等
Activity的视图是怎么附属在Window上的?getWindow().setContentView
如果没有DecorView,那么创建它;将View添加到DecorView的mContentParent中;回调Activity的onContentChanged方法通知Activity视图已经发生改变
ActivityThread#handleResumeActivity-》Activity#onResume-》DecorView被真正完成了添加和现实
Dialog的Window创建过程:
普通Dialog有一个特殊之处,那就是必须采用Activity的Context,如果采用Application的Context,会报错:WindowManager$BadToken Exception
没有应用token导致的,而应用token一般只有Activity拥有
Toast的Window创建过程:Toast内部有两类IPC过程,第一类是Toast访问NotificationManagerService,第二类是NotificationManagerService回调Toast里的TN接口
###9.四大组件的工作过程
Activity的工作过程:
Activity#startActivity-》Activity#startActivityForResult-》Instrumentation#execStartActivity -(IPC)》AMS#startActivity
ActivityManagerService继承自ActivityManagerNative,而ActivityManagerNative继承自Binder并实现了IActivityManager这个IBinder接口,因此AMS也是一个Binder
-》ActivityStackSupervisor和ActivityStack -(IPC)》ApplicationThread#scheduleLaunchActivity -》ActivityThread#handleLaunchActivity -》ActivityThead#performLaunchActivity方法最终完成了Activity对象的创建和启动过程
performLaunchActivity主要做的事情:
从ActivityClientRecord中获取待启动的Activity的组件信息
通过Instrumentation的newActivity方法使用类加载创建Activity对象
通过LoadedApk的makeApplication方法来尝试创建Application对象
创建ContextImpl对象并通过Activity的attach方法来完成一些重要数据的初始化
调用Activity的onCreate方法
Service的工作过程:
Service的启动过程:
ContextWrapper#startService-》ContextImpl#startService 桥接模式 -》远程调用AMS#startService -》ActiveServices -》远程调用ApplicationThread#scheduleCreateService -》H.sendMessage(CREATE_SERVICE)-》ActivityThread#handleCreateService
Service的绑定过程:
ContextWrapper#bindService-》ContextImpl#bindService -》远程调用AMS#bindService -》远程调用ApplicationThread#scheduleBindService -》H.sendMessage(BIND_SERVICE)-》ActivityThread#handleBindService
BroadcastReceiver的工作过程:
广播的注册过程:静态注册由PMS完成,动态注册ContextWrapper#registerReceiver -》ContextImpl#registerReceiver-》远程调用AMS#registerReceiver
广播的发送和接收过程:ContextWrapper#sendBroadcast -》ContextImpl#sendBroadcast -》远程调用AMS#broadcastIntent -》ApplicationThread#scheduleRegisteredReceiver
ContentProvider的工作过程:
ApplicationThread时一个Binder对象,它的Binder接口是IApplicationThread,它主要用于ActivityThread和AMS之间的通信
ActivityThread的attach方法会将ApplicationThread对象通过AMS的attachApplication方法跨进程传递给AMS,最终AMS会完成ContentProvider的创建过程,然后其他进程可以通过AMS来访问这个ContentProvider了
通过ContentProvider的四个方法的任何一个都可以触发ContentProvider的启动过程
ApplicationContextResolver#acquireProvider-》ActivityThread#acquireProvider
###10.Android的消息机制
Android的消息机制主要是指Handler的运行机制以及Handler所附带的MessageQueue和Looper的工作过程
Android规定访问UI只能在主线程进行,在子线程中访问UI,那么程序就会抛出异常 ViewRootImpl#checkThread,Android又建议不要在主线程中进行耗时操作,否则会导致程序无法响应即ANR
系统提供Handler的主要原因?为了解决子线程中无法访问UI的矛盾; 系统为什么不允许在子线程中访问UI? 因为Android的UI控件不是线程安全的,如果在多线程中并发访问可能会导致UI控件处于不可预期的状态
Handler创建时会采用当前线程的Looper来构建内部的消息循环系统,如果当前线程没有Looper,那么就会报错 Can't create handler inside thread that has not called Looper.prepare()
ThreadLocal的工作原理:
ThreadLocal是一个线程内部的数据存储类,通过它可以在指定的线程中存储数据,数据存储以后,只有在指定线程中可以获取到存储的数据,对于其他线程来说则无法获取到数据
消息队列的工作原理:
插入:MessageQueue#enqueueMessage; 读取: MessageQueue#next
MessageQueue通过一个单链表的数据结构来维护消息列表
Looper的工作原理:
消息循环,它会不停地从MessageQueue中查看是否有新消息,如果有新消息就会立刻处理,否则就一直阻塞在那里
如何为一个线程创建Looper? Looper#prepare --创建一个Looper,Looper#loop --开启消息循环
Looper#getMainLooper --在任何地方获取到主线程的Looper
Looper#loop()
loop方法是一个死循环,唯一跳出循环的方式是MessageQueue的next方法返回了null
msg.target.dispatchMessage(msg), 这里的msg.target是发送这条消息的Handler对象
Handler的工作原理:
Handler#sendMessage -》MessageQueue#enqueueMessage 向消息队列插入一条消息 -》MessageQueue#next 返回这条消息给Looper,Looper收到消息后就开始处理,最终消息由Looper交由Handler处理 -》Handler#dispatchMessage -》Handler#handleMessage 处理消息
主线程的消息循环:
ActivityThread#main方法中通过Looper#prepareMainLooper来创建主线程的Looper以及MessageQueue, 并通过Looper.loop来开启主线程的消息循环
###11.Android的线程和线程池
AsyncTask: 轻量级的异步任务类,可以在线程池中执行后台任务,然后把执行的进度和最终结果传递给主线程并在主线程中更新UI,AsyncTask封装了Thread和Handler
核心方法:onPreExecute, doInBackground, onProgressUpdate,onPostExecute
工作原理:
AsyncTask#execute -》executeOnExecutor -》 onPreExecute
SerialExecutor --线程池开始执行,默认情况下AsyncTask是串行的
AsyncTask中有两个线程池(SerialExecutor和THREAD_POOL_EXECUTROR)和一个Handler(InternalHandler),其中SerialExecutor用于任务的排队,而线程池THREAD_POOL_EXECUTROR用于真正地执行任务,InternalHandler用于将执行环境从线程池切换到主线程
HandlerThread:继承了Thread, 它是一种可以使用Handler的Thread,在run方法中通过Looper.prepare来创建消息队列, 并通过Looper.loop来开启消息循环
IntentService:封装了HandlerThread和Handler,比较适合执行一些高优先级的后台任务,因为它优先级高不容易被系统杀死
需要子类重写onHandleIntent方法,IntentService是顺序执行后天任务的
线程池:ThreadPoolExecutor
corePoolSize: 核心线程数
maximumPoolSize:最大线程数;当活动线程数达到这个数值后,后续的新任务将会被阻塞
keepAliveTime:非核心线程闲置时的超时时长;
unit:用于指定keepAliveTime参数的时间单位
workQueue:任务队列;通过线程池的execute方法提交的Runnable对象会存储在这个参数中
threadFactory:线程工厂,为线程池提供创建新线程的功能
执行规则:
如果线程池中的线程数量未达到核心线程的数量,那么会直接启动一个核心线程来执行任务
如果线程池中的线程数量已达到核心线程的数量,那么任务会被插入到任务队列中排队等待执行
如果任务队列已满,这个时候如果线程数量未达到线程池规定的最大值,那么会立刻启动一个非核心线程来执行任务
如果线程数量已经达到线程池规定的最大值,那么就拒绝执行此任务
线程池分类:
FixedThreadPool: 只有核心线程数并且这些核心线程数没有超时机制,任务队列没有大小限制
CachedThreadPool: 比较适合执行大量的耗时较少的任务,只有非核心线程,最大线程数为Integer.MAX_VALUE
ScheduledThreadPool: 执行定时任务和具有固定周期的重复任务
SingleThreadExecutor: 只有一个核心线程数
###12.Bitmap的加载和Cache
如何加载一个图片?BitmapFactory类提供了四类方法:decodeFile, decodeResource,decodeStream, decodeByteArray 分别用于支持从文件系统,资源,输入流以及字节流数组中加载出一个Bitmap对象,这四类方法最终都是调用Android底层native方法
如何高效加载Bitmap?采用BitmapFactory.Options来加载所需尺寸的图片,可以按一定的采样率来加载缩小后的图片,降低内存占用从而在一定程度上避免OOM
Android中的缓存策略:
当从网络上请求一张图片时,程序会首先从内存中去获取,如果内存中没有那就从存储设备中去获取,如果存储设备中也没有,那就从网络上下载这张图片
LRU(Least Recently Used)近期最少使用算法,核心思想是当缓存满时,会优先淘汰那些近期最少使用的缓存对象
LruCache:用于实现内存缓存
LruCache是一个泛型类,它内部采用一个LinkedHashMap以强引用的方式存储外界的缓存对象,其提供了get和put方法来完成缓存的获取和添加操作
DiskLruCache:用于存储设备缓存,即磁盘缓存
ImageLoader的实现:
图片压缩,内存缓存和磁盘缓存
优化列表的卡顿现象:不要在getView中执行耗时操作;控制异步任务的执行频率,onScrollStateChanged方法中判断如果是Idle列表静止才加载图片,Activity开启硬件加速android:hardwareAccelerated="true"
###13.综合技术
使用CrashHandler来获取应用的crash信息
Thread#setDefaultUncaughtExceptionHandler
使用Multidex来解决方法数越界
dexopt是一个程序,应用在安装时,系统会通过dexopt来优化dex文件,在优化过程中dexopt采用一个固定大小的缓冲区来存储应用中所有方法的信息,这个缓冲区就是LinearAlloc
在build.gradle文件,在defaultConfig中添加multiDexEnabled true这个配置项
Android的动态加载技术
插件化方案要解决三个基础性问题:资源访问,Activity生命周期管理和ClassLoader的管理; 宿主是指普通的apk,插件一般指经过处理的dex或者apk
资源访问:反射调用AssetManager中的addAssetPath方法,通过AssetManagr创建一个新的Resources对象,就可以访问插件apk中的资源了
Activity生命周期的管理
插件ClassLoader的管理
反编译初步
使用dex2jar和jd-gui反编译apk: dex2jar是一个将dex文件转换为jar包的工具,还需要jd-gui将jar包进一步转换为Java代码
使用apktool对apk进行二次打包
###14.JNI和NDK编程
JNI和NDK主要用于底层和嵌入式开发
JNI的开发流程:1.在Java中声明native方法;2.编译java源文件得到class文件,然后通过javah命令导出JNI的头文件;3.实现JNI方法;编译so库并在Java中调用
NDK的开发流程:1.下载并配置NDK;2.创建一个Android项目,并声明所需的native方法;
3.实现Android项目中所声明的native方法
4.切换到jni目录的父目录,然后通过ndk-build命令编译产生so库
JNI调用Java方法:调用静态方法JNIEnv#CallStaticVoidMethod
###15.Android性能优化
过多的使用内存会导致程序内存溢出,即OOM。而过多地使用CPU资源,一般是指做大量的耗时任务,会导致手机变得卡顿甚至出现程序无法响应的情况,即ANR
性能优化方法:
布局优化:尽量减少布局文件的层级,布局中的层级减少了,意味着Android绘制时的工作量少了
<include>标签:可以将一个指定的布局文件加载到当前的布局文件中
<merge>标签:merge标签一般和include标签一起使用从而减少布局的层级,eg:当前布局是一个竖直方向的LinearLayout,如果被包含的布局文件中也采用了竖直方向的LinearLayout, 那么显然被包含的布局文件中的LinearLayout是多余的,通过merge标签可以去掉多余的一层LinearLayout
ViewStub:按需加载所需的布局文件,eg:网络异常时的界面,这个时候没有必要在整个界面初始化的时候将其加载进来,通过ViewStub就可以做到在使用的时候再加载
绘制优化:View的onDraw方法要避免执行大量的操作;onDraw中不要创建新的局部变量,因为onDraw方法可能会被频繁调用
内存泄漏优化:
静态变量导致的内存泄漏:持有了Activity Context
单例模式导致的内存泄漏:单例模式的特点是其生命周期和Applicaiton保持一致,如果使用了Activity,会导致Activity对象无法被及时释放
属性动画导致的内存泄漏:在onDestroy中调用animator.cancel来停止动画
响应速度优化:避免在主线程中做耗时操作
当一个进程发生ANR以后,系统会在data/anr目录下常见一个文件traces.txt, 导出anr: adb pull /data/anr/traces.txt
内存泄漏分析之MAT工具:
导出hprof文件,通过hprof-conv命令转换一下
《Android进阶之光》
###5.网络编程与网络框架
网络分层:应用层,传输层,网络层,数据链路层和物理层
TCP的三次握手与四次挥手:
第一次握手:建立连接,客户端发送连接请求报文段,SYN=1,seq=x,客户端进入SYN_SENT状态,等待服务端的确认
第二次握手:服务端收到客户端的SYN报文段,对SYN报文段进行确认,ACK=x+1; 自己发送SYN请求信息SYN=1,seq=y,服务端将所有信息放在SYN+ACK报文段中,一并发送给客户端,服务端进入SYN_RCVD状态
第三次握手:客户端收到服务端的SYN+ACK报文,ACK=y+1,向服务端发送ACK报文,这个报文发送完毕后,客户端和服务端都进入ESTABLISHED状态
断开连接需要四次挥手:
每次连接,关闭都要经历三次握手四次挥手显然会造成性能低下,HTTP有一种叫作keepalive connection的机制,它可以在传输数据后仍然保持连接
HTTP请求报文:请求行(Method请求方法 Request-URI统一资源标识符 HTTP-VersionHTTP协议版本),请求报头,请求数据
HTTP响应报文:状态行(HTTP-Version Status-Code Reason-Phrase)
200 请求成功
400 Bad Request客户端请求语法错误
401 Unauthorized请求未经授权
403 Forbidden服务端收到请求,但是拒绝提供服务
500 Internal Server Error服务器内部错误
503 Server Unavailable服务器当前不能处理客户端的请求,一段时间后可能恢复正常
HttpClient: Android6.0版本直接删除了HttpClient类库
HttpUrlConnection: HttpUrlConnection的压缩和缓存机制可以有效地减少网络访问的流量,在提升速度和省电方面起到较大作用
解析Okhttp:
异步GET请求:创建OkHttpClient、Request和Call,最后调用Call的enqueue方法
异步POST请求;多了用FormBody封装请求参数,并传递给Request
设置超时时间和缓存:new OkhttpClient.Builder().connectTimeout().writeTimeout().readTimeout().cache()
取消请求:call.cancel()
源码解析OkHttp:
OkHttp的请求网络流程:
OkhttpClient#newCall -》RealCall#enqueue -》Dispatcher#enqueue -》
Dispatcher用于控制并发的请求,最大并发请求数maxRequests=64; 每个主机最大请求数maxRequestsPerHost=5;将要运行的异步请求队列readyAsyncCalls, 正在运行的异步请求队列runningAsyncCalls,
当正在运行的异步请求队列中的数量小于64并且正在运行的请求主机数小于5时,把请求加载到runningAsyncCalls中并在线程池中执行,否则加入到readyAsyncCalls中进行缓存等待
从readyAsyncCalls取出下一个请求,加入runningAsyncCalls中并交由线程池处理
AsyncCall#execute -》getResponseWithInterceptorChain (Interceptor拦截器) -》HttpEngine#sendRequest 发送请求
HttpEngine#readResponse 解析HTTP响应报头,如何判断缓存是否有效?通过缓存和网络请求响应中的Last-Modified来计算是否是最新数据
创建请求Call -》异步 创建线程池处理请求
-》同步 -》通过拦截器发送请求 -》HttpEngine发送请求 -》有缓存 缓存系统
-》无缓存 网络请求 -》Response
OkHttp的复用连接池:
ConnectionPool默认空闲的socket最大连接池为5个,socket的keepAlive时间为5分钟
解析Retrofit:
注解:HTTP请求方法注解,标记类注解和参数类注解
Retrofit是通过建造者模式构建出来的,接下来用Retrofit动态代理获取之前定义的接口得到Call对象,接下来用Call请求网络处理回调
GET请求访问网络: 动态配置URL地址:@Path; 动态指定查询条件:@Query; 动态指定查询条件组: @QueryMap
POST请求访问网络:
传输数据类型为键值对:@Field, @FormUrlEncoded注解标明是一个表单请求
传输数据类型JSON字符串: @Body
单个文件上传:@Part eg: Call<User> updateUser(@Part MutipartBody.Part photo, @Part("description") RequestBody description) 参数MutipartBody.Part表示准备上传的图片文件;
多个文件上传:@PartMap
消息报头Header: @Headers("Accept-Encoding: application/json"),如果多个可以用{}包含起来
通过参数传递Call<ResponseBody> getCarType(@Header("Location") String location)
源码解析Retrofit:
Retrofit的创建过程:Retrofit是通过建造者模式构建出来的
Call的创建过程:Retrofit#create 返回一个Proxy.newProxyInstance动态代理对象 -》loadServiceMethod(method) 创建OkHttpCall -》创建ServiceMethod
Call的enqueue方法:OkHttpCall#enqueue -》okhttp3.Call#enqueue
-》parseResponse -》 ServiceMethod#toResponse -》GsonResponseBodyConverter#convert 里将回调的数据转换为JSON格式
《基于Kotlin的Android应用程序开发》
###1.Kotlin语言基础
基本数据类型
数字:Double,Float,Long,Int,Short,Byte
类型转换:位数短的数据类型不能直接转换成位数长的数据类型,如Int类型不能赋值给Long类型,转换借助函数toByte, toShort, toInt, toLong, toFloat, toDouble, toChar
运算:shl 带符号左移运算 shr 带符号右移运算
数组:var ins = Array(5, {i -> i+1})
Kotlin程序中的字符串可使用模板表达式,基本格式为$表示符,${运算操作}
数据类型的检测与转换:类型检测 is 或 !is; 类型转换 as, as? 操作符进行安全转换
程序的控制结构:
if结构:var value = if (a>b) {a} else {b}
when结构:类似java中的switch语句, 默认执行程序的分支判断条件为else
for循环:for (变量 in 集合) {}; withIndex方法获取数组中的键值对:for ((k,v) in strs.withIndex()) {...}
while循环: while(判断条件) {} ; do {}while(判断条件)
break命令是终止当前循环;continue是跳出当前循环
集合类型:
列表(List),集合(Set),字典(Map)
系统提供的标准方法:listOf,mutableListOf,setOf, mutableSetOf, mapOf, mutableMapOf
mutableMapOf中,一个键值对按“键 to 值”方式进行声明
数值范围:
数值范围表达式:..(两个点), for(i in 1..10)
当范围起始值大于终止值时,可使用类似于for(i in 10 downto 1)的语句来进行程序控制
控制变量访问的步长:for(i in 1..10 step 2)
不需要某个范围的终止值时,使用关键字until,for(i in 1 until 10),表示数值范围是从1开始,并至9终止
等式:
等式表达式:==用于值或结构相等关系的判断(!=为对应的不相等关系的判断);===用于应用对象相等关系的判断(!==为对应的不相等关系的判断)
操作符:
Elvis操作符: 被判断对象 ?: 返回值, o?.length ?: 0
!!操作符会对被操作对象进行检查,如果该对象为空抛出异常
方法:
fun 方法名称(参数列表) :返回值列表 {}, 参数列表中参数声明的基本格式为:参数名 : 参数类型,当方法没有返回值时,上述的返回值类型使用Unit
仅包含计算表达式, 简化格式: fun 方法名称(参数列表) = 计算表达式, eg: fun add(a: Int, b: Int) = a + b; 方法定义中的参数可指定默认值, eg: fun add(a: Int, b: Int=10) = a+b
中缀方式: 定义时使用了infix关键字,方法只有一个输入参数,方法是类成员(或类的扩展方法)eg: infix fun String.extends(str: String):String{return this.toString() + str}; val ss = "string" extends "-sub"
Lambda表达式和高阶方法:
val add = {x: Float, y: Float->x+y}; 使用add(0.1f, 0.2f)
val calc:(Int, Float)->Float = {x: Int, y: Float->x*y}
Kotlin的方法中,当方法的参数只有一个时,可使用it来对参数进行访问;
{a:String -> println(a)}("testing")
类与对象:
类的构建器:
构建器在类被实例化时由系统调用
主构建器:
Kotlin类的主构建器使用constructor关键字说明,语句位置位于类声明处: class 类名 constructor(参数列表) {}
若主构建器不包含注释(annotation)或访问权限说明,则关键字constructor可省略: class 类名(参数列表) {}
若主构建器包含注释(annotation)或访问权限说明,则关键字constructor不可省略:class 类名 public @Inject constructor(type: String) {}
非主构建器:
非主构建器的定义位于类内部,使用关键字constructor说明:class 类名 { constructor(参数列表){} }
在构建器调用说明中,使用冒号并使用关键字this来实现基本的自调用
若在程序创建时还无法确定属性的具体值时,相关属性需要使用lateinit进行说明,lateinit所修饰的变量只能是可变变量
取值器在定义时使用关键字get,设值器在定义时使用关键字set,其中field关键字指代一个属性实例
类的继承:
Kotlin中所有类从Any类开始继承,Any类包含几个基本方法:equals,hashCode, toString等
允许被继承的类必须使用open关键字来进行说明 open class 父类(参数列表) {}; class 子类名(参数列表) : 父类名(参数列表) {}
子类可在自己的非主构建器定义时调用父类中的非主构建器(使用关键字super), constructor(s: String): super(s)
继承中方法和属性的覆盖:父类中允许被覆盖的属性或方法必须使用open关键字,如果子类覆盖了父类中的属性或方法,该属性或方法必须使用override关键字;var属性(普通变量)覆盖val属性(只读变量),但不是val属性覆盖var属性
抽象类与接口:
抽象类:abstract class 抽象类(参数列表) {}; 抽象类不能实例化; 抽象方法在定义时需通过abstract关键字进行说明;抽象类中可以包含非抽象方法;
接口:使用interface关键字,接口中的所有方法必须是抽象方法;接口不能被实例化;接口是实现类的一种约束或规范;Kotlin允许为接口中的抽象方法提供默认的实现
public, private, protected--本类和子类可见, internal--模块内可见
扩展: 扩展方法: fun 类名.方法名(参数列表):返回值类型 {} ;扩展属性: val 类名.属性名
数据类: data class 类名(参数列表), 编译器会为数据类增加equals,hashCode,toString,copy方法
拆分结构:
var (a, b, c) = obj, 如果不使用某个变量或常量,可使用符号_进行说明,eg: (_, e, f) = func()
嵌套类和内部类:
嵌套类:可以通过外部类名来进行访问, class A { class B{} }
内部类:必须通过外部类的实例来访问, class A { inner class BB{} }
枚举类: enum class 类名 {}
泛型:
out: 对象可以赋值给父类型变量 in: 对象可以赋值给子类型变量
泛型方法: fun <T> 方法名(v: T, ...); fun <T> T.方法名(...): ...
对象表达式:
object: 接口名{}
object: 抽象类名() {}
val 常量名 = object {}
对象声明:
object 对象名称{}; object 对象名称:父类名称(参数列表) {}; 使用:对象名.属性,对象名.方法名(参数列表)
伴随对象:companion object
伴随对象中的方法可直接通过类名进行调用
代理属性:
val 变量名: 变量类型 by 代理类名称()
var 变量名:变量类型 by 代理类名称()
val v1 by lazy() {}
网友评论