安卓知识点回顾

数据结构

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的实现,默认构造同样有initialCapacityloadFactor属性,还多了一个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.png

JVM

image.png

引用计数法

对象有引用,计数+1,引用失效时,计数-1。循环嵌套引用会有问题,需要单独的线程去处理该问题,效率低。

可达性分析(GCroot)

image.png

通过一系列的称为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链,当一个对象到GC Roots 没有任何引用链相连时,则证明此对象是可以被回收的。被GCRoot直接或间接引用的对象一律不回收。

作为GC Roots 的对象包括下面几种:

  1. 方法区中类静态属性引用的对象: 静态变量。

  2. 虚拟机栈中的引用﹔各个线程调用方法栈中使用到的参数、局部变量、临时变量等。

  3. 方法区中常量引用的对象:比如字符串常量池里的引用。

  4. 本地方法栈中JNI(即一般说的Native方法):本地方法中使用的变量、对象。

class回收条件

  1. 该类所有的实例都已经被回收,也就是堆中不存在该类的任何实例。

  2. 加载该类的 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.png

IPC 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)支持自定义拦截等

所用到设计模式:

  1. 责任链模式:单一职责,上一个处理者持有下一个处理者的引用,直到所有链条执行完毕,okhttp核心就是责任链模式,通过责任链模式五大拦截器完成请求配置。

  2. 构建者模式:将一个复杂的对象与表现分离,okhttp和request都用到了构建者模式,通过builder来处理构建。

  3. 享元模式:核心就是池中复用,从复用池子中取,用完置空,再放回池子,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.png

1)1.0时候在application.install完成初始化操作,2.0后通过ContentProvider机制在打包启动过程中将所有module的contentProvider放在一起,启动时候install。

2)通过activityLifeCycleCallbacks监听所有activity对象,通过FragmentManagerLifeCycleCallbacks监听所有fragment对象,在ObjectWatcher监听生命周期onDestory回调,以weakRefreces弱引用传给ObjectWatcher。

  1. 通过HeapDumpTrigger轮询是否引用对象是否存在泄露,找不到则已经释放;找到引用对象则手动触发gc,如果5s还没有被释放就dump出hprof文件。

  2. 调用自带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。

内存优化方式:

  1. 利用as的profiler查看堆栈快照,具体查看波动内存是哪里导致的。

  2. 利用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

  1. 把补丁包patch.dex转化为element[] patch

  2. 获得pathList的dexElements old

  3. 将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()的区别?

  1. wait线程休眠时会释放锁, sleep线程休眠时不会释放锁,其他线程无法访问;

  2. wait用于线程间的协调,sleep用于暂停当前线程的执行;

  3. 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.png

PagerAdapter适配

1)adapter satartUpdate操作,instanItem创建适配的Item数据初始化Fragment页面

2)对左右两边内容进行缓存处理,根据position来destoryItem和添加相应的fragment

3)配置完成后setPrimaryltem设置当前Item,并且调用setUservisibleHint设置可见

  1. 执行adapter的finishUpdate完成适配,由FragmentTransaction将事件commit

预加载默认情况下就已经请求和绘制,增加了开销

懒加载:要显示才加载;(布局替换方案viewStub:骨架式加载)

预加载:还不可见就已经加载页,由源码决定默认为3个pager

Activity的OnResume是在ActivityThread类中由WindowManager的addView来更新UI(ViewTree)

弱网问题数据加载时间长,导致卡顿,跳转问题、退出重进问题等

从可见到不可见时候loaderStop停止加载,从不可见到可见loader加载;在OnResume和onPause分发加载loaderView和停止loaderStop事件;ViewPager2是fianl修饰的,不可以被继承不可以定制化,copy code可以临时解决但维护不方便。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,839评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,543评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,116评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,371评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,384评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,111评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,416评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,053评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,558评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,007评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,117评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,756评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,324评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,315评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,539评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,578评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,877评论 2 345

推荐阅读更多精彩内容