数据结构
ArrayList和LinkedList的区别?
(1)ArrayList是基于数组的数据结构,LinkedList是基于链表的数据结构。
(2)ArrayList适用于查询操作,LinkedList适用于插入和删除操作。
HashMap与HashTable的区别
(1)父类不同。HashTable是基于陈旧的Dictionary类,HashMap是java1.2引进的Map接口的一个实现,而HashMap继承的抽象类AbstractMap实现了Map接口。
(2)线程安全不一样。HashTable中的方法是同步的,而HashMap中的方法在默认情况下是非同步的。在多线程并发情况下,可以直接使用hashTable,但是要使用HashMap的话就要自己增加同步处理。
(3)允不允许null值。HashTable中,key和value都不允许null值,否则会抛出NullPointException异常。而在HashMpa中,null可以作为key,这样的key只有一个;可以有一个或多个key所对应的value为null,当get()方法返回null值时,即可以表示HashMap中没有该key。也可以表示该key所对应的value为null,因此,在hashMap中不能由get()方法来判断HashMap中是否存在某个key,而应该用containsKey()方法来判断。
(4)遍历方式的内部实现上不同。HashTable、HashMap都使用了Iterator。HashTable还使用了Enumeration的方式。
(5)哈希值的使用不同。HashTable直接使用对象的hashCode。而HashMap重新设计hash值。
(6)内部实现方式的数组初始化大小和扩容的方式不一样。HashTable中的hash数组初始大小是11,增加的方式是old*2+1。HashMap中的hash数组默认大小是16,而且一定是2的指数。
HashMap的实现原理
HashMap实际上是一个“链表散列”的数据结构,即数组和链表的结合体。它是基于哈希表的Map接口的非同步实现。
数组:存储区间连续,占用内存严重,寻址容易,插入删除困难;所以在查询时候直接索引index,可以很快查询到数据,缺点是插入和删除慢,类似班级排队时每个人知道自己是一列中的第几个。
链表:存储区间离散,占用内存比较宽松,寻址困难,插入删除容易;通过节点头记录该节点的上一个和下一个节点,因为首尾记录所以不需要一块完整的内存,而且插入删除相对快,但是查询速度慢,因为要维护节点头所以内存开销大,类似班级排队时,每个人不知道自己位置,但是知道自己前面和后面是谁。
HashMap综合应用了这两种数据结构,实现了寻址容易,插入删除也容易。
LinkedHashMap工作原理和使用方式
查看LinkedHashMap源码发现是继承HashMap实现Map接口。也就是HashMap方法LinkedHashMap都有。LinkedHashMap是有序的,hashMap是无序的,有序就是插入顺序和输出顺序一致。LinkedHashMap通过维护一个双向链表实现有序,也正是要维护这个链表,内存上开销更大。
ConcurrentHashMap的理解
ConcurrentHashMap-->initalCapacity-->loadFactor-->currencyLeavel--->segment-->Hashentry-->voliate
[图片上传失败...(image-a26763-1718069274696)]
并发集合位于java.util.concurren包下。在java中普通集合通常性能最高,但是不保证多线程安全。线程安全集合仅仅给集合添加了Synchronized同步锁,并发的效率低。并发集合则保证了多线程的安全又提高了并发时的效率,ConcurrentHashMap是线程安全的HashMap的实现,默认构造同样有initialCapacity和loadFactor属性,还多了一个concurrentcyLevel属性,内部使用锁分段技术,维持Segment的数组,在Segment数组中存放着Entity数组,内部hash算法将数组分布在不同锁中。
(1)ConcurrentHashMap->put操作:没有在该方法上加Synchronized,首先对key.hashCode进行操作,得到对应Segment对象,调用put方法来完成操作。
(2)ConcurrentHashMap->get操作:首先对key.hashCode操作,找到对应的Segment对象,调用其get方法完成当前操作。而Segment的get操作获取数组上对应位置的HashEntry。
ConcurrentHashMap基于concurrentcyLevel划分了多个Segment来对key-value进行存储,从而避免每次put操作都得锁住整个数组,在默认情况下可允许16个线程并发操作集合对象,减少并发带来的阻塞现象。
对象数组大小的改变只有在put操作时可能发生,因为HashEntry对象数组对应的变量是volatile类型的,所以可以保证当hashEntry对象数组发生改变,读取操作可看到最新的对象数组大小。由于HashEntry对象中的hash、key、next属性都是final的,所以不能插入一个HashEntry对象到链表的中间或末尾。
ConcurrentHashMap默认情况下将数据分为16段进行存储,并且16段分别持有各自不同的锁Segment,锁仅用于put和remove操作;由于volatile和HashEntry的原子性实现了读取不加锁,使得ConcurrentHashMap保持较好的并发支持。ConcurrentHashMap比HashMap线程安全,比HashTable要高效。
Voliate关键字
image.pngJVM
image.png引用计数法
对象有引用,计数+1,引用失效时,计数-1。循环嵌套引用会有问题,需要单独的线程去处理该问题,效率低。
可达性分析(GCroot)
image.png通过一系列的称为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链,当一个对象到GC Roots 没有任何引用链相连时,则证明此对象是可以被回收的。被GCRoot直接或间接引用的对象一律不回收。
作为GC Roots 的对象包括下面几种:
-
方法区中类静态属性引用的对象: 静态变量。
-
虚拟机栈中的引用﹔各个线程调用方法栈中使用到的参数、局部变量、临时变量等。
-
方法区中常量引用的对象:比如字符串常量池里的引用。
-
本地方法栈中JNI(即一般说的Native方法):本地方法中使用的变量、对象。
class回收条件
-
该类所有的实例都已经被回收,也就是堆中不存在该类的任何实例。
-
加载该类的 ClassLoader 已经被回收。
对象的四种引用
强引用:内存溢出都不会回收,如String对象
软引用:当GC触发时根据当前内存情况决定是否回收,SoftReference,图片加载时可以使用
弱引用:只要有GC触发就会回收,不管内存是否充足,WeakReference,适合缓存不常使用的对象
虚引用:随时可能被回收
内存抖动原因:对象频繁创建==>频繁释放==>频繁GC==>内存抖动==>卡顿
Activity四种启动模式
standard标准模式:每次启动都会重新创建一个新的实例入栈,不管之前是否存在。
SingleTop:栈顶复用模式,如果Activity处于栈顶,则不再创建新的activity,如果不存在栈顶则重新创建实例。
SingleTask:栈内复用模式,当需要创建这个activity时,如果activity已经存在则将所有的在它之上的activity销毁,让它处于栈顶。
singleInstance:单实例模式,具有此模式的Activity单独位于一个任务栈中,只能有一个实例。
Handler线程通信
一个线程有几个Handler
有多个Handler一个线程只有一个Looper一个MessageQueue 。通过ThreadLocal保证Looper的唯一性,它的key是当前线程,值是Looper。同时Looper又持有了MessageQueue,因此MessageQueue也是唯一的。
子线程发送msg 过程
1.ActivityThread#main() 初始化Looper
2.ActivityThread#Looper.prepareMainLooper();
3.Looper#prepare(false);
4.sThreadLocal.set(new Looper(quitAllowed));初始化Looper成功,保存在ThreadLocal中
5.ActivityThread#Looper.loop();开启loop()
6.Looper#loop(){
Looper me = myLooper();//sThreadLocal.get();从ThreadLocal中取出Looper
MessageQueue queue = me.mQueue;//从Looper中取出对应的MessageQueue
for (;;) {//死循环,开始轮询
Message msg = queue.next();//不断的从msgQueue中取消息
msg.target.dispatchMessage(msg);// target就是目标Handler
7.Handler#handleMessage()
Handler是怎样完成线程切换的
子线程发送的msg,最终发送到了msgQueue中,而msgQueue属于创建Handler时的主线程。因此子线程发送消息就是发送到主线程的msgQueue,再通过loop,最终完成了跨线程的通信。
Handler内存泄漏的原因
GC Root-Looper-MessageQueue-Message-Handler-activity
我们在使用handler时,一般都是以匿名内部类的方式使用,如果发送了延时消息,内部类持有外部类的引用,即handler持有activity,而handler又被message持有,因此延迟消息导致message无法被回收,handler就不会被回收,那么持有的activity也不会被回收,而我们本意是要关闭activity,因此也就造成了内存泄漏。在OnDestroy里面清理handler,handle.removeCallbacksAndMessage(null);将handler声明为静态类,用弱引用来持有activity对象。
为何主线程可以new Handler?如果想在子线程中new Handler要做哪些准备
进程启动时在ActivityThread的main函数中,已经准备了主线程对应的Looper,并开启了loop因此可以直接new Handler;子线程中new Handler,需要准备子线程对应的Looper,并开启轮询。
子线程中创建Handle必须调用Looper.prepare()是为了创建一个Looper对象,并将该对象存储在当前线程的ThreadLocal中,每个线程都会有一个ThreadLocal,它为每个线程提供了一个本地的副本变量,实现了和其它线程隔离,由于主线程有recycleUnchecked管理message,子线程需要实现quit退出轮询,步骤有1. prepare();2. loop();3. quit();
子线程中维护的Looper,消息队列无消息的时候处理方案是什么?
进入阻塞状态,直到有消息或者当前线程被销毁。在Looper里面有一个函数,叫做quitSafely()和quit()函数,这两个函数是调用的MessageQueue的quit(),不然会一直阻塞。
发消息时各个Handler处于不同线程,内部是如何保证线程安全的?
因为加了锁,看MessageQueue.java中的enqueueMessage源码,synchronized (this),synchronized 是一个内置锁,因为加锁和解锁都是由JVM完成的这里的this代表对一个线程的messageQueue访问的时候,其他的对象不能访问。
我们使用Message时应该如何创建它?
通过Message.obtain()创建 因为这样可以复用消息,减少内存抖动。因为GC频繁会导致内存抖动,因此频繁GC就会产生卡顿现象,这种设计模式叫享元设计模式。流程:从复用池子中取,用完置空,再放回池子。
Looper死循环为啥不会导致应用卡死
如果消息队列,没消息需要处理时,会休眠阻塞
image.pngIPC Binder跨进程通信
进程间通信的方式(IPC:Inter-Process Communication):共享内存、管道、消息队列、socket、Binder
image.png共享内存:
进程A和 B共享同一块内存地址,内存值的改变,两个进程都有感知,不需要拷贝。缺点:同步问题需要自己处理,如同时改变时需要互斥访问,无法控制进程C恶意改变地址;
Socket:
C/S模式架构,两个缓冲区为读写缓冲区,两次拷贝;也有安全问题,依赖上层协议。
Binder:
内存映射方案(MMAP),通过身份标识访问安全。
过程:client---copy from user进行一次拷贝---内核空间---映射物理内存---映射服务端
Binder 通信模型
image.png image.png内存划分:用户空间、内核空间。用户空间是程序代码运行的,内核空间是内核代码运行的。为了安全,他们之间是隔离的,即使用户的程序崩溃了,内核也不受影响。
服务端先把他的binder注册在ServiceManager,客户端从ServiceManager中拿到服务端的Binder,然后再通过内核空间的Binder驱动完成和服务端的通信。华为鸿蒙跨进程也是proxy,stub和ServiceManager也是第一步注册服务,第二步获取服务,第三步使用服务。
设计模式
单例模式
(1)懒汉式 //线程不安全,指令重排,解决指令重拍可以使用volite关键字
(2)饿汉式 //效率浪费,同步问题
(3)双重校验锁 //提高效率,双重指的是2次检查instance是否为null
单例模式引发的内存泄露:
原因:单例模式里的静态实例持有对象的引用,导致对象无法被回收,常见为持有Activity的引用。优化:改为持有Application的引用,或者静态内部类加弱引用实现。
工厂模式
用于业务的实体创建,在创建的过程中考虑到后期的扩展,在Android常见的有BitmapFactory,LayoutInflater.Factory,在实体编码的过程中如果数据类型比较多或者后期需要扩展,则可以通过工厂布局的方式实现。
建造者模式
可以直接链式设置属性,比如AlertDialog,开发框架的时候方便连续设置多个属性和调用多个方法。
享元模式
从复用池子中取,用完置空,再放回池子。
双亲委派机制
某个类加载器在加载类时,首先把任务委托给父类加载器,依次递归,如果父类加载器可以完成加载任务,就成功返回;如果父类加载器无法加载或没有父类加载器,才自己去加载。
这样设计的好处:
1、避免重复加载,父类已经加载过,子类没有必要再加载一次
2、安全,防止核心API库被随意更改,默认的父加载器是系统的,它会加载系统的类,这样自定义的类就不会再次加载了,自定义的类中的安全问题则被规避了。
MVVM架构框架
MVC
Model:主要用于网络请求、数据库、业务逻辑处理等操作。View:View:视图显示层。Controller:控制层,Activity需要把业务逻辑交给Model层处理。事件从View层流向Controller层,经过Model层处理,然后通知View层更新。
缺点:
(1)Controller层,也就是Activity承担了部分View的职责,导致该层代码臃肿。
(2)在MVC中,Model层处理完数据后,直接通知View层更新,因此耦合性强。
MVP
Model:数据处理层。View:视图显示层。Presenter:业务逻辑处理层。通过接口,View和Presenter之间相互持有引用,但V和M完全解耦,而且把业务逻辑从activity中移到P层,减轻了activity的代码量。
缺点:当View层逻辑不断增加的时候,View接口会增多,不便于管理。
MVVM
Model:数据处理层。View:视图显示层。ViewModel:视图模型层,通过框架实现View层和Model层的双向绑定,ViewModel有其自己的生命周期,可以有效解决Activity横竖屏切换导致重走Oncreate()。
(1)View和Model双向绑定,一方的改变都会影响另一方,开发者不用再去手动修改UI数据。
不需要findViewById,也不需要butterknife,不需要拿到具体的View去设置数据绑定监听器,这些可以用DataBinding 完成。
数据倒灌:Activity异常销毁然后重建,ViewModel会保存销毁之前的数据,然后在Activity重建完成后进行数据恢复,所以LiveData成员变量中的mVersion会恢复到重建之前的值。但是Activity重建后会调用LiveData的observe()方法,方法内部会重新new一个实例,会将mLastVersion恢复到初始值。
解决办法:官方扩展的SingleLiveEvent或者使用butterknife。
(2)View和Model的双向绑定是支持生命周期检测的,不会担心页面销毁了还有回调发生,这个由lifeCycle完成。
(3)不会像MVC一样导致Activity中代码量巨大,也不会像MVP一样出现大量的View和Presenter接口,项目结构更加低耦合。
缺点:由于数据和视图的双向绑定,导致出现问题时不好定位来源,有可能数据问题导致,也有可能业务逻辑中对视图属性的修改导致。
Android个版本差异
5.0
样式支持:沉浸式状态栏
6.0
权限和网络改动:运行时动态权限获取和网络httpclient删除,新增httpUrlConnection。
7.0
权限改动:intent不允许包名之外的文件Uri,需要使用File Provider
8.0
通知栏改动:Notification需要设置channel
9.0
内存优化:将bitmap中的像素点放在native层等。
10.0
不需要获取权限可在储设备中访问和保存自己的文件存储文件,地址为getExternalFilesDir()下的文件夹
11
安装app动态权限request_install_packages
12
自定义通知Android 12 为自定义通知强制执行外观一致的布局模板
13
使用新的照片选择器,让用户无需向应用授予对整个媒体库的访问权限
14
对后台activity的额外限制;用户无法安装 targetSdkVersion 低于 23 (android7.0)的应用
15
将最低目标 SDK 版本从 23 提高到了 24(android7.0)
OkHttp
1)OkHttpClient#build来构建,分发器维护请求队列与线程池,分为正在请求和等待队列
2)cacheThreadPool线程池里面仅仅是维护了线程的状态具体交由dispatcher调度
3)Interceptors拦截器中,通过责任链模式持有重定向拦截器、桥接拦截器、缓存拦截器、连接拦截器和callServiceInterceptor的引用,最后把请求结果封装成一个Response对象返回。如果异常则重走retryAndfllowUpInterceptor重定向。
Okhttp****优势:
1)采用gzip压缩
2)支持重定向,减少网络请求
3)通过外观模式,将所有逻辑封装成okhttpclient对象
4)支持http1,http2,websocket
5)通过连接池ConectionPool复用底层tcp(socket通信),减少请求延时
6)支持自定义拦截等
所用到设计模式:
-
责任链模式:单一职责,上一个处理者持有下一个处理者的引用,直到所有链条执行完毕,okhttp核心就是责任链模式,通过责任链模式五大拦截器完成请求配置。
-
构建者模式:将一个复杂的对象与表现分离,okhttp和request都用到了构建者模式,通过builder来处理构建。
-
享元模式:核心就是池中复用,从复用池子中取,用完置空,再放回池子,okhttp在复用tcp请求时候用到了连接池,在处理异步请求中用到了线程池。
Glide
Gilde.with(Context).load(url).into(img)==>activiResouces==>命中加载完成/未命中找memonryCache()==>命中缓存到activiResouces/未命中找DiskCache==>命中缓存到memonryCache和activiResouce中/未命中则重新请求网络加载后依次缓存起来。
Glide偶尔内存泄露?
尽量不要用application的上下文加载,因为application绑定的是应用的生命周期,不会随着页面的销毁而销毁,不会及时的回收和释放
Glide有内存缓存,为啥还要有活动缓存
活动缓存是一个”引用计数”的图片资源的弱引用集合,因为内存缓存中的图片资源回收是按Lru算法超过maxSize会按最近最少使用原则回收,如果正在显示的图片资源被回收了,应用会崩溃。为了避免这种情况,glide中非Lru的图片缓存放在活动缓存中,活动缓存中只放显示中的图片资源。
Leakcanary
image.png1)1.0时候在application.install完成初始化操作,2.0后通过ContentProvider机制在打包启动过程中将所有module的contentProvider放在一起,启动时候install。
2)通过activityLifeCycleCallbacks监听所有activity对象,通过FragmentManagerLifeCycleCallbacks监听所有fragment对象,在ObjectWatcher监听生命周期onDestory回调,以weakRefreces弱引用传给ObjectWatcher。
-
通过HeapDumpTrigger轮询是否引用对象是否存在泄露,找不到则已经释放;找到引用对象则手动触发gc,如果5s还没有被释放就dump出hprof文件。
-
调用自带shark库的HeapAnalyzerService解析hprof文件,得到leakTrace泄露完整引用链。与jvm的GCroot根可达很像。
Leakcanary的Bug:
Activity如果不执行Ondestroy 或者fragment多层嵌套监听。
fragmentation单一activity情况,getChildSupportFrament拿到子fragment。
性能优化
内存优化:
GC释放对象包含java栈中引用的对象;静态方法引用的对象;静态常量引用的对象;Native中JNI引用的对象,Thread等。
ANR产生的原因:
Activity5s内无响应;广播10s无法处理完;前台服务service20s无法处理完。
OOM内存溢出原因:
瞬间申请了大量内存导致OOM;长期不能释放对象超过阈值导致OOM;小范围累积不能释放导致卡顿的OOM。
内存优化方式:
-
利用as的profiler查看堆栈快照,具体查看波动内存是哪里导致的。
-
利用LeakCanary工具查看生成的dump文件。
UI布局优化:
View过渡绘制;Layout过于复杂,无法在16ms内完成渲染;View频繁出发measure,layout。
开启GPU过渡绘制工具查看当前布局的绘制嵌套情况。
1:用RealtiveLayout代替LinearLayout
2:include,ViewStub,merge标签的使用
3:使用as自带的Remove Unused Resources清理无用资源
4:代码混淆
5:网络优化:用as自带的profiler进行网络监听。网络请求中的图片可以用webp格式,质量相同的情况下减少流量。
耗电优化
1:需要进行网络请求时先判断当前的网络状态。当有wifi和移动网络时候,优先选择wifi比移动网络耗电低。
2:减少后台任务的唤醒操作。
启动优化
1:冷启动,杀死进程后启动或程序第一次启动,耗时最久,因为要重新经历application初始化,启动ui线程,创建Activity,导入视图绘制视图等。
2:暖启动,当activity被销毁,但在内存中常驻时,启动减少了对象的初始化,布局加载等,启动时间短。
c:热启动,启动时间最短,比如按home键重新回到应用。
优化方式:Application创建过程尽量少,减少布局层次,启动页预加载等。如下。
Application优化
Application有多少个进程就会执行几次。可能是定位、推送、webView进程等。
1)在主工程创建BaseApplication,定义BaseInstance单例统一管理context,定义三个抽象方法。通过activityManager的runningAppprocess实现获取进程列表和名字。
2)自定义的application继承baseApplication后在oncreate中判断主进程,初始化网络和文件存储对应逻辑。
3)将组件内初始化的代码放在自己的application里面,组件自己init减少代码量,为了新增模块和减少模块不影响主applciation,通过SPI(service provider interface)机制,普通interface接口要实现的话必须引用到实现者,而SPI找到实现者可以将引用者和实现者的关系在编译时候记录到apk/meta-inf/services下,最后通过serviceLoader找到所有实现者,依次init即可。
4)在组件中创建helper接口,实现applicationInit方法。
5)在网络模块中实现组件中helper接口,通过@AutoService,判断如果在主进程下则initNetWork。
6)在主工程application中通过ServiceLoader.load这个helper接口,把与这个helper接口有关的所有实现者找到,返回了所有实现者后在依次init并记录时间。
Application源码分析
1)应用启动执行ActivityThread的main方法。
handlebindApplication:启动自定义application的onCreate
HandleLauchActivity启动activity的onCreate方法
handleReceiver启动广播
handleCreateService启动服务
2)以上方法最初由AMS通过binder触发执行,每个方法体里面会优先执行makeApplicationInner,判断application是否被创建,没有被创建则重新创建调用applciation的callApplicationOncreate()。
Ams启动应用会调用handlebindApplication+HandleLauchActivity异步有可能application没有启动。于是HandleLauchActivity会先创建newActivity然后makeApplicationInner来判断如果application创建了则activity调用attach、callActivityOncreate生命周期。
字节码插桩
apk打包流程:
资源文件|java代码|aidl代码==>通过javac编译成class文件==>aar与class合并为dex文件==>apk==>signed签名
插桩逻辑:
把class文件的代码加载到内存中,变成byte数组,然后修改,重新生成byte数组,class覆盖操作。
插桩流程:
1)在gradle中配置asm后,将class给传入classReader类的解析器,在回调的visitMethod中通过判断方法名执行插桩,在开始和结束方法体里面加入进入和退出计时器。
2)通过as插件viewerbyCode将代码转成字节码指令,invokeStatic方法中传参数完成插入代码。
解决service中调用dialog方法
通过反编译得到调用的方法,判断方法名,如果名字和方法参数一致,则进行字节码插桩,visitMethodIn判断如果是show方法则不执行,其余则super.visitMethodIn执行对应方法。不调用super的话则方法将不执行。
classLoader
BootClassLoder 用于加载Android Framework层的class文件;
dexClassLoader:可以加载文件系统上的jar、dex、apk。
PathClassLoader:可以加载/data/app目录下的apk、dex、jar、zip
PathClassLoader和DexClassLoader都是Android提供给我们的ClassLoader,都能加载dex
当一个类需要被加载时,PathclassLoader 会首先尝试自己加载这个类,如果找不到,就会向上委托给父类加载器(通常是BootClassLoader),这是双亲委派模式,这样的好处有:1避免类的重复加载2,可以保证核心库的安全性。
热修复原理
image.png加载类会调用pathList.findClass(),pathList是dexPathList,dexPathList通过makePahtElements赋值。makePahtElements()做的操作是把dex包装成Element并返回Element[],所以pathList的findClass()最终等价与dex数组的findClass(),findClass()中最终会执行loadClassBinaryName,所以最终类的加载就是传入dex文件路径,然后把dex文件都解析出包装成数组,然后在数组中找到指定的类完成加载。
热修复的原理:
获取当前应用的PathClassloader,反射获取到dexPathList属性对象pathList,反射修改pathList的dexElements
-
把补丁包patch.dex转化为element[] patch
-
获得pathList的dexElements old
-
将patch的element和old合并,反射赋值给pathList的dexElements
问题:热修复怎么修复bug
先通过反射拿到element数组,然后把包含修复代码的dex文件构成的数组插在element数组的最前面,这样当类加载时加载到正常代码对应的类时,就不会去加载后面有问题的类,bug也就被修复了。
问题:已经编译成机器码的字节码文件中有bug怎么修复
因为机器码在classLoader创建时就会加载到缓存中,用自定义的pathClassLoader替换系统的pathClassLoader,然后去做自定义的逻辑。
多线程并发协作
线程的创建方式
1)继承Thread重写run方法
2)实现Runnable接口
3)实现callable
线程的五种状态:
初始new状态,运行runnable,阻塞blocked状态,等待wait,终止terminated状态
wait()和sleep()的区别?
-
wait线程休眠时会释放锁, sleep线程休眠时不会释放锁,其他线程无法访问;
-
wait用于线程间的协调,sleep用于暂停当前线程的执行;
-
wait执行后需要唤醒(notify或notifyAll)重新获取锁,sleep不用唤醒直接进入就绪状态,等待CPU的调度。
正常主线程和子线程并行时候优先执行主线程方法,特殊场景下需要阻塞主线程,等待子线程A执行完成后再执行主线程方法:
1)让子线程A join();让子线程加入主线程当中执行,起到让主线程阻塞作用。
2)通过对对象加锁的wait和notify实现。
缺点是无法完成更细颗粒度问题。当AB子线程执行各自方法,执行完成顺序为不可控。处理多线程出现并发协作效率问题。
使用CountDownLatch闭锁,实现解决等待一个或多个线程完成的场景。CountDownLatch有await阻塞和countdown方法,countdown里面Sync静态内部类去compareAndsetState,将状态码进行比较和替换,当状态码变成0是,await不在阻塞主线程。于是可以达到AB线程中的第一阶段执行完成后,第二阶段和主线程并行的目的。
image.png image.png线程池
线程池是享元模式,线程池是一种管理线程的机制,用于复用线程资源,减少线程创建和销毁的开销,从而提高程序性能,线程池中的线程在完成任务后不会立即销毁,而是被放回线程池,,等待执行新的任务。
分类:Execute.FixedThreadPool、CachedThreadPool、ScheduledThreadPool、SingleThreadExecutor FixedThreadPool(3);//线程固定 只有核心线程且不会回收 适合快速响应的场景 CachedThreadPool();//只有非核心线程 线程数无限,60s超时 适合处理大量耗时较少的任务 ScheduledThreadPool(3);//核心线程固定,非核心线程无限制,适合执行定时任务或者固定周期的重复任务 SingleThreadExecutor();//只有一个核心线程 任务按序执行
AsyncTask是封装好的线程池,比起Thread+Handler的方式,Asynctask在操作UI线程上更方便,因为onPreExecute0)、onPostExecute()及更新UI方法onProgressUpdate()均运行在主线程中,这样就不用Handler发消息处理了;
AsyncTask的线程池只有5个工作线程,其他线程将被阻塞,自己new出来的线程不受此限制,所以AsyncTask不能用于多线程取网络等数据。AsyncTask用的是线程池机制,容量是128,最多同时运行5个核心线程,剩下的排队。
AQS(AbstractQueueSynchronizer)
执行线程通过execute来提交一个Runnable,把提交的Runnable封装成一个Worker对象,而Worker继承至AbstractQueueSynchronizer。
AQS即抽象队列式的同步器,内部定义了很多锁相关的方法,像CountDownLatch、ReentrantLock也是基于AQS来实现的,AQS中 维护了一个volatile类型的state和一个FIFO(first-in-first-out)线程等待队列,当多线程争用资源被阻塞时会进入此队列。这里volatile能够保证多线程下变量的可见性,当state=1则代表当前对象锁已经被占有,其他线程来加锁时则会失败,加锁失败的线程会被放入一个FIFO的等待队列中,等待其他获取锁的线程释放锁才能够被唤醒。另外state的操作都是通过CAS来保证并发修改的安全性。
AQS 中提供了很多关于锁的实现方法
getState():获取锁的标志state值
setState():设置锁的标志state值
tryAcquire(int):独占方式获取锁。尝试获取资源,成功则返回true,失败则返回false。
tryRelease(int):独占方式释放锁。尝试释放资源,成功则返回true,失败则返回false。
CAS(compare and swap)
CAS机制可以解决多线程并发编程对共享变量读写的原子性问题。
1)主内存中存放的共享变量的值:V,V是内存的地址值,通过地址可以获得内存中的值
2)工作内存中共享变量的副本值:old值A
3)需要将共享变量更新到的最新值:预期值B
主存中保存了V值,线程中要使用V值要先从主存中读取V值放到线程的工作内存A中,然后计算后变成B值,最后再把B值写回到内存V值中。多个线程共用V值都是如此操作。
CAS的核心是在将B值写入到V之前要比较A值和V值是否相同,如果不相同证明此时V值已经被其他线程改变,重新将V值赋给A,并重新计算得到B,如果相同,则将B值赋给V。
CAS机制中的步骤是原子性的,从指令层面提供的原子操作,所以CAS机制可以解决多线程并发编程对共享变量读写的原子性问题。
ABA问题:CAS在操作的时候会检査变量的值是否被更改过,如果没有则更新值,但是带来一个问题,最开始的值是A,接着变成B.最后又变成了A。经过检查这个值确实没有修改过,因为最后的值还是A,但是实际上这个值确实已经被修改过了。为了解决这个问题,在每次进行操作的时候加上一个版本号,每次操作的就是两个值,一个版本号和具体值,A-->B-->A问题就变成了1A-->2B->3A,在jdk中提供了AtomicStampedReference类解决ABA问题,用Pair静态内部类来实现,Reference和stamp,分别代表引用和版本号,在compareAndSet中先对当前引用进行检査,再对版本号标志进行检査,只有全部相等才更新值,才将预期值B值更新到主存V当中。
任务调度
UI操作需要用户交互,其他任务可以并行执行。
If(隐私校验通过)else{弹框提示用户下载正版}
If(隐私条款/用户协议同意)else{退出APP}
If(引导页/欢迎页/介绍页){显示广告页}==》进入主页
每个流程都需要知道下一步的具体操作,耦合性太强,违背了开闭原则。
SPI
SPl是Service Provider Interface 的简称,即服务提供者接口的意思。是一种扩展机制,我们在相应配置文件中定义好某个接口的实现类,然后再根据这个接口去这个配置文件中加载这个实例类并实例化。有了SPI机制就不必将框架的一些实现类写死在代码里面。具体文件路径在apk/META-INF/services/目录下。
IO流操作
在对IChannel应用中资讯视频流媒体的加解密时,拿到FileInputStream后,通过循环read输入流之前手动write自定义的byte[]写入到outPutStream当中,这样写入完成后会得到一个加密的视频。在解密的while循环前对InputStream先skip byte的长度再进行outPutStream的write操作,即可实现简单对文件加解密的操作。视频文件前后write差异问题。
自定义View滑动冲突
NestedScrollView(放映机原理)
自定义View顺序:继承-实现构造函数-测量-布局-绘制-事件分发-滑动冲突
布局包含:NestedSrollView-->LinearLayout-->HeadView-->TableLayout-->ViewPager-->recycleView
image.png嵌套滑动只由后代RecycleView中触发,NestedScrollView实现了NestedScrollingParent和 NestedScrollingChildf方法,NestedScrollingChild不支持NestedScrolling,NestedScrollingChild2多了Type支持嵌套滑动。
1)因为嵌套滑动只由后代RecycleView中触发,所以RecycleView的action_down操作调用StartNestedScroll死循环遍历获取recycleView的parent是否支持嵌套滑动,找到viewpager、LinearLayout、直到NestedScrollView为止。
2)在action_move中去分发滑动事件dispatchNestedPreScroll,调用祖先类的onNestedPreScroll方法,然而祖先类又会分发dispatchNestedPreScroll变成循环依赖,所以需要自定义NestedScrollView方法重写onNestedPreScroll方法
3)重写onNestedPreScroll方法中判断滑动位置小于headView则执行scrollBy滑动并且赋值偏移量,现实当headView没有完全隐藏时候滑动recycleView优先滑动headView。
4)使用NestedScrollView的fling函数遍历viewpager下的RecycleView,将惯性值交由recycleView的fling处理,来实现惯性滑动。
事件分发
Activity/View处理事件分发的方法:dispatchTouchEvent()-->onTouchEvent()
ViewGroup事件分发处理:dispatchTouchEvent()-->onInterceptTouchEvent()-->onTouchEvent()
1)在activity中当用户屏幕点击了后会先触发dispatchTouchEvent()如果view处理该事件则返回true,事件传递结束。如果返回false则调用onTouchEvent()处理该事件。
2)在ViewGroup中,ViewGroup的dispatchTouchEvent先判断ViewGroup是否拦截Touch事件,如果拦截了则不向下传递直接调用TouchEvent处理事件,如果没有拦截则遍历所有子view找到点击的那个View把touch事件传递给它。viewgroup的onInterceptTouchEvent方法默认false,viewgroup事件分发都会调用它,一旦onInterceptTouchEvent返回true则表示拦截了事件,后续进行事件分发不再调用onInterceptTouchEvent方法。
优先级:onTouch >onLongClick>onClick
内部拦截:getParent().requestDisallowInterceptTouchEvent(true)请求不允许拦截点击事件,父控件不会拦截事件
RecycleView
RecyclerView卡顿情况:布局复杂,多层嵌套,设置setNestedScrollingEnable(false);不再支持嵌套滑动。
RecyclerView的4级缓存:
AttachedScrap:当前屏幕下的ItemView,直接复用 CachedViews:刚刚移出屏幕的缓存,最大容量为2,通过position来保存,数据不变,直接复用;滑动时,该缓存一边add,一边remove。 ViewCacheExtension:自定义缓存基本用不上 RecyclerPool:缓存池保存第二级缓存中保存不了的ItemView,每种itemType可以保存5个ItemView
ViewPager+Fragment
ViewPager通过Populate函数中pagerAdapter填充Fragment
image.png image.pngPagerAdapter适配
1)adapter satartUpdate操作,instanItem创建适配的Item数据初始化Fragment页面
2)对左右两边内容进行缓存处理,根据position来destoryItem和添加相应的fragment
3)配置完成后setPrimaryltem设置当前Item,并且调用setUservisibleHint设置可见
- 执行adapter的finishUpdate完成适配,由FragmentTransaction将事件commit
预加载默认情况下就已经请求和绘制,增加了开销
懒加载:要显示才加载;(布局替换方案viewStub:骨架式加载)
预加载:还不可见就已经加载页,由源码决定默认为3个pager
Activity的OnResume是在ActivityThread类中由WindowManager的addView来更新UI(ViewTree)
弱网问题数据加载时间长,导致卡顿,跳转问题、退出重进问题等
从可见到不可见时候loaderStop停止加载,从不可见到可见loader加载;在OnResume和onPause分发加载loaderView和停止loaderStop事件;ViewPager2是fianl修饰的,不可以被继承不可以定制化,copy code可以临时解决但维护不方便。
网友评论