Android知识总结
2026-03-27 11:47:37 0 举报AI智能生成
android知识总结
android知识总结
模版推荐
作者其他创作
大纲/内容
NDK
c与c++
数据类型
内存与管理
预处理指令,Typedef别名
结构体与共用体
指针、智能家居指针、方法指针
线程
类
volatile<br>
静态与动态注册
java语言翻译成c++语言
方法签名,java通信
本地引用与全局引用
volatile关键字可以禁止虚拟机重排序
volatile字段的作用只能保证数据的可见性和有序性,即:线程A对volatile count字段执行写操作后,会立即将count值写回到主存中,并通知其他线程count值发生变化,这样count对其他线程就是可见的了,但是volatile并不会保证操作的原子性。<br>
Native开发工具
CPU架构与注意事项
gcc/g++/clang编译器
静态与动态库
交叉编译移植
构建脚本与构建工具
AS构建NDK项目
Linux编程
jni调用流程<br>
创建流程
1. 创建Native c++项目<br>
配置Cmake文件<br>
1、编写声明了native方法的Java类<br>2、javac编译文件<br>3、javah获得包含该方法的C声明头文件(将class中用native声明的函数生成jni规则的函数)<br>4、用本地代码实现.h头文件中的函数<br>5、将本地代码编译成动态库(共享库)<br>6、在Java程序中加载该类库<br>
设计模式
结构型模式
桥接模式
定义:将抽象部分与实现部分分离,使它们可以独立地进行变化<br>
使用场景<br>
代理模式
定义:<br>
为其他对象提供一种代理以控制对这个对象的访问
使用场景
组合模式
定义:组合模式允许客户端统一处理单个对象和组合对象,从而使得客户端无需区分对象的类型,简化了客户端的代码<br>
优点:<br>
应用:自定义View,l里面包含多个其它按钮(TextView、Button、ImageView等)<br>
适配器模式
定义:把一个类的接口变换成客户端所期待的另一种接口,从而使原本因接口不匹配而无法在一起工作的两个类能够在一起工作<br>
适配器模式是为了解决接口不兼容问题
使用场景:<br>
图片编辑器,画笔,中间增加了一个类去衔接两边
享元模式
有重复就复用,没有就直接创建,避免了重复对象的大量创建
应用:消息队列<br>
装饰者模式
定义:装饰模式指的是在不必改变原类文件和使用继承的情况下,动态地扩展一个对象的功能。它是通过创建一个包装对象,也就是装饰来包裹真实的对象<br>
获得被装饰对象。添加新的功能
Context类族
外观模式
定义:要求一个子系统的外部与其内部的通信必须通过一个统一的对象进行。外观模式提供一个高层次的接口,使得系统更易于使用<br>
使用场景:<br>
1.为一个复杂子系统提供一个简单接口
2.当用户需要构建一个层次结构的子系统时,使用外观模式定义子系统中每层的入口点
优点:<br>
1.对客户程序隐藏子系统细节,因而减少了客户对于子系统的耦合,能够拥抱变化
2.外观类对子系统的接口封装,使得系统更易于使用
缺点
1.外观类接口膨胀。由于子系统的接口都有外观类统一对外暴露,使得外观类的API接口较多,在一定程度上增加了用户使用成本。
2.外观类没有遵循开闭原则,当业务出现变更时,可能需要直接修改外观类。
创建型模式
单例模式<br>
定义:保证一个类只有一个实例,并且这个单例类提供一个函数接口让其他类获取到这个唯一的实例<br>
①构造私有。构造函数不对外开放,一般为private。<br>②以静态方法或枚举返回实例。<br>③确保实例只有一个,尤其是在多线程环境。<br>④确保实例在反序列化时不会重新构建对象。
创建方式
1.懒汉式
普通懒汉式
线程不安全
同步懒汉式
线程安全
用的时候才去加载
每个线程都要去访问,效率比较低
2.饿汉式
线程安全,加载时候就初始化,同一个内存
缺点:如果没用过这个实例的话,就容易造成内存的浪费<br>
3.双重检查加锁式DCL(double check lock)线程安全
4.静态内部类方式
5.枚举方式
使用场景
①频繁访问数据库或文件的对象;<br>
②工具类对象;<br>
③创建对象时耗时过多或耗费资源过多,但又经常用到的对象;<br>Android中习惯使用单例的常见类: xxxManager , xxxHelper , xxxUtils 等5.
使用
WindowManager服务引用
可能内存泄漏:<br>
因为单例的静态特性使得单例的生命周期和应用的生命周期一样长, 如果一个对象已经不需要使用了,但是单例对象还持有该对象的引用,那么这个对象就不能被正常回收,因此会导致内存泄漏
抽象工厂模式
抽象类定义两个抽象方法
系统的产品有多于一个的产品族,而系统只消费其中某一族的产品时
缺点:产品族扩展非常困难,改动或增加一个产品需同时改动多个类<br>
使用场景:一个对象族(或一组没有任何关系的对象)都有相同的约束<br>
工厂方法模式
定义:通过定义工厂父类负责定义创建对象的公共接口,而子类则负责生成具体的对象<br>
图片编辑器,画笔实现
原型模式
定义:创建重复的对象,同时又能保证性能<br>
应用:OkHttp、realm数据库<br>
适用场景
1. 类初始化需要消耗比较多的资源,通过原型拷贝可以避免这些消耗
2.通过new产生一个对象需要非常繁琐的数据准备或者访问权限,使用原型拷贝可以避免;
3. 一个对象需要提供给其他对象访问,而且各个调用者可能都需要修改其值,可以考虑使用原型拷贝来复制多个对象给调用者;
建造者模式
定义:将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。<br>
适用场景
1. 方便用户创建复杂的对象(不需要知道实现过程)
2.代码复用性 & 封装性(将对象构建过程和细节进行封装 & 复用)
使用
UserInfo.Builder builder=new UserInfo.Builder();<br> UserInfo person=builder<br> .name("张三")<br> .age(18)<br> .height(178.5)<br> .weight(67.4)<br> .build();<br><br>
行为型模式
访问者模式
定义:核心思想是将数据操作与数据结构分离,允许在不改变数据结构的前提下,定义新的操作<br>
适用场景
1. 对象结构稳定但需要频繁添加新操作
2.避免操作污染对象类<br>
策略模式
定义:定义一系列的算法,把每一个算法封装起来,并且使它们可相互替换。策略模式模式使得算法可独立于使用它的客户而独立变化。(如:当有许多的if else 的时候,就可以考虑使用策略模式来处理)<br>
策略模式定义了一系列的算法,并将每一个算法封装起来,使它们可以相互替换
优点
结构清晰、扩展方便、动态切换
缺点
增加复杂性,性能影响
备忘录模式
定义:在不破坏对象封闭的前提下,捕获到对象的当前状态,并且在该对象之外保存这个状态,以便于以后该对象恢复到该状态<br>
使用场景
1.需要保存对象在某一时刻的状态和部分状态
2.不希望外部直接访问其内部状态,通过中间对象,间接访问该对象的内部状态
比如MP3,单独写一个类来保存歌名,播放百分比。退出则保存这些数据。再次进入就读取这些状态。
观察者模式
定义:用于定义对象之间的一对多依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都会收到通知并自动更新<br>
使用场景
1.关联行为场景:当多个对象需要同时响应某个事件时。
2.事件多级触发场景:当一个事件触发后,需要多个对象依次响应
3.跨系统的消息交互场景:如消息队列、事件总线等处理机制<br>
模板模式
定义
说白了就是必须得有继承,父类一般都是流程和通用部分的封装,子类一般都是实现父类的方法,然后实现自己具体的功能
使用场景
1.多个子类有公共的方法,并且逻辑进本相同<br>2.重构时,模板方法模式是一个经常使用的模式,把相同的代码抽取到父类中,然后通过钩子函数来约束其行为。
Activity的生命周期采用了模板设计模式,都是继承Activity<br>
AsyncTask也采用了模板设计模式
迭代器模式
定义:迭代器模式由两个主要角色组成:迭代器(Iterator)和集合(Collection)<br>
使用场景
1.抽象了遍历过程,迭代器模式将遍历过程抽象为一个独立的迭代器对象
2.支持不同类型的集合:迭代器模式可以适用于不同类型的集合
3.支持并发遍历:在某些情况下,可能需要在多个线程中同时遍历集合
4.支持遍历过程中的操作:迭代器模式允许在遍历过程中执行操作,如删除元素或修改集合
状态模式
定义:类的行为是基于它的状态改变,不同状态下有不同的行为。<br>
适用场景
①一个对象的行为取决于它的状态,并且它必须在运行时刻根据状态改变它的行为。<br><br>②一个操作中含有庞大的多分支结构,并且这些分支决定于对象的状态。
优点
①将繁琐的状态判断转换成结构清晰的状态类族,在避免代码膨胀的同时也保证了可扩展性和可维护性。<br><br>②体现了开闭原则和单一职责原则,每个状态都是一个子类,要增加状态只需增加子类,要修改状态只需修改一个子类即可。<br><br>③符合迪米特法则。
缺点
会增加系统类和对象的个数。
命令模式
定义:将一系列的方法调用封装,用户只需要调用一个方法执行,那么所有这些被封装的方法就会被挨个执行调用<br>
使用场景
1.抽象出待执行的动作:命令模式可以将待执行的动作抽象出来,以参数的形式提供,类似于过程设计中的回调机制<br>
2.请求排队或记录日志:用户可以在不同的时刻指定、排列和执行请求,一个命令对象可以有与初始请求无关的生存期<br>
3.支持取消操作:命令模式使得取消操作变得容易,因为所有的操作都被封装在对象中<br>
4.支持事务操作:在需要支持事务处理的应用中,命令模式可以确保操作的原子性,当系统崩溃时,可以重做之前的操
中介者模式
定义:降低系统中各个对象之间的耦合度,通过引入一个中介者对象来协调多个对象之间的交互<br>
使用场景
1.当多个对象之间的交互复杂且频繁时
2.当需要独立修改某个对象的行为而不影响其他对象时。
3.当需要动态设置对象集合中的对象交互时<br>
比如登录注册界面,我们使用EditText的addTextChangedListener监听输入密码的位数、用户名是否为空,密码与确认密码是否一致等等判断时,此时多个控件交互,就是由Activity充当中介者来协调
解释器模式
定义:给定一个语言,定义它的文法的一种表示,并定义一个解释器,该解释器使用该表示来解释语言中的句子<br>
使用场景
1.如果某个简单的语言需要解释执行而且可以将该语言中的语句表示为一个抽象语法树时,可以考虑使用解释器模式
2.在某些特定的领域出现不断重复的问题时,可以将该领域的问题转化为一种语法规则下的语句,然后构建解释器来解释该语句;
责任链模式
定义:责任链模式定义了多个对象都有机会处理同一个请求,但具体由哪个对象处理则在运行时动态决定。这些对象形成一个链条,请求从链的开始传递,直到被处理为止。每个处理者都有机会处理请求,如果当前处理者无法处理,则将请求传递给下一个处理者,直到找到能够处理的处理<br>
使用
事件分发机制<br>
当用户接触屏幕时,Android会将事件对象从ViewTree的顶部至上而下分发传递,这就是责任链模式的应用之一<br>
BroadcastReceiver<br>
有序广播可以根据优先级依次传播,指导有接受者将其终止或者所有接受者都不终止它,这与责任链模式相似<br>
原则
1.单一职责原则
一个类应该只有一个引起它变化的原因,只承担一项职责
2.开闭原则
扩展是开放的,对于修改是封闭的
3.里氏替换原则
子类对象应该能够替换父类对象,并且程序逻辑不受影响
4.依赖倒置原则
高层模块不应该依赖于低层模块
5.接口隔离原则
不应该强迫用户依赖他们不需要的接口方法,接口应该尽量小而精
6.迪米特原则
一个对象应当尽可能少地了解其他对象的细节
IO
java io 体系
序列化与反序列化
Serializabile原理
Pacelable接口原理解析
Json
xml
File文件操作
数据结构
红黑树
定义
1.每个节点要么是红色,要么是黑色
2.根节点必须黑色
3.所有叶子节点(NIL节点)都是黑色
4.如果一个节点是红色的,则它的两个子节点必须是黑色的。这意味着不会有连续的两个红色节点
5.从任意节点到其叶子节点的所有路径上,黑色节点的数量是相同的
平衡机制
1.旋转
2.变色
链表
链表是一个一个内存块,用指针把它们链接起来
链表适用于当数据结合频繁变化,需要快速插入和删除,内存空间分散的场景
特点
动态内存分配
节点动态创建,根据需要动态分配释放内存
可扩展性
链表的大小可以根据需要动态增长或缩小,没有固定的大小限制
非连续存储
链表中的节点可以分散在内存的不同位置,每个节点包含数据和指向下一个节点的指针
AVL平衡二叉树
性质
1.要么是空树
2.左右子树均为AVL树
3.左右树的高度差(平衡因子)的绝对值不超过1
复杂度
空间 O(n)
查找
O(n)
插入
O(n)
删除
O(n)
B-树
定义:它属于自平衡的树状数据结构,能够保持数据有序,并允许查找、顺序访问、插入和删除操作都在对数时间内完成<br>
Splay树
Treap
java集合框架
HashMap
HashMap的put是如何实现的
hashMap.put(key,value)
存储结构链表
计算hashCode,存到数组,出现哈希冲突,用链表处理,如果链表长度大于8并且数组大于64,转用红黑树<br>
1.根据key计算hash值
2.确定下标 i
3.检查是否冲突
没有元素直接插入
有元素
key相同修改value
key不同
红黑树节点
链表节点
4.处理哈希冲突
链表、红黑树
5.插入后检查是否需要扩容
数组达到64跟长度达到8,变成红黑树
如何提高HashMap效率?
尽可能避免rehash,尽可能提高容量,避免哈希冲突
new HashMap(100/0.75+1)
0.75哈希因子
HashMap是否可以序列化?
HashMap 本身实现了 Serializable 接口,这意味着 HashMap 对象可以被序列化。
序列化是将对象状态转换为可以存储或传输的格式的过程。
HashMap扩容的原理,为什么要2的指数幂容量,输入17会是多少容量?
大于17,最大32
是通过按位运算所以需要2的指数幂
HashMap的扩容机制是以2倍的形式进行扩容的
17不是2的次幂,无法找到均匀下标,变成单链表,查找复杂度会很高
当链表长度过长进行扩容,即增加数组长度,然后将数据重新放入数组中,达到扩容
HashMap怎么变成线程安全?<br>
1.Collections.synchronizedMap 可以将 HashMap 包装成线程安全的 Map
2.ConcurrentHashMap 是专门为并发设计的线程安全 Map,性能优于 Collections.synchronizedMap
3.通过 synchronized 关键字手动控制 HashMap 的访问
4.使用 ReadWriteLock,通过 ReadWriteLock 实现读写分离,提升并发性能
两个hashCode相同会怎么样?
导致哈希冲突
key跟hash相同才会替换
通过链表法解决冲突
hashMap1.7缺点?
链表效率太低,引入红黑树
HashTable
HashMap所有函数家锁(Synchronized)
ConcurrentHashMap
ConcurrentHashMap 的底层数据结构依然采用“数组+链表+红黑树
数据结构可以看成是”Segment数组+HashEntry数组+链表”
采用了synchronized + CAS 算法来保证线程安全
支持多线程进行扩容操作。在扩容过程中主要使用 sizeCtl 和 transferIndex 这两个属性来协调多线程之间的并发操作
ArrayList<br>
ArrayList的内部使用一个对象数组(即Object[]类型)来存储元素
由于是数组实现,元素的访问是通过索引直接进行的,时间复杂度为O(1)
动态调整:当添加的元素数量超过当前容量时,ArrayList会自动扩容
非同步,需要使用Collections.synchronizedList()
容量(Capacity):容量指的是数组中元素的数量。在创建ArrayList时,可以指定初始容量,如果没有指定,则默认容量为10。当添加的元素数量超过当前容量时,ArrayList会自动增加容量,通常是当前容量的1.5倍(即扩容为1.5倍)。这个过程是通过创建一个新的、更大的数组并将旧数组的元素复制到新数组来实现的
注意
HashMap和HashTable区别?
hashMap线程不安全,效率高
HashTable所有函数加了锁(Synchronized),线程安全,但是效率会变低
HashMap跟ArrayList区别?<br>
与 HashMap 和 ConcurrentHashMap 不同,它不存储键值对,只存储单个元素。和 LinkedList 相比,ArrayList 的随机访问速度更快,但是在插入和删除元素(特别是在中间位置插入和删除)时,由于需要移动后面的元素,效率相对较低
Object<br>
toString()、equals()、hashCode()
Java中的基本数据类型包括byte、short、int、long、float、double、boolean、char等。这些类型直接存储数据值,而不是对象引用
什么是双亲委派机制?
双亲委派机制是指当一个类加载器需要加载某个类时,它首先会委托其父类加载器去加载这个类。只有当父类加载器无法加载时,子类加载器才会尝试自己加载
当一个类加载器需要加载某个类时,它会按照以下步骤工作:<br>1.委托父类加载器:<br>首先检查是否已经加载过该类,如果已经加载则直接返回。<br>如果没有加载过,则委托父类加载器去加载。<br>2.父类加载器尝试加载:<br>父类加载器会重复同样的过程,继续向上委托,直到启动类加载器(Bootstrap ClassLoader)。<br>3.启动类加载器尝试加载:<br>如果启动类加载器无法加载该类,则向下返回给扩展类加载器尝试加载。<br>如果扩展类加载器也无法加载,则继续向下返回给应用程序类加载器。<br>4.子类加载器尝试加载:<br>如果所有父类加载器都无法加载该类,则由子类加载器自己尝试加载。
JVM
本质内存管理
堆
堆是一种动态内存分配,主要存储对象跟数组
适合存储大对象跟需要跨多个方法调用对象
年轻代
eden
so
s1
老年代
不容易被回收,经过多次Gc存活下来的对象,每次Gc+1,一般到达15次就到老年代<br>
新生代跟老年代内存比一般1:2<br>
老年代满了会触发Major GC 即 Full GC清空内存
Permanent Generation(持久代)<br>
主要存放静态文件,如java类,方法等<br>
GC管理,没有引用时释放<br>
栈
方法跟局部变量
后进先出(LIFO)的数据结构<br>
方法调用时创建,结束自动释放
方法区
方法区中保存着,类、静态变量、静态方法、常量、普通方法
方法区的大小不必是固定的,jvm可以根据应用的需要动态调整
本地方法区
本地方法栈是虚拟机调用native方法时使用的
程序计数器
定义:控制指令执行流程、函数调用、线程切换和调试工具支持<br>
程序计数器就是用来记录线程指令历史位置的区域
程序计数器记录着下一条要执行的指令的地址
在多线程编程中,每个线程都有自己的程序计数器
垃圾标记算法
引用计数法
1.需要单独的字段作为存储计数器,这样的做法会增加内存的开销<br> 2.每一次的赋值都需要更新计数器,会增加我们的时间开销<br> 3.无法处理循环引用的问题
可达性分析算法
以对象集合为起点(GC Root)为起始点,按从上到下的方式搜索跟踪目标对象,看看能否到达,<br> 在内存当中根对象和内存中对象,直接或者间接的连接,被称为引用链,如果没有的话,说明这个<br> 垃圾对象(死亡对象),将其清除
内存回收算法
1. 标记-清除算法
1.标记所有需要回收的对象
2.统一回收所有被标记的对象
3. 优点:实现简单<br>
4.缺点:标记清除效率不高,产生大量内存碎片<br>
2. 复制算法
1.将内存划分为大小相等的两块
2. 一块内存用完之后复制存活对象到另一块
3.清理另一块内存
优点:实现简单,运行高效,每次仅需遍历标记一半的内存区域<br>
缺点:会浪费一半的空间,代价大<br>
3. 标记-整理算法
1.标记过程与 标记-清除算法 一样
2.存活对象往一端进行移动
3. 清理其余内存<br>
优点:避免 标记-清除 导致的内存碎片,避免复制算法的空间浪费<br>
4.分代收集算法
1.结合多种算法优势
2.新生代对象存活率低,使用 复制算法
3.老年代对象存活率高,使用 标记-整理算法
Gc Roots<br>
根可达
Gc<br>
Scavenge GC
新对象生成,并且在Eden申请空间失败时,就会触发Scavenge GC
Full GC<br>
· 年老代(Tenured)被写满<br><br>· 持久代(Perm)被写满<br><br>· System.gc()被显示调用<br><br>·上一次GC之后Heap的各域分配策略动态变化
注意
1.避免频繁创建对象,会增加堆压力
2.及时释放不再使用的对象,置null<br>
3.使用弱引用
bitmap存储到nativen内存中,Native内存不受Java堆内存的限制<br>
启动流程
BootRom
BootLoader
idle(pid=0)
init(pid=1)
zygote(孵化器)
SystemServer
Apps
SystemServer.main
创建各种服务
AMS/PMS/WMS等
Looper.loop()一直循环
SystemServiceManager
startSystemUI
Zygote
初始化fork的时候会复制所有zygote
部分运行native,方便调用sdk
startVM(创建虚拟机大小)
startReg(注册常用jni)
将java语言翻译成c++
register_jni_procs
执行ZygoteInit.main函数
调到java层,必须用反射
preload
加载各种系统通用资源,so库、webview相关资源
创建Socket服务
ZygoteServer
forkSystemServer
Zygote fork出的第一个进程System_server
forkSystemServer()<br>
调用nativeForkSystemServer
ForkCommon
Linux fork
SystemServer
run()<br>
启动各种系统服务
Looper.loop()<br>
所有系统服务都添加到SystemServiceManager<br>
startService()<br>
zygoteServer.runSelectLoop()<br>
handleSystemServerProcess去初始化所有参数
runSelectLoop
死循环确保Zygote进程一直存活
fork
产生一个基本一样的进程
zygoteInit
zygoteInit.nativeZygoteInit()
启动Binder线程池,方法在appRuntime.cp中注册
RuntimeInit.applicationInit
通过反射创建程序入口函数的Method对象,并返回Runnable对象
open_driver
初始化binder的驱动
每个进程都有一个binder
mmap()分配binder内存大小(1m-2个page大小),1个page 4k
App_main.cpp的main函数,Zygote的第一个函数
frameworks/base/cmds/app_process/app_main.cpp
注意
为什么用socket而不用binder??<br>
1. 避免死锁风险
2.启动速度
3.多线程问题
4.Binder拷贝问题
init进程
1.挂载文件:识别各类文件,相当于解析硬盘
2.设置selinux--安全策略
3.启动属性服务
4.解析init.rc
5.循环处理脚本--包括启动zygote,包括启动serviceManager进程
6.循环等待
问题注意
app进程启动,为什么是从zygote fork ,而不是从init进程fork?
zygote进程有jvm、各种资源、reg(注册jni),直接复制即可
为什么通知zygote启动的时候是采用socket而不是binder呢?
binder是可以异步处理的,异步处理的时候Zygote fork出子线程处于死锁状态,binder处于等待锁状态
AMS和WMS是什么关系?
不同进程,但是都是继承SystemService,都是SystemSeviceManager去管理,通过Binder去通信
系统如何去存储AMS对象的?应用层是怎么取AMS?这样设计有什么好处?
ServiceManager.getService("activity")
使用Binder机制来实现不同进程间的通信,所有服务放SystemServiceManager管理
解耦、安全、高效
ServiceManager/SystemServiceManager关系?
所有服务继承SystemService
SystemServiceManager:创建启动各种服务
ServiceManager负责管理和调度系统中所有的Binder服务,负责IPC通信
Handler
Linux的epoll机制
handler.sendMessage
MessageQueue
Looper
Handler.dispatchMessage
handlerMessage
问题注意:<br>
1. idleHandler做什么用?<br>
1. 轻量级任务:如加载数据、更新UI等轻量级操作,这些操作可以在不影响用户体验的前提下,利用主线程的空闲时间完成。<br>2. 延迟初始化:对于一些不需要立即初始化的组件或资源,可以注册为IdleHandler,在空闲时进行初始化,以减少应用启动时的加载时间。<br>3. 性能监控与优化:利用IdleHandler实现性能监控和优化,如统计每次空闲时的内存占用情况,或者执行一些内存释放操作。 <br>
IdleHandler是Android中MessageQueue类定义的一个接口,它允许开发者在Looper事件循环的过程中,当消息队列(MessageQueue)出现空闲时执行特定的任务。IdleHandler提供了一种机制,使得开发者能够充分利用主线程的空闲时间,执行一些轻量级的操作,从而提升应用的性能和用户体验<br>
1.性能优化<br>
通过利用主线程的空闲时间执行一些轻量级的任务,如预加载数据、更新UI等,可以减少用户操作的等待时间,提升应用的响应速度和流畅度。
2.资源管理
在空闲时执行内存释放或垃圾回收等操作,有助于管理应用的内存使用,防止内存泄漏等问题。
3.任务调度
对于一些优先级较低或不需要立即执行的任务,可以注册为IdleHandler,在空闲时执行,从而避免阻塞主线程
应用
2.避免内存泄漏?<br>
1.myHandler.removeCallbacksAndMessages(null);
匿名内部类,如果MessageQueue消息队列中有待处理的消息,并且Activity在消息处理之前被销毁(例如,由于屏幕旋转),那么MyActivity实例将无法被垃圾回收,因为它仍然被myHandler持有<br>
2.使用WeakReference,如果必须在Handler内部持有对Activity或Fragment的引用
匿名内部类,默认持有外部类引用<br>
1.退出的时候仍有 Thread 在处理中,其引用着 Handler<br>2.退出的时候虽然 Thread 结束了,但 Message 尚在队列中排队处理或正在处理中,间接持有 Handler
调用 Looper#quit() 或 quitSafely(),它将清空所有的 Message 或未来的 Message,并促使 loop() 轮询的结束
MessageQueue持有了Message持有了Handler,handler又持有了activity,如果消息没有处理完,就可能会出现内存泄漏<br>
3.Looper跟handler对应关系,一对多还是多对一?<br>
1.一个线程只会有一个Looper对象,所以线程和Looper是一一对应的
2. MessageQueue对象是在new Looper的时候创建的,所以Looper和MessageQueue是一一对应的
3.Handler的作用只是将消息加到MessageQueue中,并后续取出消息后,根据消息的target字段分发给当初的那个handler,所以Handler对于Looper是可以多对一的,也就是多个Hanlder对象都可以用同一个线程、同一个Looper、同一个MessageQueue
综合:Looper、MessageQueue、线程是一一对应关系,而他们与Handler是可以一对多的<br>
4.主线程为什么不用初始化Looper?<br>
应用在启动的过程中就已经初始化了一个主线程Looper。每个java应用程序都是有一个main方法入口,Android是基于Java的程序也不例外
5.为什么主线程的Looper是一个死循环,但是却不会ANR?<br>
Looper的死循环,是循环执行各种事务,包括UI绘制事务。Looper死循环说明线程没有死亡,如果Looper停止循环,线程则结束退出了,Looper的死循环本身就是保证UI绘制任务可以被执行的原因之一
当没有消息的时候,会阻塞在loop的queue.next()中的nativePollOnce()方法里,此时主线程会释放CPU资源进入休眠状态,直到下个消息到达或者有事务发生,所以死循环也不会特别消耗CPU资源
6.Message是怎么找到它所属的Handler然后进行分发的?<br>
在使用Hanlder发送消息的时候,会设置msg.target = this,所以target就是当初把消息加到消息队列的那个Handler
7.Handler是如何切换线程的?<br>
使用不同线程的Looper处理消息。我们知道,代码的执行线程,并不是代码本身决定,而是执行这段代码的逻辑是在哪个线程,或者说是哪个线程的逻辑调用的。每个Looper都运行在对应的线程,所以不同的Looper调用的dispatchMessage方法就运行在其所在的线程了
Handler如何保证MessageQueue并发访问安全的?<br>
循环加锁,配合阻塞唤醒机制。我们发现,MessageQueue其实是【生产者-消费者】模型,Handler不断地放入消息,Looper不断地取出,这就涉及到死锁问题。如果Looper拿到锁,但是队列中没有消息,就会一直等待,而Handler需要把消息放进去,锁却被Looper拿着无法入队,这就造成了死锁,Handler机制的解决方法是循环加锁<br>
HandlerThread
HandlerThread 是一个线程类,可以单独运行
自带 Looper:HandlerThread 内部自动创建并管理 Looper,无需手动调用 Looper.prepare() 和 Looper.loop()
支持 Handler:可以通过 Handler 向 HandlerThread 发送消息或任务
适合后台任务:适合执行耗时操作,如网络请求、文件读写等
IPC
Binder
C/S架构
传统的linux中IPC通信原理
copy_from_user/copy_to_user
Binder通信原理
Binder跨进程通信原理
动态内核加载模块
内存映射mmap原理
Binder IPC实现原理
Binder通信模型
Client/Server/ServiceManager/驱动
Binder Dirver
Binder线程池
启动ServerManager
获取ServerManager
Binder通信的代理模式
Binder java层实现
IBinder/IInterface/Binder/Stub
AIDL使用原理
binder异步调用
oneway
注意
内存只拷贝一次,创建一个共享内存
同步数据传输大小是1M-8k,异步是同步的一半
ServiceManager进程去管理所有Binder
处理DeadObjectException异常
lintToDeath回调移除监听
添加isBinderAlive跟pingBinder()<br>
内存共享
Socket
C/S架构
internet socket
tcp/ip
unix domain socket
Linux的IPC机制
Socket/管道/共享内存/信号量/消息队列
管道:handler epoll使用管道机制
Zygote 进程(Android 系统的孵化进程)使用管道与子进程通信。
简单易用:管道是操作系统提供的基础 IPC 机制,使用简单。<br>高效:管道基于内核缓冲区,数据传输效率较高。、<br>轻量级:适合小规模数据传输。<br><br>缺点<br>单向通信:管道是半双工的,无法同时进行双向通信。<br>仅限于父子进程:匿名管道通常只能用于具有亲缘关系的进程(如父子进程)。<br>数据量限制:管道缓冲区大小有限,不适合传输大量数据。
共享内存:mmkv
信号量:matrix/xcrash/友盟apm
Binder
共享内存
AIDL
android 接口定义语言
DSL配置文件,翻译器,编译器
Andromeda/hermes
服务
PKMS(PackageManager Service)
定义:应用的安装、卸载、信息查询
路径:frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java
通过binder机制PackageManager去PackageManagerService获取应用信息
应用启动流程
Launcher点击图标
Service_manager(Binder)
System_server(AMS/WMS)
Zygote(fork)
App
ActivityThread.main()
AT.attach()
AT.bindApplication()
Context
ActivityThread.performLauncherActivity()
Activity.onCreate()
Activity.setContentView()
Activity.onResume()
attachApplication
attachApplicationLocked
IApplication.bindApplication
ActivityThrad.bindApplication
服务添加到SystemServer<br>
sendMessage
只有父进程执行了fork之前的代码,fork之后,父子进程都要执行后续代码,id不一样,0/1
ActivityTaskManagerService
startProcessAsync
startProcessLocked
PackageManager
IPackageManager.Stub
PackageManagerService
app解析流程
Manifest清单文件
PKMS管理所有应用权限
PMS面试
为什么要PKMS?
借助Web架构思路,C/S架构
安卓开机慢,什么原因?
1.PKMS构造方法会扫描所有apk,重新安装一遍,
2.会对所有dex优化
MainActivity跳转到DerryActivity(launchMode)请问launcMode什么时候解析的?
1.手机开机的时候,会执行PKMS构造方法,把所有apk扫描进来,所有应用重新安装一遍,读取所有应用信息存储到PKMS<br>
2. 所以手机在开机的时候,已经解析完成了launcMode
安装apk原理是什么?
1.copy apk到指定目录
2.扫描指定目录的apk
3SystemServer启动服务
1.Installer服务
2.获取社保是否加密
3.调用PKMS.main方法,实例化PKMS,扫描所有应用重新安装一遍
4.如果设备没有加密,操作它,管理A/B OTAderopting
5.如果设备没有加密,执行performDexOptUpdate,完成dex优化,会导致开机慢
6.执行performFstrim,完成磁盘维护
7.PKMS准备完成,PKMS.systemReady
Launcher3如何展示应用的?
Launcher3系统应用,通过binder去访问PKMS(读取所有应用信息)
手机开机后为什么能收到开机广播?
因为手机开机的时候,PMKS构造方法已经扫描了所有APK,清单文件(所有静态广播已经注册完成)
AspectJ
WMS(WindowManagerService)
定义
1. 窗口管理:管理所有应用程序的窗口,包括窗口的显示、布局、大小和位置。<br>
2.焦点管理:当用户点击某个窗口时,WMS会将焦点设置到该窗口上,并通知AMS更新Activity的状态<br>
window创建流程
activity创建window
window删除
View的绘制流程
onMeasure()
onSizeChange()
onLayout()
onDraw()
requestLayout()
将mLayoutRequested置为true
ActivityThread.handleResumeActivity
WindowManagerGlobal.updateViewLayout
viewRootIml.setLayoutParams
viewRootImpl.requestLayout
viewRootImpl.scheduleTraversals
viewRootImpl.doTraversal()
viewRootImpl.performTraversals
performMeasure
onMeasure
performLayout
onLayout
performDraw
onDraw
WindowManagerGlobal管理WindowManager然后管理window
WindowManagerGlobal是ViewRootImpl的二次封装
通过IWindowSession将view添加到WindowManagerService
问题注意:
onResume中度量宽高是否有效?
无效,添加view是在onResume后面的,在onMeasure中才知道view大小
消息屏障,本质也是用消息队列实现
子线程中更新View是否一定会报错?
不会,只有在添加view后子线程中更新view才会报错
WMS和AMS通过Binder机制进行交互。Binder是Android系统的一种进程间通信(IPC)机制,允许不同进程间的对象进行高效通信。当AMS需要创建一个新的Activity时,它会调用WMS来创建对应的窗口并显示;当Activity的布局或位置发生改变时,WMS会通知AMS更新Activity的状
AMS
AMS是Android系统中管理Activity和其他应用组件的服务。其主要职责包括:<br>
1.活动管理:管理应用程序中的各种活动(Activity)的生命周期,包括启动、调度、关闭等。<br>
2.组件管理:管理应用程序的组件(如Activity、Service等)的启动、停止和通信,以及进行任务管理和权限管理等
Activity
fragment
View
Fragment事务管理
Fragment转场动画
镶嵌处理
FragmentManager
生命周期
onCreate()
onStart()
已经准备好,但是还不可见
onRestart()
onResume()
此时界面可见
onResume后执行addView,在此之前控件可以在子线程执行不会报错
onPause()
当应用程序从前台切换到后台时,系统会调用此方法。此时,应用程序仍然可见,但用户无法与其交互。例如,当用户按下Home键或切换到另一个应用程序时,当前应用程序就会进入后台
在这个阶段,用户仍然可以看到应用程序的界面,但无法与其交互。例如,如果应用程序正在播放音乐,用户可能仍然可以看到播放控制,但无法进行操作
onStop()
当应用程序完全不可见时,系统会调用此方法。这意味着用户已经离开了应用程序,无法再与其交互。例如,当用户按下返回键或切换到另一个应用程序时,当前应用程序可能会进入停止状态。
onStop():在这个阶段,用户无法看到应用程序的界面,也无法与其交互。应用程序将不再接收用户的输入事件,如点击、触摸等
onDestroy()
问题注意:<br>
1.横竖屏切换时Activity的生命周期****Activity的状态都有哪些?
2. Activity的启动方式?<br>
ActivityThread
handleLaunchActivity
windowManagerGlobal.initialize()
performLaunchActivity()
newActivity()
makeApplication()
activity.attach()
此方法创建Activity的PhoneWindow,并且绑定对应WindowManager
此时创建了window
执行activity的onCreate()
setContentView
handleResumeActivity
performActivity
onReume
decorview跟Activity进行绑定
addView(DecorView)
onResume后view才添加到window里面
WindowManager管理ViewManager
WindowManagerGlobal
ViewRootImpl.setView
binder进程
sufacefling
DecorView在setContentView里面创建
Activity栈管理
Activity任务栈模型
Launch Mode
Intent的Flag与taskAffinity
Activity管理
activity运行机制
adj内存管理机制
activity内核管理方案详细讲解
hook插件化
启动耗时优化?
时间:Application+onCreate+onResume
onCreate的setContentView中view的层级减少
减少application到onResume过程耗时操作
事件分发机制
dispatchTouchEvent()<br>
dispatchTouchEvent()<br>
onInterceptTouchEvent()<br>
onTouchEvent()<br>
onTouchEvent()<br>
performClickInternal()<br>
performClick()
ApplicationThread
问题注意
Activity如何与window与view进行分工合作?
view用于显示内容
activity管理window,window管理view,实际是viewRootImpl管理View
通过IWindowSession将view添加到WindowManagerService
onResume函数中度量宽高有效吗?
无效,因为addView是执行在onResume后面的,addView后才能测量宽高
解决:延迟去获取(textView.post(new Runnable(){}))
post实际就是一个handle message,按消息队列执行
在onMeasure之后执行
子线程中view.setText一定会报错吗?
不一定,只有在addView之后执行UI刷新才会报错,第二次执行才会报错
View的绘制过程都是用的同一个canvas吗?
是
view创建过程?
WindowManagerImpl<br>
addView
WindowManagerGlobal.addView
ViewRootImpl.setView
addToDisplay
Binder
ATMS
ActivityTaskManagerSevice的启动
startService,将所有服务添加到SystemServiceManager里面进行管理
publisBinderService将ATM保存到SystemManager
执行start
activity Stack的管理
ActivityRecord的管理
Activity生命周期管理
PMS
线程
线程池
线程池优点
1. 提高线程的利用率
2.提高程序的响应速度
3. 便于统一管理线程对象
4. 可以控制最大并发数
线程池原理
简单使用
ThreadPoolExecutor()<br>
线程工厂:Executors.defaultThreadFactory()<br>
核心线程是否已经满
阻塞队列是否已经满
最大线程数是否已经满
拒绝策略
Executors.newSingleThreadExecutor()<br>
Executors.newCachedThreadPool()<br>
建议使用此线程池
注意
1. 执行任务出现异常,并抛出异常,会销毁掉该线程,再重新创建一个线程
2// 创建线程池<br> val executor = ThreadPoolExecutor(<br> 2, // 核心线程数,始终存活的线程数<br> 4, // 最大线程数,线程池中允许的最大线程数量<br> 60, // 非核心线程空闲存活时间<br> TimeUnit.SECONDS, // 时间单位<br> LinkedBlockingQueue<Runnable>(10), // 任务队列,用于存放待执行的任务<br> Executors.defaultThreadFactory(), // 线程工厂,线程工厂,用于创建新线程,名称等<br> ThreadPoolExecutor.AbortPolicy() // 拒绝策略,拒绝策略,当任务队列已满且线程数达到最大线程数时,如何处理新任务<br> )
当提交一个新任务时:<br>如果当前线程数小于核心线程数,则创建新线程执行任务。<br>如果当前线程数等于核心线程数,则将任务放入任务队列。<br>如果任务队列已满且当前线程数小于最大线程数,则创建新线程执行任务。<br>如果任务队列已满且当前线程数等于最大线程数,则触发拒绝策略。<br>当线程池中的线程数量超过核心线程数时:<br>空闲时间超过 keepAliveTime 的非核心线程会被销毁。
LinkedBlockingQueue:无界队列,任务数量无限制。<br>ArrayBlockingQueue:有界队列,任务数量有限制。<br>SynchronousQueue:不存储任务,直接将任务交给线程执行。
threadFactory:线程工厂,用于创建新线程。<br>可以自定义线程的名称、优先级等。
常用的拒绝策略:<br>ThreadPoolExecutor.AbortPolicy:默认策略,直接抛出 RejectedExecutionException。<br>ThreadPoolExecutor.CallerRunsPolicy:由提交任务的线程直接执行任务。<br>ThreadPoolExecutor.DiscardPolicy:直接丢弃任务,不抛出异常。<br>ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列中最旧的任务,然后重新尝试提交任务。
线程共享和协作
CPU核心数、线程数、时间片轮转机制
synchronized、lock、volatile、ThreadLocal如何实现线程共享
volatile能保证变量的可见性,禁止指令重排序<br>
Wait、notify/notifyAll、join如何实现线程间协作
线程深入理解
线程生命周期
1.新建状态(New)<br>
当一个线程对象被创建时,它处于新建状态。此时线程对象已经被创建,但还没有开始运行
2. 就绪状态(Runnable)<br>
当线程调用start()方法后,线程进入就绪状态。此时线程已经准备好运行,但可能还没有被分配到CPU时间片
3.运行状态(Running)<br>
当线程获得CPU时间片并开始执行时,线程进入运行状态。此时线程正在执行任务
4.阻塞状态(Blocked)<br>
当线程因为某些原因无法继续执行时,线程进入阻塞状态。阻塞状态可以分为多种类型,如等待I/O、等待锁、等待信号等
5. 等待状态(Waiting)<br>
当线程需要等待某些条件满足时,线程进入等待状态。等待状态可以通过wait()方法、join()方法等实现
6.计时等待状态(Timed Waiting)<br>
当线程需要等待一定时间或者等待某些条件满足时,线程进入计时等待状态。计时等待状态可以通过sleep()方法、wait(timeout)方法等实现
7.终止状态(Terminated)<br>
当线程完成了任务或者因为异常等原因退出时,线程进入终止状态。此时线程的生命周期结束
死锁和更多的并发安全
ThreadLocal深入理解
原理:<br>
CAS
CAS的原理
CAS带来的3大问题
原子操作的正确使用实战
仅使用线程池不够
阻塞队列
线程池底层实现分析
线程池排队机制
线程池示例
Executor框架
注意:
volatile和Synchronized的解析
Synchronized<br>
1.用于实现代码块的互斥访问
确保同一时刻只有一个线程能够访问同步代码块或方法,从而避免竞态条件
2.保证可见性和原子性
synchronized 确保进入同步代码块或方法的线程能够看到由其他线程对共享变量的最新修改。进入同步块时,线程会从主内存中重新读取共享变量,而退出同步块时,线程会将共享变量的最新值刷新到主内存
3.同步方法<br>
锁住的是当前对象实例,对于静态方法则锁住的是类对象(Class对象
4. 同步代码块
可以指定锁对象,通常是当前对象(this)或其他共享资源
锁
类锁: 当synchronized修饰一个static方法时,获取到的是类锁,作用于这个类的所有对象
对象锁: 当synchronized修饰一个非static方法时,获取到的是对象锁,作用于调用该方法的当前对象。
volatile<br>
用于修饰变量的关键字。它主要用于确保被修饰的变量在多个线程之间的可见性
1. 可见性
当一个线程修改了 volatile 变量的值,其他线程立即可以看到这个修改。这是通过确保对 volatile 变量的读写操作直接发生在主内存中,而非线程的缓存中来实现的
2.禁止指令重排序
volatile 变量可以防止指令重排序,确保变量的操作顺序在多线程环境中保持一致性
仅保证变量的可见性,不保证原子性。适用于简单的标志位或状态变量
原子性
定义:原子操作:一个不可分割的操作,在执行过程中不会被其他线程干扰<br>
如果一个操作是原子的,那么在多线程环境中,不需要额外的同步机制(如锁)来保证其正确性。
AQS解析
JMM
性能优化
工具
perfetto
systrace
Memory Profiler<br>
1. 实时图表展示应用内存使用量
2.用于识别内存泄漏、抖动等
3.提供捕获堆转储、强制GC以及根据内存分配的能力
Memory Analyzer
强大的 Java Heap 分析工具,查找 内存泄漏及内存占用,<br>生成 整体报告、分析内存问题
LeakCannery<br>
自动化 内存泄漏检测神器
发生内存泄漏的引用链
查看耗时
StrictMode
Trace.beginSection("my_section");
BlockCanary
Debug<br>
Debug.startMethodTracing("my_trace");
/storage/sdcard0/Android/data/com.zong.sen.photo.photoeditor/files/1111.trace
Debug.stopMethodTracing()
可以看到start到end这段时间所有方法的执行时间<br>
内存优化
内存抖动
原因:频繁创建释放变量导致频繁Gc,出现内存抖动
导致问题:内存抖动会导致卡顿,甚至崩溃
解决:减少变量频繁创建
内存泄漏
Android系统虚拟机的垃圾回收是通过虚拟机GC机制来实现的
处理
1.资源对象未关闭,资源性对象如Cursor、File、Socket,应该在使用后及时关闭。未在finally中关闭,会导致异常情况下资源对象未被释放的隐患
2.注册对象未反注册,未反注册会导致观察者列表里维持着对象的引用,阻止垃圾回收
3.Handler临时性内存泄露,Handler通过发送Message与主线程交互,Message发出之后是存储在MessageQueue中的,有些Message也不是马上就被处理的
4.类的静态变量持有大数据对象,静态变量长期维持到大数据对象的引用,阻止垃圾回收
内存溢出
OOM时会导致程序异常
大部分都是图片处理不当导致
原因:内存不足,可分配内存不足<br>
注意:<br>
为什么内存抖动会导致OOM?<br>
1.频繁创建对象,导致内存不足及碎片(不连续)
2.不连续的内存片无法被分配,导致OOM<br>
如何优化内存抖动??<br>
1.使用StringBuilder替代<br>
2.初始化时设置容量,减少StringBuilder的扩容
3.资源复用,使用全局缓存池,重复频繁申请和释放的对象,结束 使用后,需要 手动释放对象池中的对象
4.ondraw、getView 中创建的对象尽量进行复用,避免在循环中不断创建局部变量
5.使用合理的数据结构,使用 SparseArray类族、ArrayMap 来替代 HashMap
内存优化?<br>
使用ViewStub进行占位<br>
ViewStub 对那些没有马上用到的资源去做延迟加载,并且还有很多大概率不会出现的 View 更要去做懒加载,这样可以等到要使用时再去为它们分配相应的内存
使用弱引用(WeakReference,没有强引用才能被回收)、虚引用或者软引用(SoftReference)<br>
RAM优化<br>
1.防止应用发生oom<br>
2.降低应用由于内存过大被LMK机制杀死的概率
3.避免不合理使用内存导致GC次数增多,从而导致应用发生卡顿
ROM优化<br>
降低应用占ROM的体积,进行APK瘦身。它的 目的 主要是为了 降低应用占用空间,避免因ROM空间不足导致程序无法安装
项目
微信互联跟鉴权优化
重构了项目,旧项目代码太乱,维护不方便,可阅读性差
删除大量代码,sdk大大减少,代码可阅读性大大提高,维护起来更简单方便
图片编辑器内存优化
1. 我们以前是做工具类应用,上架谷歌商店,操作图片以前是所有手机都是加载一样大小的图片去操作,性能差的手机就会非常卡顿,性能好的手机显示操作的时候清晰度不够,根据手机性能去加载不同图片显示
2. 以前为了做历史记录,就是上下一步处理,每操作一次就会缓存一个bitmap,操作过多会导致内存消耗很大,很多操作保存操作步骤即可,必要的才缓存bitmap,可以减少内存消耗。
3.算法以前是用c++实现的,后面改造成使用openGl实现图片处理,处理速度会快很多倍
4. 启动优化,将很多耗时的操作放子线程去初始化或者获取,数据保存之前是用sp,后面改用mmkv,mmkv是使用内存共享的,效率高很多
5.布局也做了优化,使用ConstraintLayout布局,减少了层级
6.下载优化,有很多资源是放服务器的,使用了workManager去下载,资源分小图跟大图,默认预览是显示小图,真正使用再用大图显示,而且很多图片都替换成了svg图片,减少应用大小
7.使用完的bitmap要及时手动释放,因为bitmap一般比较大,容易引起oom或者触发GC<br>
LUT
表示RGB三个维度的颜色映射规则表,cube格式的核心思想是,将三维维度转换为二维维度,<br>
将R与G分量组成小的二维坐标系,小方片B分量互相之间自然增长
全称为Lookup Table,即查找表。它是一种将输入颜色值转换为目标颜色值的映射工具。简单来说,LUT就是一张对照表,用于快速查找和替换图像中的颜色信息
抠图
音乐
2.
'iːkwəlaɪzə equalizer
音乐裁剪
launcher
壁纸
文件管理器
美瑟
wifi灯具控制器
TCP跟UDP区别?
1. TCP是面向连接的协议,数据传输需要先建立连接,三次握手四次挥手,UDP是无连接协议,数据传输前不需要连接,直接发送即可
2.可靠性,TCP传输数据更可靠,有确认机制、重传机制等保证数据的完整性和顺序,UDP有可能出现丢失、重复跟错乱,发送后旧不管了
3.实时性,UDP实时性更好,因为不需要连接,适用游戏、实时通信等
4.流量控制和拥塞控制,TCP可以根据网络状态动态调整传输速率,UDP无
5. TCP适用可靠的传输场景,文件、邮件、浏览器,UDP适用视频会议、在线游戏
6.TCP需要建立连接,开销大,,需要占用比较多资源<br>
鉴权
加密方式
AES加密
对称加密
加密跟解密使用相同密钥
内存需求低,安全性高
RSA加密
非对称加密算法:公共密钥跟私有密钥,私钥加密,公钥解密<br>
加密数据又长度限制,密钥长度值为11,可以使用分段加密
MD5
不可逆的,一般用于保存密码,只是一种哈希算法<br>
缓存机制
子主题
ANR
耗时
触摸超过五秒
前台服务20秒后台服务200秒
ContentProvider调度超时10秒<br>
前台广播10秒,后台广播60秒
死锁
等待锁
其它应用占用大量内存和CPU,导致系统内存不足,无法正常执行,导致anr<br>
CPU被抢占:当其他应用在前台运行高负载任务时,可能会抢占当前应用的CPU时间片,导致当前应用无法及时响应,从而触发ANR<br>
命令
adb shell top --查看当前系统中排名前20的进程信息
(1) 应用在主线程上非常缓慢地执行涉及 I/O 的操作,如有复杂的layout布局、频繁的I/O操作。<br><br>(2) 应用在主线程上进行长时间的计算,如一些耗时操作。<br><br>(3) 主线程在对另一个进程进行同步 binder 调用,而后者需要很长时间才能返回。<br><br>(4) 主线程处于阻塞状态,为发生在另一个线程上的长操作等待同步的块。<br><br>(5) 主线程在进程中或通过 binder 调用与另一个线程之间发生死锁。主线程不只是在等待长操作执行完毕,而且处于死锁状态。
进一步原因
2. 发生ANR的进一步原因:<br><br>(1) 主线程存在耗时操作:主线程阻塞(Blocked)、挂起(suspend)、死锁、死循环、耗时操作等;<br><br>(2) cpu资源被抢占:其他进程某一时间点cpu占比高、某一刻系统的cpu占比过高,都会导致这一时间段无法抢到cpu时间片;<br><br>(3) 主线程卡在 binder 通信的对端:需要通过 binder info 查看对端信息;<br><br>(4) 系统或者应用自身可用内存紧张:系统一直在执行 lowmemory killer 操作查杀进程;<br><br>(5) 应用频繁crash:包括应用自身也容易导致前台应用出现anr的现象;<br><br>(6) 应用内存泄露;<br><br>(7) 系统原因导致:如冻结、温度过高、多媒体(音视频、编解码)、包管理、Block I/O、底层服务NE(native crash)、watchdog、内存黑洞、芯片能力等。
Android 应用的主线程(UI 线程)负责处理用户输入事件(如点击、滑动等)和界面更新。<br><br>如果 CPU 被大量占用(例如主线程或后台线程执行了复杂的计算、死循环、频繁的 GC 等),主线程可能无法及时响应用户输入
分析步骤:<br>
1. 首先在 android(logcat)日志中搜索“ANR in”关键字,通过此关键字主要查看,原因<br>
a. 在 logcat.txt 中查看发生ANR的应用进程、pid、类型;<br><br>b. cpu负载、内存压力、cpu使用情况等
2.其次查看 event (logcat -b events)日志
检索 am_anr 可以看到进入anr的App包名、时间、原因
3.然后查看 /data/anr下的 trace日志
4. CPU使用率信息分析从android(高通平台在android.txt,MTK平台在system*.txt)日志中,查看系统中各个进程的cpu使用率,首先关注的进程就是发生anr的进程、system_server、kswapd0、kworker和其他占比较高的进程、以及最终统计的整体cpu使用率信息
5. Memory角度分析查看发生anr时间点前后的可用内存情况,以及系统查杀应用的频繁程度
7. kernel日志分析思路
日志中直接搜索关键字“lowmemorykiller”、“iowait”等,查看发生的时间点与发生anr的时间点是否基本对应
iowait<br>
CPU 在等待 I/O 操作(如磁盘读写、网络传输等)完成时所花费的时间百分比
磁盘 I/O 瓶颈
磁盘读写速度慢,导致 CPU 长时间等待
网络传输速度慢,导致 CPU 等待网络 I/O 完成
应用程序频繁进行 I/O 操作(如大量日志写入、数据库查询等)
系统配置问题:系统 I/O 调度策略不合理
8. 综合系统功能进行整体分析
部分情况下,根据trace日志很难能够直接确定发生anr的原因,需要根据当时的系统运行情况进行辅助分析。综合当前的系统环境,可以从消息队列、系统可用内存、发热功耗、后台GC频率和时长、dex2oat耗时、冻结、频繁crash、温度过高、lowmemorykiller、root权限、system.err、system.out、binder_sample、slow operation 等角度在日志中搜索关键字进行分析。也存在这种情况,系统日志信息不足以分析出anr的问题,此时需要借助日志中的systrace日志进行详细分析,虽然说大部分时间都对不上,但是也存在对上的时候
通信协议
TCP协议
(传输控制协议)属于传输层协议,负责端到端的可靠数据传输。它通过三次握手四次挥手建立连接,并通过确认机制确保数据的可靠传输
顺序性:TCP保证数据按照发送的顺序进行传输,接收端可以按照相同顺序重组数据<br>
流量控制,拥塞控制
定义
传输数据前先进行三次握手确认连接,面向连接,成功后再进行数据传输
四次挥手,客户端发送FIN+ACK给服务端,服务端接收到发送确认ACK信号回去,如果服务端没有数据发送了再发送FIN+ACK给客户端,客户端收到再发送确认信号ACK给服务端,完成断开连接<br>
SYN :同步位<br>
ACK:确认位,1有效 ,0无效<br>
UDP协议
是一种无连接的协议,不需要建立连接,直接发送数据
数据量小,速度快,但是数据可能会丢失
Socket套接子<br>
Socket是一种通信机制,位于应用层和传输层之间,提供了一组接口用于应用程序之间的通信
unix domain socket<br>
不用连接用于本机进程之间通信
高度封装的接口,内核通过socket去传输数据<br>
注意
什么是socket?<br>
将底层复杂的协议体系,执行流程,进行了封装,封装完的结果,就是一个socket<br>
网络通信都是用socket<br>
子主题
HTTP<br>
http协议传输的数据都是未加密的,也就是明文的
Https
超文本传输协议HTTP协议被用于在Web浏览器和网站服务器之间传递信息
https协议的主要作用可以分为两种:一种是建立一个信息安全通道,来保证数据传输的安全;另一种就是确认网站的真实性。
优点
1.使用https协议可认证用户和服务器,确保数据发送到正确的客户机和服务器;<br>
2.https协议是由SSL+http协议构建的可进行加密传输、身份认证的网络协议,要比http协议安全,可防止数据在传输过程中不被窃取、改变,确保数据的完整性。<br>
3.https是现行架构下最安全的解决方案,虽然不是绝对安全,但它大幅增加了中间人攻击的成本。<br>
5.谷歌曾在2014年8月份调整搜索引擎算法,并称“比起同等http网站,采用https加密的网站在搜索结果中的排名将会更高”。
缺点
1.https协议握手阶段比较费时,会使页面的加载时间延长近50%,增加10%到20%的耗电;<br>
2.https连接缓存不如http高效,会增加数据开销和功耗,甚至已有的安全措施也会因此而受到影响;
3.SSL证书需要钱,功能越强大的证书费用越高,个人网站、小网站没有必要一般不会用。
4.SSL证书通常需要绑定IP,不能在同一IP上绑定多个域名,IPv4资源不可能支撑这个消耗。
5.https协议的加密范围也比较有限,在黑客攻击、拒绝服务攻击、服务器劫持等方面几乎起不到什么作用。最关键的,SSL证书的信用链体系并不安全,特别是在某些国家可以控制CA根证书的情况下,中间人攻击一样可行。
https加密的核心目标<br>
1.数据加密
防止第三方窃听或截取传输的数据(如用户名、密码、信用卡信息等
加密后的数据即使被截获,也无法被解读。
2.数据完整性
防止数据在传输过程中被篡改(如插入恶意代码或修改内容)。
3.身份验证
确保客户端正在与真实的服务器通信,而不是假冒的服务器(防止中间人攻击)。
HTTPS 加密的实现方式
HTTPS 的加密是通过 SSL/TLS 协议(Secure Sockets Layer / Transport Layer Security)实现的。SSL/TLS 协议在 HTTP 协议的基础上增加了加密层
1.SSL/TLS 握手
客户端和服务器建立连接时,会进行 SSL/TLS 握手,协商加密算法和密钥
服务器会向客户端发送其 数字证书,证书中包含了服务器的公钥和身份信息
2.证书验证
客户端会验证服务器的数字证书是否由受信任的 证书颁发机构(CA) 签发
如果证书有效,客户端会生成一个随机的 对称密钥,并用服务器的公钥加密后发送给服务器
3.对称加密通信
服务器使用自己的私钥解密客户端发送的对称密钥
之后,客户端和服务器使用该对称密钥加密和解密通信内容
HTTPS 加密的具体技术<br>
1.非对称加密(Asymmetric Encryption
2.对称加密(Symmetric Encryption
3.数字证书(Digital Certificate)
4.哈希算法(Hash Algorithm):
用于确保数据的完整性,防止数据被篡改
常用的算法:SHA-256
总结:HTTPS 是针对 HTTP 协议 的加密,主要通过 SSL/TLS 协议实现<br>
使用 非对称加密 确保密钥的安全交换。<br>使用 对称加密 加密实际传输的数据。<br>使用 数字证书 验证服务器的身份。<br>使用 哈希算法 确保数据的完整性。
通过 HTTPS,可以有效防止数据被窃听、篡改或伪造,保护用户的隐私和安全
SSL协议
SSL(Secure Sockets Layer)安全套接字层协议主要用于在客户端和服务器之间建立加密的连接,以保证数据传输的安全性
TLS
Transport Layer Security
1.对等协商支持的密钥算法<br>2.基于非对称密钥的信息传输加密和身份认证、基于PKI证书的身份认证<br>3.基于对称密钥的数据传输保密
注意:<br>
http跟https区别?<br>
1. https协议需要到ca申请证书,一般免费证书较少,因而需要一定费用。
2.http是超文本传输协议,信息是明文传输,https则是具有安全性的ssl加密传输协议。
3.http和https使用的是完全不同的连接方式,用的端口也不一样,前者是80,后者是443。
4.http的连接很简单,是无状态的;https协议是由SSL+http协议构建的可进行加密传输、身份认证的网络协议,比http协议安全。
okhttp<br>
子主题
mqtt原理<br>
在 MQTT(Message Queuing Telemetry Transport)协议中,它是一种轻量级的、基于发布/订阅模式的消息传输协议,广泛用于物联网(IoT)和移动应用程序的消息传递。MQTT 协议的设计初衷是为了在低带宽、不可靠的网络环境下,如机器对机器(M2M)通信或移动设备通信中,提供高效、可靠的消息传输
断开连接:客户端可以选择在任何时候断开与 MQTT 服务器的连接<br>
连接:客户端首先与 MQTT 服务器建立连接。这个连接可以是持久的,也可以是临时的,取决于客户端和服务器的配置
MQTT最大优点在于,用极少的代码和有限的带宽,为连接远程设备提供实时可靠的消息服务
JWT 令牌认证(适用于云原生部署),双向 TLS 证书验证(高安全场景)
数据存储方式
SharedPreference<br>
采用键值对方式存储数据
存储到/data/data/应用包名/shared_prefs/文件名.xml<br>
使用Editor对象的apply()或commit()方法来提交更改
内存使用HashMap保存<br>
保存比较慢,读取比较快
MMKV
MMKV每次读取时都需要重新解码,除了时间上的消耗之外,还需要每次都创建新的对象,读取比较慢
有so,会比较大<br>
FastKV<br>
1. 读写速度快,二进制编码,编码后的体积相对XML等文本编码要小很多
2.FastKV记录了各个key-value相对文件的偏移量(包括失效的key-value)
3.默认用mmap的方式记录数据,更新数据时直接写入到内存即可,没有IO阻塞
4.支持同步阻塞和异步阻塞(分别类似于SharePreferences的commit和apply)
5.支持boolean/int/float/long/double/String/ByteArray(byte[])/对象/内置Set<String>的编码器<br>
6.代码少,稳定可靠
implementation 'io.github.billywei01:fastkv:2.6.0'
文件
SQLite数据库<br>
Room数据库<br>
SQLite 的封装,简化操作
网络存储
DataSoure<br>
ContentProvider
Android 中的一个核心组件,主要用于管理应用程序之间共享数据的访问。它提供了一种标准化的接口,允许一个应用程序访问另一个应用程序的数据,同时确保数据的安全性和一致性
1. 数据共享
是 Android 中实现应用程序之间数据共享的标准机制。它允许一个应用程序将其数据暴露给其他应用程序,而无需直接访问底层数据库或文件系统
2. 数据封装
封装了底层数据的存储细节(如 SQLite 数据库、文件系统等),对外提供统一的访问接口。这样,即使底层数据存储方式发生变化,也不会影响外部应用程序的访问
3. 数据安全
提供了细粒度的权限控制,允许开发者定义哪些数据可以被其他应用程序访问,以及访问的权限级别
4. 数据查询和操作
提供了标准的 CRUD 操作接口,其他应用程序可以通过 ContentResolver 调用这些接口来查询和操作数据
5. 数据变更通知
支持数据变更通知机制。当数据发生变化时,可以通过 ContentResolver.notifyChange() 方法通知注册的观察者(如 CursorLoader),以便及时更新 UI
6. 使用场景
共享系统数据:例如,访问通讯录、媒体库、日历等系统数据。<br>跨应用数据共享:例如,一个应用程序提供数据给另一个应用程序使用。<br>数据封装:将底层数据存储细节隐藏,提供统一的访问接口。<br>数据安全:通过权限控制确保数据的安全性。
其它
Rxjava<br>
通过使用 Observable、Observer 和 Scheduler 等核心组件,实现了事件的发布与订阅、线程切换和链式操作等功能
内存、加密、网络、项目熟悉
Kotlin<br>
定义:<br>
Kotlin 是一种静态类型的编程语言,可以编译为 JVM 字节码,也可以编译为 JavaScript 或原生代码。Kotlin 被设计为与 Java 100% 互操作
协程
定义:协程(Coroutines)是处理异步操作的一种强大方式<br>
作用:协程就是将程序进行挂起和恢复,解决异步回调让异步代码同步化<br>
suspend<br>
用于将普通函数转换为挂起函数,当你调用一个挂起函数时,执行会被挂起,直到协程恢复执行
非阻塞挂起,挂起是不占用CPU<br>
挂起或者暂停,用于暂停当前执行的协程,并保存所有局部变量
当遇到suspend函数时,协程会挂起,等待suspend函数执行完毕后再继续执行<br>
resume<br>
已经暂停的协程恢复
runBlocking
阻塞调用,如果通过它来处理协程,那么只有当runBlocking的方法体中执行完,才会往下执行,会一直霸占cpu,直到运行完才会释放
job.join()/job.cancel()<br>
控制协程的执行与取消
GlobalScope.async{}<br>
launch启动后台调度线程,并且不堵塞当前线程
delay(100)<br>
协程中使用的挂起函数
用于延迟当前协程<br>不会阻塞当前运行的线程<br>允许其他协程在同线程运行<br>当延迟的时间到了,协程会被恢复并继续执行
CoroutineScope:协程作用域,管理生命周期
Dispatcher:指定协程运行的线程
Dispatchers.Main:Android主线程<br>Dispatchers.IO:IO密集型任务<br>Dispatchers.Default:CPU密集型任务
ViewModel:使用viewModelScope自动取消协程
kotlin.reflect
implementation "org.jetbrains.kotlin:kotlin-reflect:1.8.20"
问题注意:<br>
1. 解释Kotlin中的协程是什么,以及它们在Android开发中的用途??<br>
1. Kotlin中的协程是一种轻量级的线程,它们允许以更简洁和结构化的方式编写异步代码。
2. 协程挂起函数执行时不会阻塞线程,而是将控制权交还给协程调度器,直到可以恢复执行。
3.在Android开发中,协程用于简化异步任务,如网络请求、数据库操作等,而不阻塞主线程。
2.描述Kotlin中的扩展函数和扩展属性,以及它们在Android开发中的应用?<br>
Kotlin中的扩展函数和扩展属性允许给已存在的类添加新的行为和属性,而不需要修改原始类。这在Android开发中非常有用,可以增加现有类的功能性,同时保持代码的整洁和可读性
3. Kotlin 中的 val 和 var 有什么区别?<br>
val 声明不可变变量(只读),而 var 声明可变变量
4. Kotlin 的空安全特性是什么?<br>
Kotlin 通过类型系统的可空性标记(如 String?)来防止空指针异常 (NullPointerException),使开发者明确处理空值
5.什么是 Kotlin 中的扩展函数?<br>
扩展函数允许你为已有类添加新函数,而无需继承或使用设计模式
6、什么是 Kotlin 中的委托属性?
委托属性允许你将属性的实现委托给另一个对象。例如,lazy 委托用于延迟初始化
7. 如何在 Kotlin 中实现 MVVM 架构?<br>
使用 ViewModel 和 LiveData 来实现 MVVM 架构,并通过 Data Binding 实现视图和数据的绑定
8.Kotlin 中的协程和 RxJava 的区别是什么?<br>
协程提供了更简洁的异步编程模型,而 RxJava 是基于响应式编程的框架,两者都可以处理异步任务,但语法和实现方式不同
协程vs线程<br>
协程创建成本低,几十个字节,线程要1Mb左右<br>
切换开销:无需内核参与,线程需要系统调度<br>
并发数量:单线程可运行数千个,线程通常数百个<br>
内联函数<br>
let<br>
非空判断,返回it<br>
run
可以执行函数引用,多个函数引用可以链式调用,返回it<br>
with
需要传入参数
applay
返回this
also<br>
flow<br>
定义:可以异步计算的数据流称之为Flow,专门用于处理异步数据流的,它是作为协程的补充<br>
生产者:生成数据,添加到数据流中<br>加工者:处于数据流中间,可以对数据流中的数据进行各种变换、加工等操作<br>消费者:处于数据流的末尾,对数据流中的数据进行最终的消费
动画
属性动画(Property Animation)<br>
ObjectAnimator<br>ValueAnimator<br>PropertyValueHolder<br>AnimatorSet<br>Interpolator<br>TypeEvaluator
帧动画
补间动画
透明度动画 – AlphaAnimation<br>缩放动画 – ScaleAnimation<br>旋转动画 – RotateAnimation<br>位移动画 – TranslateAnimation
lottie<br>
矢量动画
算法
哈希算法
将任意长度的输入数据映射为固定长度的输出数据,这个输出称为哈希值(Hash Value)或消息摘要(Message Digest)
MD5
SHA-1
SHA-256
SHA-256 使用逻辑运算、位移和压缩函数生成 256 位哈希值
CRC32
CRC32 的计算速度最快,适合大规模数据校验
设计模式
mvc<br>
mvp<br>
mvvm<br>
wModel和View之间通过双向数据绑定实现数据的自动同步。当Model的数据发生变化时,ViewModel会自动更新View;同样,当用户在View上进行操作时,ViewModel也会自动更新Model的数据。<br><br>事件驱动:ViewModel中定义了View可能触发的事件,并在这些事件发生时执行相应的逻辑。这使得View和ViewModel之间的交互更加清晰和可控。<br><br>命令绑定:ViewModel中定义了一系列命令(Command),这些命令可以在View中通过特定的方式进行绑定。当用户触发这些命令时,ViewModel会执行相应的逻辑。
Model: 负责数据和业务逻辑。<br>View: 负责 UI 显示,通常是 Activity 或 Fragment。<br>ViewModel: 处理 UI 相关的数据,负责与 Model 交互。
用户与 View 交互。<br>View 通过数据绑定或观察者模式与 ViewModel 交互。<br>ViewModel 更新 Model。<br>Model 通知 ViewModel 数据变化。<br>ViewModel 更新 View(通过 LiveData 或其他观察者模式)。
<br>
启动模式
SingleInstance<br>
与 singleTask 类似,但该 Activity 会独占一个任务栈,且该任务栈中只能有这一个 Activity。其他 Activity 不能与该 Activity 共享同一个任务栈
如果发现栈中已经有该activity时,直接复用,不新建,如果没有,直接新开辟一个栈,新建该activity入栈,独享一个任务栈<br>
standard:标准模式<br>
每次启动 Activity 都会创建一个新的实例,即使该 Activity 已经存在于任务栈
singleTop:栈顶复用<br>
如果目标 Activity 已经位于任务栈的顶部,则不会创建新的实例,而是重用该实例,并调用 onNewIntent() 方法。如果目标 Activity 不在栈顶,则会创建新的实例
singleTask:栈内复用<br>
系统会检查任务栈中是否已存在相同类型的Activity实例。如果存在,系统会将其移动到栈顶并接收新的Intent,同时清除其上的所有Activity实例
应用打开方式
1.通过包名启动
2.通过 ADB 命令打开
3.通过点击桌面小部件(App Widget)打开应用
4.创建通知时设置 PendingIntent
5.通过 App Shortcuts 打开
6.通过显式 Intent 打开
7.通过隐式 Intent 打开
8.通过 Launcher 图标打开
jetpack<br>
LiveData
LiveData是Android架构组件的一部分,它能保存数据、能感知生命周期、利用观察者模式在可用的生命周期范围内将最新的数据通知给观察者
生命周期感知:避免在 UI 不可见时更新数据。<br>自动清理:当观察者的生命周期结束时,LiveData 会自动清理观察者。<br>数据一致性:确保 UI 始终显示最新的数据
自定义观察者
定义被观察者(Subject)。<br>定义观察者(Observer)。<br>在被观察者中注册和通知观察者。
生命周期感知:LiveData 只会将数据更新通知给处于活跃生命周期状态(如 STARTED 或 RESUMED)的观察者。<br>数据驱动 UI:当数据发生变化时,LiveData 会自动通知观察者更新 UI。<br>避免内存泄漏:LiveData 会自动清理无效的观察者(如已销毁的 Activity 或 Fragment)。<br>数据一致性:确保 UI 始终显示最新的数据。
如果 Activity 或 Fragment 处于后台(例如 onStop),LiveData 不会触发 UI 更新
如果观察者从不活跃状态变为活跃状态,LiveData 会立即将最新的数据传递给观察者
ViewModel<br>
ViewModel是一种设计模式,用于存储和管理UI相关的数据,使得这些数据在配置改变(如屏幕旋转)时不会丢失。它主要用于保持UI组件的状态,特别是在使用如Fragment或Activity等组件时。ViewModel的生命周期与Activity或Fragment的生命周期紧密相连,但它的生命周期更长,因为它不会因为Activity或Fragment的重建而重建。<br>
okhttp<br>
定义:OkHttp 是一个强大的 HTTP 客户端库,适用于 Android 和 Java 应用程序。它支持同步和异步请求,提供了连接池、缓存、拦截器等功能,是现代 Android 开发中网络请求的首选工具之一<br>
特点:<br>
简单易用:API 设计简洁,易于上手。<br><br>高效性能:支持连接池和缓存,减少网络请求的开销。<br><br>支持同步和异步请求:满足不同场景的需求。<br><br>拦截器机制:支持自定义拦截器,方便扩展功能(如日志、重试、缓存等)。<br><br>支持 HTTP/2 和 WebSocket:提供更高效的网络通信
1.拦截器
1.日志拦截器<br>
适合记录日志、添加公共请求头等应用层操作
记录请求和响应的详细信息,例如 URL、请求头、响应体等,方便调试和分析
2.网络拦截器
适合监控网络层行为、修改网络请求或响应等操作
重试机制:在请求失败时,自动重试请求<br>
日志记录:记录请求和响应的详细信息,例如 URL、请求头、响应体等,方便调试和分析。<br>修改请求:在请求发出之前,动态修改请求的 URL、请求头或请求体。<br>修改响应:在响应返回之后,动态修改响应的状态码、响应头或响应体。<br>重试机制:在请求失败时,自动重试请求。<br>缓存处理:实现自定义的缓存策略,例如根据特定条件缓存响应。<br>认证和授权:自动添加认证信息(例如 Token)到请求头中。<br>性能监控:统计请求的耗时、成功率等性能指标<br>
2.在 build.gradle 中添加 OkHttp 依赖:
3.创建
1.创建请求:OkHttpClient client = new OkHttpClient();<br>
2.创建请求
Request request = new Request.Builder()<br> .url("https://example.com") // 请求地址<br> .build();
3.发送同步请求
4.发送异步请求
高级:<br>
添加请求头
Request request = new Request.Builder()<br> .url("https://example.com")<br> .header("User-Agent", "OkHttp Example") // 添加请求头<br> .addHeader("Accept", "application/json") // 添加多个请求头<br> .build();
发送 POST 请求
<br>
添加拦截器
OkHttpClient client = new OkHttpClient.Builder()<br> .addInterceptor(new HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY)) // 添加日志拦截器<br> .build();
设置超时
OkHttpClient client = new OkHttpClient.Builder()<br> .connectTimeout(10, TimeUnit.SECONDS) // 连接超时<br> .readTimeout(10, TimeUnit.SECONDS) // 读取超时<br> .writeTimeout(10, TimeUnit.SECONDS) // 写入超时<br> .build();
使用缓存
// 创建缓存目录和大小<br>int cacheSize = 10 * 1024 * 1024; // 10 MB<br>File cacheDirectory = new File(getCacheDir(), "okhttp-cache");<br>Cache cache = new Cache(cacheDirectory, cacheSize);<br><br>// 创建 OkHttpClient 并设置缓存<br>OkHttpClient client = new OkHttpClient.Builder()<br> .cache(cache)<br> .build();
绘制
SurfaceFlinger
定义:SurfaceFlinger 是 Android 图形系统的核心组件,负责图形合成和显示管理<br>
1.应用程序绘制:应用程序通过 Canvas 或 OpenGL ES 绘制 UI 内容。绘制结果被存储到图形缓冲区中。<br>
2.提交缓冲区:应用程序将图形缓冲区提交到 BufferQueue。SurfaceFlinger 从 BufferQueue 中获取缓冲区。<br>
3.图形合成:SurfaceFlinger 根据图层的顺序和属性(如透明度、位置)进行合成。如果支持硬件合成,SurfaceFlinger 将部分任务交给硬件合成器处理<br>
4.显示帧合成后的帧被发送到显示设备。显示设备根据 VSync 信号刷新屏幕。<br>
Surface<br>
Surface对应了一块屏幕缓冲区,每个window对应一个Surface,任何View都要画在Surface的Canvas上(后面有原因解释)。传统的view共享一块屏幕缓冲区,所有的绘制必须在UI线程中进行
Surface是用来管理数据的,SurfaceView是用来控制Surface中View的位置和尺寸的<br>
从设计模式的高度来看,Surface、SurfaceView和SurfaceHolder实质上就是广为人知的MVC,即Model-View-Controller。Model就是模型的意思,或者说是数据模型,或者更简单地说就是数据,也就是这里的Surface;View即视图,代表用户交互界面,也就是这里的SurfaceView;SurfaceHolder很明显可以理解为MVC中的Controller(控制器)
SurfaceHolder.Callback<br>
SurfaceHolder.Callback主要是当底层的Surface被创建、销毁或者改变时提供回调通知
surfaceView是在一个新起的单独线程中可以重新绘制画面而View必须在UI的主线程中更新画面
SurfaceView背后有一个单独的Surface,这个Surface不在View层级中,因此它可以独立于UI线程进行绘制。这使得SurfaceView特别适合于需要频繁更新或进行复杂绘制的场景。
view跟SurfaceView区别<br>
1.绘制线程:<br>View:绘制是在UI线程中完成的,如果绘制操作耗时较长,可能会导致UI卡顿。<br>SurfaceView:绘制可以在单独的线程中进行,不会阻塞UI线程,因此可以保持界面的流畅性。<br>
2. 绘制频率与双缓冲<br>View:通常依赖于UI线程的刷新频率,可能会出现闪烁现象。<br>SurfaceView:支持双缓冲机制,可以在后台线程中准备好一帧后,再切换到前台显示,从而减少闪烁。
3.使用场景<br>View:适用于简单的UI元素和不需要频繁更新的界面。<br>SurfaceView:适用于需要频繁更新、进行复杂绘制或播放视频、游戏等高性能要求的场景。
5.生命周期与资源管理:View:生命周期与Activity或Fragment紧密相关,资源管理相对容易。<br>SurfaceView:需要额外管理Surface的生命周期,如创建、销毁和大小变化等<br>
4.触摸事件处理<br>View:触摸事件直接由View体系处理,相对简单。<br>SurfaceView:由于它有一个独立的绘图表面,触摸事件的处理可能需要额外的逻辑来确保正确性。
收藏
立即使用
Collect
Get Started
Collect
Get Started
Collect
Get Started
Collect
Get Started
评论
0 条评论
下一页