Interview
自我介绍
您好,我是谌先雄,来自湖北武汉,目前从事 Android 和 Flutter 客户端开发工作。过去几年,我在多个领域有着丰富的开发经验,包括物联网、金融、车载和 SAAS 等行业。
我使用 Java、Kotlin 和 Dart都有多年时间,有着足够的熟练度,在工作中,我参与了多个大型项目的开发,比如岚图 APP,负责核心模块的开发与维护,尤其在多媒体控制、支付集成以及性能优化方面积累了不少经验。
我有良好的团队协作能力,并且始终保持学习的热情,关注新技术在实践中的应用。
JAVA
-
Java中hashcode和equals的作用是什么?
- equals() 用于比较两个对象是否在逻辑上相等。
- hashCode() 用于生成对象的哈希码,以支持基于哈希的集合(如 HashMap、HashSet)的快速查找。
-
Java中hashCode方法的工作原理?
- 默认情况下,Java中的hashCode()方法是在Object类中定义的,它根据对象的内存地址生成一个整数。这意味着不同的对象在没有重写hashCode()方法的情况下,可能具有不同的哈希码,即使它们的内容相同。对于Object类的hashCode()实现,哈希码是由JVM生成的,通常是基于对象的内存地址。
-
Java中equals方法的工作原理?
- 重写 hashCode() 时,一般遵循以下原则:
- 一致性原则: 如果两个对象是相等的(根据 equals()),那么它们的 hashCode() 值必须相同。
- 低冲突性: 理想的 hashCode() 实现应该尽量避免哈希冲突(即不同对象有相同的哈希码)。
- 高效率: 生成哈希码的算法应该尽量高效,以避免影响集合操作的性能。
@Override public int hashCode() { return Objects.hash(field1, field2, field3); } - 重写 hashCode() 时,一般遵循以下原则:
-
Java中CountDownLatch的原理是什么?
- CountDownLatch 是 Java 中 java.util.concurrent 包的一部分,用于协调多个线程的并发执行。它允许一个或多个线程等待,直到其他线程完成一组操作。CountDownLatch 可以被视为一个倒计数器,线程可以减少计数,而等待的线程则会阻塞,直到计数器的值达到零。
-
Java中CountDownLatch和CyclicBarrier的异同?
- CountDownLatch 是一次性使用的。倒计数器只能从给定的值减到零,不能重置或重新使用。如果需要多次使用,应该使用 CyclicBarrier 或重新创建一个新的 CountDownLatch 实例。
- 侧重点不同,CountDownLatch一般用于一个线程等待一组其它线程;而CyclicBarrier一般是一组线程间的相互等待至某同步点
-
Java中乐观锁和悲观锁是什么?
- 乐观锁:乐观锁基于一个假设,即在大多数情况下,多个事务或线程对同一资源的操作不会产生冲突。因此,它允许多个事务/线程并发地读取资源,而不使用实际的锁来阻止其他事务/线程的访问。在提交修改之前,系统会检查是否有其他事务/线程在此期间修改了资源。如果检测到冲突,通常会拒绝提交,要求重试或回滚事务。
- 悲观锁:悲观锁基于一种假设,即在多线程或多事务环境下,资源冲突是常见的,因此在访问共享资源之前,先对其进行加锁,阻止其他事务/线程同时访问该资源。只有在当前事务/线程释放锁之后,其他事务/线程才可以访问该资源。
- 使用场景对比
- 乐观锁:适用于读多写少的场景,例如一些在线阅读系统,多个用户读取数据的频率远高于写入数据的频率。
- 悲观锁:适用于写多读少或频繁更新的场景,例如银行转账系统,确保在处理转账时,账户数据不会被其他操作修改。
-
Java中String类为什么是final?
- Java 中
String类被声明为final,是为了确保其不可变性,支持字符串池机制,增强安全性,保证性能优化,并遵循良好的面向对象设计原则。这些因素结合在一起,使得String成为一个高效且可靠的类,广泛应用于各种 Java 程序中。 - 不可变性 (Immutability)
String类的不可变性是其最重要的特性之一。不可变性意味着一旦创建了一个String对象,它的值就不能再被改变。如果String类不是final的,那么其他类可以继承它并修改它的行为,破坏其不可变性。这会导致严重的安全问题和难以预测的行为。
- 字符串池 (String Pool)
- Java 中有一个字符串池,存储所有使用字面量方式创建的字符串(即直接用引号括起来的字符串,如
"Hello")。当一个字符串被创建时,Java 会先检查字符串池中是否已经存在相同的字符串,如果存在,则直接返回引用,而不是重新创建对象。
- Java 中有一个字符串池,存储所有使用字面量方式创建的字符串(即直接用引号括起来的字符串,如
- 安全性 (Security)
- 在许多 Java 应用程序中,
String被广泛用于表示关键的、敏感的数据,例如文件路径、网络连接地址、用户名、密码等。如果String类不是final的,恶意代码可以通过子类化String类来改变其行为,可能会导致安全漏洞。通过将String类声明为final,Java 防止了这种潜在的攻击路径。
- 在许多 Java 应用程序中,
- 哈希值缓存 (HashCode Caching)
String类的hashCode方法会缓存计算出的哈希值,以便在后续使用时可以直接返回缓存值,而不必重新计算。由于String是不可变的,其哈希值一旦计算出来就不会改变。如果String不是final的,子类可能会修改字符串的内容,从而破坏哈希值的缓存机制。将String类设为final可以确保哈希值缓存的正确性,提升性能。
- 设计原则
- 将
String类设计为final符合开闭原则(Open-Closed Principle),即“对扩展开放,对修改关闭”。通过使String不可变且不可继承,Java 提供了一个稳定且安全的基础类,开发者可以信赖String的行为,而不用担心其会在不同的上下文中表现出意外的行为。
- 将
- Java 中
-
Java中volatile的作用是什么?
- 当一个变量被声明为volatile时,任何线程对该变量的修改都会立刻被刷新到主内存中。其他线程在读取这个变量时,会直接从主内存中读取最新的值,而不是从各自的线程缓存中读取旧值。
-
Java中多态的实现原理是什么?
- 继承(Inheritance): 子类继承父类的属性和方法。
- 方法重写(Method Overriding): 子类提供了父类方法的具体实现。
- 向上转型(Upcasting): 父类的引用指向子类的对象。即使引用是父类类型,方法调用仍会被定向到子类的重写方 - 法。
- 动态绑定(Dynamic Binding): Java在运行时根据对象的实际类型选择要执行的方法。这是通过虚拟方法表 - (VMT,Virtual Method Table)实现的,表中存储了类中各个方法的地址,JVM根据实际对象的类型在运行时查找 - 对应的方法并执行。
-
Java的可重入锁和不可重入锁的区别?
- 可重入锁(Reentrant Lock)
- 指的是一个线程可以多次获取同一个锁而不会导致死锁的锁。这意味着,如果一个线程已经持有了该锁,它可以再次获取这个锁,而不会被阻塞。
- Java中的可重入锁:
- 在Java中,synchronized 关键字和 ReentrantLock 类都是可重入的。 可重入性意味着同一线程可以在持有锁的同时,再次进入由同一锁保护的代码块,而不会被阻塞。
- 可重入锁(Reentrant Lock)
-
Java中Final类有哪些特点?
- 不能被继承:final类不能作为父类,不能被其他类继承。
- 成员变量必须初始化:final类中的final成员变量必须在声明时或构造函数中进行初始化。
- 方法不可重写:final类中的方法是不可重写的,这保证了final类的行为是固定的。
- 编译器优化:JVM可以对final类进行编译时优化,提高程序的运行效率。
- 提高安全性:final类的不可继承性和不可重写性可以提高代码的安全性。
-
Java中HashMap的的实现原理是什么?
- HashMap 内部主要依赖于一个数组(称为“桶”或“bucket”)来存储键值对。每个桶可能包含一个或多个键值对。当多个键的哈希码相同时,它们将存储在同一个桶中,这种现象称为哈希冲突。
- 在 Java 8 之前,HashMap 的每个桶是一个链表结构;在 Java 8 之后,当链表的长度超过一定阈值时,链表会转换为红黑树,以提高查找效率。
- 哈希冲突的处理
- 链表法: 当冲突发生时,将冲突的键值对链接到桶的链表中。
- 红黑树: 如果链表的长度超过了一个阈值(默认是 8),链表将转换为红黑树,以加快查找速度(从 O(n) 提升到 O(log n))。
- 负载因子和扩容
- 默认负载因子: HashMap 的默认负载因子为 0.75。
- 扩容机制: 当 HashMap 中的元素数量达到阈值(capacity * load factor)时,HashMap 会进行扩容,将桶数组的长度扩大一倍,并重新计算所有元素的桶位置(rehashing),以减少冲突。
- Java 8 中的改进
- 链表到红黑树的转换: 当一个桶中的链表长度超过 8 时,链表将转换为红黑树,从而提高查找的效率。
- 红黑树回退为链表: 如果由于删除操作导致红黑树的节点减少到 6 以下,树将回退为链表,以节省内存。
-
Java中ConcurrentHashMap的的实现原理是什么?
- ConcurrentHashMap 通过多种并发控制技术(如 CAS、局部锁定、分段操作等)实现了高效的线程安全性。它在多线程环境中提供了极佳的读写性能,适用于高并发的场景。与 HashMap 相比,
- 锁分离机制:
- ConcurrentHashMap 并不是通过一个全局锁来保护整个哈希表,而是通过更细粒度的锁机制。Java 8 中使用了 CAS (Compare-And-Swap) 操作 和 内置的 synchronized 来确保线程安全。
- CAS 操作 用于无锁的更新操作,比如插入和更新元素。
- synchronized 用于在同一个桶(即链表或树)上需要进行复杂的操作时(如树化或扩容)进行同步保护。
- CAS 操作:
- ConcurrentHashMap 通过使用 Unsafe 类的 compareAndSwapXXX 方法(底层为原子操作)来实现无锁更新,例如在插入新节点时使用 CAS 操作来确保只有一个线程能成功插入。
- 读取操作:
- get() 操作通常是无锁的,直接从数组中读取值。在读取时,只要数组的结构不变,即使有其他线程在进行写操作,读取操作也可以正常进行。
-
Java中对象创建的过程?
- 创建对象是一个多步骤的过程,涉及类加载、内存分配、初始化等多个阶段。下面详细介绍 Java 中对象创建的全过程。
- 类加载检查
- 在创建对象之前,Java 虚拟机(JVM)首先会检查对象的类是否已经加载、连接和初始化。如果类还没有被加载,JVM 会通过类加载器(ClassLoader)将类文件加载到内存中,并执行类的初始化操作。这包括静态变量的初始化以及静态代码块的执行。
- 内存分配
- 当类加载完毕后,JVM 会为新对象分配内存。内存分配的方式取决于 JVM 的内存管理策略。
- 初始化内存空间
- 分配好内存之后,JVM 会将该内存区域初始化为零值(null、0、false 等),这一步称为“零值初始化”。
- 设置对象头
- 每个对象都有一个对象头(Object Header),它存储了对象的元数据信息,包括对象的类信息、哈希码、GC 状态信息等。
- 执行构造方法
- 对象的内存分配和初始化完成后,JVM 会调用对象的构造方法进行进一步初始化。构造方法负责设置对象的初始状态,包括初始化实例变量。
- 返回对象的引用
- 当构造方法执行完毕后,新创建的对象的内存地址(即对象的引用)会返回给调用者。此时,创建对象的过程结束。
-
Java中类加载的过程?
- 加载: 通过类加载器找到并加载类的字节码,生成 Class 对象。
- 验证: 确保类的字节码文件符合 JVM 规范。
- 准备: 为类的静态变量分配内存并初始化为默认值。
- 解析: 将符号引用转换为直接引用。
- 初始化: 执行静态初始化代码,完成类的初始化。
-
Java中object对象的大小?
-
在 Java 中,所有对象的大小由以下几个部分构成:
- 对象头(Object Header): 包含一些元数据,比如哈希码、锁信息、GC 信息等。
- Mark Word: 用于存储对象自身的状态信息,比如哈希码、GC 分代年龄、锁状态等。在 32 位系统上为 4 字节,在 64 位系统上为 8 字节。
- Class Pointer: 指向对象的类元数据,即对象所属的 Class 对象的引用。在 32 位系统上为 4 字节,在 64 位系统上为 8 字节(如果开启了压缩指针,可能为 4 字节)。
- 可选的数组长度: 如果对象是数组类型,这里还会有一个用于存储数组长度的字段,通常为 4 字节。
- 实例数据(Instance Data): 实例变量的实际数据。对于一个空的 Object 对象,没有任何实例数据。
- 对齐填充(Padding): JVM 规定对象的大小必须是 8 字节的倍数。如果对象的大小不满足这个要求,JVM 会在对象末尾添加填充字节。
- 对象头(Object Header): 包含一些元数据,比如哈希码、锁信息、GC 信息等。
-
对象大小由对象头、实例数据和对齐填充组成。
-
在典型的 64 位 JVM 中(启用了压缩指针),一个空的 Object 对象的大小通常为 16 字节。
-
在 32 位 JVM 中,一个空的 Object 对象的大小为 8 字节。
-
使用 Instrumentation 或 JOL 等工具可以精确测量对象的实际大小。
-
Java 基础对象在内存中占用的空间如下:
- 类型 占用空间(byte)
- boolean 1
- byte 1
- short 2
- char 2
- int 4
- float 4
- long 8
- double 8
-
-
Java中代码块初始化时机?
- 静态代码块:在类加载时执行,初始化静态变量或执行静态初始化逻辑。只执行一次。
- 实例初始化代码块:在对象创建时执行,每次创建对象时都会执行。用于初始化实例变量或执行实例级别的初始化逻辑。
静态变量初始化 静态代码块1 静态代码块2 实例变量初始化 实例初始化代码块1 实例初始化代码块2 构造方法 -
Java中类型初始化为默认值有哪些?
- 基本数据类型
- byte: 0
- short: 0
- int: 0
- long: 0L
- float: 0.0f
- double: 0.0d
- char: ‘\u0000’ (即 Unicode 编码的空字符,值为 0)
- boolean: false
- 引用类型
- 所有引用类型(如类、接口、数组、String 等):null
- 基本数据类型
-
Java中的类在什么时候加载?
- 触发类加载的条件包括创建实例、访问静态成员、初始化子类、通过反射访问类、访问类的静态代码块以及启动类。
- 类加载分为加载、链接和初始化三个主要阶段。
-
Java中注解的作用是什么?
- Java 中的注解是一种强大的工具,用于嵌入元数据、进行编译时检查、生成代码、配置运行时行为等。通过理解和正确使用注解,开发者可以编写更简洁、可维护性更高的代码,并能够与许多 Java 框架和工具进行更好的集成。
Android
- Handler中有Loop死循环,为什么没有阻塞主线程,原理是什么?
- Looper 的事件循环会调用 MessageQueue 的 next() 方法,该方法会阻塞当前线程,直到有新的消息到来。但是这个阻塞并不会影响主线程的执行,原因如下:
- 阻塞是非消耗式的:当 next() 方法阻塞时,它实际上是在等待消息队列中出现新的消息或任务。在没有消息的时候,主线程实际上是“空闲的”,没有消耗 CPU 资源。只有当有消息到来时,next() 才会返回,Looper 再将消息分发出去。
- 事件驱动模型:整个系统是基于事件驱动的。主线程并不依赖于固定的时间间隔去执行某些任务,而是被动地等待事件(如用户输入、网络响应)发生,然后处理这些事件。因此,主线程的工作模式是处理事件、空闲等待、再处理新的事件。
- 异步消息处理:许多任务被安排在后台线程中执行,并且使用 Handler 将结果或更新请求发送到主线程。当主线程处理这些消息时,只处理一个消息,然后继续循环等待下一个消息。这使得主线程始终保持响应性,避免被长时间阻塞。
- Looper 的事件循环会调用 MessageQueue 的 next() 方法,该方法会阻塞当前线程,直到有新的消息到来。但是这个阻塞并不会影响主线程的执行,原因如下:
- Android中onMeasure的作用
- onMeasure 的主要作用是计算视图的大小,这个大小是由父视图给出的约束条件(MeasureSpec)和视图本身的内容来决定的。这个方法允许视图根据父容器的布局需求以及视图的内容来自行确定自己的宽度和高度。
- Android中onMeasure的参数作用
- MeasureSpec 的三种模式
- UNSPECIFIED(未指定):父容器对视图的大小没有任何限制,视图可以任意大小。这通常用于一些特殊的情况,如 ScrollView 的内部视图。
- EXACTLY(精确值):父容器决定了视图的精确尺寸。视图的宽度和高度将被限制为这个值。
- AT_MOST(最大值):视图可以是任意大小,但不能超过父容器指定的最大尺寸。
- MeasureSpec 的三种模式
- Android中IPC的方式和特点
- Bundle
- 简单易用:Bundle 常用于在 Activity、Service 或 BroadcastReceiver 之间传递少量数据。
- 数据类型有限:Bundle 支持的基本数据类型有限,通常用于简单的键值对传输。
- 效率高:由于不涉及跨进程通信的复杂性,Bundle 在进程内部通信时非常高效。
- Messenger
- 基于 Handler 的消息传递:Messenger 是基于 Handler 的 IPC 机制,用于在不同进程间发送和接收消息(Message)。
- 单向通信:Messenger 主要用于单向通信,适合需要传递简单消息或命令的场景。
- 简单实现:相对于 AIDL,Messenger 的实现较为简单。
- AIDL
- 强大但复杂:AIDL 提供了强大的跨进程通信功能,允许不同进程之间传递复杂的数据结构。
- 支持多种数据类型:包括基本数据类型、自定义对象(必须实现 Parcelable 接口)等。
- 双向通信:AIDL 支持双向通信,可以实现进程间的接口回调。
- 性能开销:由于涉及序列化和反序列化,AIDL 的性能开销较大,适合处理需要传输大量数据或复杂对象的场景。
- BroadcastReceiver
- 消息广播:BroadcastReceiver 用于发送和接收系统全局或应用内的广播消息,适合实现解耦的事件通知机制。
- 异步消息:广播消息是异步的,不保证接收顺序。
- 可跨进程:可以用于跨进程的简单通知和消息传递。
- Bundle
- RecyclerView缓存机制
- ViewHolder 模式:减少视图查找次数,提高数据绑定效率。
- 视图回收机制:重用离开屏幕的视图,降低内存消耗和视图创建开销。
- RecycledViewPool:集中管理回收视图,提高视图重用率,支持多个 RecyclerView 共享。
- 多视图类型处理:确保不同视图类型的视图正确回收和重用。
- LayoutManager 的优化:通过预取和高效布局管理,提升滚动性能。
- 预取和预测性缓存:提前准备即将显示的视图,减少滚动延迟。
- ItemAnimator 的优化:平衡动画效果和性能,确保视图回收不受影响。
- 缓存大小调整:根据需求合理配置缓存大小,平衡内存和性能。
- 嵌套 RecyclerView 的优化:通过共享回收池,提高视图重用效率。
- onViewRecycled方法清除缓存
- Android AIDL binder如何重连
- IBinder.DeathRecipient监听机制:通过这个机制,客户端可以检测到服务端Binder连接的断开,并在binderDied()中实现重连逻辑。
- 重连服务:在服务端重新启动后,客户端可以通过重试机制重新连接服务,以确保应用的稳定性和用户体验。
- onNewIntent的作用和调用时机
- onNewIntent 是 Android 中 Activity 类的一个回调方法,主要用于处理一个已经存在的 Activity 被重新启动时传递给它的新 Intent。在特定场景下,比如当一个已经存在的 Activity 需要在不重新创建的情况下接收新的 Intent 数据时,onNewIntent 方法就显得非常有用。
- 只有通过 singleTop 或 singleTask 模式重新启动时生效
- 如果在 onNewIntent 中更新了界面或状态,请确保在 onResume 或 onStart 中也能够正确处理相关逻辑,以应对 Activity 被重新启动的情况。
- IdleHandler 的原理分析和妙
- IdleHandler 是 Android 中用于处理 MessageQueue 处于空闲状态时的回调机制。它允许开发者在消息队列(MessageQueue)空闲时执行一些非紧急任务,而不影响主线程的响应性。理解 IdleHandler 的原理和妙用可以帮助你优化应用的性能和用户体验。
- 虽然 IdleHandler 适合处理低优先级任务,但这些任务仍然是在主线程上执行的,任务过于耗时可能导致界面卡顿。
- IdleHandler 的妙用
- 延迟执行非紧急任务
- 提高主线程的响应性优化应用启动速度
- 提高主线程的响应性提高主线程的响应性
- 进入Activity时,为何页面布局内View#onMeasure会被调用两次?
- 第一次调用 onMeasure 是由父视图(通常是 DecorView)发起的,目标是测量并确定整个视图树的布局大小。在这个阶段,父视图传递的 MeasureSpec 会影响子视图的测量结果。初步测量后,子视图的尺寸和布局信息被计算出来。
- 第二次调用 onMeasure 是因为布局过程中可能会发生变化,比如:
- 匹配父布局 (match_parent):某些视图可能根据父视图的大小调整自己的尺寸。如果父视图的尺寸在第一次测量后发生变化,则需要重新测量子视图,以确保它们与父视图的新尺寸匹配。
- 权重分配 (layout_weight):在某些布局中(如 LinearLayout),子视图的尺寸可能依赖于权重分配,在第一次测量后,父视图可能需要重新调整子视图的尺寸,这会导致再次调用 onMeasure。
- LinearLayout#onMeasure对child的measure调用次数的影响 第一次测量,同时满足下面几个条件的child不参与第一次测量,否则就参与第一次测量。 - LinearLayout的宽度固定(MeasureSpec.EXACTLY) - child的宽度为0且child的权重大于0 - child设置了baselineAligned属性 第二次测量,针对有权重的child进行测量,第一次测量跳过的View,会在这里进行测量。 第三次测量,关注height的测量。如果heightMode != MeasureSpec.EXACTLY,且有child的高度是match_parent,则forceUniformHeight方法,开启for循环,将高度是match_parent的view重新measure一遍。
- BLE相关
- 这个示例展示了如何在Android中进行BLE开发的基本步骤,包括设备扫描、连接、服务发现、特征读取和写入。根据具体需求,还可以进一步扩展功能,如处理通知(Notifications)、指示(Indications)以及处理更多复杂的蓝牙操作。
- 事件分发与拦截
- Android 的触摸事件分发机制围绕着三个核心方法展开:
- dispatchTouchEvent(MotionEvent event):负责分发事件。所有事件都会首先到达此方法。
- onInterceptTouchEvent(MotionEvent event):用于拦截事件,决定是否阻止事件继续向下传递给子视图。
- onTouchEvent(MotionEvent event):处理事件。此方法决定当前视图是否要处理该事件。
- 事件的基本流程
- Activity 接收事件:触摸事件首先传递到当前的 Activity,Activity 会调用 Window 的 dispatchTouchEvent 方法。
- Window 传递事件:Window 会将事件传递给 DecorView(即顶层视图),然后由 DecorView 将事件分发给其子视图,一直到最底层的 ViewGroup 或 View。
- ViewGroup 分发事件:当事件到达 ViewGroup 时,ViewGroup 的 dispatchTouchEvent 方法负责分发事件。
- ViewGroup 会首先调用 onInterceptTouchEvent 来判断是否拦截该事件。
- 如果拦截 (onInterceptTouchEvent 返回 true),事件将交由该 ViewGroup 自己处理,即调用 onTouchEvent。
- 如果不拦截 (onInterceptTouchEvent 返回 false),事件会继续传递给 ViewGroup 的子视图。
- 如果事件到达一个普通的 View,它的 dispatchTouchEvent 方法会直接调用 onTouchEvent 来处理事件。
- ViewGroup 会首先调用 onInterceptTouchEvent 来判断是否拦截该事件。
- Android 的触摸事件分发机制围绕着三个核心方法展开:
- 自定义控件的三大方法
- 在 Android 中,自定义控件通常需要重写以下三大核心方法,以控制控件的绘制、布局和触摸事件处理。这些方法是:
- onMeasure(int widthMeasureSpec, int heightMeasureSpec):测量控件尺寸
- onLayout(boolean changed, int left, int top, int right, int bottom):布局子视图(如果有)
- onDraw(Canvas canvas):绘制控件内容
- 在 Android 中,自定义控件通常需要重写以下三大核心方法,以控制控件的绘制、布局和触摸事件处理。这些方法是:
- Binder的工作原理
- 服务的注册与查找
- 服务注册:一个进程可以通过 ServiceManager 注册一个 Binder 服务,其他进程通过服务名找到该服务- 并进行通信。
- 服务查找:客户端通过 ServiceManager 请求服务的 Binder 引用。
- 客户端与服务端的通信
- 客户端调用:
- 客户端通过 Binder 对象向服务端发送请求。
- 请求通过 Parcel 包装,然后通过 Binder 驱动传递给服务端。
- Binder 驱动:
- Binder 驱动是 Linux 内核中的一部分,负责在不同进程间传递 Parcel 数据。
- 驱动程序会将请求从客户端的 Binder 对象传递给服务端的 Binder 实例。
- 服务端处理:
- 服务端收到请求后,将 Parcel 解包,处理请求。
- 处理完成后,服务端可以通过 Parcel 返回结果,结果通过 Binder 驱动传回客户端。
- 客户端接收结果:
- 客户端接收到服务端返回的 Parcel,解包并使用返回的数据。
- 客户端调用:
- 服务的注册与查找
- Android第一个Activity启动流程
- 点击图标,启动过程开始
- 当用户点击应用图标时,Launcher 应用会通过 Binder IPC 向 ActivityManagerService (AMS) 发送一个 - startActivity 请求,要求启动应用的主 Activity。
- ActivityManagerService (AMS) 处理请求
- ActivityManagerService (AMS) 是 Android 系统的核心服务之一,负责管理整个应用的生命周期。
- AMS 收到 startActivity 请求后,会先检查目标应用是否已经运行。如果没有运行,则需要启动应用进程;如- 果已经运行,则直接启动目标 Activity。
- 启动应用进程
- 如果应用尚未运行,AMS 会向 Zygote 进程发送请求,通过 fork 机制创建一个新的应用进程。
- Zygote 是 Android 系统中的一个守护进程,负责创建新应用进程。新进程启动后,会加载应用的 APK 文件,- 并初始化应用环境。
- ActivityThread 启动
- 在新进程中,Zygote 启动后,会创建并启动 ActivityThread。
- ActivityThread 是应用进程的主线程,负责管理应用的主事件循环(UI 线程),并处理 AMS 发送过来的各种- 消息,包括启动 Activity。
- 创建 Activity
- ActivityThread 调用 Instrumentation 的 callActivityOnCreate 方法,最终通过反射创建 Activity - 实例。
- 在 Activity 创建过程中,会调用 Activity 的 onCreate 方法,初始化用户界面、绑定数据、设置事件处理- 等。
- Activity 显示
- Activity 初始化完成后,ActivityThread 会调用 Activity 的 onResume 方法,激活并显示 Activity。
- Activity 的界面显示是通过 WindowManager 和 SurfaceFlinger 协作完成的,最终将渲染后的内容显示到屏- 幕上。
- 渲染 Activity
- Activity 的视图树由 ViewRootImpl 负责管理。ViewRootImpl 将应用的 UI 绘制到 Surface 上,并通过 - SurfaceFlinger 将内容显示在屏幕上。
- 用户可以与 Activity 交互
- Activity 完全启动并显示后,应用进入了事件循环,开始响应用户的输入事件(如点击、滑动等)。
- 点击图标,启动过程开始
- 在Android如何使用ffmpeg
- 通过使用 ffmpeg-kit 或 JNI,您可以在 Android 应用中利用 FFmpeg 的强大功能来处理音视频文件。使用 ffmpeg-kit 是更直接的方法,而 JNI 提供了更大的灵活性。选择哪种方法取决于您的具体需求和项目要求。
- Android SharedPreferences流程原理
- SharedPreferences 底层基于 XML 文件进行存储。每个 SharedPreferences 实例对应于应用内部存储中的一个 XML 文件,该文件保存了所有键值对的数据。
- 当你通过 commit() 或 apply() 方法将数据写入 SharedPreferences 时,数据会被序列化为 XML 格式并写入到文件中。
- SharedPreferences 支持存储以下几种基本数据类型:
- String
- int
- float
- long
- boolean
- Set(API 11 以上支持)
- commit() 方法和 apply() 方法的区别
- commit() 方法
- 同步提交:commit() 方法会同步地将数据写入到文件中,并返回一个布尔值表示操作是否成功。
- 阻塞主线程:由于 commit() 是同步操作,它会阻塞调用线程,直到数据写入完成。如果在主线程中调用且数据量大时,可能会导致应用卡顿。
- 返回值:commit() 方法有返回值 boolean,可以用来检查数据是否成功保存。
- apply() 方法
- 异步提交:apply() 方法是异步操作,它会将数据写入内存,然后在后台线程中将数据写入文件。
- 不阻塞主线程:由于是异步操作,apply() 不会阻塞主线程,适合在主线程中频繁调用。
- 无返回值:apply() 方法没有返回值,所以无法直接得知操作是否成功。
- commit() 方法
- Android的不同版本区别种Bitmap回收机制有什么区别
- Android 2.3 及以下 (API 10 及以下): Bitmap 的像素数据存储在本地内存中,需要手动调用 recycle() 来释放内存。
- Android 3.0 到 4.4 (API 11 到 19): Bitmap 的像素数据转移到 Dalvik 堆中,recycle() 不再是必须的,但可以加快内存释放。
- Android 5.0 及以上 (API 21 及以上): 在 ART 环境下,垃圾回收机制进一步优化,手动调用 recycle() 更加不必要,除非在特定场景下需要强制释放内存。
- 在编写 Android 应用程序时,一般建议:
- 只在必要时调用 recycle(),尤其是在低内存设备或处理大量大尺寸 Bitmap 时。
- 优化图片加载(例如使用 inSampleSize 缩放图片)和缓存策略,以减少内存占用。
- 从 API 19(Android 4.4)开始引入的 BitmapFactory.Options.inBitmap 选项允许重用已回收的 Bitmap 内存,从而进一步优化内存使用。
- Android不同版本的权限存在哪些差异
- 早期版本 (1.0 - 5.0): 权限在安装时授予,用户无法在运行时控制。
- Android 6.0 (API 23): 引入运行时权限,用户可以在应用运行时授予或拒绝危险权限。
- Android 8.0 (API 26) 及以后: 开始限制后台权限访问,自动撤销长时间未使用的权限。
- Android 10 (API 29) 及以后: 引入分区存储,进一步细化位置权限,限制后台启动。
- Android 11 (API 30): 引入一次性权限和自动重置权限。
- 权限管理方式
- 一次性权限: Android 11 引入了“仅此一次”的权限选项,允许用户仅在一次操作中授予权限,应用下次使用时需重新请求。
- 自动重置权限: 如果用户长时间不使用应用,系统会自动重置(撤销)应用的所有运行时权限,并在下次用户打开应用时通知用户。
- 存储权限进一步收紧
- 增强的分区存储: Android 11 对分区存储进行了进一步的增强和限制,应用对外部存储的访问权限被进一步限制,除非是针对特定的应用场景,如媒体文件的访问。
- 权限管理方式
- Android 12 (API 31): 新增蓝牙权限和位置权限细化控制。
- 权限管理方式
- 蓝牙权限: Android 12 引入了新的权限来访问蓝牙设备(BLUETOOTH_SCAN, BLUETOOTH_ADVERTISE, BLUETOOTH_CONNECT),以替代之前更宽泛的 LOCATION 权限。
- 微调位置权限: 用户可以选择授予大致位置权限,而不是精确位置权限,这进一步增强了对位置隐私的控制。
- 新增权限
- 相机和麦克风权限开关: 系统提供了全局的相机和麦克风访问开关,用户可以通过系统控制面板直接关闭所有应用的相机和麦克风访问权限。
- 权限管理方式
- Android 13 (API 33): 新增通知权限和细化的媒体访问权限。
- 通知权限
- 通知权限: Android 13 引入了新的通知权限(POST_NOTIFICATIONS),应用必须请求此权限才能发送通知。这是为了防止应用过度打扰用户,增强用户对通知的控制。
- 媒体权限
- 媒体文件访问权限: Android 13 进一步细化了对媒体文件的访问权限,分为“图片和视频”(READ_MEDIA_IMAGES, READ_MEDIA_VIDEO)和“音频”(READ_MEDIA_AUDIO)两种权限。应用只需请求最小范围的权限,而不是访问整个外部存储。
- 通知权限
- Android如何编写图片拖拽和缩放
- 拖拽操作:通过处理 ACTION_MOVE 事件,计算触摸位置的差值并更新图像的平移位置。
- 缩放操作:通过 ScaleGestureDetector 检测缩放手势,并相应地调整图像的缩放比例。
- 矩阵变换:使用 Matrix 进行平移和缩放操作的组合,确保图像的平滑变换。
- 进一步的优化
- 边界控制:在实际应用中,可能需要增加边界控制,防止图像拖动时超出屏幕边界。
- 双击缩放:可以进一步增强用户体验,比如通过双击放大或缩小图像。
- 惯性滚动:可以通过 Scroller 实现惯性滚动效果,使图像拖拽后具有惯性滑动。
- 安卓的虚拟机版本差异
- Android 的虚拟机(VM)实现经历了几个重要的演变,主要包括 Dalvik 和 ART(Android Runtime)。不同版本的虚拟机在性能、内存管理、垃圾回收、代码执行效率等方面存在显著差异。
- Dalvik 虚拟机
- 字节码格式: Dalvik 使用的是 .dex(Dalvik Executable)格式,它与标准的 .class 文件不同,优化了多类文件的整合,以减少内存占用。
- 即时编译(Just-In-Time, JIT): Dalvik 使用即时编译技术,这意味着应用程序的代码在运行时按需编译。这种方法减少了启动时间和内存需求,但在运行过程中可能导致性能波动,因为代码的编译是在应用运行时动态进行的。
- 内存管理: Dalvik 虚拟机使用了分代垃圾回收(Generational Garbage Collection),不过它的垃圾回收性能相对较弱,可能导致应用在运行时出现卡顿。
- ART(Android Runtime)
- 提前编译(Ahead-Of-Time, AOT): ART 在应用安装时就会将应用的字节码预先编译为机器码,这样在运行时不需要再进行即时编译,大大提高了运行效率。
- 改进的垃圾回收: ART 引入了改进的垃圾回收机制,采用并行和并发的垃圾回收策略,减少了 Dalvik 中常见的垃圾回收“停顿”现象,提高了应用的响应性。
- 优化内存管理: ART 在内存管理方面进行了多项优化,包括对象的内存对齐、内联缓存(inline caching)等,进一步提升了内存的使用效率和访问速度。
- 调试和监控增强: ART 提供了更强大的调试功能,支持更精细的内存和性能分析工具,有助于开发者优化应用。
- ActivityManagerService的作用是什么
- 任务和活动管理:ActivityManagerService 负责跟踪和管理系统中所有的活动(Activity)。它决定哪个活动应该被启动、暂停、恢复或销毁。此外,它也管理任务(Task)的堆栈,即一组关联的活动。
- 内存管理:该服务监控系统的内存使用情况,决定何时杀死后台进程来释放内存。它确保系统在内存紧张时能够保持流畅运行,优先保留前台任务所需的资源。
- 进程管理:ActivityManagerService 管理系统中所有的应用程序进程,包括启动、停止、调度等。它还负责处理进程崩溃和重启等情况。
- 权限和安全:该服务还涉及到权限管理,确保应用程序之间的安全隔离。它验证应用程序的权限请求,并控制它们的访问权限。
- 应用程序状态:ActivityManagerService 提供接口给其他系统组件和应用程序,以查询或改变应用程序的状态。这包括获取活动的列表、当前运行的任务、后台任务等信息。
- 应用程序生命周期:它管理应用程序的生命周期,包括启动、运行、暂停、停止和销毁。这个过程涉及到与系统的各种事件的交互,例如屏幕旋转、设备休眠等。
- 系统事件处理:ActivityManagerService 还处理一些系统级的事件,如系统升级、配置变化等,并确保这些事件对活动和任务的影响最小化。
- ActivityManagerService和ActivityManager的区别?
- ActivityManagerService 是 Android 系统内部的服务,负责管理和调度活动、任务、进程等系统级功能。它是系统核心的一部分,不直接暴露给应用开发者。
- ActivityManager 是一个提供应用级别接口的类,允许开发者与 ActivityManagerService 进行交互,获取系统状态信息或控制应用程序的行为。
- Android的插件化方案
- ClassLoader:插件化框架通常使用自定义的 ClassLoader 来动态加载插件中的类。通过创建一个独立的 ClassLoader 实例来加载插件 APK 中的类,而不是使用默认的 ClassLoader。这允许插件的代码和资源与主应用程序隔离开来。
- DexClassLoader:在 Android 中,DexClassLoader 是一个专门用于动态加载 dex 文件的 ClassLoader。插件化框架通常使用 DexClassLoader 来加载插件的 dex 文件,从而实现动态加载插件代码的功能。
- Dynamic Feature Module是什么?
- Dynamic Feature Module是Android提供的一种插件化技术,允许开发者将应用的一部分功能作为单独的模块打包,这些模块可以在应用需要时动态下载并使用。Dynamic Feature Module使得应用的安装体积更小,提高了用户的下载体验,并且可以在不影响应用性能的情况下提供更多功能。当用户需要使用某个特定功能时,应用可以动态下载相应的Dynamic Feature Module,提供更好的用户体验。
- FrameAnimation优化
- 减少帧数和使用恰当的图像分辨率
- 从Android 3.0 (API Level 11)开始,引进了BitmapFactory.Options.inBitmap字段。如果使用了这个设置字段,decode方法会在加载Bitmap数据的时候去重用已经存在的Bitmap。这意味着Bitmap的内存是被重新利用的,这样可以提升性能,并且减少了内存的分配与回收。
- Android中Handler postDelayed是否会受到系统时间影响
- 修改系统时间不会直接影响Handler中Message的delay时间。但是,如果您同时修改了系统时间并且重新启动了设备,则可能会影响消息处理的时间,因为它可能导致重新计算消息的delay。
- Android属性动画的原理?
- ValueAnimator:ValueAnimator 是属性动画的基础类,它用于生成一系列在动画过程中变化的值。ValueAnimator 不直接更新视图属性,而是生成动画值供其他组件使用。
- ObjectAnimator:ObjectAnimator 是 ValueAnimator 的子类,它将动画值应用到对象的属性上。通过设置动画的目标对象和属性,ObjectAnimator 可以直接对对象的属性进行动画处理。
- ValueAnimator对象会在动画的整个生命周期内持续计算动画的值,并且通过动画更新函数更新对象的值,从而实现动画的效果。
- Android中Handler的原理是什么?
- Android中Handler的原理是基于消息循环(Message Loop)实现的。
- Handler是一种消息处理器,它可以在主线程(Main Thread)上执行处理任务。
- 它通过将消息封装为Message对象并加入到主线程的消息队列中,在主线程的消息循环中处理消息,从而实现异步任务的处理。
- 主线程消息循环不断地读取消息队列中的消息,并且对消息进行处理,从而实现主线程上的异步任务处理。
- Handler类还提供了定时和循环处理任务的能力,使用postDelayed()和sendEmptyMessageDelayed()方法即可实现。
- Android平台如何实现MVVM?
- MVVM 的基本结构
- Model:
- Model 层负责数据的处理和业务逻辑的实现。它可以是数据库、网络请求、或其他数据源。
- Model 层应该是独立的,与 View 或 ViewModel 无关,以便于复用和测试。
- View:
- View 层是用户界面,负责显示数据和接收用户输入。在 Android 中,View 通常由 Activity、Fragment 或自定义视图(View)组成。
- View 层通过绑定或观察数据来更新 UI。
- ViewModel:
- ViewModel 是 MVVM 模式的核心。它持有 UI 所需的数据,并将这些数据暴露给 View。ViewModel 负责与 Model 层交互,并处理业务逻辑。
- ViewModel 不持有对 View 的引用,这使得它独立于 Android 的生命周期,更加适合测试。
- Model:
- LiveData
- Transformations.map:适用于将 LiveData 的值通过简单的映射转换为另一种类型的 LiveData。使用场景简单且转换逻辑明确。
- Transformations.switchMap:适用于当输入的 LiveData 变化时,需要动态生成新的 LiveData,并根据新的输入取消旧的订阅,切换到新的 LiveData。适合处理需要动态更改的复杂逻辑。
- MediatorLiveData:适用于需要合并或协调多个 LiveData 的场景,它可以监听多个 LiveData 并基于这些 LiveData 的变化来更新自身的值。适合在需要处理来自多个数据源的复杂数据合并逻辑时使用。
- MVVM 的基本结构
- MVVM有哪些优缺点
- MVVM 的优点
- 分离关注点:
- MVVM 模式将 UI 逻辑(View)、业务逻辑(ViewModel)、和数据处理(Model)进行分离。这样,代码更加模块化,职责更加清晰,使得每个部分可以独立开发、测试和维护。
- 提高代码的可测试性:
- ViewModel 独立于 View,因此可以在不依赖 UI 的情况下进行单元测试。由于 ViewModel 不直接与 Android 的生命周期组件交互,测试变得更加简单。
- 数据绑定(Data Binding):
- MVVM 中常常结合数据绑定(Data Binding)库使用,能够实现双向绑定,使得 UI 和数据的同步变得自动化,减少了手动更新 UI 的代码。这不仅简化了代码,还降低了出错的可能性。
- 提高代码的可维护性:
- 由于关注点的分离,View、ViewModel 和 Model 都是松散耦合的,当需求变化或代码需要重构时,只需修改相关部分,而不必担心对其他部分的影响。
- 减少样板代码:
- 数据绑定、LiveData、ViewModel 等工具和库的使用能够减少大量的样板代码,开发者不再需要手动在各个生命周期中处理 UI 更新,降低了开发复杂性。
- 响应式编程:
- 使用 LiveData 或其他响应式编程工具(如 RxJava)结合 ViewModel 可以轻松实现响应式编程模式,UI 能够自动响应数据变化,提升用户体验。 MVVM 的缺点
- 学习曲线较陡:
- MVVM 模式对初学者来说较为复杂,特别是如果结合使用了数据绑定(Data Binding)库,开发者需要掌握更多的概念和工具(如 ViewModel、LiveData、Data Binding),这对新手可能有一定的挑战。
- 过度工程化:
- 对于简单的应用或界面,使用 MVVM 可能显得过于复杂,增加了不必要的复杂性和代码量。在这种情况下,简单的 MVC 或 MVP 模式可能更合适。
- 数据绑定(Data Binding)的潜在问题:
- 数据绑定库虽然强大,但它也带来了调试难度。数据绑定的错误通常只在运行时暴露,而且有时候错误信息不够直观,增加了开发和调试的难度。
- 难以管理复杂的绑定逻辑:
- 如果一个 View 需要绑定大量的数据,或者绑定逻辑非常复杂,可能会导致 XML 布局文件变得非常庞大且难以维护。尽管 MVVM 的设计是为了简化 UI 逻辑,但当绑定逻辑变得复杂时,反而可能会导致维护成本增加。
- 可能导致过度依赖数据绑定:
- 数据绑定虽然简化了开发,但过度依赖数据绑定可能会让开发者忽略了传统方法的优势,特别是在数据绑定框架出现兼容性问题或者性能问题时,可能会难以调试和优化。
- 性能开销:
- 虽然 MVVM 提高了代码的清晰度和可维护性,但它也增加了应用的复杂性,特别是在使用 Data Binding 时,会增加一定的性能开销,尤其是在大量复杂绑定的情况下。
- 分离关注点:
- MVVM 的优点
- HashMap对比SparseArray有什么区别
- 内存效率: SparseArray 在特定条件下(如 int 键和少量数据)更高效,可以减少内存开销,而 HashMap 对于通用键值对存储更为灵活但内存开销较大。
- 性能: HashMap 在大量数据情况下性能更优,因为它的平均时间复杂度为 O(1)。而 SparseArray 在处理少量整数键值对时虽然稍慢,但其内存效率更高。
- 使用建议: 在 Android 开发中,如果仅需要存储少量 int 键值对,优先考虑 SparseArray;对于更复杂或通用的映射需求,使用 HashMap。
- View真的只能在UI线程更新?
- ViewRootImpl 还未初始化,在绘制过程中才会检查是否在UI线程中,跳过了 checkThread ,导致更新正常
- ConcurrentModificationException ?
- 使用 Iterator 的 remove 方法
- 使用 for 循环和索引
- CopyOnWriteArrayList
- 修改时先复制一个快照来修改,改完再让内部指针指向新数组,适合读多写少
- 读操作无锁的ArrayList,写操作则通过创建底层数组的新副本来实现,是一种读写分离的并发策略
- 自定义View?啥时候能获取宽高?
- 最早在onMeasure方法就能获取宽高
- 动态代理是什么?
- 动态代理在 Java 中常用于实现面向切面编程(AOP),如事务管理、日志记录、权限验证等功能。
- IoC是什么?
- IoC在Android中如何实现?
- Android中可以使用Dependency Injection (DI) 框架来实现IoC。常用的DI框架有Dagger, Butter Knife, RoboGuice等。它们通过分析代码并使用注解来自动生成代码,从而在应用程序中自动生成和管理对象。例如,如果某个类需要某个依赖,可以使用@Inject注解,DI框架会自动创建该依赖并将其注入到目标类中。
- IoC (Inversion of Control) 是指一种编程范式,它的思想是将对象的创建和管理交给容器,而不是由对象本身负责,这样做的目的是解耦对象之间的依赖关系。
- IoC在Android中如何实现?
- Android的Dagger框架实现原理是什么?
- Dagger是一个Android依赖注入框架,它通过使用Java代码生成实现依赖注入。它的原理是使用注解来定义依赖关系,并在编译时生成实现这些依赖关系的代码。例如,如果您的代码需要使用一个服务对象,您可以在类中使用注解标记该服务对象,并且Dagger会在编译时生成代码来实现该依赖关系,使得服务对象可以在类中使用。
- Android中intent的工作原理
- Android中的Intent是一种异步消息,用于在应用程序的不同组件之间传递数据,以执行不同的任务,例如启动Activity,启动服务或发送广播。Intent除了数据外,还包含要执行的操作,例如要启动的目标组件(通常是Activity或Service)。当Intent发送到系统时,系统会解析Intent,并根据其内容启动相应的组件或广播,从而执行Intent中定义的操作。
- Android中intent可以传递哪些数据?
- 基本数据类型(如int、float、double)
- 字符串
- 数组
- 序列化对象
- Bundle:一种键值对集合
- 集合类型
- IBinder:Android 中的一种低级接口,主要用于与系统服务进行交互
- 其他支持序列化的数据类型(如Uri、Parcelable)
- Intent:可以将一个 Intent 对象作为 Extra 传递到另一个 Intent 中
- AIDL工作原理?
- Stub 与 Proxy 机制:服务端实现的 Stub 类继承自 Binder 类,并实现了 AIDL 接口。客户端通过 bindService() 获取到一个 IBinder 对象,这个对象实际上是 Proxy 类的实例。Proxy 类负责将客户端的调用请求通过 Binder 驱动程序传递给服务端的 Stub,并返回结果。
- Binder 驱动:Binder 是 Android 系统中的核心组件之一,它实现了进程间通信的基础。客户端的请求会通过 Binder 驱动发送给服务端,Binder 驱动负责将数据序列化和反序列化,并将数据从一个进程传递到另一个进程。
- 序列化与反序列化:在进程间传递数据时,AIDL 会将方法的参数和返回值序列化为字节流,然后通过 Binder 传输。当数据到达目标进程时,再将字节流反序列化为原始数据类型。
- 线程处理:服务端的 Stub 类中的方法会在 Binder 线程池中执行,因此需要注意线程安全问题。可以通过在服务端实现中使用 Handler 或其他机制将处理移交到主线程。
- 子线程到主线程通信都有哪些方式?
- Handler:Android 中处理线程间通信最常用的方式之一
- runOnUiThread:是一个简单的方法
- AsyncTask:Android 提供的用于执行异步任务的类
- LiveData:Android Jetpack 的一个类,专门用于持有并观察数据。
- EventBus:EventBus 是一种发布/订阅事件的库,允许在不同的线程之间传递消息。
- handler内存泄露的原因是什么?
- 由于 Handler 持有对其外部类(通常是 Activity 或 Fragment)的引用,可能会导致内存泄漏。当 Activity 或 Fragment 被销毁,但 Handler 仍在处理消息时,外部类不能被及时回收。避免这种问题的方法有:
- 静态内部类:将 Handler 声明为静态内部类,并使用 WeakReference 引用外部类。
- 移除消息:在 Activity 或 Fragment 的 onDestroy() 方法中,调用 handler.removeCallbacksAndMessages(null) 来移除所有未处理的消息和任务。
- MessageQueue中存储的Message数量有上限吗? 为什么这么设计?能不能用阻塞队列做MessageQueue
- 在 Android 的 MessageQueue 中,存储的 Message 数量实际上并没有固定的上限。MessageQueue 的设计是基于一个单链表的数据结构,Message 对象被按时间顺序插入到链表中。因此,理论上这个链表可以无限延长,只要内存允许。
- 使用链表的方式组织 Message,能够简化消息的插入和删除操作,并且可以方便地按时间顺序排列消息,而不需要复杂的管理机制。
- MessageQueue 是高度优化的,专门用于处理 UI 线程的消息循环。它避免了不必要的锁和上下文切换,而阻塞队列通常会引入这些开销,可能会影响性能,尤其是在高频消息处理场景下。
- 我们使用 Message 时应该如何创建它?
- 使用 Message.obtain() 方法
- Message.obtain() 是创建 Message 对象的推荐方法。这个方法会从内部的消息池中获取一个空闲的 Message 对象,从而避免频繁地创建和销毁对象,减少 GC(垃圾回收)的压力。
- 使用 Message.obtain() 方法
- handler没有消息处理是阻塞的还是非阻塞的?为 什么不会有ANR产生?
- 消息处理不会阻塞线程:Handler 的设计是为了非阻塞地处理消息。即使 MessageQueue 中有大量消息,Handler 在处理这些消息时也不会阻塞线程的执行。消息会被排队,并按照顺序处理,这个过程是异步的。
- ANR 的产生是因为主线程被阻塞:ANR 通常是由于主线程执行了耗时操作或长时间无响应引起的。因为 Handler 的消息处理机制是非阻塞的,Handler 本身不会导致主线程的阻塞,从而避免 ANR 的发生。问题通常出现在应用程序对 UI 的操作或者其他长时间的操作在主线程中执行,导致主线程长时间没有响应。
- LocalBroadcast与常用的Broadcast功能类似,但是具有如下优点:
- 因广播数据在本应用范围内传播,因此不必担心隐私数据泄露的问题。
- 不必担心别的应用伪造广播,造成安全隐患。
- 相比在系统内发送全局广播,它更高效。
- Android开发中遇到的疑难问题
- 编译错误或应用崩溃
- 检查代码错误:仔细检查代码并确保没有语法错误、命名错误或逻辑错误。
- 检查依赖项:确保您的依赖库和插件的版本与您的代码兼容。
- 更新工具和库:确保您正在使用最新版本的Android Studio、Gradle和相关的工具和库。
- 清除和重新构建:尝试清除项目并重新构建应用程序,以解决可能的构建问题。
- 查找错误解决方案:在开发者社区、Stack Overflow等平台上搜索类似的问题,并查找可能的解决方案。
- 设备兼容性问题
- 目标SDK版本:确保您的应用程序的目标SDK版本与设备兼容,并遵循最佳实践和API指南。
- 测试不同设备:在开发和测试阶段,尽量测试应用程序在不同的设备上,包括不同的品牌、型号和操作系统版本。
- 适配布局:根据不同屏幕尺寸、密度和方向,适配布局并使用正确的资源。
- 处理权限:处理运行时权限,并确保在需要权限时适当地请求和处理权限。
- 性能问题
- 使用性能分析工具:使用Android Studio提供的性能分析工具,如Profiler,检查应用程序的CPU、内存和网络使用情况,并确定潜在的性能瓶颈。
- 优化代码和资源:优化耗时操作,避免不必要的内存分配,压缩和优化图像资源,减少网络请求等。
- 异步处理:使用异步任务、线程或协程来处理耗时操作,确保主线程不会被阻塞。
- 内存管理:确保及时释放不再使用的对象和资源,
- 后台进程被杀问题
- 使用前台服务:将关键的后台任务封装在前台服务中,这样系统更有可能保留该服务在后台运行的优先级。通过调用**
startForeground()**方法将服务设置为前台服务,并在通知栏显示一个可见通知来提高其重要性。 - 使用JobScheduler或WorkManager:使用JobScheduler或WorkManager来调度后台任务,这些API能够根据设备的资源情况和用户行为智能地管理任务的执行时间。它们可以根据系统条件进行灵活的调度,以便在系统资源可用时执行任务。
- 使用JobIntentService:如果您需要在后台执行较短的任务,可以使用JobIntentService。它是一个便捷的抽象类,将任务排队为Intent,并自动处理适当的后台线程。
- 合理管理内存:确保在不需要时及时释放资源、关闭数据库连接、取消不必要的监听器和回调。避免内存泄漏和资源浪费。
- 最小化后台任务:尽量减少后台任务的数量和复杂性。只在必要时才进行后台处理,并使用适当的条件和策略来触发任务。
- 使用分时处理:对于长时间运行的任务,可以考虑将其分解为较小的子任务,并使用Handler或定时器进行分时处理,以避免在一次性处理过多数据时消耗大量资源。
- 优化应用程序:通过优化应用程序的性能、减少内存占用和优化算法等方式,减少应用程序对系统资源的需求。
- 使用前台服务:将关键的后台任务封装在前台服务中,这样系统更有可能保留该服务在后台运行的优先级。通过调用**
- 性能优化
- 电量优化
- 测试
- 注册ACTION_BATTERY_CHANGED广播 / Battery Historian
- 优化
- cpu、wakelock、数据传输(流量和wifi)、wifi运行、gps、other sensor
- 数据压缩、选择合适的传输方式、无网络状态避免请求
- 选择合适的LocationProvider / 及时注销定位监听
- WakeLock谨慎使用,传感器的使用 / JobScheduer 优化请求
- 测试
- 网络优化
- 测试
- AS / Fiddler / Stetho
- 优化
- GZip优化 / IP直连HttpDns
- 图片下载使用webp ,缩略图 / 图片上传分片上传,根据网络类型动态修改分片大小,分片失败重传
- 合并请求,比如日志 / 网络缓存
- 测试
- 流畅度优化
- Overdraw优化:减少嵌套 / 合理的界面设计 / Fragment 常驻内存,频繁GC / 大图常驻内存,GC
- ANR分类 : KeyDispatchTimeout 5s/ ServiceTimeout 10s/ BroadcastTimeout 10s
- Service创建发送延时消息,未在指定消息内完成发生ANR,否则移除掉消息,不会有ANR
- 异步操作 / 优化布局
- 启动优化
- 主题设置,设置 windowBackground
- 异步延迟初始化组件
- 内存优化
- LruCache
- Least Recently Used:LinkedHashMap强引用方式缓存对象
- JVM内存模型
- JMM
- 程序计数器:用来在多线程环境下恢复切换之前执行的位置;
- JVM栈:存放的是栈帧,执行一个方法就有一个栈帧入栈,执行完成后便出栈。包含局部变量表、操作数栈、方法返回地址等;
- 本地方法栈:与JVM栈类似,但是是为本地方法服务的;
- 堆:存放对象和数组的,被所有线程共享,GC的服务区域;
- 方法区:共享区域,存放常量池、静态变量、常量、类信息,过去版本也被称为永久代;
- 引用计算 / 可达性分析
- 引用类型
- 软引用:系统内存不足时,会被gc回收
- 弱引用:随时被gc回收
- GC
- 算法
- 复制算法:将存活对象复制到另一半,对已使用的内存一次清理掉
- 标记整理:将存活对象向一端移动,清理掉边界外的内存
- 标记清除:效率不高,产生不连续的内存碎片
- 分代收集算法
- 分为新生代(8:1:1)和老年代
- 新生代采用复制算法,多次GC将存活对象转移到老年代,进行复制转移,存活对象少,回收对象多。
- 老年代采用标记整理算法,大对象,存活率高的对象。
- 算法
- JMM
- 内存优化
- OOM:onTrimMemory根据不同level进行处理
- MemoryChurn:大量的对象在新生代被创建又在短时间内马上被释放
- MemoryLeak:无法被gc但是又需要被回收的对象
- 建议:
- 优先使用IntentService,完成任务后会自动停止
- 使用优化后的集合,SparseArray…
- 优先使用静态对象,而不是枚举
- 避免在for循环创建临时对象
- Bitmap用完及时释放
- LruCache
- 电量优化
- 异步处理
- 网络请求异常问题
- 崩溃问题
- 界面显示问题
- 电量消耗问题
- 编译错误或应用崩溃
- Android开发中解决过的性能问题和思路
- Android开发中解决性能问题的思路包括:
- 优化布局:选择最适合的布局类型,避免不必要的嵌套布局和视图。
- 使用合适的图片格式:选择适当的图片格式,并对大小进行压缩。
- 避免频繁的页面跳转:使用Fragment代替频繁的页面跳转,提高用户体验。
- 使用缓存:使用缓存技术来避免重复加载数据。
- 避免内存泄漏:定期监测内存使用情况,并及时释放不再需要的内存。
- 优化网络请求:对网络请求进行优化,减少不必要的请求和数据传输。
- 使用内存优化技巧:例如使用lruCache、WeakReference等内存优化技巧来减少内存使用。
- Serializeable Parceable 区别
- Serializable在序列化的时候会产生大量的临时变量,从而引起频繁的GC,使用了反射,序列化的过程较慢,serialVersionUID写入文件,反序列化来校验,transient和类变量不参与序列化,JDK提供
- Parcelable将一个完整的对象进行分解,而分解后的每一部分都是Intent所支持的数据类型,这样也就实现传递对象的功能了。不能使用在要将数据存储在磁盘上的情况,因为Parcelable不能很好的保证数据的持续性在外界有变化的情况下,SDK提供,可在Binder自由传输
- Activity有四种启动模式,它们是:
- standard 标准模式,每次都新建一个实例对象
- singleTop 如果在任务栈顶发现了相同的实例则重用,否则新建并压入栈顶
- singleTask 如果在任务栈中发现了相同的实例,将其上面的任务终止并移除,重用该实例。否则新建实例并入栈
- singleInstance 允许不同应用,进程线程等共用一个实例,无论从何应用调用该实例都重用
- Service的两种启动模式
- Service 有两种启动方式,分别是 “启动(Start)” 和 “绑定(Bind)”。
- 启动服务(Start Service)
- 这种方式适用于需要在后台执行某些操作且不需要与用户交互的情况。启动服务后,它会在后台运行,直到明确停止它为止。
- 绑定服务(Bind Service)
- 绑定服务主要用于服务与客户端(如活动、另一个服务等)之间的交互。客户端可以与服务绑定并进行通信。绑定服务的流程如下:
- Activity 生命周期超时设定
- onPause是500毫秒
- onStop和onDestory是10秒
- Android View和Viewgroup的onlayout方法的作用是什么
- View的onLayout方法: 在普通的View中通常是空实现,不负责布局其他视图。
- ViewGroup的onLayout方法: 是布局子视图的核心,负责遍历所有子视图,并根据计算结果确定它们的位置和尺寸。
- requestDisallowInterceptTouchEvent 的作用
- requestDisallowInterceptTouchEvent 是一个用于控制父视图是否拦截子视图触摸事件的重要工具。在处理复杂的触摸事件分发和嵌套滚动视图时,它能够帮助你确保子视图能够完整地处理用户输入,而不受到父视图的干扰。
- onSaveInstanceState 和 onRestoreInstanceState 的应用总结
- onSaveInstanceState 用于保存用户界面的临时状态。
- onRestoreInstanceState 或 onCreate 中恢复这些状态。
- AIDL in、out、inout 的含义
- in表示数据只能由客户端流向服务端;表现为服务端将会接收到一个那个对象的完整数据,但是客户端的那个对象不会因为服务端对传参的修改而发生变动
- out表示数据只能由服务端流向客户端;表现为服务端将会接收到那个对象的的空对象,但是在服务端对接收到的空对象有任何修改之后客户端将会同步变动
- inout则表示数据可在服务端与客户端之间双向流通;表现为服务端将会接收到客户端传来对象的完整信息,并且客户端将会同步服务端对该对象的任何变动
- kotlin协程的理解
Kotlin 协程(Coroutine)是 Kotlin 语言提供的一种轻量级并发编程方式,它相较于传统的线程更加高效和易用。协程可以被理解为一种“轻量级线程”,但它并不直接映射到底层操作系统线程,而是由 Kotlin 运行时库管理,具有更低的资源开销。
- 挂起(Suspending)
- 协程可以在某些函数执行时“挂起”自身,而不会阻塞线程。
- 例如 delay() 函数可以让协程暂停,但不会影响其他协程或线程的运行。
- 作用域
- 用于管理协程的生命周期,确保协程不会因为外部原因泄漏。
- GlobalScope: 全局作用域,协程会一直运行直到应用退出。
- CoroutineScope: 限定协程的生命周期,适用于结构化并发。
- viewModelScope: Android 开发中与 ViewModel 绑定的作用域。
- lifecycleScope: 绑定到 Android 生命周期的作用域。
- 上下文
- 决定了协程在哪个调度器上运行,例如:
- Dispatchers.Main(主线程)
- Dispatchers.IO(IO 线程池)
- Dispatchers.Default(计算密集型任务)
- Dispatchers.Unconfined(不限制线程)
- 结构化并发
- 通过 launch 或 async 启动子协程,并用 CoroutineScope 管理它们的生命周期,避免协程泄漏。
- 挂起(Suspending)
-
Android内存优化的方式?
- 减少不必要的内存分配
- 使用缓存机制(如Bitmap缓存)
- 使用对象池
- 避免对象的频繁创建和销毁
- 避免内存泄露
- 使用不同的图片格式和压缩策略
- 设置合适的堆大小限制
- 回收无用的资源,如销毁无用的Activity
- 利用低内存设备的特性,如使用 Hardware Rendering
- 对大内存应用进行特殊处理,如大内存优化
-
Android基础
- View
- GestureDetector / Scroller / VelocityTracker
- Fragment
- Dialog
- Window
- RecyclerView
- 自定义View
- 弹性滑动
- scroller + 重写computeScroll + postInvalidate ,scrollTo达到效果
- 滑动冲突处理
- 处理规则:
- 根据滑动距离形成的夹角,距离差,速度差
- 外部拦截
- 重写onInterceptTouchEvent
- 内部拦截
- dispatchTouchEvent + rquestDisallowInterceptTouchEvent
- ACTION_ DOWN事件并不受FLAG_ DISALLOW_ INTERCEPT这个标记位的控制,所以一旦父容器拦截ACTION_DOWN事件,那么所有的事件都无法传递到子元素中去,这样内如拦截就无法起作用了
- 处理规则:
- 弹性滑动
- ViewRootImpl 绘制流程
- invalidate -> ViewRootImpl.performTravesal -> performMesure/Layout/Draw -> onMeasure -> onLayout -> onDraw
- 触摸事件分发流程
- 责任链模式
- Activity -> Window -> View ,如果都不消耗,返回给Activity的onTouchEvent处理
- dispatchEvent -> onInterceptTouchEvent -> onTouchEvent
- requestDisallowInterceptTouchEvent 可以让子View干预父View的事件分发
- Activity
-
启动模式
!https://api2.mubu.com/v3/document_image/68cd09ba-9b75-40e6-9080-8b480ac43323-23124478.jpg
- standard:每次启动会重新创建新的实例,谁启动了这个Activity,这个Activity就在谁的栈;
- singleTop: 栈顶复用模式。该Activity的onNewIntent方法会被回调,onCreate和onStart并不会被调用;
- singleTask:栈内复用模式。只要该Activity在一个栈中存在,都不会重新创建,onNewIntent会被回调。如果不存在,系统会先寻找是否存在需要的栈,如果不存在该栈,就创建一个任务栈,然后把这个Activity放进去;如果存在,就会创建到已经存在的这个栈中;
- singleInstance:此种模式的Activity只能单独存在于一个任务栈。
-
- Webview
- JsBridge
- 重写webviewchromclient的onJsPromt方法
- H5->通过某种方式触发一个url->Native捕获到url,进行分析->原生做处理->Native调用H5的JSBridge对象传递回调
- JsBridge
- Service
- IntentService
- HandlerThread与Service的组合,内部工作线程和调度依赖于HandlerThread
- Service加持,优先级提升
- IntentService
- HandlerThread
- 带looper的线程,串行执行,没有并发带来的问题
- 串行场景,在构造方法指定优先级
- View
-
TCP的三次握手
- 第一次握手:客户端发送syn包(syn=j)到服务器,并进入SYN_SEND状态,等待服务器确认;
- 第二次握手:服务器收到syn包,必须确认客户的SYN(ack=j+1),同时自己也发送一个SYN包(syn=k),即SYN+ACK包,此时服务器进入SYN_RECV状态;
- 第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED状态,完成三次握手。
-
dart 语言简介
- google 公司开发,目的克服 JavaScript 语言的缺点
- 同时支持 JIT(Just In Time 即时编译) 和 AOT (Ahead of Time 运行前编译)。
- 垃圾回收,则是采用了多生代算法。新生代在回收内存时采用“半空间”机制,触发垃圾回收时,Dart 会将当前半空间中的“活跃”对象拷贝到备用空间,然后整体释放当前空间的所有内存。回收过程中,Dart 只需要操作少量的“活跃”对象,没有引用的大量“死亡”对象则被忽略,这样的回收机制很适合 Flutter 框架中大量 Widget 销毁重建的场景。
- 单线程模型
- Isolate (隔离区)
- Isolate 之间不共享内存,通过事件循环(Event Looper)在事件队列(Event Queue)上传递消息通信
- Isolate (隔离区)
- 为什么用 dart ?
- Google 说:Dart 语言开发组就在隔壁…………
- 同时支持 JIT 和 AOT。开发期使用 JIT 实现热重载,缩短开发周期。
- 有许多优秀编程语言的特性
- 避免了抢占式调度和共享内存,不存在资源竞争和状态同步的问题。
-
基础知识
-
列举常见的设计模式以及特点
- 设计模式是软件开发中解决常见问题的可复用解决方案。它们为软件开发提供了标准化的做法,有助于提高代码的可维护性、可扩展性和可读性。以下是一些常见的设计模式及其特点:
- 单例模式
- 确保一个类只有一个实例,并提供一个全局访问点。
- 常用于需要控制资源访问的场景,如线程池、缓存、日志记录器等。
- 工厂模式
- 提供一个创建对象的接口,但由工厂类决定实例化哪一个类。
- 当你不想暴露具体的类,并希望由工厂方法来创建对象时。
- 建造者模式
- 将对象的构建过程与其表示分离,使得同样的构建过程可以创建不同的表示。
- 适用于创建复杂对象时,需要将对象的创建过程分步进行的情况。
- 原型模式
- 通过复制现有对象来创建新的对象,而不是通过实例化。
- 提供对象的快速复制,适用于需要大量相似对象的场景。
- 适配器模式
- 将一个类的接口转换成客户希望的另一个接口,使原本由于接口不兼容而无法一起工作的类可以协同工作。
- 分为类适配器(通过继承)和对象适配器(通过组合)。
- 装饰器模式
- 通过将对象放入一个包含行为的特殊封装对象中,实现动态地为对象增加行为或职责。
- 适用于不改变原有对象的情况下,为对象添加新的功能。
- 代理模式
- 为其他对象提供一种代理,以控制对这个对象的访问。
- 常用于远程代理、虚拟代理、保护代理等场景。
- 观察者模式
- 定义对象间的一对多依赖关系,当一个对象的状态发生变化时,所有依赖它的对象都会收到通知并自动更新。
- 适用于事件驱动系统,如GUI事件处理,或当某个对象的改变需要影响其他对象时。
-
集合框架
- String/StringBuilder/StringBuffer
- final 修饰不可变
- StringBuilder和StringBuffer都是可变字符串,前者线程不安全,后者线程安全。StringBuilder和StringBuffer的大部分方法均调用父类AbstractStringBuilder的实现。其扩容机制首先是把容量变为原来容量的2倍加2。最大容量是Integer.MAX_VALUE,也就是0x7fffffff。StringBuilder和StringBuffer的默认容量都是16,最好预先估计好字符串的大小避免扩容带来的时间消耗。
- ArrayList
- 数组实现,默认大小为10,不是同步的,查询速度快,增加删除慢
- LinkedList
- 双向循环链表实现,增加删除快,查询慢
- HashMap
- 基于哈希数组,初始容量16,加载因子0.75
- 链表位桶+链表/红黑树,链表法解决冲突
- 某个位桶得链表长度达到8的时候,转化为红黑树(避免链表过长&碰撞)
- 多线程环境下可以使用 ConcurrentHashMap
- LinkedHashMap
- 双向循环链表
- 非线程安全
- concurrenthashmap
- 锁分离、CAS(compare and swap)和Synchronized
- 只要hash不冲突,就不会出现并发获得锁的情况。它首先使用无锁操作CAS插入头结点,如果插入失败,说明已经有别的线程插入头结点了,再次循环进行操作。如果头结点已经存在,则通过synchronized获得头结点锁,进行后续的操作
- CAS
- Compare And Swap
- 基于乐观锁的实现
- 多个线程同时更新一个变量时,只有一个线程会更新成功,其他线程都会失败,但不会挂起
- String/StringBuilder/StringBuffer
-
数据结构
- 链表/队列/栈
- 排序
- 快速排序
- 搜索
- 二分查找
- DFS
- 从一个顶点V0开始,沿着一条路一直走到底,如果发现不能到达目标解,那就返回到上一个节点,然后从另一条路开始走到底,找到解,不是最优
- 给定初始状态跟目标状态,要求判断从初始状态到目标状态是否有解
- BFS
- 从一个顶点V0开始,辐射状地优先遍历其周围较广的区域
- 数据量太大,很难用一个大数组来记录时,采用hash是最好的做法
- 给定初始状态跟目标状态,要求从初始状态到目标状态的最短路径
- 子节点数较多,内存消耗多
-
网络基础
- tcp
- 面向连接的协议,提供稳定的双向通信功能
- tcp 3次连接握手流程
- SYN,SEQ / ACK = SEQ + 1, SEQ / ACK = SEQ + 1
- tcp 4次连接断开流程
- FIN / ACK / FIN / ACK
- udp
- 无连接的,提供不稳定的单向通信功能
- tcp
-
-
插件化
- 65536 问题
- dex 文件格式限制
- 缓冲区大小限制
- MultiDex
- 校验提取 dex -> apk解压dex,写zip文件 -> 动态装载zip -> 运行
- 加载 class 时会使用 DexPathList 的findClass方法,其中对 dexElements 遍历,其中每个元素对应一个 DexFile,正在加载是 DexFile 实现,反射该数组将其他dex加载进来
- 缺点:1. INSTALL_FAILED_DEXOPT ,dexopt时缓冲区太小 2.ANR,提取加载工作在主线程
- 解决:配置独立进程的Actvity去执行MultiDex.install,通过SP文件读写来标识是否完成dexopt,前台Application进入死循环等待完成,需要注意到时候多进程 application 会被实例化多次,需要处理。 / ART无需MultiDex / SecondaryDex的优化尚未完成或者没有被加入导致CNFE/ 非主进程不会ANR
- 类加载
- 将.class文件的二进制流加载到内存中的过程,进行验证、准备、解析、初始化,将类信息、常量、静态变量等存入方法区;
- 变量类型:
- 本地变量(局部):在方法体、构造体内定义的变量,结束时就被摧毁;
- 静态变量(类变量,+final就是全局常量):static关键字;
- 实例变量:在类中,不在方法中,在类被装载时被实例化;
- 常量:方法区的常量池;
- 双亲委托机制
- 优先去父类加载器查找,如果有直接返回,否则子类继续查找
- 维护自身类的安全,保证每个类只会被加载一次
- 加载方式
- JVM初始化
- Class.forName 加载
- ClassLoader.loadClass 加载
- 类加载流程
- BootstrapClassLoader(核心类加载) -> ExtendedClassLoader(扩展类加载) ->AppClassLoader(应用类加载)
- 加载、验证、准备、解析、初始化
- 代码动态加载
- DexClassLoader,双亲委托机制
- DexElement 放在第一个位置
- 加载类的标识 ClassLoader id +PackageName+ClassName / ClassLoader隔离,让冲突包的parentClassLoader为同一个并且加载过公共包
- 资源动态加载
- 动态换肤
- LayoutInflatorFactory 标识需要换肤的控件,通过ViewId得到entry name/type ,去皮肤包匹配相应的View
- AssetManager隐藏方法addAssets来构建新的Resource对象
- 动态换肤
- 65536 问题
-
音视频
- 视频编辑
- https://mp.weixin.qq.com/s/XMU0tfK942uaVOK4tWeYMQ
- MediaExtractor获取音视频流 -> 创建Decoder ->EGL创建texureId -> 创建SurfaceTexture -> 作为Surface的构造参数对Decoder进行 configure EGLSurface -> 解码 -> 通知OpenGL渲染 -> 编码 -> …
- 创建Encoder -> 通过Encoder创建OutputSurface -> AV流编码 -> MediaMuxer 混合
- Projection
- MediaCodec
- inByteBuffer -> outByteBuffer
- MediaMuxer
- MediaExtractor
- MediaFormat
- 视频编辑
-
多进程/多线程
- 跨进程
- 背景
- 进程和线程的区别:线程是CPU调度的最小单元,同时线程是一种有限的系统资源。而进程一般是指一个执行单元,在PC和移动设备上指一个程序或者一个应用
- 需要更大内存或者需要向其他应用获取数据
- 问题
- 静态成员单例模式失效 / 线程同步失效 / SP可靠性下降 / App多次创建
- 方式
- Bundle / 文件 / Messenger / AIDL / ContentProvider / Socket
- RemoteCallbackList
- 用于删除IPCListener
- 客户端对象注册后,服务端会反序列化新对象保存,在解除注册时无法找到,而RemoteCallbackList内部使用底层Binder,这个Binder在客户端中和服务端中反序列化后的是同一个对象。
- Binder连接池
- Binder
- 高效、安全
- IPC中Stub充当S,Proxy充当C,还有linkToDeath和unlinkToDeath也很重要
- IPC原理:每个Android进程都拥有用户空间和内核空间,内核空间是共享的,用来进行底层通信,用户空间与内核空间通过ioctl来交互;
- Binder原理:采用C/S架构,包含C、S、ServiceManager和Binder驱动,其中Binder驱动在内核空间,其他都在用户空间,Server向ServiceManager注册好服务后,Client向SM请求服务,请求成功后便可以拿到服务进行操作;
- 流程
- 获取server的代理接口,对server进行调用,代理接口中的参数会被打包成Parcel对象,代理接口将Parcel发送给binder驱动,server从binder驱动解包Parcel对象,返回结果,由于整个过程同步,因此不应该在主线程中请求。
- 背景
- Messenger
- Handler作为参数构造
- 多线程
- AsyncTask
- 1.6 ~ 3.0 是串行,其余是并行
- Executor 内部维护请求队列,当有新的请求时,会入队列,执行完后,执行scheduleNext,出队列,使用Handler进行线程切换,doInBackground 使用 Thread.setBackgroundPriority切换优先级;
- 线程
- Thread
- sleep 进入 Blocked,让出CPU,不释放对象锁
- join 进入 Blocked
- yield 进入 Runnable
- Callable / Runnable
- 增加资源消耗,增加上下文开销,增加编码复杂度
- Thread
- 并发框架
- 原子性、有序性、可见性
- ExecutorService
- newCachedThreadPool
- 适合并发执行大量短期小任务,适合负载轻的服务(IO密集型)
- newFixedThreadPool
- 适合负载重的服务,限制线程数量(CPU密集型)
- newScheduledThreadPool
- 多个线程执行周期性的任务
- newSingleThreadExecutorPool
- 串行执行任务
- newCachedThreadPool
- Volatile
- 只能保证可见性和有序性,无法保证原子性
- AtomicInteger / AtomicBoolean
- 使用volatile修饰value保证可见性,以及CPU级别的基于乐观锁CAS机制保证了原子性
- 使用 Unsafe 机制实现
- CountDownLatch / CyclicBarrier
- 在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待
- CyclicBarrier实现线程互相等待
- AsyncTask
- 跨进程
以下是 Java 实现的 LRU Cache 示例代码
import java.util.*;
public class LRUCache<K, V> {
private final int capacity;
private final Map<K, Node<K, V>> cache;
private Node<K, V> head, tail;
private static class Node<K, V> {
final K key;
V value;
Node<K, V> prev, next;
Node(K key, V value) {
this.key = key;
this.value = value;
}
}
public LRUCache(int capacity) {
this.capacity = capacity;
this.cache = new HashMap<>();
}
public V get(K key) {
Node<K, V> node = cache.get(key);
if (node == null) {
return null;
}
moveToHead(node);
return node.value;
}
public void put(K key, V value) {
Node<K, V> node = cache.get(key);
if (node == null) {
node = new Node<>(key, value);
cache.put(key, node);
addNode(node);
} else {
node.value = value;
moveToHead(node);
}
if (cache.size() > capacity) {
removeTail();
}
}
private void addNode(Node<K, V> node) {
if (head == null) {
head = tail = node;
} else {
node.next = head;
head.prev = node;
head = node;
}
}
private void removeNode(Node<K, V> node) {
if (node == head) {
head = head.next;
if (head != null) {
head.prev = null;
}
} else if (node == tail) {
tail = tail.prev;
if (tail != null) {
tail.next = null;
}
} else {
node.prev.next = node.next;
node.next.prev = node.prev;
}
}
private void moveToHead(Node<K, V> node) {
removeNode(node);
addNode(node);
}
private void removeTail() {
if (tail != null) {
cache.remove(tail.key);
removeNode(tail);
}
}
}
Java自定义实现一个线程池
import java.util.*;
import java.util.concurrent.*;
public class MyThreadPool {
private int poolSize;
private List<Worker> workers;
private BlockingQueue<MyTask> taskQueue;
public MyThreadPool(int poolSize, BlockingQueue<MyTask> taskQueue) {
this.poolSize = poolSize;
this.taskQueue = taskQueue;
workers = new ArrayList<>();
for (int i = 0; i < poolSize; i++) {
Worker worker = new Worker();
workers.add(worker);
worker.start();
}
}
public void submit(Runnable task) {
MyTask myTask = new MyTask(task);
taskQueue.offer(myTask);
}
public void shutdown() {
for (Worker worker : workers) {
worker.interrupt();
}
}
private class Worker extends Thread {
@Override
public void run() {
while (true) {
try {
MyTask task = taskQueue.take();
task.run();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
private class MyTask implements Runnable {
private Runnable task;
public MyTask(Runnable task) {
this.task = task;
}
@Override
public void run() {
task.run();
}
}
}
源码理解
-
LeakCanary
-
EventBus
- EventBus通过反射、线程调度和事件队列等机制实现了一个高效的发布/订阅系统。它的核心在于:
- 使用@Subscribe注解标注订阅方法,并通过反射或预生成索引进行查找。
- 使用线程模式ThreadMode控制订阅方法的执行线程。
- 使用事件队列和线程调度确保事件可以在不同的线程环境中进行分发。
- 这些机制使得EventBus在Android和Java开发中成为一个非常实用且高效的组件,适合应用程序内部的松耦合事件驱动通信。
-
RxJava
- 创建 Observable:通过工厂方法(如 just、fromArray 等)创建一个 Observable 实例。
- 订阅 Observer:使用 subscribe 方法将 Observer 订阅到 Observable,此时会调用 subscribeActual 方法。
- 数据发射与接收:Observable 发射数据,并通过操作符流向 Observer。Observer 根据 onNext、onError、 - onComplete 的实现来处理接收到的数据。
- 线程调度:如果使用了 subscribeOn 和 observeOn,Scheduler 会在合适的线程上执行相应的操作。
- RxJava 通过 Observable、Observer、Scheduler 和 Operator 实现了一个强大而灵活的异步编程模型。其源码设计体现了极高的抽象性和扩展性,通过细致的分层与组合,使得开发者可以轻松地应对复杂的异步任务和数据流处理。理解 RxJava 的源码不仅可以帮助开发者更好地使用它,也能为学习其他响应式编程库提供参考。
-
Retrofit
- Retrofit:主要配置类,用于创建网络请求的接口实例。
- CallAdapter:用于将网络请求转换为不同的数据类型,比如Call,RxJava的Observable等。
- Converter:用于将请求体或响应体转换为指定的数据格式,比如GsonConverter用于JSON与对象的转换。
- Call:代表一次HTTP请求。
- OkHttpCall:Call的具体实现类,底层基于OkHttp来执行网络请求。
-
OkHttp
- OkHttpClient是OkHttp的核心类,它负责管理所有的HTTP请求和响应。它通过建造者模式(Builder Pattern)创建,允许开发者定制配置,例如超时时间、缓存、拦截器等。
- Dispatcher:负责管理请求队列和线程池。
- readyAsyncCalls:等待执行的异步请求队列。
- runningAsyncCalls:正在执行的异步请求队列。
- ConnectionPool:管理HTTP连接复用。
- ConnectionPool管理连接复用。HTTP/1.1默认支持连接复用,HTTP/2进一步优化了这一点。
- ConnectionPool通过维护一个连接池,减少了重复的TCP握手和TLS握手,提高了性能。
- Interceptor:处理请求、响应的拦截器链。
- 应用拦截器(Application Interceptors):处理应用层面的逻辑,通常用于添加全局请求头、参数等。
- 网络拦截器(Network Interceptors):处理网络层面的逻辑,可以在请求发出前、响应返回后进行操作。
-
AMS
- ActivityManagerService:AMS的核心实现类,负责处理系统的应用管理任务。它通过Binder机制接收客户端请求,并对请求进行处理。
- ActivityStackSupervisor:管理所有的ActivityStack,负责调度和协调Activity的状态转换。
- ActivityRecord:表示一个具体的Activity实例,包含Activity的所有状态信息。
- AMS工作流程
- 启动Activity:当应用请求启动一个新的Activity时,AMS接收请求并通过ActivityStackSupervisor找到合适的ActivityStack,然后调用startActivityLocked方法启动Activity。
- 管理Activity生命周期:AMS根据系统状态(如内存不足)或用户操作(如按下Home键),通过状态转换方法(如moveTaskToBack)来管理Activity的生命周期。
- 进程管理:AMS监控应用进程的状态,根据优先级决定哪些进程可以继续运行,哪些需要被杀死以释放资源。
-
WMS
- WindowManagerService:WMS的核心实现类,负责管理系统中的所有窗口。
- WindowState:表示一个具体的窗口实例,包含窗口的属性、状态等信息。
- DisplayContent:表示一个显示内容区域,通常对应一个物理显示器或虚拟显示器。
- WMS工作流程
- 窗口的添加与删除:当应用通过addView方法请求添加一个新的窗口时,WMS接收请求并创建一个WindowState对象,管理该窗口的生命周期。WMS还负责处理窗口的销毁和资源释放。
- 窗口的布局与更新:每当窗口的大小或位置发生变化时,WMS会重新计算窗口的布局,并将布局变化应用到窗口上。这个过程通常涉及到performLayoutAndPlaceSurfaces方法。
- 窗口层级管理:WMS根据窗口的类型和应用状态,调整窗口的Z轴顺序,确保界面显示的正确性。例如,系统级窗口(如状态栏)始终在普通应用窗口之上。
- 输入事件的分发:WMS接收系统的输入事件,并根据窗口的层级和焦点状态,将事件分发到正确的窗口。例如,触摸事件会分发到当前焦点窗口。
-
Kotlin:https://developer.aliyun.com/article/1334364
-
职业规划:https://www.zhihu.com/question/587708885/answer/2973458013
-
Flutter性能优化:https://juejin.cn/post/7066954522655981581
-
页面跳转小结:https://www.jianshu.com/p/380aa4725adb
-
深入理解BuildContext:https://juejin.cn/post/6844903777565147150
-
Flutter路由管理:https://juejin.cn/post/6844904031538642952