2023-Android-常见面试题

[TOC]

HASH算法

  • 常用的摘要算法包括MD5,SHA1,SHA256

  • 消息摘要算法的特点:

    1. 无论输入的消息有多长,计算出来的消息摘要的长度总是固定的。

    2. 消息摘要看起来是“随机的”。这些比特看上去是胡乱的杂凑在一起的。

    3. 一般地,只要输入的消息不同,对其进行摘要以后产生的摘要消息也必不相同;但相同的输入必会产生相同的输出。

    4. 消息摘要函数是无陷门的单向函数,即只能进行正向的信息摘要,而无法从摘要中恢复出任何的消息,甚至根本就找不到任何与原信息相关的信息。

    5. 好的摘要算法,无法找到两条消息,是它们的摘要相同。

    6. 主要特征是加密过程不需要密钥,并且经过加密的数据无法被解密,只有输入相同的明文数据经过相同的消息摘要算法才能得到相同的密文。消息摘要算法不存在 密钥的管理与分发问题,适合于分布式网络相同上使用。由于其加密计算的工作量相当可观,所以以前的这种算法通常只用于数据量有限的情况下的加密,例如计算机的口令就是 用不可逆加密算法加密的。

https握手过程

  • 非对称加密算法用于在握手过程中加密生成的密码

  • 对称加密算法用于对真正传输的数据进行加密

  • 而HASH算法用于验证数据的完整性。

  • RSA公私钥密钥加密算法,DES,AES对称加密算法,SHA1摘要算法

  • Client端和Server端:

    • Client发出https的请求->Server

    • Server回应公钥SPublic->Client

    • Client生成随机数作为以后消息的加密密钥key

    • Client用SPublic加密key发出https请求->Server

    • Server用私钥SPrivate解密key

    • [图片上传失败...(image-dd4729-1682881853345)]

第一次HTTP请求

  1. 客户端向服务器发起HTTPS请求,连接到服务器的443端口。

  2. 服务器端有一个密钥对,即公钥和私钥,是用来进行非对称加密使用的,服务器端保存着私钥,不能将其泄露,公钥可以发送给任何人。

  3. 服务器将自己的公钥发送给客户端。

  4. 客户端收到服务器端的公钥之后,会对公钥进行检查,验证其合法性,如果公钥合格,那么客户端会生成一个随机值,这个随机值就是用于进行对称加密的密钥,我们将该密钥称之为client key,即客户端密钥,这样在概念上和服务器端的密钥容易进行区分。然后用服务器的公钥对客户端密钥进行非对称加密,这样客户端密钥就变成密文了,至此,HTTPS中的第一次HTTP请求结束。

第二次HTTP请求

  1. 客户端会发起HTTPS中的第二个HTTP请求,将加密之后的客户端密钥发送给服务器。

  2. 服务器接收到客户端发来的密文之后,会用自己的私钥对其进行非对称解密,解密之后的明文就是客户端密钥,然后用客户端密钥对数据进行对称加密,这样数据就变成了密文。

  3. 然后服务器将加密后的密文发送给客户端。

  4. 客户端收到服务器发送来的密文,用客户端密钥对其进行对称解密,得到服务器发送的数据。这样HTTPS中的第二个HTTP请求结束,整个HTTPS传输完成。

hashmap原理

  • HashMap是基于哈希表实现的,用Entry[]来存储数据,而Entry中封装了key、value、hash以及Entry类型的next

  • HashMap存储数据是无序的

  • hash冲突是通过拉链法解决的,也就是数组+链表

  • HashMap的容量永远为2的幂次方,有利于哈希表的散列

  • HashMap不支持存储多个相同的key,且只保存一个key为null的值,多个会覆盖

  • put过程,是先通过key算出hash,然后用hash算出应该存储在table中的index,然后遍历table[index],看是否有相同的key存在,存在,则更新value;不存在则插入到table[index]单向链表的表头,时间复杂度为O(n)

  • get过程,通过key算出hash,然后用hash算出应该存储在table中的index,然后遍历table[index],然后比对key,找到相同的key,则取出其value,时间复杂度为O(n)

  • HashMap是线程不安全的,如果有线程安全需求,推荐使用ConcurrentHashMap。

socket

  • 粘包问题:包头有包长度

  • 心跳包:5s一次登录检查,10s一次心跳

  • TCP的keepalive选项

Netty

  • 方便处理粘包问题,自带由定长拆包器,分隔符拆包器等

  • 零拷贝,java的FileChannel.transferTo方法,通过bytebuffer直接进行socket的读写,利用ByteBuf机制进行多bytebuffer数组的合成(实际上ByteBuf只是记录了bytebuffer的引用)

线程池

一个线程只能有一个Looper,但可以有多个Handler

  1. newCachedThreadPool

    • 建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。

    • ****线程池为无限大****

  2. newFixedThreadPool

    • 可控制线程最大并发数,超出的线程会在队列中等待。
  3. newScheduledThreadPool

    • 支持定时,延迟及周期性任务执行
  4. newSingleThreadExecutor

    • 它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行

app保活

保活:监听SCREEN_ON/OFF广播,启动1px的透明Activity; 启动空通知,提高fg-service; 申请权限,加入白名单

Activity Bundle

  • Bundle可对对象进行操作,而Intent是不可以。Bundle相对于Intent拥有更多的接口,用起来比较灵活,但是使用Bundle也还是需要借助Intent才可以完成数据传递总之,Bundle旨在存储数据,而Intent旨在传值。

  • 为什么Bundle不直接使用Hashmap代替呢?

      • Bundle内部是由ArrayMap实现的,ArrayMap的内部实现是两个数组,一个int数组是存储对象数据对应下标,一个对象数组保存key和value,内部使用二分法对key进行排序,所以在添加、删除、查找数据的时候,都会使用二分法查找,只适合于小数据量操作,如果在数据量比较大的情况下,那么它的性能将退化。而HashMap内部则是数组+链表结构,所以在数据量较少的时候,HashMap的Entry Array比ArrayMap占用更多的内存。因为使用Bundle的场景大多数为小数据量,我没见过在两个Activity之间传递10个以上数据的场景,所以相比之下,在这种情况下使用ArrayMap保存数据,在操作速度和内存占用上都具有优势,因此使用Bundle来传递数据,可以保证更快的速度和更少的内存占用。
      • 另外一个原因,则是在Android中如果使用Intent来携带数据的话,需要数据是基本类型或者是可序列化类型,HashMap使用Serializable进行序列化,而Bundle则是使用Parcelable进行序列化。而在Android平台中,更推荐使用Parcelable实现序列化,虽然写法复杂,但是开销更小,所以为了更加快速的进行数据的序列化和反序列化,系统封装了Bundle类,方便我们进行数据的传输。
  • Activity/Fragment状态数据的保存与恢复

  • 消息机制中的Message的setData

LeakCanary v2

  1. 先注册监听观察Activity/Fragment

  2. 在Activity destroy后将Activity的弱引用关联到ReferenceQueue中,这样Activity将要被GC前,会出现在ReferenceQueue中。

  3. 随后,会向主线程的MessageQueue添加一个IdleHandler,用于在idle时触发一个发生在HandlerThread的等待5秒后开始检测内存泄漏的代码。 这段代码首先会判断是否对象出现在引用队列中,如果有,则说明没有内存泄漏,结束。否则,调用Runtime.getRuntime().gc()进行GC,等待100ms后再次判断是否已经出现在引用队列中,若还没有被出现,那么说明有内存泄漏,开始dump hprof。

Service

在后台执行长时间运行操作而没有用户界面的应用组件

  • 启动状态

    • startService()后台无限期运行,即使启动服务的组件已被销毁也不受影响,除非手动调用才能停止服务, 已启动的服务通常是执行单一操作,而且不会将结果返回给调用方。
  • 绑定状态

    • bindService()绑定服务提供了一个客户端-服务器接口,允许组件与服务进行交互、发送请求、获取结果,甚至是利用进程间通信 (IPC) 跨进程执行这些操作。 仅当与另一个应用组件绑定时,绑定服务才会运行。 多个组件可以同时绑定到该服务,但全部取消绑定后,该服务即会被销毁

    • 先绑定服务后启动服务 则宿主(Activity)被销毁了,也不会影响服务的运行

    • 先启动服务后绑定服务 则解除绑定后,服务依然按启动服务的生命周期在后台运行,直到有Context调用了stopService()或是服务本身调用了stopSelf()方法抑或内存不足时才会销毁服务

  • onBind() 当另一个组件想通过调用 bindService() 与服务绑定(例如执行 RPC)时,系统将调用此方法。在此方法的实现中,必须返回 一个IBinder 接口的实现类,供客户端用来与服务进行通信。无论是启动状态还是绑定状态,此方法必须重写,但在启动状态的情况下直接返回 null。

  • onCreate() 首次创建服务时,系统将调用此方法来执行一次性设置程序(在调用 onStartCommand() 或onBind() 之前)。如果服务已在运行,则不会调用此方法,该方法只调用一次

  • onStartCommand() 当另一个组件(如 Activity)通过调用 startService() 请求启动服务时,系统将调用此方法。一旦执行此方法,服务即会启动并可在后台无限期运行。 如果自己实现此方法,则需要在服务工作完成后,通过调用 stopSelf() 或 stopService() 来停止服务。(在绑定状态下,无需实现此方法。)

    • intent :启动时,启动组件传递过来的Intent,如Activity可利用Intent封装所需要的参数并传递给Service

    • flags:表示启动请求时是否有额外数据,可选值有 0,START_FLAG_REDELIVERY,START_FLAG_RETRY,0代表没有,它们具体含义如下:

      START_FLAG_REDELIVERY 这个值代表了onStartCommand方法的返回值为 START_REDELIVER_INTENT,而且在上一次服务被杀死前会去调用stopSelf方法停止服务。其中START_REDELIVER_INTENT意味着当Service因内存不足而被系统kill后,则会重建服务,并通过传递给服务的最后一个 Intent 调用 onStartCommand(),此时Intent时有值的。

      START_FLAG_RETRY 该flag代表当onStartCommand调用后一直没有返回值时,会尝试重新去调用onStartCommand()

    • startId : 指明当前服务的唯一ID,与stopSelfResult (int startId)配合使用,stopSelfResult 可以更安全地根据ID停止服务。

    • 实际上onStartCommand的返回值int类型才是最最值得注意的,它有三种可选值, START_STICKY,START_NOT_STICKY,START_REDELIVER_INTENT,它们具体含义如下:

      START_STICKY   当Service因内存不足而被系统kill后,一段时间后内存再次空闲时,系统将会尝试重新创建此Service,一旦创建成功后将回调onStartCommand方法,但其中的Intent将是null,除非有挂起的Intent,如pendingintent,这个状态下比较适用于不执行命令、但无限期运行并等待作业的媒体播放器或类似服务。

      START_NOT_STICKY   当Service因内存不足而被系统kill后,即使系统内存再次空闲时,系统也不会尝试重新创建此Service。除非程序中再次调用startService启动此Service,这是最安全的选项,可以避免在不必要时以及应用能够轻松重启所有未完成的作业时运行服务。

      START_REDELIVER_INTENT   当Service因内存不足而被系统kill后,则会重建服务,并通过传递给服务的最后一个 Intent 调用 onStartCommand(),任何挂起 Intent均依次传递。与START_STICKY不同的是,其中的传递的Intent将是非空,是最后一次调用startService中的intent。这个值适用于主动执行应该立即恢复的作业(例如下载文件)的服务。

  • onDestroy() 当服务不再使用且将被销毁时,系统将调用此方法。服务应该实现此方法来清理所有资源,如线程、注册的侦听器、接收器等,这是服务接收的最后一个调用

  • [图片上传失败...(image-48132c-1682881853345)]

前台服务

通知栏Notification

后台服务

后台服务优先级相对比较低,当系统出现内存不足的情况下,它就有可能会被回收掉

IntentService

IntentService是专门用来解决Service中不能执行耗时操作这一问题的

系统服务

broadcastreceiver 监视系统服务

广播

BroadcastReceiver

  • 普通广播(Normal Broadcast)

  • 系统广播(System Broadcast)

  • 有序广播(Ordered Broadcast)

    • 接收广播按顺序接收

    • 先接收的广播接收者可以对广播进行截断,即后接收的广播接收者不再接收到此广播;

    • 先接收的广播接收者可以对广播进行修改,那么后接收的广播接收者将接收到被修改后的广播

  • 粘性广播(Sticky Broadcast)

  • App应用内广播(Local Broadcast)

  • 特别注意

    • 对于静态注册(全局+应用内广播),回调onReceive(context, intent)中的context返回值是:ReceiverRestrictedContext;

    • 对于全局广播的动态注册,回调onReceive(context, intent)中的context返回值是:Activity Context;

    • 对于应用内广播的动态注册(LocalBroadcastManager方式),回调onReceive(context, intent)中的context返回值是:Application Context。

    • 对于应用内广播的动态注册(非LocalBroadcastManager方式),回调onReceive(context, intent)中的context返回值是:Activity Context;

  • 本地广播和全局广播的区别

    • 本地广播

      • 广播事件的发送和接收都在本应用,不影响其他应用也不受其他应用影响,只能被动态注册,不能静态注册,主要用法都在LocalBroadcastManager类中
    • 全局广播

      • 可以接收其他应用发的广播,也可以发送广播让其他应用接收,全局广播既可以动态注册,也可以静态注册,接受其他应用和系统广播是全局广播的一个重要应用点。总体来说两者应用场景不同

LiveData

  • 观察者对象必须得处于主线程中

    • setValue()方法相信大家都很熟悉,我们必须在主线程中进行调用,否则会抛出异常。在代码中体现出来的,正是通过assertMainThread("setValue");来保证方法处于主线程中

    • postValue用于在子线程通知主线程livedata发生了变化

  • 专用于 Android 的具备自主生命周期感知能力的可观察的数据存储器类

  • newFixThreadPool(2)

  • 通过对比ObserverWrapperLiveData之间的version,来判断是否更新

Databing\ViewModel

  • 绑定的方式分两种

    • 单向绑定 直接在xml中写对应的参数名 android:text="@{userInfo.name}"

    • 双向绑定 android:text="@={userInfo.nickName}"

  • 原理

    1. 首先通过DataBindingUtil.setContentView()来查找对应的布局

    2. 然后生成一个全新的tag值并且赋值给每个view

    3. 进行脏标记

    4. 注册监听

jni

普通应用

java->jni: .so/.dll

jni->java:

  1. jclass cls = (*env)->FindClass(env, "jni/test/Demo"); //把点号换成斜杠

  2. jclass cls = (*env)-> GetObjectClass(env, obj); //其中obj是要引用的对象,类型是jobject

  3. Call<TYPE>Method或者 CallStatic<TYPE>Method(访问类的静态方法)

  4. jfieldID (JNICALL *GetFieldID) (JNIEnv *env, jclass clazz, const char *name, const char *sig); jfieldID (JNICALL *GetStaticFieldID) (JNIEnv *env, jclass clazz, const char *name, const char *sig);

  5. (*env)->SetObjectField(env, jobj, fid, new_jstr);

直播

音频

  1. AudioRecord

  2. 单双声道

  3. 采样率

  4. 缓冲区

  5. 单帧大小计算

  6. p帧(与前一帧的差值,较小) i帧(关键帧,较大),B帧(是前一帧及后一帧的差别,但解码麻烦,需要先知道前一帧和后一帧)

  7. webrtc - (ns降噪模块,vad语音端点识别模块-可用于人声识别,aecm回声消除模块)

    1. 帧间隔毫秒必须是80的倍数

    2. 帧大小必须是160的倍数

视频

  1. 编码

  2. 摄像头(摄像头数,前后,采集高宽,闪光灯,对焦方式)

  3. 预览帧数

  4. 缩放和对焦

  5. 软/硬解码

  6. h264/h265

    1. 压缩比接近50%

    2. 解码cpu负担大

    3. 减少实时的时延、减少信道获取时间和随机接入时延、降低复杂度

  7. ijkplayer

  8. ffmepg

    • 音视频解码器
  9. yuv

    • 负责处理图像颜色

推流

推荐:RTMP RTSP

两者区别:

[图片上传失败...(image-1cc5df-1682881853347)]

注: RTP传输是指实时传输协议,是建立在udp协议之上

udp与tcp区别

UDP TCP
是否连接 无连接 面向连接
是否可靠 不可靠传输,不使用流量控制和拥塞控制 可靠传输,使用流量控制和拥塞控制
连接对象个数 支持一对一,一对多,多对一和多对多交互通信 只能是一对一通信
传输方式 面向报文 面向字节流
首部开销 首部开销小,仅8字节 首部最小20字节,最大60字节
适用场景 适用于实时应用(IP电话、视频会议、直播等) 适用于要求可靠传输的应用,例如文件传输

IM

  • 千人群优化

    • 消息合并插入

      • 将单一一条消息合并成一个消息块进行传输
    • 并发限制(?)

      • 当进入消息未读较多的群时,分页

内存

  1. 内存泄漏 Memory Leak: 本该回收的对象不能被回收而停留在堆内存中,从而产生了内存泄漏。

  2. 内存溢出 Out Of Memory: 内存溢出是指APP向系统申请超过最大阀值的内存请求

强引用、软引用、弱引用和虚引用

强引用 垃圾回收器绝不会回收它
软引用 内存空间充足时,垃圾回收器不会回收它;如果内存空间不足了,就会回收这些对象的内存
弱引用 不管当前内存空间足够与否,都会回收它的内存,但不一定及时
虚引用 任何时候都可能被垃圾回收器回收

LruCache

LRU是近期最少使用的算法,它的核心思想是当缓存满时,会优先淘汰那些近期最少使用的缓存对象

  • 原理是把最近使用的对象用强引用(即我们平常使用的对象引用方式)存储在 LinkedHashMap 中。当缓存满时,把最近最少使用的对象从内存中移除,并提供了get和put方法来完成缓存的获取和添加操作。

  • LinkedHashMap是以访问顺序排序的

Bitmap的三级缓存

  1. 通过BitmapFactory.Options中的inJustDecodeBounds = true 不对bitmap进行实际的内存分配,但仍然可以获得所有属性

  2. 通过对比bitmap的实际大小和imageView的大小进行二次采样inSimpleSize

  3. 使用LruCache

Parcelable和Serializable的区别和比较

Parcelable和Serializable都是实现序列化并且都可以用于Intent间传递数据,Serializable是Java的实现方式,可能会频繁的IO操作,所以消耗比较大,但是实现方式简单 Parcelable是Android提供的方式,效率比较高,但是实现起来复杂一些 , 二者的选取规则是:内存序列化上选择Parcelable, 存储到设备或者网络传输上选择Serializable(当然Parcelable也可以但是稍显复杂)

启动模式

standard 模式

这是默认模式,每次激活Activity时都会创建Activity实例,并放入任务栈中。使用场景:大多数Activity。

singleTop 模式

如果在任务的栈顶正好存在该Activity的实例,就重用该实例( 会调用实例的 onNewIntent() ),否则就会创建新的实例并放入栈顶,即使栈中已经存在该Activity的实例,只要不在栈顶,都会创建新的实例。使用场景如新闻类或者阅读类App的内容页面。

singleTask 模式

如果在栈中已经有该Activity的实例,就重用该实例(会调用实例的 onNewIntent() )。重用时,会让该实例回到栈顶,因此在它上面的实例将会被移出栈。如果栈中不存在该实例,将会创建新的实例放入栈中。使用场景如浏览器的主界面。不管从多少个应用启动浏览器,只会启动主界面一次,其余情况都会走onNewIntent,并且会清空主界面上面的其他页面。

singleInstance 模式

在一个新栈中创建该Activity的实例,并让多个应用共享该栈中的该Activity实例。一旦该模式的Activity实例已经存在于某个栈中,任何应用再激活该Activity时都会重用该栈中的实例( 会调用实例的 onNewIntent() )。其效果相当于多个应用共享一个应用,不管谁激活该 Activity 都会进入同一个应用中。使用场景如闹铃提醒,将闹铃提醒与闹铃设置分离。singleInstance不要用于中间页面,如果用于中间页面,跳转会有问题,比如:A -> B (singleInstance) -> C,完全退出后,在此启动,首先打开的是B。

View和ViewGroup的绘制

  • 对于 ViewGroup,除了要完成自己的测量,还要遍历调用子元素的 measure() 方法,而 View 只需要通过 measure() 方法就能确定测量规格

  • layout() 方法的作用是 ViewGroup 用于确定子元素的位置,当 ViewGroup 的位置确定后,会在 onLayout() 方法中遍历所有子 View 并调用子 View 的 layout() 方法。

  • View 和 ViewGroup 没有实现 onDraw() 方法,接下来就是 dispatchDraw() 方法,View 没有实现这个方法

View ViewGroup的事件分发机制

点击事件被拦截,但是相传到下面的view

getParent().requestDisabllowInterceptTouchEvent(true)

  1. 表示事件是否会继续分发出去,默认返回false,返回true时表示事件不会再继续分发,甚至都不会分发到自身的onTouchEvent方法;

  2. dispatchTouchEvent 分发事件,当该方法返回true时,该View不会继续分发事件,包括该事件不会继续分发到该View的onInterceptTouchEvent方法和onTouchEvent方法;

  3. onInterceptTouchEvent 拦截事件的传递,是否会继续向子View、子ViewGroup传递,当该方法返回true时,事件不会继续向子View、子ViewGroup传递,相当于父级View把事件在此处截断了;

  4. onTouchEvent 消费事件,对点击事件做相应的点击响应处理,具体执行点击后的操作,如果子View不做处理,即返回false,该事件还会继续传递到父View的onTouchEvent方法去处理,直到传递到组外层; 如果该方法返回true,表示这个事件被消费掉了,这个事件就此终止了,不会再有任何传递;

  5. Android事件分发是先传递到ViewGroup,再由ViewGroup传递到View的。

  6. 在ViewGroup中可以通过onInterceptTouchEvent方法对事件传递进行拦截,onInterceptTouchEvent方法返回true代表不允许事件继续向子View传递,返回false代表不对事件进行拦截,默认返回false。

  7. 子View中如果将传递的事件消费掉,ViewGroup中将无法接收到任何事件

  8. [图片上传失败...(image-e2672b-1682881853345)]

android与JS互动

  • android调用JS: WebView的loadUrl(); WebView的evaluateJavascript()

    • 被调用的Js方法是有返回值的,如果是采用loadUrl()调用,返回值也会用loadUrl()载入,直接显示在WebView上,这显然是不对的,我们只想隐形的接收返回值,而evaluateJavascript()就提供了这样的隐形接收方式,不会调用到loadUrl()。
  • JS调用android: 通过WebView的addJavascriptInterface()进行对象映射; WebViewClient 的shouldOverrideUrlLoading ()方法回调拦截 url; WebChromeClient 的onJsAlert()、onJsConfirm()、onJsPrompt()方法回调拦截JS对话框alert()、confirm()、prompt() 消息

kotlin

  1. reified 使得泛型的方法假装在运行时能够获取泛型的类信息

  2. 原来在Java中,类处于顶层,类包含属性和方法,在Kotlin中,函数站在了类的位置,我们可以直接把函数放在代码文件的顶层,让它不从属于任何类,可以通过import 包名.函数名来

  3. 导入我们将要使用的函数

    协程

    关键字 yield resume suspend

    总结下,协程是跑在线程上的,一个线程可以同时跑多个协程,每一个协程则代表一个耗时任务,我们手动控制多个协程之间的运行、切换,决定谁什么时候挂起,什么时候运行,什么时候唤醒,而不是 Thread 那样交给系统内核来操作去竞争 CPU 时间片

    1. 协程其实就相当于回调,利用挂起函数来等待回调结果,不会阻塞线程

    2. 协程可以用来直接标记方法,由程序员自己实现切换,调度,不再采用传统的时间段竞争机制

    3. launch - 创建协程 3个参数和返回值 Job:

      <pre class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" lang="kotlin" cid="n545" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: "Fira Code", Consolas, "Lucida Console", Courier, monospace, "Helvetica Neue", Helvetica, Arial, sans-serif; font-size: 0.9rem; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(40, 44, 52); position: relative !important; padding: 6px 10px; box-shadow: rgba(0, 0, 0, 0.16) 0px 2px 5px 0px, rgba(0, 0, 0, 0.12) 0px 2px 10px 0px; margin-bottom: 2.5rem; border: none; border-radius: 6px; width: inherit;">launch(CoroutineContext,CoroutineStart,block): Job</pre>

      1. CoroutineContext - 可以理解为协程的上下文

        1. Dispatchers.Default

        2. Dispatchers.IO -

        3. Dispatchers.Main - 主线程

        4. Dispatchers.Unconfined - 没指定,就是在当前线程

      2. CoroutineStart - 启动模式,默认是DEAFAULT,也就是创建就启动

        1. DEAFAULT - 模式模式,不写就是默认

        2. ATOMIC -

        3. UNDISPATCHED

        4. LAZY - 懒加载模式,你需要它的时候,再调用启动

      3. block - 闭包方法体,定义协程内需要执行的操作

      4. Job - 协程构建函数的返回值,可以把 Job 看成协程对象本身

        1. job.start() - 启动协程,除了 lazy 模式,协程都不需要手动启动

        2. job.join() - 等待协程执行完毕

        3. job.cancel() - 取消一个协程

        4. job.cancelAndJoin() - 等待协程执行完毕然后再取消

    4. async - 创建带返回值的协程,返回的是 Deferred 类

      • async 同 launch 唯一的区别就是 async 是有返回值的

      • Deferred 继承自 Job 接口,Job有的它都有,增加了一个方法 await ,这个方法接收的是 async 闭包中返回的值,async 的特点是不会阻塞当前线程,但会阻塞所在协程,也就是挂起

        但是注意啊,async 并不会阻塞线程,只是阻塞锁调用的协程

    5. withContext - 不创建新的协程,在指定协程上运行代码块

    6. runBlocking - 不是 GlobalScope 的 API,可以独立使用,区别是 runBlocking 里面的 delay 会阻塞线程,而 launch 创建的不会

      • 在 runBlocking 闭包里面启动另外的协程,协程里面是可以嵌套启动别的协程的
    7. 协程执行时, 协程和协程,协程和线程内代码是顺序运行的

    8. 协程挂起时,就不会执行了,而是等待挂起完成且线程空闲时才能继续执行

    9. relay 和 yield 方法是协程内部的操作,可以挂起协程,区别是 relay 是挂起协程并经过执行时间恢复协程,当线程空闲时就会运行协程;yield 是挂起协程,让协程放弃本次 cpu 执行机会让给别的协程,当线程空闲时再次运行协程。

hook

glide

原理

[图片上传失败...(image-b7389a-1682881853347)

  1. Glide.with(context)创建RequestManager

    • RequestManager负责管理当前context的所有Request

    • Context可以传Fragment、Activity或者其他Context,当传Fragment、Activity时,当前页面对应的Activity的生命周期可以被RequestManager监控到,从而可以控制Request的pause、resume、clear。这其中采用的监控方法就是在当前activity中添加一个没有view的fragment,这样在activity发生onStart onStop onDestroy的时候,会触发此fragment的onStart onStop onDestroy。

    • RequestManager用来跟踪众多当前页面的Request的是RequestTracker类,用弱引用来保存运行中的Request,用强引用来保存暂停需要恢复的Request。

  2. Glide.with(context).load(url)创建需要的Request

    • 通常是DrawableTypeRequest,后面可以添加transform、fitCenter、animate、placeholder、error、override、skipMemoryCache、signature等等

    • 如果需要进行Resource的转化比如转化为Byte数组等需要,可以加asBitmap来更改为BitmapTypeRequest

    • Request是Glide加载图片的执行单位

  3. Glide.with(context).load(url).into(imageview)

    • 在Request的into方法中会调用Request的begin方法开始执行

    • 在正式生成EngineJob放入Engine中执行之前,如果并没有事先调用override(width, height)来指定所需要宽高,Glide则会尝试去获取imageview的宽和高,如果当前imageview并没有初始化完毕取不到高宽,Glide会通过view的ViewTreeObserver来等View初始化完毕之后再获取宽高再进行下一步

优点

  1. 指定图片大小

    • 自动判断imageView大小,然后只加载等大的像素,而不会全部加载进imageView
  2. 方便图片格式的切换

  3. 方便自定义图片的裁剪等转换(BitmapTransformation

Retrefit

本质上 Retrofit 是一个对 OkHttp 进行进一步封装的框架

原理

  • 通过 addConverterFactoryaddCallAdapterFactory 进行数据格式的二次/自定义封装

  • 利用MainThreadExecutor来进行线程之间的切换

  • 核心思想是将 http 请求过程抽象成了一个对象 ServiceMethod, 这个对象的构造的时候,会通过 java 反射的方式传入一个 method 对象,而这个对象就是我们在 interface 中定义的请求方法

RxJava

常用操作符

  • map 转换事件,返回普通事件

  • flatMap 转换事件,返回` Observable

  • conactMap concatMap 与 FlatMap 的唯一区别就是 concatMap 保证了顺序

  • subscribeOn 规定被观察者所在的线程

  • observeOn 规定下面要执行的消费者所在的线程

  • take 接受一个 long 型参数 count ,代表至多接收 count 个数据

  • debounce 去除发送频率过快的项,常用在重复点击解决上,配合 RxBinging 使用效果很好

  • timer 定时任务,多少时间以后发送事件

  • interval 每隔一定时间执行一些任务

  • skip 跳过前多少个事件

  • distinct 去重

  • takeUntil 直到到一定条件的是停下,也可以接受另外一个被观察者,当这个被观察者结束之后则停止第一个被观察者

  • Zip 专用于合并事件,该合并不是连接(连接操作符后面会说),而是两两配对,也就意味着,最终配对出的 Observable 发射事件数目只和少的那个相同。不影响Observable的发射,Observable 被观察者会一直发射,不会停,只是Observer 接收不到

  • merge 多个 Observable 发射的数据随机发射,不保证先后顺序

  • Concat 多个 Observable 组合以后按照顺序发射,保证了先后顺序,不过最多能组合4个 Observable ,多的可以使用 contactArray

  • onErrorReturn 遇到错误是发射指定的数据到 onNext,并正常终止

  • onErrorResumeReturn 遇到错误时,发射设置好的一个 Observable ,用来发送数据到 onNext,并正常终止

  • onExceptionResumeReturn 和onErrorResumeReturn 类似,不同之处在于会判断是否是 Exception。如果是和 onErrorResumeReturn 一样,不是则会调用 onError。不会调用onNext

Map和flatMap的区别

  • 前者是严格按照1.2.3.4.5顺序发的,经过map以后还是按照这个顺序

  • 后者是1.2.3.4.5发送完到 flatMap 里面,然后经过flatmap进行组装以后再发出来,顺序可能会打乱

  • 使用 contactMap 可以保证转换后的事件发射顺序。

[图片上传失败...(image-db706b-1682881853

[图片上传失败...(image-bf9436-1682881853347)]

跨进程

[图片上传失败...(image-96a68e-1682881853347)]

AIDL

利用了liunx下的Binder机制 IPC

Linux现有的进程通信手段有以下几种:

  • 管道:在创建时分配一个page大小的内存,缓存区大小比较有限;

  • 消息队列:信息复制两次,额外的CPU消耗;不合适频繁或信息量大的通信;

  • 共享内存:无须复制,共享缓冲区直接附加到进程虚拟地址空间,速度快;但进程间的同步问题操作系统无法实现,必须各进程利用同步工具解决;

  • 套接字:作为更通用的接口,传输效率低,主要用于不同机器或跨网络的通信;

  • 信号量:常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。 不适用于信息交换,更适用于进程中断控制,比如非法内存访问,杀死某个进程等;

Binder来说,数据从发送方的缓存区拷贝到内核的缓存区,而接收方的缓存区与内核的缓存区是映射到同一块物理地址的 ,节省了一次数据拷贝的过程,如图:

img

一次完整的 Binder IPC 通信过程通常是这样:

  • 首先 Binder 驱动在内核空间创建一个数据接收缓存区。

  • 接着在内核空间开辟一块内核缓存区,建立内核缓存区和内核中数据接收缓存区之间的映射关系,以及内核中数据接收缓存区和接收进程用户空间地址的映射关系。

  • 发送方进程通过系统调用 copyfromuser() 将数据 copy 到内核中的内核缓存区,由于内核缓存区和接收进程的用户空间存在内存映射,因此也就相当于把数据发送到了接收进程的用户空间,这样便完成了一次进程间的通信。

img
img

ContentProvider

通过 ContentResolver 来访问 ContentProvider 提供的数据,而 ContentResolver 通过 uri 来定位自己要访问的数据

  • public boolean **onCreate()**:在创建 ContentProvider 时使用

  • public Cursor **query()**:用于查询指定 uri 的数据返回一个 Cursor

  • public Uri **insert():**用于向指定uri的 ContentProvider 中添加数据

  • public int **delete()**:用于删除指定 uri 的数据

  • public int **update()**:用户更新指定 uri 的数据

  • public String **getType()**:用于返回指定的 Uri 中的数据 MIME 类型

  • 数据源可能是数据库,也可以是文件、xml或网络等其他存储方式。

动画

属性动画

  • ValueAnimator

    • 无法像ObjectAnimator一样直接作用于对象,只能通过添加监听,获取动画过程之,然后手动设置给对象改变对象的属性
  • ObjectAnimator

补间动画TweenAnimation

逐帧动画FrameAnimation

滑动时不加载图片

通过RecyclerView的滑动状态改变,来对glide的图片加载请求的暂停/恢复请求

<pre class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" lang="java" cid="n783" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: "Fira Code", Consolas, "Lucida Console", Courier, monospace, "Helvetica Neue", Helvetica, Arial, sans-serif; font-size: 0.9rem; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(40, 44, 52); position: relative !important; padding: 6px 10px; box-shadow: rgba(0, 0, 0, 0.16) 0px 2px 5px 0px, rgba(0, 0, 0, 0.12) 0px 2px 10px 0px; margin-bottom: 2.5rem; border: none; border-radius: 6px; width: inherit; color: rgb(40, 44, 52); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;"> mRecycleView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
if (newState == RecyclerView.SCROLL_STATE_IDLE) {
if (getActivity() != null){
Glide.with(getActivity()).resumeRequests();//恢复Glide加载图片
}
}else {
if (getActivity() != null){
Glide.with(getActivity()).pauseRequests();//禁止Glide加载图片
}
}
}
});</pre>

RecyclerView

  • 四级缓存

    • mAttachedScrap:缓存屏幕中可见范围中的ViewHolder。

      • mAttachedScrap缓存的是当前屏幕上的ViewHolder,对应的数据结构是ArrayList,没有大小限制。在调用LayoutManager#onLayoutChildren方法时对views进行布局

      • 特性是:如果和RecyclerView上的position或者itemId匹配上了,那么就可以直接拿来使用,不需要调用onBindViewHolder重新绑定数据

    • mCachedViews :缓存滑动中即将与RecyclerView分离的ViewHolder

      • mCachedViews缓存滑动时即将与RecyclerView分离的ViewHolder,其数据结构为ArrayList,该缓存对大小是有限制的,默认为2个

      • 该缓存中的ViewHolder的特性是:只要position和itemId匹配上了,则可直接使用,不需要调用onBindViewHolder重新绑定数据。

    • mViewCacheExtension:自定义实现的缓存。

    • mRecyclerPool:ViewHolder缓存池,可支持不同的ViewType。

      • 本质上是一个SparseArray,其中key是ViewType,value是ArrayList<ViewHolder>,默认每个ArrayList中最多存储5个

      • ViewHolder存储在缓存池的前会进行重置变成一个干净的ViewHolder,所以在复用时,需要调用onBindViewHolder重绑数据。

  • 复用流程

    • 复用肯定是在填充子元素过程中完成的

    • 先通过getChangedScrapViewForPosition

      • notifyItemChanged()方法,数据发生变化时,item缓存在mChangedScrap和mAttachedScrap中,后续拿到的ViewHolder需要重新绑定数据。此时查找ViewHolder就会通过position和id分别在scrap的mChangedScrap中查找
    • 然后getScrapOrHiddenOrCachedHolderForPosition

      • 没有找到视图,根据position分别在scrap的mAttachedScrap、mHiddenViews、mCachedViews中查找
    • 再然后RecycledViewPool

    • 最后创建新的ViewHolder

Context

img

首先什么是Context

Context 相当于 Application 的大管家,主要负责:

  • 四大组件的交互,包括启动 Activity、Broadcast、Service,获取 ContentResolver 等

  • 获取系统/应用资源,包括 AssetManager、PackageManager、Resources、System Service 以及 color、string、drawable 等文件,包括获取缓存文件夹、删除文件、SharedPreference 相关等 数据库(SQLite)相关,包括打开数据库、删除数据库、获取数据库路径等

  • 其它辅助功能,比如设置 ComponentCallbacks,即监听配置信息改变、内存不足等事件的发生

如果把整个App当作一个内存块,那context相当于一个指针

Context实例个数 = Service个数 + Activity个数 + 1(Application对应的Context实例)

Context的几种类型

  1. ContextWrapper、ContextThemeWrapper、ContextImpl 的区别:

    • ContextWrapper、ContextThemeWrapper 都是 Context 的代理类,二者的区别在于 ContextThemeWrapper 有自己的 Theme 以及 Resource,并且 Resource 可以传入自己的配置初始化

    • ContextImpl 是 Context 的主要实现类,Activity、Service 和 Application 的 BaseContext 都是由它创建的,即 ContextWrapper 代理的就是 ContextImpl 对象本身

    • ContextImpl 和 ContextThemeWrapper 的主要区别是, ContextThemeWrapper 有 Configuration 对象,Resource 可以根据这个对象来初始化

  2. Activity、Service 和 Application 的 Base Context 都是由 ContextImpl 创建的,且创建的都是 ContextImpl 对象,即它们都是 ContextImpl 的代理类 Service 和 Application 使用同一个 Recource,和 Activity 使用的 Resource 不同

getApplicationContext 返回的就是 Application 对象本身,一般情况下它对应的是应用本身的 Application 对象。

baseContext和applicationContext的区别

  • getApplicationContext() 是返回应用的上下文,也就是把Application作为Context,生命周期是整个应用,应用摧毁它才摧毁

  • Activity的Context,Activity.this的context 返回当前Activity的上下文,及把Activity用作Context,生命周期属于Activity ,Activity 摧毁他就摧毁

  • 通常不会建议使用baseContext,是因为baseContext是具体的系统生产的对象,其中不包含了具体用户的一些定制信息和内容,所以通过baseContext获取的Resource/Theme是会与预期的不符。

ClassLoader及类加载机制

Android中ClassLoader的种类&特点

  • BootClassLoader(Java的BootStrap ClassLoader): 用于加载Android Framework层class文件。

  • PathClassLoader(Java的App ClassLoader): 用于加载已经安装到系统中的apk中的class文件。

  • DexClassLoader(Java的Custom ClassLoader): 用于加载指定目录中的class文件。

    1. PathClassLoader :只能加载已经安装到Android系统中的apk文件(/data/app目录),是 Android默认使用的类加载器.

    2. DexClassLoader :可以加载任意目录下的dex/jar/apk/zip文件,比 PathClassLoader 更灵活,是 实现热修复的重点。

  • BaseDexClassLoader: 是PathClassLoader和DexClassLoader的父类。

各个ClassLoader的加载顺序

首先是BootClassLoader在Jvm刚起动的时候去加载java核心API的

然后是PathClassLoader/DexClassLoader加载Apk的应用类(热修复,也就是向PathClassLoader的dexElements进行插入新的dex)

最后是CustomerClassLoader加载自定义的class文件

需要注意CLASS_ISPREVERIFIED 标记

[图片上传失败...(image-2c4c8b-1682881853346

双亲委托模式

什么是双亲委托模式

当某个类加载器需要加载某个.class文件时,它首先把这个任务委托给他的上级类加载器,递归这个操作,如果上级的类加载器没有加载,自己才会去加载这个类。

流程图

img

作用

  1. 防止重复加载同一个.class。通过委托去向上面问一问,加载过了,就不用再加载一遍。保证数据安全。

  2. 保证核心.class不能被篡改。通过委托方式,不会去篡改核心.clas,即使篡改也不会去加载,即使加载也不会是同一个.class对象了。不同的加载器加载同一个.class也不是同一个Class对象。这样保证了Class执行安全。

插件化

DexClassLoader去加载外部的apk

  • DexClassLoader重载了findClass方法,在加载类时会调用其内部的DexPathList去加载

  • 插件调用主工程

    • 在构造插件的ClassLoader时会传入主工程的ClassLoader作为父加载器,所以插件是可以直接可以通过类名引用主工程的类
  • 主工程调用插件

    • 若使用多ClassLoader机制,主工程引用插件中类需要先通过插件的ClassLoader加载该类再通过反射调用其方法。插件化框架一般会通过统一的入口去管理对各个插件中类的访问,并且做一定的限制。

    • 若使用单ClassLoader机制,主工程则可以直接通过类名去访问插件中的类。该方式有个弊病,若两个不同的插件工程引用了一个库的不同版本,则程序可能会出错,所以要通过一些规范去避免该情况发生。

热修复

热修复的原理就是将补丁 dex 文件放到 dexElements 数组靠前位置,这样在加载 class 时,优先找到补丁包中的 dex 文件,加载到 class 之后就不再寻找,从而原来的 apk 文件中同名的类就不会再使用,从而达到修复的目的

  • Android SDK给我们单独提供了dex打包工具d8

dex插件

先将修改了的类进行打包成dex包,在将dex进行加载,插入到dexElements集合的前面即可。而打包流程是先将.java文件编译成.class文件,然后使用SDK工具打包成dex文件并发布到远程服务端,然后APP端请求下载

apk插件

重新打了一个新的apk包作为插件,打包很简单方便,缺点就是文件大。使用apk的话就没必要是将dex插入dexElements里面去,直接将之前的dexElements替换就可以了

修复资源

  • 反射创建新的 AssetManager 对象,反射调用 addAssetPath 方法加载外部的资源。

  • 将 AssetManager 类型的 mAssets 字段的引用全部替换为新创建的 AssetManager 对象。

组件化

采用接口 + 实现的结构。每个组件声明自己提供的服务 Service,这些 Service 都是一些抽象类或者接口,组件负责将这些 Service 实现并注册到一个统一的路由 Router 中去。如果要使用某个组件的功能,只需要向 Router 请求这个 Service 的实现,具体的实现细节我们全然不关心,只要能返回我们需要的结果就可以了。这与 Binder 的 C/S 架构很相像。

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

推荐阅读更多精彩内容