jetpack

Google推出的现代化Android开发组件库集合,统一兼容Android各个版本,解决碎片化,内存泄漏,生命周期管理,代码冗余等问题

统一开发范式 MVVM,支持Kotlin协程,Flow,Compose现代开发方式

四大分类:

架构组件

1、viewmodel

2、LiveData/StateFlow/SharedFlow

3、Lifecycle

4、Room数据库,SQLite封装ORM框架,替代原生SQLite

5、DataStore:轻量本地存储,替代SharedPreferences

6、Paging:分页式加载框架,处理海量列表,可以自动分页、预加载、加载状态,适配RecycleView/Compose LazyList

7、Navigation导航:统一页面跳转管理,替代Intent跳转

8、WorkManager:后台任务调度

UI组件

1、Compose

2、AppCompat

3、Material Components

4、COnstraintLayout

5、Fragment

6、View Binding:自动生成控件绑定类,替代findviewbyid / Data Binding:支持xml直接绑定数据,表达式,MVVM双向绑定

7、WindowManager:适配折叠屏,大屏,多窗口设备

基础工具组件

1、Core KTX:给原生API扩展kotlin语法糖

2、Annotations:注解

3、Multidex:方法数超过65536的分包方案

4、Startup:应用初始化框架,替代ContentProvider启动,优化冷启动速度

行为组件-系统权限,媒体,安全

1、Permissions:简化动态权限申请,封装授权回调,判断权限状态

2、Media3:音视频播放器

3、Security:加密存储密钥,文件加密,适配不同系统加密方案

4、Biometric:生物识别

5、SplashScreen:统一标准启动页,替代自定义闪屏,优化冷启动体验

核心优势:

1、版本兼容,全部组件通过androidx.*包名,最低可兼容API14,不用写大量版本判断

2、生命周期安全:Lifecycle体系杜绝大量的内存泄露:组件可以自动感知页面生命周期状态,页面销毁时自动取消订阅

四大组件

1、Activity:

负责跟用户交互的界面组件,一个activity通常对应一个屏幕页面,核心是生命周期的管理,oncreate初始化,onstart可见,onresume可交互,onpause部分可见,onstop不可见,ondestroy页面销毁,他还有四种启动模式-standard,singletop,singtask,singleinstance,决定了activity如何实例化和压栈的

2、Service

用于执行后台长时间运行的任务,启动方式分为两种

startService:启动后一直运行,直到主动调用停止

bindService:提供客户端-服务器模式,当所有绑定者解绑后服务自动销毁,需要注意的是,Service默认再主线程,耗时操作需要另外开子线程,否则可能ANR

3、BroadcastReceiver

广播接收器,用来接收系统或应用发出的广播,如开机完成,网络变化等,注册方式有静态注册 Manifest中声明和动态注册 (代码中registerReceiver)。动态注册需要在合适的生命周期中去注册和反注册,比如onresume和onpause,防止内存泄漏或者崩溃。onReceive方法运行在主线程,不能耗时操作,也不可以直接弹窗。通常用于启动service或发送通知,进程内通信现在更倾向于LiveData或Flow来代替

4、ContentProvider

为应用间数据共享提供标准接口,底层可以是SQLite,文件等。外部应用通过ContentResolver 配合URI进行CRUD操作,系统通过他来管理联系人,媒体库等,需要在manifest中注册,可以精细控制读写能力,保证数据安全。

这四个组件大多用Intent交互

四大启动模式

1、standard 标准模式

默认模式

2、singTop 栈顶复用模式

系统会判断要启动的activity是否在目标栈的栈顶,如果在就不建立新实例,而是调用他的onnewIntent方法复用现有实例;如果不在栈顶就新建,适合那些被重复打开,又不希望栈顶出现相同连续页面的场景

3、singleTask 栈内复用模式

整个栈内只存在一个实例,启动时,会先检测目标任务栈里是否已经有该activity实例了,如果没有就新建并放入栈中;如果有,会把该实例上面的所有activity全部出栈,让这个实例重新置于栈顶,并调用onNewIntent,通常用来做主页面

4、singleInstance 全局单例模式

是singleTask加强版,不仅栈内唯一,而且这个activity独占一个任务栈,不允许有其他activity,无论从哪个任务栈启动,它都会在一个独立的任务栈中。场景为:来电界面,闹钟提醒。

这些启动方式设置除了用manifest中硬编码设置launchMode外,还可以用Intent的FLAG_ACTIVITY_SINGLE_TOP,FLAG_ACTIVITY_NEW_TASK等来动态覆盖行为。

谈谈对Handler的了解和看法

handler的话是Android中非常重要的消息机制,主要用于线程间的通信,他主要是靠四个角色来一起协同工作的:

Handler:发送和接受消息入口,sendMessage/post最终都走到enqueueMessage将消息放入队列中去

MessageQueue:存储消息,单链表实现的优先级队列

Looper:死循环从MessageQueue中去消息,然后发给对应的Handler的dispatchMessage

Message:消息载体,内部可以用obtain回收复用,避免频繁创建对象

运行流程:

1、线程创建Looper:调用Looper.prepare()创建本线程唯一Looper实例,同时会初始化一个MessageQueue,然后调用Looper.loop()开始无限循环

2、handler发送消息:创建Handler时会绑定当前线程的Looper,Handler发出的消息携带targe会指向自身,进入Looper对应的MessageQueue

3、消息分发:Looper的loop()中,queue.next()取出下一条到时的消息,然后执行msg.target.dispatchMessage(msg),最终回调到Handler的handleMessage或Runnable

延时消息与阻塞唤醒

MessageQueue中会按照when字段升序排列,next取消息时如果队头还没到时间,就计算等待时间,调用nativePollOnce进入epoll等待,释放CPU,当新消息插入对头或时间更早时,通过nativeWake()唤醒等待,所以主线程的Looper.loop()不会导致CPU空转,无消息时线程会挂起

主线程与子线程:

主线程在应用启动时已调用prepareMainLooper和loop,所有可以直接创建Handler更新ui

子线程必须手动调用Looper.prepare()和loop(),或者使用HandlerThread

内存泄漏与优化

调用handler.removeCallbacksAndMessages()清空所有待处理消息,现在更推荐用Lifecycle组件配合LiveData

高级特性

同步屏障:通过postSyncBarrier插入屏障消息,优先处理异步消息,如view的绘制调度

IdleHandler:在MessageQueue空闲时回调,可做低优先级任务,如GC触发

HandlerThread:自带Looper的子线程,需注意及时quit释放

Binder机制 进程间的通信

选用binder的理由:

1、安全性:UID/PID可信传递,内核可以识别调用方身份,便于权限校验,而Socket无法确认对方真实身份。

2、高效性:一次数据拷贝(通过copy_from_user到内核空间,再映射到目标进程,本质是拷贝一次),比管道,Socket两次拷贝快,而共享内存缺乏同步机制且安全控制弱

3、面向对象:基于C++接口,服务端暴露IInterface,客户端像调用本地方法一样调用远程服务,开发体验好

4、引用计数与死亡通知:自动管理服务生命周期,死亡时能通知客户端,避免悬空指针

核心原理:

基于Client-Server-驱动三层架构

注册服务:ServiceManager再binder驱动中注册为句柄0,系统服务如AMS,WMS启动时,通过addService将服务名和Binder引用注册到ServiceManager。

获取服务:客户端通过getService来查询服务,得到服务Binder的句柄,封装成远程代理Proxy,客户端拿到的是本地代理对象

调用服务:客户端调用Proxy的方法时,将参数打包到Parcel,通过transact将数据发给Binder驱动。驱动找到对应的服务端Binder实体,唤醒服务端线程处理。服务端收到后解析参数,执行真正方法,将结果写回Parcel,返回给客户端,是同步的,客户端线程会被挂起等待返回,

数据传递:大件数据利用ashmem匿名共享内存传递,Bitmap的writeToParcel内部用了Blob和FileDescriptor来传递共享内存fd,因此能够零拷贝传递大图。

Binder线程池与同步:

每个进程再初始化Binder时会启动一个Binder线程池,默认最大16个线程,服务端需要保证线程安全

隐式意图

让我们在不明确指定类名的情况下,通过描述要做什么来启动其他应用的activity。

1、隐式意图的匹配机制

依靠action,category,data三要素与IntentFilter进行匹配

2、实战场景

打开网页,分享内容,调用系统拍照,自定义协议拉起

3、安全陷阱和防护

4、包可见性限制

JAVA锁机制

1、悲观锁 VS 乐观锁

这是最高维度分类,核心是 是否假设竞争必然发生

悲观锁:认为每次读写都会冲突,必须先加锁再操作,synchronized、ReentrantLock都是悲观锁。适合写多,竞争激烈的场景。

乐观锁:假设冲突很少,先直接操作,提交时再检查是否被修改。通常通过CAS自旋或版本号实现。适合读多写少场景。如Android中AtomicInteger计数上报,用CAS自旋更新,避免线程挂起唤醒的开销。

2、公平锁VS非公平锁 (获取顺序)

这是ReentrantLock内部的调度策略,对应sync的实现类 FairSync和 NonfairSync。

非公平锁:新线程可直接抢锁,无需排队。可能造成某些线程长时间饥饿

公平锁:按照CLH队列顺序获取,先到先得,降低饥饿概率,但频繁切换导致吞吐量下降。再Android中用的少

3、共享锁VS独占锁(访问权限)

独占锁(写锁):同时只能一个线程持有,synchronized,ReentrantLock的写锁都是这种

共享锁(读锁):允许多个线程同时持有,如ReentrantReadWriteLock的读锁。条件是所有线程都只读,不改。当线程持有读锁时,写线程必须等待所有读锁释放。读写锁适合Android中配置数据的管理:多个界面线程读取,后台线程偶尔更新。

4、可重入锁

同一个线程在外层方法获取锁后,进入内层方法时会自动再次获取同一把锁,不会死锁。synchronized和ReentrantLock都是可重入的,内部维护持有者线程ID和计数器。Android中几乎所有用到锁的场景都默认需要可重入性,因为常常会递归或嵌套调用受保护的代码路径。

5、锁升级与锁降级(偏向锁/轻量级锁/重量级锁)

synchronized底层针对竞争的优化过程,发生在对象的Mark Word中。

偏向锁:无竞争时,锁对象偏向第一次获取他的线程,该线程再次进入只需检查ID,不用CAS,Android的启动优化中,大量单线程初始化的代码如果用了synchronized,会受益于偏向锁。

轻量级锁:当另线程尝试获取偏向锁但失败,偏向锁撤销升级为轻量级锁,线程通过CAS自旋尝试获取。自旋耗CPU但减少挂起开销,适合锁持有时间短的场景

重量级锁:自旋一定次数后失败,膨胀为重量级锁,

锁降级:读写锁中,持有写锁的线程获取读锁,再释放写锁,从写锁降为了读锁。

6、锁粗化:

JIT编译器的优化。若一段代码内频繁加锁,解锁同一个锁对象,JIT会将锁的范围扩大,合并成一次加解锁,减少加锁开销。

synchronized 原理与volatile

synchronized有两种使用形式

第一种:

public void method() {
    synchronized (this) {
        // 临界区
    }
}

编译后,在字节码层面会表现为monitorenter和monitorexit指令。monitorenter在代码块入口,monitorexit在代码块出口和异常表出口各一个,保证异常也能正确释放锁。

第二种:

public synchronized void method() {
    // 临界区
}

方法标志位会被设置为ACC_SYNCHRONIZED, JVM在执行方法调用时,检查这个标志,自动获取对象Monitor,方法返回或异常时自动释。

对象头与Monitor

每个java对象在堆中由三部分组成:对象头,实例数据,对齐填充。synchronized的核心对象就是对象头中的Mark Word。

Mark Word存储了:哈希码,GC分代年龄,锁状态标志,锁指针,偏向线程ID等

volatile原理:

volatile是轻量级的同步机制,只保证可见性和有序性,不保证原子性

1、可见性原理

被volatile修饰的变量在读写时,会通过内存屏障与主内存直接交互:

写volatile变量:JVM会在写操作后插入一条StoreLoad屏障,强制将工作内存中该变量的最新值刷回主内存

读volatile变量:JVM会在读操作前插入LoadLoad,LoadStore屏障,强制从主内存重新读取该变量的最新值,使线程工作内存中的缓存失效。

线程池

核心原理:生产者-消费者模式:

核心参数:

ThreadPoolExecutor(
    int corePoolSize,          // 核心线程数   
    int maximumPoolSize,       // 最大线程数
    long keepAliveTime,        // 非核心线程空闲存活时间
    TimeUnit unit,             // 时间单位
    BlockingQueue<Runnable> workQueue, // 阻塞队列
    ThreadFactory threadFactory, // 线程工厂,可自定义线程名、优先级
    RejectedExecutionHandler handler // 拒绝策略 1、默认直接抛异常 2、默认丢弃新任务,无异常 3、丢弃队列最老未执行任务,再尝试提交新任务 4、让提交任务的主线程执行该任务,限流
)

执行流程:

1、当前运行数 < corePoolSize,新建核心线程执行任务;

2、核心线程已满,尝试将任务放入阻塞队列;

3、队列已满,且当前线程数 < maximumPoolSize,创建非核心线程

4、线程总数达到maximumPoolSize,触发拒绝策略

Executors工具类提供的4中内置线程池(不推荐生产直接用)

1、newFixedThreadPool

核心=最大线程,无非核心线程;

队列LinkedBlockingQueue无界,容易oom

2、newSingleThreadExecutor

单一线程池,顺序执行所有任务,底层同样无界队列

3、newCachedThreadPool

无核心线程,最大线程数无限

4、newScheduledThreadPool

定时周期任务线程池,最大线程无上限

Java常量池

JVM堆中一块独立的内存区域(JDK1.7以后永久移到堆),底层是HashTable,存储字符串字面量,目的复用字符串,减少重复对象创建,节省内存

规则:

1、双引号直接写的字面量String s1 = ‘abc’ 回先去常量池寻找

2、存在直接返回池内引用;不存在,再池中创建对象,再返回引用

gc垃圾回收

目标:自动识别堆中不在被任何有效引用指向的对象;自动回收这部分对象,释放内存

判断垃圾核心标准:可达性分析

1、引用计数法:对象加引用计数,计数0则回收。致命缺陷:循环引用无法回收,内存泄漏

2、可达性分析:从GC Root根对象向下遍历,形成引用链;链上能走到的对象=存活,遍历不到=垃圾

GC Roots常见来源:

正在执行的线程栈中局部变量,方法参数

静态static变量

本地JNI引用

系统类加载器的类对象

存活的Thread,全局Application

Android特有的GC Roots:Application全局单例;ViewRootImpl(当前页面根布局);系统服务持有你的应用对象

Java堆分代模型(分代回收思想)

1、新生代 存放刚创建,生命周期短的对象,分为三块

2、老生代 长期存活对象,大对象,晋升过来的对象

3、元空间 存放类信息,常量,静态变量,不在堆内

四大GC算法

1、标记-清除 Mark-Sweep :容易产生大量内存碎片

2、复制 新生代常用:两块内存,标记存活对象复制到另一块,清空原区域,无碎片,但是需要双倍内存空间

3、标记-整理 老年代常用:标记存活对象,全部向内存一端压缩整理,清除边界外垃圾,无内存碎片,耗时久

4、分代收集:新生代复制,老年代标记整理

Android ART虚拟机GC

Android5.0废弃Dalvik,改用ART,与标准JVM有明显区别:

1、Dalvik GC: 标记清除,频繁GC卡顿,不支持分代;

2、ART采用分代并发GC,大幅降低STW: 新生代复制,老年代标记整理

3、特有机制:

        并发压缩GC:后台空闲时整理内存碎片

        卡片表:快速记录跨代引用,加速可达性扫描

        大对象单独分配,避免Survivor区溢出

四种引用类型(GC回收规则)

1、强引用 GC永不回收

2、软引用:内存不足才回收

3、弱引用:只要触发GC,就会回收

4、虚引用:仅用于监控对象被回收时机,做资源释放监控

类加载器 

负责读取 .class 字节码文件,加载到JVM/ART虚拟机,生成class对象

类生命周期分为5步: 加载-验证-准备-解析-初始化

加载阶段做三件事:

1、通过类全限定名,获取类二进制字节流

2、将字节流转换成方法区运行时的数据结构

3、在堆中生成代表这个类的Class对象,作为访问入口

JVM四大内置类加载器(双亲委派模型主体)

1、Bootstrap ClassLoader 启动类加载器

2、Extension ClassLoader 扩展类加载器

3、Application ClassLoader应用程序类加载器

4、自定义ClassLoader

双亲委派模型
执行流程:

当一个ClassLoader收到加载类请求:

1、先检查这个类是否已经被自己加载过,有则直接返回

2、没加载过,委托父加载器去加载

3、父加载器重复上述逻辑,一直向上委托到Bootstrap

4、Bootstrap找不到该类,逐层退回,由当前加载器自己调用findClass 加载

5、全部层级都找不到,抛出异常

总结:先向上委托,上层加载不了,自己再加载

双亲委派两大目的:

1、安全,防止核心类串改:比如自己手写一个Java.lang.string,自定义加载器无法优先加载,回向上委托Bootstrap,系统加载原生string,避免替换核心类造成安全漏洞。

2、类全局唯一,保证同一个类只加载一次:同一个类+同一个classloader才视为同一个class,不同加载器加载同名类是两个独立的class,转换回异常

如何破环双亲委派:

重写loadclass(),不走委托逻辑,如:热修复,插件化

双亲委托底层破坏场景:

标准双亲是父先加载,插件化/热修复需要自己优先加载,需打破双亲委派

Android类加载器

1、BootClassLoader启动类加载器:对应jvm的Bootstrap,加载Android系统核心类 activity context view jetpack等系统api

2、PathClassLoader应用默认加载器: app代码,第三方依赖默认由他加载,只能读取app安装目录 /data/app/xxx下的dex

3、DexClassLoader动态加载专用:继承BaseDexClassLoader,支持外部存储的dex jar apk; 构造需要传入:dex路径,优化odex缓存目录,native库路径,父加载器;插件化,热修复需要他

BaseDexClassLoader:是PathClassLoader和DexClassLoader共同父类,底层通过dexPathList维护dex数组,读取dex字节码

Android类加载特殊机制:Dex数组 & 类查找顺序

BaseDexClassLoader内部持有DexPathList,里面维护一个Element数组,每个Element对应一个dex文件。 从数组头部往尾部遍历,找到第一个匹配的类直接返回

热修复核心原理:

1、补丁dex插入到DexPathList数组最前面

2、加载类时优先读取补丁中的修复类

3、实现新类覆盖原有bug类     这样没有重写loadclass破坏双亲,只是修改dex优先级

双亲委派在Android中的表现:

Android的ClassLoader依然遵循双亲委派

加载自定义Activity时,先委托父加载器BootClassLoader;Boot找不到,再由PathClassLoader从dex数组查找

类的唯一性判断

一个类在虚拟机中唯一标识由两点组成:

类全限定名+定义它的ClassLoader实例

抛出异常场景:1、同一个类,由不同ClassLoader加载 2、插件化中:宿主和插件各加载一份SDK类,互相强转报错,方案可以统一类加载器,或共享父加载器,只隔离业务类,基础库交给PathClassLoader加载

自定义类加载器实现思路:

1、继承ClassLoader

2、重写findClass,读取Class/dex字节码

3、调用defineClass 将字节码转为class对象

4、如需家婆双亲委派:重写loadclass,不执行父委托逻辑,直接findclass

类加载初始化时机(懒加载)

类加载是延迟懒加载,只有主动引用才会触发完整加载+初始化

1、new创建对象

2、调用类静态方法

3、访问类静态常量

4、反射Class.forName

loadclass和findclass区别

来自顶层抽象类 ClassLoader

loadClass:双亲委派调度器(负责加载流程控制)

核心作用:实现双亲委派模型的完整调度逻辑,加载类的入口

1、findLoadedClass:先查缓存,该类是否已经被当前加载器加载,有就直接返回

2、缓存无,则委托父加载器执行

3、父加载器向上委托,直到BootstrapClassLoader

4、如果所有父加载器都找不到类,才调用自身findclass尝试加载

5、全部失败抛异常

6、resolve参数为true时,执行链接

findClass:真正读取字节码生成Class

子类必须重写这个方法,实现自定义读取逻辑,根据类型,读取字节流,调用defindClass生成class对象。不处理双亲委派,不处理缓存,不向上委托

dcl单例 Double Check Lock 双重检查锁单例

目标:兼顾懒加载,线程安全,高性能

解决:饿汉式浪费内存,同步方法加锁并发性能差的问题

public class Singleton {
    // volatile 禁止指令重排,保证可见性
    private static volatile Singleton instance;

    // 私有构造,防止外部new
    private Singleton() {
        // 防反射破坏单例
        if (instance != null) {
            throw new RuntimeException("禁止反射创建实例");
        }
    }

    public static Singleton getInstance() {
        // 第一次校验:避免每次抢锁
        if (instance == null) {
            synchronized (Singleton.class) {
                // 第二次校验:多个线程阻塞在锁,避免重复创建
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐