Thread

image-20210602170532688

对于dalvik虚拟机而言其检测到执行频率较高的函数时就会进行jit编译将其编译为本地机器码,这样下次此函数执行的时候就会直接执行编译后的机器码,编译后的机器码只存在于内存中并不会以文件的形式保存,app重启后此函数依然会以解释模式执行。在JIT编译函数生成机器码的同时还会生成配置文件profile记录热点函数信息,供AOT守护进程使用编译生成oat文件,以提速执行。

以下为JIT工作流:

img

但在我们测试中 AE 无论运行多少次启动阶段依然有JIT的运行,但TEMU在启动阶段JIT是无执行的,判断TEMU已做了AOT优化。

AOT事前编译,即在代码运行前进行编译。对于android 7.0之前的art虚拟机而言其会在apk安装的过程中利用dex2oat程序将apk中的dex文件编译为本地机器指令并保存为oat文件,这样在apk启动时直接加载此oat文件并运行,提高了程序了执行效率。但是因为他需要在apk安装的时候使用dex2oat程序进行编译,所以增加了apk在安装过程中的时间。

通过AOT优化的中端机有100ms收益

该优化需要对APP内所有工程做改造,升级AGP后,进行BaselineProfile优化

Thread

线程/进程

进程:进程是系统进行资源分配和调度的一个独立单位 (拥有独立内存空间),一个app就是一个进程,进程包含线程。

线程:是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。线程自己基本上不拥有系统资源,只拥有一些在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源。

静态的是资源和动态的是计算

  1. 进程是一个资源的容器,为进程里的所有线程提供共享资源,是对程序的一种静态描述

  2. 线程是计算机最小的调度和运行(计算)单位,是对程序的一种动态描述

Java里的线程有哪些状态?

JDK中,线程(Thread)定义了6种状态: NEW(新建)、RUNNABLE(可执行)、BLOCKED(阻塞)、WAITING(等待)、TIMED_WAITING(限时等待)、TERMINATED(结束)。

源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
/**
* A thread state. A thread can be in one of the following states:
* <ul>
* <li>{@link #NEW}

* A thread that has not yet started is in this state.
* </li>
* <li>{@link #RUNNABLE}

* A thread executing in the Java virtual machine is in this state.
* </li>
* <li>{@link #BLOCKED}

* A thread that is blocked waiting for a monitor lock
* is in this state.
* </li>
* <li>{@link #WAITING}

* A thread that is waiting indefinitely for another thread to
* perform a particular action is in this state.
* </li>
* <li>{@link #TIMED_WAITING}

* A thread that is waiting for another thread to perform an action
* for up to a specified waiting time is in this state.
* </li>
* <li>{@link #TERMINATED}

* A thread that has exited is in this state.
* </li>
* </ul>
*
* <p>
* A thread can be in only one state at a given point in time.
* These states are virtual machine states which do not reflect
* any operating system thread states.
*
* @since 1.5
* @see #getState
*/
public enum State {
/**
* Thread state for a thread which has not yet started.
*/
NEW,
/**
* Thread state for a runnable thread. A thread in the runnable
* state is executing in the Java virtual machine but it may
* be waiting for other resources from the operating system
* such as processor.
*/
RUNNABLE,
/**
* Thread state for a thread blocked waiting for a monitor lock.
* A thread in the blocked state is waiting for a monitor lock
* to enter a synchronized block/method or
* reenter a synchronized block/method after calling
* {@link Object#wait() Object.wait}.
*/
BLOCKED,
/**
* Thread state for a waiting thread.
* A thread is in the waiting state due to calling one of the
* following methods:
* <ul>
* <li>{@link Object#wait() Object.wait} with no timeout</li>
* <li>{@link #join() Thread.join} with no timeout</li>
* <li>{@link LockSupport#park() LockSupport.park}</li>
* </ul>
*
* <p>A thread in the waiting state is waiting for another thread to
* perform a particular action.
*
* For example, a thread that has called <tt>Object.wait()</tt>
* on an object is waiting for another thread to call
* <tt>Object.notify()</tt> or <tt>Object.notifyAll()</tt> on
* that object. A thread that has called <tt>Thread.join()</tt>
* is waiting for a specified thread to terminate.
*/
WAITING,
/**
* Thread state for a waiting thread with a specified waiting time.
* A thread is in the timed waiting state due to calling one of
* the following methods with a specified positive waiting time:
* <ul>
* <li>{@link #sleep Thread.sleep}</li>
* <li>{@link Object#wait(long) Object.wait} with timeout</li>
* <li>{@link #join(long) Thread.join} with timeout</li>
* <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li>
* <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li>
* </ul>
*/
TIMED_WAITING,
/**
* Thread state for a terminated thread.
* The thread has completed execution.
*/
TERMINATED;
}

复制代码

状态说明

线程在一个给定的时间点只能处于下面其中一种状态:

这些状态是虚拟机状态,并不能反映任何操作系统的线程状态。

  • NEW:尚未启动的线程处于这个状态。Thread thread = new Thread(new Runnable(){…});处于这个状态。

  • RUNNABLE:可运行的线程处于这个状态。对应操作系统中的两种状态:ready和running,也就是说RUNNABLE状态既可以是可运行的,也可以是实际运行中的,有可能正在执行,也有可能没有正在执行。关于这个问题的理解,可以对比想一下,thread.start()调用之后线程会立刻执行吗?

  • BLOCKED:阻塞,进入synchronized修饰的方法或者代码块,等待监视器锁的线程处于这个状态。

  • WAITING:无限期等待另一个线程执行特定操作的线程处于这种状态。

  • TIMED_WAITING:正在等待另一个线程执行某个操作的线程在指定的等待时间内处于这种状态。

  • TERMINATED:已经退出的线程处于这个状态。

状态转移

NEW:线程尚未启动的线程状态。当在程序中创建一个线程的时候Thread t = new Thread(Runnable);,线程处于NEW状态。

RUNNABLE:可运行线程的线程状态。处于可运行状态的线程正在Java虚拟机中执行,但它可能正在等待操作系统中的其他资源,比如处理器。也就是说, 这个状态就是可运行也可不运行的状态。注意Runnable ≠ Running。

BLOCKED:进入synchronized修饰的方法或者代码块,等待监视器锁的阻塞线程的线程状态。比如,线程试图通过synchronized去获取监视器锁,但是其他线程已经独占了,那么当前线程就会处于阻塞状态。等到获得了监视器锁之后会再次进入RUNNABLE状态。

WAITING:调用以下方法之一,线程会处于等待状态:

  • Object.wait()注意:括号内不带参数;
  • Thread.join()注意:扩号内不带参数;
  • LockSupport.park();

其实wait()方法有多重形式,可以不带参数,可以带参数,参数表示等待时间(单位ms),如图所示:

imgimg

“BLOCKED(阻塞状态)”和“WAITING(等待状态)”的区别:阻塞状态在等待获取一个排它锁,这个事件将会在另外一个线程放弃这个锁的时候发生,然后由阻塞状态变为可执行状态;而等待状态则是在等待一段时间,或者等待唤醒动作的发生。

TIMED_WAITING:一个线程调用了以下方法之一(方法需要带具体的等待时间),会处于定时等待状态:

  • Thread.sleep(long timeout)
  • Object.wait(long timeout)
  • Thread.join(long timeout)
  • LockSupport.parkNanos()
  • LockSupport.parkUntil()

TERMINATED: 该线程已经执行完毕。执行完毕指的是线程正常执行完了run方法之后退出,也可以是遇到了未捕获的异常而退出。

img

初始(NEW)

新创建了一个线程对象,但还没有调用start()方法。

运行(RUNNABLE)

Ready

Running

Java线程中将就绪(ready)和运行中(running)两种状态笼统 的称为“运行”。线程对象创建后,其他线程(比如main线程)调用了该对象的start()方 法。该状态的线程位于可运行线程池中,等待被线程调度选中,获取CPU的使用权,此 时处于就绪状态(ready)。就绪状态的线程在获得CPU时间片后变为运行中状态 (running)。

阻塞(BLOCKED)

表示线程阻塞于锁。或称“挂起”

阻塞或唤醒一个Java线程需要操作系统切换CPU状态来完成,这种状态转换需要耗费处理器时间

等待(WAITING)

等待状态,处于等待状态的线程是由于执行了Thread.joinObject.wait方法

处于waiting状态的线程会等待另外一个线程处理特殊的行为。 再举个例子,如果一个线程调用了一个对象的wait方法,那么这个线程就会处于waiting状态直到另外一个线程调用这个对象的notify或者notifyAll方法后才会解除这个状态

超时等待(TIMED_WAITING)

有等待时间的等待状态,比如调用了**Thread.sleep(long timeout)、Thread.join(long timeout)、Object.wait(long timeout)**,并且指定了等待时间,线程就会处于这个状态。

终止(TERMINATED)

表示该线程已经执行完毕。

对比分析Java中的各个线程相关的wait()、notify()、sleep()、interrupt()方法

线程相关方法

Thread类

sleep:暂停当前正在执行的线程;(类方法

​ 是Thread的静态方法,很显然它是让当前线程按照指定的时间休眠,其休眠时间的精度取决于处理器的计时器和调度器。需要注意的是如果当前线程获得了锁,sleep方法并不会失去锁。sleep方法经常拿来与Object.wait()方法进行比价,这也是面试经常被问的地方。

sleep() VS wait()

两者主要的区别:

1. sleep()方法是Thread的静态方法,而wait是Object实例方法
2. wait()方法必须要在同步方法或者同步块中调用,也就是必须已经获得对象锁。而sleep()方法没有这个限制可以在任何地方种使用。另外,wait()方法会释放占有的对象锁,使得该线程进入等待池中,等待下一次获取资源。而sleep()方法只是会让出CPU并不会释放掉对象锁;
3. sleep()方法在休眠时间达到后如果再次获得CPU时间片就会继续执行,而wait()方法必须等待Object.notift/Object.notifyAll通知后,才会离开等待池,并且再次获得CPU时间片才会继续执行。

yield:暂停当前正在执行的线程,并执行其他线程;(类方法

​ 是Thread的静态方法,一旦执行,它会是当前线程让出CPU,但是,需要注意的是,让出的CPU并不是代表当前线程不再运行了,如果在下一次竞争中,又获得了CPU时间片当前线程依然会继续运行。另外,让出的时间片只会分配给当前线程相同优先级的线程。

​ yield()方法使当前线程出让CPU执行时间,但并不会释放当前线程所持有的锁。执行完yield()方法后,线程从Running状态转变为Runnable状态,既然是Runnable状态,那么也很可能马上会被CPU调度再次进入Running状态。

什么是线程优先级了?下面就来具体聊一聊。

现代操作系统基本采用时分的形式调度运行的线程,操作系统会分出一个个时间片,线程会分配到若干时间片,当前时间片用完后就会发生线程调度,并等待这下次分配。线程分配到的时间多少也就决定了线程使用处理器资源的多少,而线程优先级就是决定线程需要或多或少分配一些处理器资源的线程属性。

在Java程序中,通过一个整型成员变量Priority来控制优先级,优先级的范围从1~10.在构建线程的时候可以通过**setPriority(int)**方法进行设置,默认优先级为5,优先级高的线程相较于优先级低的线程优先获得处理器时间片。需要注意的是在不同JVM以及操作系统上,线程规划存在差异,有些操作系统甚至会忽略线程优先级的设定。

另外需要注意的是,sleep()和yield()方法,同样都是当前线程会交出处理器资源,而它们不同的是,sleep()交出来的时间片其他线程都可以去竞争,也就是说都有机会获得当前线程让出的时间片。而yield()方法只允许与当前线程具有相同优先级的线程能够获得释放出来的CPU时间片。

join:等待该线程终止;

​ join()方法的作用,是等待这个线程结束,是主线程等待子线程的终止。也就是说主线程的代码块中,如果碰到了t.join()方法,此时主线程需要等待(阻塞),等待子线程结束了(Waits for this thread to die.),才能继续执行t.join()之后的代码块。

interrupt:中断该线程,

interrupt()方法的工作仅仅是改变中断状态,并不是直接中断正在运行的线程。中断的真正原理是当线程被Object.wait(),Thread.join()或sleep()方法阻塞时,调用interrupt()方法后改变中断状态,而wait/join/sleep这些方法内部会不断地检查线程的中断状态值,当发现中断状态值改变时则抛出InterruptedException异常;对于没有阻塞的线程,调用interrupt()方法是没有任何作用。

Object类

  • wait:暂停当前正在执行的线程,直到调用notify()或notifyAll()方法或超时,退出等待状态;
  • notify:唤醒在该对象上等待的一个线程;
  • notifyAll:唤醒在该对象上等待的所有线程;

对比

sleep VS wait

sleep()和wait()方法都是暂停当前正在执行的线程,出让CPU资源。

方法 所属类 方法类型 解除方法 场景 用途
sleep Thread 静态方法 不释放锁 timeout,interrupt 无限制 线程内的控制
wait Object 非静态方法 释放锁 timeout,notify,interrupt 同步语句块 线程间的通信
1
2
3
4
5
6
public static void sleep(long millis) throws InterruptedException 
public static void sleep(long millis, int nanos) throws InterruptedException

public final void wait() throws InterruptedException
public final void wait(long timeout) throws InterruptedException
public final void wait(long timeout, int nanos) throws InterruptedException

wait && notify

调用对象的wait()、notify()、notifyAll()方法的线程,必须是作为此对象监视器的所有者。常见的场景便是就是synchronized关键字的语句块内部使用这3个方法,如果直接在线程中使用wait()、notify()、notifyAll()方法,那么会抛出异常IllegalMonitorStateException,抛出的异常表明某一线程已经试图等待对象的监视器,或者试图通知其他正在等待对象的监视器而本身没有指定监视器的线程。。

调用wait()方法的线程,在调用该线程的interrupt()方法,则会重新尝试获取对象锁。只有当获取到对象锁,才开始抛出相应的异常,则执行该线程之后的程序。

怎么终止一个线程

首先,一个线程不应该由其他线程来强制中断或停止,而是应该由线程自己自行停止。

所以,Thread.stop, Thread.suspend, Thread.resume 都已经被废弃了。

interrupt:

Thread.interrupt 的作用其实也不是中断线程,而是「通知线程应该中断了」,

具体到底中断还是继续运行,应该由被通知的线程自己处理。

具体来说,当对一个线程,调用 interrupt() 时,

① 如果线程处于被阻塞状态(例如处于sleep, wait, join 等状态),那么线程将立即退出被阻塞状态,并抛出一个InterruptedException异常。仅此而已。

② 如果线程处于正常活动状态,那么会将该线程的中断标志设置为 true,仅此而已。被设置中断标志的线程将继续正常运行,不受影响。

interrupt() 并不能真正的中断线程,需要被调用的线程自己进行配合才行。

① 在正常运行任务时,经常检查本线程的中断标志位,如果被设置了中断标志就自行停止线程。

② 在调用阻塞方法时正确处理InterruptedException异常。(例如,catch异常后就结束线程。)

1
2
3
4
5
6
7
8
9
Thread thread = new Thread(() -> {
while (!Thread.interrupted()) {
// do more work.
}
});
thread.start();

// 一段时间以后
thread.interrupt();

stop():

弃用,因为在stop时会释放所有的锁,可能导致线程不同步,另一个也可能导致资源如文件文件数据库的关闭行为不被执行

  1. 调用 stop() 方法会立刻停止 run() 方法中剩余的全部工作,包括在 catch 或 finally 语句中的,并抛出ThreadDeath异常(通常情况下此异常不需要显示的捕获),因此可能会导致一些清理性的工作的得不到完成,如文件,数据库等的关闭。
  2. 调用 stop() 方法会立即释放该线程所持有的所有的锁,导致数据得不到同步,出现数据不一致的问题。

使用volatile标志位

以标志位为循环条件

Https

Http和Https

  • 其实HTTPS就是从 HTTP 加上 SSL/TLS (加密处理+认证+完整性保护)

完整的https通信过程,三次RTT:tcp握手一次RTT,TLS握手两次RTT

img

HTTPS工作原理

image

  • 一、首先客户端发起连接请求,将自身支持的加密(RSA)和哈希算法(sha256)连带发给服务器(服务器端有非对称加密的公钥和私钥)
  • 二、服务器端接受请求,选取一组加密和哈希算法后,将服务器证书(含公钥)发送给客户端
  • 三、客户端收到服务器端证书并使用根证书验证证书后,从中取出公钥,然后本地生成一段随机数,将此随机数用公钥加密后发送给服务器端
  • 四、服务器端用私钥解密出这段随机数作为对称加密的秘钥。之后双方就可以进行对称加密(DES、AES等)。

证书校验过程

校验证书的过程:

第一步是校验证书网站域名、有效期等。

第二步是校验证书本身是否可信:主要是依靠验证证书的信任链条完成。

比如根证书A->B->服务端证书C,那么首先要验证C是由B签署的,这一步的具体步骤是:

1. **用B证书的公钥解密C证书的签名信息后拿到C证书的hash值(签署时该Hash值由B的私钥加密生成),**
2. **然后再用hash算法(B证书上带的签名算法)计算B证书的<u>待签名数据</u>后得到计算hash值,**
3. **将证书解密的hash值与计算hash值比较即可。**

第三步是使用CRL(证书吊销列表)或OCSP(在线证书状态协议)确认证书是否已被吊销。

1
2
3
4
5
6
7
8
9
10
11
12
证书内容可简化为:
待签名数据:
版本: v3
序列号: 123456
签名算法: SHA256withRSA
颁发者信息: CN=A, O=A Corporation, C=US
有效期: 2022-01-01 至 2023-01-01
主题信息: CN=B, O=B Corporation, C=US
主题公钥: (公钥数据)
扩展字段: (可选)
签名:
签名: (签名数据)

image-20240703175159171

image-20211123141219076 image-20211123141130160

抓包软件怎么实现的

以我熟悉的抓包软件whistle为例,抓包软件能抓https内容的核心是:你的手机安装了whistle的自签名根证书,同时你的请求都是代理给whistle的,然后whistle会发送给客户端经自签名根证书签署过的伪造服务器证书,故而能通过客户端的证书校验,从而拿到请求原文。

手机安装了抓包软件的自签名证书后,抓包工具在作为代理进行HTTPS流量捕获时,实际上充当了中间人。抓包开启后,客户端https请求的握手对象实际上是抓包软件,服务端拿到的请求地址是 抓包软件的地址。手机的请求也实际上是与抓包软件在交互,抓包软件再与服务器交互。故而抓包软件能记录并修改客户端的请求与响应

image-20240703170642767

自签名证书:抓包软件生成一个自签名的证书(自签名证书是一种灵活且方便的SSL/TLS证书解决方案,是由证书的所有者自己签发的,适用于于开发测试环境和内部网络。然而,由于它不是由受信任的三方证书颁发机构即CA签发的,使得它无法提供可靠的身份验证,无法适用于公网)

  1. 客户端信任自签名证书
    • 当用户在设备上安装抓包软件的自签名证书后,这个证书被设备作为受信任的根证书。设备会信任由这个自签名证书签发的所有证书。
  2. 中间人代理握手
    • 抓包软件充当“中间人”,拦截客户端的HTTPS请求。客户端发出的请求首先到达抓包软件。
    • 抓包软件向客户端提供一个由它自己的自签名根证书签署的伪造服务器证书(目标服务器的替身证书)。
    • 因为客户端已经信任抓包软件的自签名证书,所以它会信任这个伪造的服务器证书,并与抓包软件进行TLS握手。

这样,手机上的浏览器或应用程序在连接时实际上是与抓包软件建立的https握手

抓包软件的主要目的是捕获、分析和调试网络流量。其工作步骤如下:

  1. 客户端通过代理发送请求
    • 抓包软件配置为代理,客户端配置通过此代理发送所有网络请求。
    • 抓包软件生成一个自签名证书,客户端将其安装为受信任的根证书。
  2. 代理解析并再发送请求
    • 客户端的请求首先由抓包软件接收。对于HTTPS请求,抓包软件会使用自签名证书解密这些请求。
    • 抓包软件记录和分析解密后的请求内容,然后重新加密并发送到目标服务器。
  3. 服务器响应经过代理返回客户端
    • 目标服务器返回响应,抓包软件解密并记录这些响应数据。
    • 抓包软件再重新加密这些数据并发送回客户端。

VPN与抓包软件的原理类似,都是通过一层中间人,将客户端的请求实际上代理到中间人层发起请求。

不同的是由于抓包软件使客户端安装的自签名证书,客户端在证书校验时遇到抓包软件自签名证书签署的伪造服务器证书会校验通过,使客户端的请求都能在中间人层解密后查看和修改,之后抓包再转发。

而VPN则是通过加密隧道(加密整个传输链路):使用协议如IPsec、OpenVPN、L2TP等),在原本就由ssl/tls加密的数据上再加密一层,之后无法感知传输内容原原本本的转发。

LeakType

内存泄漏实质

内存泄漏实质上是GC时候,被GC Root引用或间接引用着的对象无法被回收,而可以作为GC Root的对象在java中有几种:

  1. 虚拟机栈或叫JVM栈(栈帧中的本地变量表)中引用的对象; (线程泄露)

    虚拟机栈是线程私有的,每个java方法被执行的时候都会同时创建一个栈帧(Stack Frame)用于存储局部变量表、操作栈、动态链接、方法出口等信息。每一个方法被调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。

  2. 方法区中的类静态属性引用的对象; (static变量)

    方法区存储类信息、常量、静态变量等数据,是线程共享的区域

  3. 本地方法栈中JNI(即一般说的Native方法)中引用的对象 (Jni持有的对象)

    对应虚拟机栈为虚拟机执行java方法服务,而本地方法栈为虚拟机使用到的Native方法服务

  4. 方法区中常量引用的对象; (final修饰的int/float/long等基本数据类型和String) 不常见

Read more

FirstScreenCost.md

Activity启动耗时

首先:测试方法:

AMS会打印启动Activity的耗时,Android 10之后打印tag为ActivityTaskManager,Android10之前打印ActivityManager;

在Android 10(Q)版本中,Android系统引入了ActivityTaskManager(ATM),并在很大程度上取代了之前的ActivityManagerService(AMS)的许多职责。具体来说,AMS负责管理整个系统的活动生命周期和任务堆栈,而在Android 10中,这些职责被重新分配并分离到新的ActivityTaskManager和ActivityManager中。

ActivityTaskManager专注于任务和活动的管理,处理任务堆栈的操作和活动的启动、切换等。而ActivityManager则更多地处理与应用进程管理相关的功能,如进程的生命周期、内存管理等。

这一改变是为了简化代码结构、提升系统的模块化和可维护性,同时也是为了更好地支持多窗口和多任务操作等新的特性。

总结起来,ActivityTaskManager的引入和实现从Android 10开始正式应用,取代了原有的部分ActivityManagerService的功能。

ActivityManager : Display / startActivity Android6/7

简单结论:display 只统计A onPause之后(不包含A onPause)

AMS 启动新ActivityB 并 执行 B的onCreate、onstart、onresume 与 B向WMS注册窗口到编舞者发起的第一次测绘 完成

Activity的启动可以分为三个步骤,以ActivityA启动ActivityB为例,三步骤分别为:

  1. 以ActivityA调用startActivity,到ActivityA成功pause为止

    displayTimeStart

  2. ActivityB成功初始化,到执行完resume为止

  3. ActivityB向WSM注册窗口,到第一帧绘制完成为止
    displayTimeEnd

ThreadPool

https://tech.meituan.com/2020/04/02/java-pooling-pratice-in-meituan.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
/**
* Creates a new {@code ThreadPoolExecutor} with the given initial
* parameters.
*
* @param corePoolSize the number of threads to keep in the pool, even
* if they are idle, unless {@code allowCoreThreadTimeOut} is set
* 核心线程数量,线程常驻即使空闲,除非设置了allowCoreThreadTimeOut
* @param maximumPoolSize the maximum number of threads to allow in the
* pool
* 最大线程数,整个线程池的线程数量(核心线程数+普通线程数)
* @param keepAliveTime when the number of threads is greater than
* the core, this is the maximum time that excess idle threads
* will wait for new tasks before terminating.
* 超过核心线程数后的空闲线程存活时间
* @param unit the time unit for the {@code keepAliveTime} argument
* 时间单位
* @param workQueue the queue to use for holding tasks before they are
* executed. This queue will hold only the {@code Runnable}
* tasks submitted by the {@code execute} method.
* 工作队列:任务被执行前的存放队列
* @param threadFactory the factory to use when the executor
* creates a new thread
* executor创建线程的工厂
* @param handler the handler to use when execution is blocked
* because the thread bounds and queue capacities are reached
* ...
*/
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)

图4 任务调度流程

通过Executors工具类可以创建多种类型的线程池,包括:

  • FixedThreadPool:固定线程数量的线程池。该线程池中的线程数量始终不变。当有一个新的任务提交时,线程池中若有空闲线程,则立即执行。若没有,则新的任务会被暂存在一个任务队列中,待有线程空闲时,便处理在任务队列中的任务。核心线程数为n,最大线程数为n,任务队列长度为Interger.MAX_VALUE的LinkedBlockingQueue

  • SingleThreadExecutor: 只有一个线程的线程池。若多余一个任务被提交到该线程池,任务会被保存在一个任务队列中,待线程空闲,按先入先出的顺序执行队列中的任务。核心线程数、最大线程数都为1,任务队列长度为Interger.MAX_VALUE的LinkedBlockingQueue

  • CachedThreadPool: 可根据实际情况调整线程数量的线程池。线程池的线程数量不确定,但若有空闲线程可以复用,则会优先使用可复用的线程。若所有线程均在工作,又有新的任务提交,则会创建新的线程处理任务。所有线程在当前任务执行完毕后,将返回线程池进行复用。核心线程数为0,最大线程数为Interger.MAX_VAULE,任务队列为无容量的SynchronousQueue

  • ScheduledThreadPool:给定的延迟后运行任务或者定期执行任务的线程池。核心线程为n,最大线程为Interger.MAX_VALUE,任务队列长度为最大Interger_MAX_VALUE的DelayQueue

《阿里巴巴 Java 开发手册》中强制线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 构造函数的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险

Executors 返回线程池对象的弊端如下:

  • FixedThreadPoolSingleThreadExecutor:使用的是无界的 LinkedBlockingQueue,任务队列最大长度为 Integer.MAX_VALUE,可能堆积大量的请求,从而导致 OOM。
  • CachedThreadPool:使用的是同步队列 SynchronousQueue, 允许创建的线程数量为 Integer.MAX_VALUE ,如果任务数量过多且执行速度较慢,可能会创建大量的线程,从而导致 OOM。
  • ScheduledThreadPoolSingleThreadScheduledExecutor:使用的无界的延迟阻塞队列DelayedWorkQueue,任务队列最大长度为 Integer.MAX_VALUE,可能堆积大量的请求,从而导致 OOM。

ThreadPoolExecutor使用详解

其实java线程池的实现原理很简单,说白了就是一个线程集合workerSet和一个阻塞队列workQueue。当用户向线程池提交一个任务(也就是线程)时,线程池会先将任务放入workQueue中。workerSet中的线程会不断的从workQueue中获取线程然后执行。当workQueue中没有任务的时候,worker就会阻塞,直到队列中有任务了就取出来继续执行。

img

# Execute原理

image
https://excalidraw.com/#json=Y7a8uMRrIrZNGfr7GnQ0M,2qUB-ZmXM-hwP0a3MWnA1A

当一个任务提交至线程池之后:

  1. 线程池首先当前运行的线程数量是否少于corePoolSize。如果是,则创建一个新的工作线程来执行任务。如果都在执行任务,则进入2.
  2. 判断BlockingQueue是否已经满了,倘若还没有满,则将线程放入BlockingQueue。否则进入3.
  3. 如果创建一个新的工作线程将使当前运行的线程数量超过maximumPoolSize,则交给RejectedExecutionHandler来处理任务。

运行机制(当任务来了之后的执行流程):

  1. 判断核心线程数是否已满;如果未满创建核心线程执行任务;如果满了执行后续操作。
  2. 判断任务队列是否已满;如果未满将任务添加到队列;如果满了执行后续流程。
  3. 判断最大线程数是否已满;如果未满创建临时线程执行任务;如果满了执行后续流程。
  4. 执行拒绝策略(内置4种拒绝策略+自定义的拒绝策略)。

当ThreadPoolExecutor创建新线程时,通过CAS来更新线程池的状态ctl.

# 参数

1
2
3
4
5
6
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
RejectedExecutionHandler handler)
  • corePoolSize 线程池中的核心线程数,当提交一个任务时,线程池创建一个新线程执行任务,直到当前线程数等于corePoolSize, 即使有其他空闲线程能够执行新来的任务, 也会继续创建线程;如果当前线程数为corePoolSize,继续提交的任务被保存到阻塞队列中,等待被执行;如果执行了线程池的prestartAllCoreThreads()方法,线程池会提前创建并启动所有核心线程。
  • workQueue 用来保存等待被执行的任务的阻塞队列. 在JDK中提供了如下阻塞队列: 具体可以参考JUC 集合: BlockQueue详解
    • ArrayBlockingQueue: 基于数组结构的有界阻塞队列,按FIFO排序任务;
    • LinkedBlockingQueue: 基于链表结构的阻塞队列,按FIFO排序任务,吞吐量通常要高于ArrayBlockingQueue;
    • SynchronousQueue: 一个不存储元素的阻塞队列,每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于LinkedBlockingQueue;
    • PriorityBlockingQueue: 具有优先级的无界阻塞队列;

LinkedBlockingQueueArrayBlockingQueue在插入删除节点性能方面更优,但是二者在put(), take()任务的时均需要加锁,SynchronousQueue使用无锁算法,根据节点的状态判断执行,而不需要用到锁,其核心是Transfer.transfer().

  • maximumPoolSize 线程池中允许的最大线程数。如果当前阻塞队列满了,且继续提交任务,则创建新的线程执行任务,前提是当前线程数小于maximumPoolSize;当阻塞队列是无界队列, 则maximumPoolSize则不起作用, 因为无法提交至核心线程池的线程会一直持续地放入workQueue.
  • keepAliveTime 线程空闲时的存活时间,即当线程没有任务执行时,该线程继续存活的时间;默认情况下,该参数只在线程数大于corePoolSize时才有用, 超过这个时间的空闲线程将被终止;
  • unit keepAliveTime的单位
  • threadFactory 创建线程的工厂,通过自定义的线程工厂可以给每个新建的线程设置一个具有识别度的线程名。默认为DefaultThreadFactory
  • handler 线程池的饱和策略,当阻塞队列满了,且没有空闲的工作线程,如果继续提交任务,必须采取一种策略处理该任务,线程池提供了4种策略:
    • AbortPolicy: 直接抛出异常,默认策略;
    • CallerRunsPolicy: 用调用者所在的线程来执行任务;
    • DiscardOldestPolicy: 丢弃阻塞队列中靠最前的任务,并执行当前任务;
    • DiscardPolicy: 直接丢弃任务;

当然也可以根据应用场景实现RejectedExecutionHandler接口,自定义饱和策略,如记录日志或持久化存储不能处理的任务。

※线程池ThreadPoolExecutor

常见的线程池有:

无缓存线程

· 定长线程池(最常见,如Glide)

FixedThreadPool:根据入参决定有多少个核心线程,无缓存线程。 可重用固定线程数的线程池。(适用于负载比较重的服务器) FixedThreadPool使用无界队列LinkedBlockingQueue作为线程池的工作队列,该线程池中的线程数量始终不变。当有一个新的任务提交时,线程池中若有空闲线程,则立即执行。若没有,则新的任务会被暂存在一个任务队列中,待有线程空闲时,便处理在任务队列 中的任务。

· 单线程线程池

SingleThreadExecutor:只有一个核心线程,最大线程也为1,无缓存线程。所有任务在此线程中FIFO进行只会创建一个线程执行任务。(适用于需要保证顺序执行各个任 务;并且在任意时间点,没有多线程活动的场景。) SingleThreadExecutorl也使用无界队列LinkedBlockingQueue作为工作队列 若多余一个任务被提交到该线程池,任务会被保存在一个任务队列中,待线程空闲,按先入先 出的顺序执行队列中的任务。

· 定时线程池

ScheduledThreadPool:只有入参数量的核心线程,无缓存线程。继承自ThreadPoolExecutor。它主要用来在给定的延迟之后运行 任务,或者定期执行任务。使用DelayQueue作为任务队列。如newScheduledThreadPool,用于定时任务。

无核心线程

· 缓存线程池

CachedThreadPool:无核心线程,无限制地增加执行完成就销毁(根据keepaliveTime决定)的缓存线程,是一个会根据需要调整线程数量的线程池。(大小无界,适用于执行很 多的短期异步任务的小程序,或负载较轻的服务器) CachedThreadPool使用没有容量的SynchronousQueue作为线程池的工作队列,但 CachedThreadPool的maximumPool是无界的。线程池的线程数量不确定,但若有空闲线程可以复用,则会优先使用可复用的线程。若所有线 程均在工作,又有新的任务提交,则会创建新的线程处理任务。所有线程在当前任务执行完毕 后,将返回线程池进行复用。

作用

  1. 降低资源消耗:通过重复利用现有的线程来执行任务,避免多次创建和销毁线程。一个线程保留1M大小的内存空间,有效降低OOM
  2. 提高相应速度:因为省去了创建线程这个步骤,所以在拿到任务时,可以立刻开始执行。

线程数应该怎么设置

  • 如果任务是IO密集型,一般线程数需要设置2倍CPU数以上(2N),以此来尽量利用CPU资源。
  • 如果任务是CPU密集型,一般线程数量只需要设置CPU数加1即可,更多的线程数也只能增加上下文切换,不能增加CPU利用率。

在计算密集型任务中,将线程池大小设置为 CPU 核心数 + 1 的原因是为了应对可能出现的阻塞情况。 虽然计算密集型任务主要消耗 CPU 资源,但在实际应用中,任务内部可能仍然存在一些阻塞操作, 例如:
同步 IO: 如果任务需要进行磁盘读写或网络通信等 IO 操作,并且这些操作是同步阻塞的,那么当前线程会被阻塞, 无法继续执行计算任务。
锁竞争: 如果任务中存在对共享资源的访问,并且使用了锁机制进行同步, 那么当多个线程同时竞争锁时, 部分线程会被阻塞, 等待获取锁。
页面错误: 当线程访问的内存页面不在物理内存中时,会发生页面错误, 导致线程被阻塞, 等待操作系统将页面从磁盘加载到内存。 如果线程池大小刚好等于 CPU 核心数,那么当一个线程被阻塞时,CPU 就无法充分利用,导致整体性能下降。 而增加一个额外的线程,可以确保在某个线程被阻塞时, 仍然有足够的线程可以继续执行计算任务, 从而提高 CPU 利用率和整体性能。 当然,这只是一个经验法则,并不是绝对的。 在某些情况下, 如果任务中不存在阻塞操作, 或者阻塞情况非常少见, 那么将线程池大小设置为 CPU 核心数也可能足够。 最佳的线程池大小仍然需要根据你的具体应用场景和硬件环境进行调整和测试。

简单来说就是 io多,则用更多线程充分利用cpu;计算多,则用少的线程数减少线程切换,但仍存在的io操作使数量应为n+1;

Android一般认为多数操作是IO密集,如网络io,本地文件io,所以会设置2N

Android系统对每个进程线程数限制

root 下adb shell cat /proc/sys/kernel/threads-max

结果如:57439

但每个线程1M左右,基本上几百个线程就可能OOM了

常见的三方库线程池默认数量

Okhttp

OkHttp中的线程池是定义在分发器中的,即定义在Dispatcher

1
2
3
4
5
6
7
public synchronized ExecutorService executorService() {
if (executorService == null) {
executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,
new SynchronousQueue<>(), Util.threadFactory("OkHttp Dispatcher", false));
}
return executorService;
}

用的其实相当于就是一个无核心线程,最大线程池为Integer.MAX_VALUE,任务队列为SynchronousQueue的缓存线程池

高并发,最大吞吐量。SynchronousQueue队列是无容量队列,
在OkHttp中,配置的线程池的核心线程数为0,最大线程数为Integer.MAX_VALUE,线程的存活时间为60s,采用的队列是SynchronousQueue。

  1. okhttp 默认同时支持 64 个异步请求(不考虑同步请求),一个 host 同时最多请求 5 个
  2. okhttp 内部的线程池都是 CacheThreadPool:核心线程数为 0,非核心线程数无限,永远添加不到等待队列中
  3. okhttpClient 如果不单例,会出现 oom:因为大量的 Dispatcher 对象,不同的对象会使用不同的线程去发起网络请求,从而导致线程过多,OOM

Glide

Glide用的都是核心线程数与最大线程数一致(cpu数量与4的最小值),任务队列为PriorityBlockingQueue的定长线程池。(所以Glide的最大并发量是四个图片?)

glide加载的线程池的配置,使用cpu数量与4的最小值,即线程池的核心线程和最大线程数不超过4个

image-20240424204306358

image-20240426113224120

线程池关闭方法区别

shutdown() 、 shutdownNow() 、 awaitTermination() 的用法和区别

shutdown()

将线程池状态置为SHUTDOWN,并不会立即停止:

  • 停止接收外部submit的任务
  • 内部正在跑的任务和队列里等待的任务,会执行完
  • 等到第二步完成后,才真正停止

shutdownNow()

将线程池状态置为STOP。企图立即停止,事实上不一定:

  • 跟shutdown()一样,先停止接收外部提交的任务
  • 忽略队列里等待的任务
  • 尝试将正在跑的任务interrupt中断

返回未执行的任务列表

awaitTermination()

awaitTermination(long timeOut, TimeUnit unit)

当前线程阻塞,直到

  • 等所有已提交的任务(包括正在跑的和队列中等待的)执行完
  • 或者等超时时间到
  • 或者线程被中断,抛出InterruptedException

然后返回true(shutdown请求后所有任务执行完毕)或false(已超时)

小结:

  • 优雅的关闭,用shutdown(),停止接受任务等待进行中的和队列中的任务都执行完后停止。
  • 想立马中断并关闭,并得到未执行任务列表,用shutdownNow(),会interrupt正在进行的任务忽略队列中任务,返回未执行的任务队列
  • 优雅的关闭,并允许关闭声明后新任务能提交,用awaitTermination()

线程池都有哪几种工作队列?

ArrayBlockingQueue:是一个基于数组结构的有界阻塞队列,此队列按FIFO(先进先出)原则对元素进行排序。

LinkedBlockingQueue:是一个基于链表结构的阻塞队列,此队列按FIFO排序元素,吞吐量 通常要高于ArrayBlockingQueue。静态工厂方法Executors.newFixedThreadPool()、newSingleThreadExecutor使用了 这个队列。

SynchronousQueue:是一个不存储元素的阻塞队列。每个插入操作必须等到另一个线程调用 移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于LinkedBlockingQueue。

DelayedWorkQueue:是一个阻塞队列。保证添加到队列中的任务,会按照任务的延时时间进行排序,延时时间少的任务首先被获取。newScheduledThreadPool使用了这个队列。

假设向线程池提交任务时,核心线程都被占用的情况下:

ArrayBlockingQueue:基于数组的阻塞队列,初始化需要指定固定大小。

​ 当使用此队列时,向线程池提交任务,会首先加入到等待队列中,当等待队列满了之后,再次提交任务,尝试加入队列就会失败,这时就会检查如果当前线程池中的线程数未达到最大线程,则会新建线程执行新提交的任务。所以最终可能出现后提交的任务先执行,而先提交的任务一直在等待。

LinkedBlockingQueue:基于链表实现的阻塞队列,初始化可以指定大小,也可以不指定。

​ 当指定大小后,行为就和ArrayBlockingQueu一致。而如果未指定大小,则会使用默认的Integer.MAX_VALUE作为队列大小。这时候就会出现线程池的最大线程数参数无用,因为无论如何,向线程池提交任务加入等待队列都会成功。最终意味着所有任务都是在核心线程执行。如果核心线程一直被占,那就一直等待。

SynchronousQueue : 无容量的队列。

​ 使用此队列意味着希望获得最大并发量。因为无论如何,向线程池提交任务,往队列提交任务都会失败。而失败后如果没有空闲的非核心线程,就会检查如果当前线程池中的线程数未达到最大线程,则会新建线程执行新提交的任务。完全没有任何等待,唯一制约它的就是最大线程数的个数。因此一般配合Integer.MAX_VALUE就实现了真正的无等待。

//lqr:TODO https://juejin.cn/post/6847902225730109454

拒绝策略rejectHander

当 Executor 已关闭时,以及当 Executor 对最大线程和工作队列容量使用有限界限且已饱和时,在方法execute(Runnable)中提交的新任务将被拒绝。在任一情况下, execute方法都会调用其RejectedExecutionHandlerRejectedExecutionHandler.rejectedExecution(Runnable, ThreadPoolExecutor)方法。提供了四种预定义的处理程序策略:

  1. 在默认的ThreadPoolExecutor.AbortPolicy中,处理程序在被拒绝时会抛出运行时RejectedExecutionException

    1
    2
    3
    throw new RejectedExecutionException("Task " + r.toString() +
    " rejected from " +
    e.toString());
  2. 在ThreadPoolExecutor.CallerRunsPolicy中,调用execute的线程本身会运行任务。这提供了一种简单的反馈控制机制,可以减慢提交新任务的速度。

    1
    2
    3
    if (!e.isShutdown()) {
    r.run();
    }
  3. 在ThreadPoolExecutor.DiscardPolicy中,无法执行的任务将被直接丢弃。

    1
    //do nothing
  4. 在ThreadPoolExecutor.DiscardOldestPolicy中,如果执行器未关闭,则工作队列头部的任务将被删除,然后重试执行(这可能会再次失败,导致重复此操作。)

    1
    2
    3
    4
    if (!e.isShutdown()) {
    e.getQueue().poll();
    e.execute(r);
    }

如果提交任务时,线程池队列已满,会发生什么

如果使用的LinkedBlockingQueue,也就是无界队列的话,继续添加任务到阻塞队列中等待执行,因为LinkedBlockingQueue可以无限存放任务;如果使用的是有界队列比方说ArrayBlockingQueue的话,则会使用拒绝策略RejectedExecutionHandler处理满了的任务。

JvmMemoryStructure

image-20240425141403378

程序计数器不会OOM和StackOverflow

有栈的结构(栈 java stack、navtive stack)可能发生 StackOverflowError(栈过深) 和 OOM

StackOverFlowError ︰若Java 虚拟机栈的内存大小不允许动态扩展,那么当线程请求栈的深度超过当前Java 虚拟机栈的最大深度的时候,就抛出StackOverFlowError错误。
OutOfMemoryError :如果虚拟机栈可以动态扩展(当前大部分的 Java 虚拟机都可动态扩展,只不过 Java 虚拟机规范中也允许固定长度的虚拟机栈),当扩展时无法申请到足够的内存时会抛出 OutOfMemoryError 异常。

没栈的结构(堆heap、方法区Method Area)只可能发生 OOM

Read more

CodeOptimization

# 代码优化

1:纯函数

纯函数是函数编程中的一个概念,指的是一个方法函数,就像一个数学的函数式一样,同样的参数输入会得到同样的输出。
可以近似理解为函数内部不依赖任何的外部状态,外部变量。

2:空安全

java是强对象类型语言,故对于java来说我们很容易关注到类型强转的错误

Read more

Fragment

https://developer.android.com/guide/fragments/lifecycle?hl=zh-cn

生命周期

//简述:还有一个生命周期是 onViewCreated(),该生命周期会在onCreateView后立即调用(此时布局inflate已完成),故而一般fragment的onCreateView中执行inflate layout操作后返回rootView,之后在onViewCreated中执行具体的View操作。

168137f2adfe6b44~tplv-t2oaga2asx-jj-mark:3024:0:0:0:q75

Use

FragmentTransaction的4种提交方式

commit():

commit是非同步提交(我认为不应称为异步)检查是否存储状态

The commit does not happen immediately; it will be scheduled as work on the main thread to be done the next time that thread is ready.

非同步提交:即操作会被post到主线程handler的消息队列中,等候轮到时执行;

检查存储状态:如果宿主(FragmentActivity)已经执行了onSaveInstanceState再执行该操作,会抛出异常

1
2
3
4
5
6
//FragmentManager.class
private void checkStateLoss() {
if (this.isStateSaved()) {
throw new IllegalStateException("Can not perform this action after onSaveInstanceState");
}
}

commitAllowingStateLoss():

非同步提交不检查状态

如果在宿主执行了onSaveInstanceSate之后再执行该操作,不会去检查宿主状态,不会抛出异常。但该操作不会被Activity记录,恢复时也就没办法恢复这些提交操作,所以该操作适用不重要的事务。同属于异步事务。

commitNow():

同步提交检查状态

会立刻执行当前提交的transaction事务。

commitNowAllowingStateLoss():

同步提交不检查状态

既是同步执行,也不会检查宿主的状态,有可能该操作不会被正确恢复

同时:使用 commitNow() 或 commitNowAllowingStateLoss() 提交的事务不允许加入回退栈

1
2
3
4
5
6
7
8
9
10
11
@Override
public void commitNow() {
disallowAddToBackStack();
mManager.execSingleAction(this, false);
}

@Override
public void commitNowAllowingStateLoss() {
disallowAddToBackStack();
mManager.execSingleAction(this, true);
}

Principle

源码中fragment的生命周期方法回调在于 内置定义的五种状态的转移:

当宿主生命周期发生变化时,Fragment 的状态会同步到宿主的状态。从源码看,体现在宿主生命周期回调中会调用 FragmentManager 中一系列 dispatchXXX() 方法来触发 Fragment 状态转移。

1
2
3
4
5
6
7
8
//FragmentActivity
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

mFragmentLifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_CREATE);
mFragments.dispatchCreate();
}
1
2
3
4
5
6
7
8
9
10
11
12
//android.fragment:fragment:1.3.6   Fragment		
static final int INITIALIZING = -1; // Not yet attached.
static final int ATTACHED = 0; // Attached to the host.
static final int CREATED = 1; // Created.
static final int VIEW_CREATED = 2; // View Created.
static final int AWAITING_EXIT_EFFECTS = 3; // Downward state, awaiting exit effects
static final int ACTIVITY_CREATED = 4; // Fully created, not started.
static final int STARTED = 5; // Created and started, not resumed.
static final int AWAITING_ENTER_EFFECTS = 6; // Upward state, awaiting enter effects
static final int RESUMED = 7; // Created started and resumed.

int mState = INITIALIZING;

INITIALIZING,ATTACHED,CREATED,VIEW_CREATED,ACTIVITY_CREATED,STARTED,RESUMED七种状态轮换,每个状态轮换之间,生命周期也就自然被调用到

如一次创建流程,

枚举状态INITALIZING->RESUME,就会依次调用到:onAttach(), onCreate(), onCreateView(), onActivityCreate(), onStart(), onResume();

反之,RESUME->INITIALIZING,就会依次调用:onPause(), onStop(), onSaveInstanceState(), onDestroyView(), onDestroy(), onDetach()

当然,是否需要被调用,case也会自己判断;

image-20231225150420117
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
 @SuppressWarnings("deprecation")
void moveToState(@NonNull Fragment f, int newState) {
FragmentStateManager fragmentStateManager = mFragmentStore.getFragmentStateManager(f.mWho);
if (fragmentStateManager == null) {
// Ideally, we only call moveToState() on active Fragments. However,
// in restoreSaveState() we can call moveToState() on retained Fragments
// just to clean them up without them ever being added to mActive.
// For these cases, a brand new FragmentStateManager is enough.
fragmentStateManager = new FragmentStateManager(mLifecycleCallbacksDispatcher,
mFragmentStore, f);
// Only allow this FragmentStateManager to go up to CREATED at the most
fragmentStateManager.setFragmentManagerState(Fragment.CREATED);
}
// When inflating an Activity view with a resource instead of using setContentView(), and
// that resource adds a fragment using the <fragment> tag (i.e. from layout and in layout),
// the fragment will move to the VIEW_CREATED state before the fragment manager
// moves to CREATED. So when moving the fragment manager moves to CREATED and the
// inflated fragment is already in VIEW_CREATED we need to move new state up from CREATED
// to VIEW_CREATED. This avoids accidentally moving the fragment back down to CREATED
// which would immediately destroy the Fragment's view. We rely on computeExpectedState()
// to pull the state back down if needed.
if (f.mFromLayout && f.mInLayout && f.mState == Fragment.VIEW_CREATED) {
newState = Math.max(newState, Fragment.VIEW_CREATED);
}
newState = Math.min(newState, fragmentStateManager.computeExpectedState());
if (f.mState <= newState) {
// If we are moving to the same state, we do not need to give up on the animation.
if (f.mState < newState && !mExitAnimationCancellationSignals.isEmpty()) {
// The fragment is currently being animated... but! Now we
// want to move our state back up. Give up on waiting for the
// animation and proceed from where we are.
cancelExitAnimation(f);
}
switch (f.mState) {
case Fragment.INITIALIZING:
if (newState > Fragment.INITIALIZING) {
fragmentStateManager.attach();
}
// fall through
case Fragment.ATTACHED:
if (newState > Fragment.ATTACHED) {
fragmentStateManager.create();
}
// fall through
case Fragment.CREATED:
// We want to unconditionally run this anytime we do a moveToState that
// moves the Fragment above INITIALIZING, including cases such as when
// we move from CREATED => CREATED as part of the case fall through above.
if (newState > Fragment.INITIALIZING) {
fragmentStateManager.ensureInflatedView();
}

if (newState > Fragment.CREATED) {
fragmentStateManager.createView();
}
// fall through
case Fragment.VIEW_CREATED:
if (newState > Fragment.VIEW_CREATED) {
fragmentStateManager.activityCreated();
}
// fall through
case Fragment.ACTIVITY_CREATED:
if (newState > Fragment.ACTIVITY_CREATED) {
fragmentStateManager.start();
}
// fall through
case Fragment.STARTED:
if (newState > Fragment.STARTED) {
fragmentStateManager.resume();
}
}
} else if (f.mState > newState) {
switch (f.mState) {
case Fragment.RESUMED:
if (newState < Fragment.RESUMED) {
fragmentStateManager.pause();
}
// fall through
case Fragment.STARTED:
if (newState < Fragment.STARTED) {
fragmentStateManager.stop();
}
// fall through
case Fragment.ACTIVITY_CREATED:
if (newState < Fragment.ACTIVITY_CREATED) {
if (isLoggingEnabled(Log.DEBUG)) {
Log.d(TAG, "movefrom ACTIVITY_CREATED: " + f);
}
if (f.mView != null) {
// Need to save the current view state if not
// done already.
if (mHost.onShouldSaveFragmentState(f) && f.mSavedViewState == null) {
fragmentStateManager.saveViewState();
}
}
}
// fall through
case Fragment.VIEW_CREATED:
if (newState < Fragment.VIEW_CREATED) {
FragmentAnim.AnimationOrAnimator anim = null;
if (f.mView != null && f.mContainer != null) {
// Stop any current animations:
f.mContainer.endViewTransition(f.mView);
f.mView.clearAnimation();
// If parent is being removed, no need to handle child animations.
if (!f.isRemovingParent()) {
if (mCurState > Fragment.INITIALIZING && !mDestroyed
&& f.mView.getVisibility() == View.VISIBLE
&& f.mPostponedAlpha >= 0) {
anim = FragmentAnim.loadAnimation(mHost.getContext(),
f, false, f.getPopDirection());
}
f.mPostponedAlpha = 0;
// Robolectric tests do not post the animation like a real device
// so we should keep up with the container and view in case the
// fragment view is destroyed before we can remove it.
ViewGroup container = f.mContainer;
View view = f.mView;
if (anim != null) {
FragmentAnim.animateRemoveFragment(f, anim,
mFragmentTransitionCallback);
}
container.removeView(view);
if (FragmentManager.isLoggingEnabled(Log.VERBOSE)) {
Log.v(FragmentManager.TAG, "Removing view " + view + " for "
+ "fragment " + f + " from container " + container);
}
// If the local container is different from the fragment
// container, that means onAnimationEnd was called, onDestroyView
// was dispatched and the fragment was already moved to state, so
// we should early return here instead of attempting to move to
// state again.
if (container != f.mContainer) {
return;
}
}
}
// If a fragment has an exit animation (or transition), do not destroy
// its view immediately and set the state after animating
if (mExitAnimationCancellationSignals.get(f) == null) {
fragmentStateManager.destroyFragmentView();
}
}
// fall through
case Fragment.CREATED:
if (newState < Fragment.CREATED) {
if (mExitAnimationCancellationSignals.get(f) != null) {
// We are waiting for the fragment's view to finish animating away.
newState = Fragment.CREATED;
} else {
fragmentStateManager.destroy();
}
}
// fall through
case Fragment.ATTACHED:
if (newState < Fragment.ATTACHED) {
fragmentStateManager.detach();
}
}
}

if (f.mState != newState) {
if (isLoggingEnabled(Log.DEBUG)) {
Log.d(TAG, "moveToState: Fragment state for " + f + " not updated inline; "
+ "expected state " + newState + " found " + f.mState);
}
f.mState = newState;
}
}

ServiceManager

ServiceManager的启动

ServiceManager进程service_manager.c并没有使用libbinder框架代码,而是自行编写了binder.c直接和Binder驱动来通信,ServiceManager是单线程的进程, 不断地循环在binder_loop()过程来读取和处理事务,从而对外提供查询和注册服务的功能,这样的好处是简单而高效。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// service_manager.c
int main(int argc, char **argv) {
struct binder_state *bs;
char *driver;
if (argc > 1) {
driver = argv[1];
} else {
driver = "/dev/binder"; // 默认的Binder设备节点
}

//Step 1: 打开binder驱动,申请128k字节内存
bs = binder_open(driver, 128*1024);
...

//Step 2: 成为上下文管理者
if (binder_become_context_manager(bs)) {
return -1;
}
...

//Step 3: 进入无限循环,处理client端发来的请求
binder_loop(bs, svcmgr_handler);
return 0;
}

启动过程主要划分为以下几个阶段:(每个阶段具体见5.2.1 启动ServiceManager服务)

create_servicemanager

  • 首先,打开设备驱动:调用binder_open()方法来打开binder驱动,默认地采用/dev/binder设备节点,申请地内存空间大小为128KB;

  • 其次,注册成为大管家:调用binder_become_context_manager()方法,将自己注册成为binder服务的唯一管家;

    通过ioctl系统调用向Binder驱动发送命令BINDER_SET_CONTEXT_MGR,成为上下文的管理者,由于servicemanager进程启动非常早(先于Zygote),可以确定在Binder整体机制正式投入产线之前,就能完成向Binder驱动注册成为大管家的工作。 关于驱动层处理BINDER_SET_CONTEXT_MGR命令的主要任务:

    • 保证每个Binder上下文有且仅有一个binder管家实体,如果已存在则不再创建
    • 创建binder管家实体,初始化异步事务和binder工作两个队列,并分别增加其强弱引用计数
    • 初始化当前binder_context的管家实体(binder_context_mgr_node)和管家uid(binder_context_mgr_uid)信息
    • handle等于0的服务实体都是指servicemanager管家实体
  • 最后,等待客户请求:调用binder_loop()方法进入无限循环,作为守护进程,随时待命等待处理client端发来的请求。

servicemanager先向Binder驱动发送BC_ENTER_LOOPER协议,让ServiceManager进入循环。然后再向驱动发送BINDER_WRITE_READ命令, 进入内核态,等待客户端的请求数据。若没有数据,则进入等待状态,直到收到数据后返回用户态,解析并处理,周而复始地不断循环该过程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// servicemanager/binder.c
void binder_loop(struct binder_state *bs, binder_handler func) {
int res;
struct binder_write_read bwr;
uint32_t readbuf[32];

bwr.write_size = 0;
bwr.write_consumed = 0;
bwr.write_buffer = 0;

readbuf[0] = BC_ENTER_LOOPER;
//向binder驱动发送BC_ENTER_LOOPER协议
binder_write(bs, readbuf, sizeof(uint32_t));

for (;;) {
bwr.read_size = sizeof(readbuf);
bwr.read_consumed = 0;
bwr.read_buffer = (uintptr_t) readbuf;
//等待客户的数据
res = ioctl(bs->fd, BINDER_WRITE_READ, &bwr);
//解析binder信息
res = binder_parse(bs, 0, (uintptr_t) readbuf, bwr.read_consumed, func);
}
}

ServiceManager提供的服务

ServiceManager对外提供查询/注册功能,通过接收到客户端进程发送过来的BR_TRANSACTION协议。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
// service_manager.c
int svcmgr_handler(struct binder_state *bs,
struct binder_transaction_data *txn,
struct binder_io *msg,
struct binder_io *reply)
{
struct svcinfo *si;
uint16_t *s;
size_t len;
uint32_t handle;
uint32_t strict_policy;
int allow_isolated;
...
strict_policy = bio_get_uint32(msg);
s = bio_get_string16(msg, &len);
...

switch(txn->code) {
case SVC_MGR_GET_SERVICE:
case SVC_MGR_CHECK_SERVICE:
s = bio_get_string16(msg, &len); //服务名
//根据名称查找相应服务
handle = do_find_service(bs, s, len, txn->sender_euid, txn->sender_pid);
bio_put_ref(reply, handle);
return 0;

case SVC_MGR_ADD_SERVICE:
s = bio_get_string16(msg, &len); //服务名
handle = bio_get_ref(msg); //服务实体在servicemanager中的handle
allow_isolated = bio_get_uint32(msg) ? 1 : 0;
//注册指定服务
if (do_add_service(bs, s, len, handle, txn->sender_euid,
allow_isolated, txn->sender_pid))
return -1;
break;

case SVC_MGR_LIST_SERVICES: {
uint32_t n = bio_get_uint32(msg);
if (!svc_can_list(txn->sender_pid)) {
return -1;
}
si = svclist;
while ((n-- > 0) && si)
si = si->next;
if (si) {
bio_put_string16(reply, si->name);
return 0;
}
return -1;
}
}

bio_put_uint32(reply, 0);
return 0;
}

该方法的功能:查询服务,注册服务,以及列举所有服务。不同的code对应不同的工作,定义在IBinder.h文件,跟IServiceManager.h中定义的code具有一一对应关系,具体关系如下所示。

code IBinder.h IServiceManager.h
1 SVC_MGR_GET_SERVICE GET_SERVICE_TRANSACTION
2 SVC_MGR_CHECK_SERVICE CHECK_SERVICE_TRANSACTION
3 SVC_MGR_ADD_SERVICE ADD_SERVICE_TRANSACTION
4 SVC_MGR_LIST_SERVICES LIST_SERVICES_TRANSACTION

ServiceManager

servicemanager进程里面有一个链表svclist,记录着所有注册的服务svcinfo,每一个服务用svcinfo结构体来表示,该handle值是在注册服务的过程中,由服务所在进程那一端所确定的。svcinfo结构体如下所示。

ServiceManager进程
1
2
3
4
5
6
7
8
9
struct svcinfo
{
struct svcinfo *next;
uint32_t handle; //服务的handle值
struct binder_death death;
int allow_isolated;
size_t len; //服务名的长度
uint16_t name[0]; //服务名
};

整个ServiceManager启动过程的完整流程,这里整个过程都的都离不开Binder驱动层的实现。

ServiceManager启动过程

Other

vndservicemanager

以前,Binder 服务通过 servicemanager 注册,其他进程可从中检索这些服务。在 Android 8 中,servicemanager 现在专供框架使用,而应用进程和供应商进程无法再对其进行访问。

不过,供应商服务现在可以使用 vndservicemanager,这是一个使用 /dev/vndbinder(作为构建基础的源代码与框架 servicemanager 的相同)而非 /dev/binderservicemanager 的新实例。供应商进程无需更改即可与 vndservicemanager 通信;当供应商进程打开 /dev/vndbinder 时,服务查询会自动转至 vndservicemanager

vndservicemanager 二进制文件包含在 Android 的默认设备 Makefile 中。

Animation

Android动画分三种:View动画、帧动画、属性动画

image-20231027151841094

是的, check, √

View动画

View动画定义了渐变Alpha、旋转Rotate、缩放Scale、平移Translate四种基本动画,并且通过这四种基本动画的组合使用,可以实现多种交互效果。
View动画使用非常简单,不仅可以通过XML文件来定义动画,同样可以通过Java代码来实现动画过程。

原理:

首先view的绘制是 drawBackground() -> onDraw() -> dispatchDraw() -> onDrawForeground() 的顺序,

//android/view/View.java中的boolean draw(Canvas canvas, ViewGroup parent, long drawingTime)方法

View.setAnimation会将旋转、缩放、平移等动画存下来,动画启动后通过invalidate() ,每一帧中在draw的时候通过canvas.translate、canvas.scale、cavas.setLayerAlpha等方式,执行动画。

故而view动画只会影响view的视觉效果,而不影响起事件响应区域,因为只有draw中处理了,measure和layout都没动

Xml文件实现

通过xml来定义View动画涉及到一些公有的属性(在AndroidStudio上不能提示):

1
2
3
4
5
6
7
android:duration     动画持续时间
android:fillAfter 为true动画结束时,View将保持动画结束时的状态
android:fillBefore 为true动画结束时,View将还原到开始开始时的状态
android:repeatCount 动画重复执行的次数
android:repeatMode 动画重复模式 ,重复播放时restart重头开始,reverse重复播放时倒叙回放,该属性需要和android:repeatCount一起使用
android:repeatCount 默认是0,-1是无限循环
android:interpolator 插值器,相当于变速器,改变动画的不同阶段的执行速度

这些属性是从Animation中继承下来的,在alpharotatescaletranslate标签中都可以直接使用。
利用xml文件定义View动画需要在工程的res目录下创建anim文件夹,所有的xml定义的View动画都要放在anim目录下。其中标签 translate、scale、alpha、rotate,就是对应四种动画。set标签是动画集合,对应AnimationSet类,有多个动画构成。

其中android:duration是指动画时间,fillAfter为true是动画结束后保持,false会回到初始状态。interpolator是指动画的执行速度,默认是先加速后减速。其他标签及属性较简单可自行研究验证。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="5000"
android:fillAfter="true"
android:interpolator="@android:anim/accelerate_decelerate_interpolator">
<!--set里面的duration如果有值,会覆盖子标签的duration-->

<translate
android:duration="1000"
android:fromXDelta="0"
android:toXDelta="400" />
<scale
android:duration="2000"
android:fromXScale="0.5"
android:fromYScale="0.5"
android:toXScale="1"
android:toYScale="1" />
<alpha
android:duration="3000"
android:fromAlpha="0.2"
android:toAlpha="1" />

<rotate
android:fromDegrees="0"
android:toDegrees="90" />
</set>

定义好动画后,使用也很简单,调用view的startAnimation方法即可。

1
2
3
//view动画使用,方式一:xml,建议使用。
Animation animation = AnimationUtils.loadAnimation(this, R.anim.animation_test);
textView1.startAnimation(animation);

rotatescale动画的android:pivotXandroid:pivotY属性、translate动画的android:toXDeltaandroid:toYDelta属性的取值都可以是都可以数值、百分数、百分数p,比如:5050%50%p,他们取值的代表的意义各不相同:
50表示以View左上角为原点沿坐标轴正方向(x轴向右,y轴向下)偏移50px的位置;
50%表示以View左上角为原点沿坐标轴正方向(x轴向右,y轴向下)偏移View宽度或高度的50%处的位置;
50%p表示以View左上角为原点沿坐标轴正方向(x轴向右,y轴向下)偏移父控件宽度或高度的50%处的位置(p表示相对于ParentView的位置)。

“50”:img

“50%”img

“50%p”img

代码动态实现

在平常的业务逻辑中也可以直接用Java代码来实现Veiw动画,Android系统给我们提供了AlphaAnimationRotateAnimationScaleAnimationTranslateAnimation四个动画类分别来实现View的渐变、旋转、缩放、平移动画。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
//view动画使用,方式二:new 动画对象
AnimationSet animationSet = new AnimationSet(false);
animationSet.setDuration(3000);
animationSet.addAnimation(new TranslateAnimation(0, 100, 0, 0));
animationSet.addAnimation(new ScaleAnimation(0.1f, 1f, 0.1f, 1f));
animationSet.setFillAfter(true);
textView2.startAnimation(animationSet);

//view动画使用,方式二:new 动画对象,使用setAnimation
AnimationSet animationSet2 = new AnimationSet(false);
animationSet2.setDuration(3000);
animationSet2.addAnimation(new TranslateAnimation(0, 100, 0, 0));
animationSet2.addAnimation(new ScaleAnimation(0.1f, 1f, 0.1f, 1f));
animationSet2.setFillAfter(true);
animationSet2.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {

}
@Override
public void onAnimationEnd(Animation animation) {
MyToast.showMsg(AnimationTestActivity.this, "View动画:代码 set:View动画结束~");
}
@Override
public void onAnimationRepeat(Animation animation) {

}
});
textView3.setAnimation(animationSet2);

注意点:

  1. startAnimation方法是立刻播放动画;setAnimation是设置要播放的下一个动画。
  2. setAnimationListener可以监听动画的开始、结束、重复。

自定义动画

[3D旋转动画]

Like:ProgressBarAnimation

1
2
3
4
5
6
7
8
class ProgressBarAnimation(private val progressBar: ProgressBar, private val from: Int, private val to: Int) :
Animation() {
override fun applyTransformation(interpolatedTime: Float, t: Transformation?) {
super.applyTransformation(interpolatedTime, t)
val value = from + (to - from) * interpolatedTime
progressBar.progress = value.toInt()
}
}

布局动画

LayoutTransition

使用LayoutAnimation给ViewGroup指定child的出场动画,方法如下:

1.先用xml定义标签LayoutAnimation:

  • android:animation设置child的出场动画
  • android:animationOrder设置child的出场顺序,normal就是顺序
  • delay是指:每个child延迟(在android:animation中指定的动画时间)0.8倍后播放动画。如果android:animation中的动画时间是100ms,那么每个child都会延迟800ms后播放动画。 如果不设置delay,那么所有child同时执行动画。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?xml version="1.0" encoding="utf-8"?>
<layoutAnimation xmlns:android="http://schemas.android.com/apk/res/android"
android:animation="@anim/enter_from_left_for_child_of_group"
android:animationOrder="normal"
android:delay="0.8">
</layoutAnimation>
R.anim.enter_from_left_for_child_of_group

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate
android:duration="1000"
android:fromXDelta="-100%p"
android:toXDelta="0"/>

</set>

2.把LayoutAnimation设置给ViewGroup

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<LinearLayout
android:id="@+id/ll_layout_animation"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layoutAnimation="@anim/layout_animation">
<TextView
android:layout_width="50dp"
android:layout_height="wrap_content"
android:textColor="#ff0000"
android:text="呵呵呵"/>
<TextView
android:layout_width="60dp"
android:layout_height="wrap_content"
android:textColor="#ff0000"
android:text="qq"
android:background="@color/colorPrimary"/>
<TextView
android:layout_width="30dp"
android:layout_height="wrap_content"
android:textColor="#ff0000"
android:text="啊啊"/>
</LinearLayout>

除了xml,当然也可以使用LayoutAnimationController 指定:

1
2
3
4
5
6
//代码设置LayoutAnimation,实现ViewGroup的child的出场动画
Animation enterAnim = AnimationUtils.loadAnimation(this, R.anim.enter_from_left_for_child_of_group);
LayoutAnimationController controller = new LayoutAnimationController(enterAnim);
controller.setDelay(0.8f);
controller.setOrder(LayoutAnimationController.ORDER_NORMAL);
llLayoutAnimation.setLayoutAnimation(controller);

animateLayoutChanges用处及原理

1
android:animateLayoutChanges="true"

animateLayoutChanges的实际实现就是LayoutTransition,

android.view.ViewGroup.java

image-20231026114011752

Dialog/Activity转场动画

Activity转场

overridePendingTransition

1
2
3
4
5
6
7
8
9
class XXActivity : Activity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_checkout_rec)
overridePendingTransition(
R.anim.slide_in_down, R.anim.slide_in_down
)
}
}
1
2
3
4
5
6
7
<?xml version="1.0" encoding="utf-8"?>

<set
xmlns:android="http://schemas.android.com/apk/res/android"
android:interpolator="@android:anim/decelerate_interpolator" >
<translate android:duration="200" android:fromYDelta="100%p" android:toYDelta="0%p" />
</set>
1
2
3
4
5
6
7
<?xml version="1.0" encoding="utf-8"?>

<set
xmlns:android="http://schemas.android.com/apk/res/android"
android:interpolator="@android:anim/decelerate_interpolator" >
<translate android:duration="200" android:fromYDelta="0%p" android:toYDelta="100%p" />
</set>

Dialog转场

Window?.setWindowAnimatinos()

1
2
3
4
5
6
7
8
9
class XXDialog(context: Context, val anim: PointBean.PointAnimation) :Dialog(context) {
init {
setContentView(R.layout.threshold_dialog)
setCanceledOnTouchOutside(true)
window?.setGravity(Gravity.CENTER)
window?.setLayout(MATCH_PARENT, AndroidUtil.getScreenWidth(context))
window?.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT));
window?.setWindowAnimations(R.style.XXDialogAnim)
}
1
2
3
4
<style name="XXDialogAnim" mce_bogus="1" parent="android:Animation">
<item name="android:windowEnterAnimation">@anim/cart_threshold_dialog_enter_anim</item>
<item name="android:windowExitAnimation">@anim/cart_threshold_dialog_exit_anim</item>
</style>

属性动画

属性动画本质上是 使用反射调用对象的setXX()、getXX()方法,根据插值器的值变化曲线修改对象属性,所以是视图实实在在的位置、尺寸等属性发生变化并会触发measure、layout,因此点击区域也就发生变化。

属性动画可对任意对象做动画,不仅仅是View。默认动画时间是300ms,10ms/帧。具体理解就是:可在给定的时间间隔内 实现 对象的某属性值 从 value1 到 value2的改变。

使用很简单,可以直接代码实现(推荐),也可xml实现,举例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
//属性动画使用,方式一:代码,建议使用。 横移
ObjectAnimator translationX = ObjectAnimator
.ofFloat(textView6, "translationX", 0, 200)
.setDuration(1000);
translationX.setInterpolator(new LinearInterpolator());
setAnimatorListener(translationX);

//属性动画使用,方式二:xml。 竖移
Animator animatorUpAndDown = AnimatorInflater.loadAnimator(this, R.animator.animator_test);
animatorUpAndDown.setTarget(textView6);

//文字颜色变化
ObjectAnimator textColor = ObjectAnimator
.ofInt(textView6, "textColor", 0xffff0000, 0xff00ffff)
.setDuration(1000);
textColor.setRepeatCount(ValueAnimator.INFINITE);
textColor.setRepeatMode(ValueAnimator.REVERSE);
//注意,这里如果不设置 那么颜色就是跳跃的,设置ArgbEvaluator 就是连续过度的颜色变化
textColor.setEvaluator(new ArgbEvaluator());

//animatorSet
mAnimatorSet = new AnimatorSet();
mAnimatorSet
.play(animatorUpAndDown)
.with(textColor)
.after(translationX);

mAnimatorSet.start();


/**
* 设置属性动画的监听
* @param translationX
*/
private void setAnimatorListener(ObjectAnimator translationX) {
translationX.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
//每播放一帧,都会调用
}
});
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
translationX.addPauseListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationResume(Animator animation) {
super.onAnimationResume(animation);
}
});
}

translationX.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
}
});
}

R.animator.animator_test,是放在res/animator中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
<?xml version="1.0" encoding="utf-8"?>
<!--属性动画test,一般建议采用代码实现,不用xml-->
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:ordering="sequentially">


<!--repeatCount:默认是0,-1是无限循环-->
<!--repeatMode:重复模式:restart-从头来一遍、reverse-反向来一遍-->
<!--valueType:指定propertyName的类型可选intType、floatType-->

<!--android:pathData=""
android:propertyXName=""
android:propertyYName=""-->
<objectAnimator
android:propertyName="translationY"
android:duration="1000"
android:valueFrom="0"
android:valueTo="120"
android:startOffset="0"
android:repeatCount="0"
android:repeatMode="reverse"
android:valueType="floatType"
android:interpolator="@android:interpolator/accelerate_decelerate" />

<!--animator对用vueAnimator,比objectAnimator少了propertyName-->
<!--<animator-->
<!--android:duration="2000"-->
<!--android:valueFrom=""-->
<!--android:valueTo=""-->
<!--android:startOffset=""-->
<!--android:repeatCount=""-->
<!--android:repeatMode=""-->
<!--android:valueType=""-->
<!--android:interpolator=""-->
<!--android:pathData=""-->
<!--android:propertyXName=""-->
<!--android:propertyYName=""/>-->

</set>

translationX是实现横移,animatorUpAndDown是实现竖移、textColor是实现文字颜色变化。其中animatorUpAndDown是使用xml定义,标签含义也很好理解。 最后使用AnimatorSet的play、with、after 实现 先横移,然后 竖移和颜色变化 同时的动画集合效果。

注意点

  1. 关于View动画和属性动画的平移属性动画改变属性值setTranslationX 的视图效果像view动画的平移一样,都是view实际的layout位置没变,只改变了视图位置;不同点是属性动画 给触摸点生效区域增加了位移(而view动画仅改变了视图位置)。
  2. 插值器:Interpolator,根据 时间流逝的百分比,计算当前属性值改变的百分比。 例如duration是1000,start后过了200,那么时间百分比是0.2,那么如果差值器是LinearInterpolator线性差值器,那么属性值改变的百分比也是0.2
  3. 估值器:Evaluator,就是根据 差值器获取的 属性值百分比,计算改变后的属性值。 ofInt、onFloat内部会自动设置IntEvaluator、FloatEvaluator。如果使用ofInt且是颜色相关的属性,就要设置ArgbEvaluator。 上面例子中 文字颜色变化动画 设置了ArgbEvaluator:textColor.setEvaluator(new ArgbEvaluator())。
  4. 动画监听:主要是两个监听接口,AnimatorUpdateListener、AnimatorListenerAdapter。AnimatorUpdateListener的回调方法在每帧更新时都会调用一次;AnimatorListenerAdapter可以监听开始、结束、暂停、继续、重复、取消,重写你要关注的方法即可。

对任意属性做动画

一个问题,针对下面的Button,如何实现 的宽度逐渐拉长的动画,即文字不变,仅拉长背景宽度?

1
2
3
4
5
<Button
android:id="@+id/button_animator_test"
android:layout_width="180dp"
android:layout_height="wrap_content"
android:text="任意属性动画-宽度拉长"/>

首先,View动画的ScaleAnimation是无法实现的,因为view的scale是把view的视图放大,这样文字也会拉长变形。那么属性动画呢?试试~

1
2
3
ObjectAnimator width1 = ObjectAnimator.ofInt(button, "width", 1000);
width1.setDuration(2000);
width1.start();

但是发现,没有效果!这是为啥呢?解释如下.

对object 的任意属性做动画 要求两个条件:

  1. object有 对应属性 的set方法,动画中没设置初始值 还要有get方法,系统要去取初始值(不满足则会crash)。
  2. set方法要对object有所改变,如UI的变化。不满足则会没有动画效果

上面Button没有动画效果,就是没有满足第二条。看下Button的setWidth方法:

1
2
3
4
5
6
public void setWidth(int pixels) {
mMaxWidth = mMinWidth = pixels;
mMaxWidthMode = mMinWidthMode = PIXELS;
requestLayout();
invalidate();
}

实际就是TextView的setWidth方法,看到设置进去的值仅影响了宽度最大值和最小值。按照官方注释和实测,发现只有当Button/TextView在xml中设置android:layout_width为”wrap_content”时,才会setWidth改变宽度;而当Button/TextView在xml中设置android:layout_width为固定dp值时,setWidth无效。 而我们上面给出的Button xml中确实是固定值180dp,所以是属性”width”的setWidth是无效的,即不满足第二条要求,就没有动画效果了。(当修改Button xml中设置android:layout_width为”wrap_content”时,上面执行的属性动画是生效的。)

那么,当不满足条件时,如何解决此问题呢? 有如下处理方法:

  1. 给object添加set、get方法,如果有权限。(一般不行,如TextView是SDK里面的不能直接改)
  2. 给Object包装一层,在包装类中提供set、get方法。
  3. 使用ValueAnimator,监听Value变化过程,自己实现属性的改变。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
    private void testAnimatorAboutButtonWidth() {
//Button width 属性动画:如果xml中宽度是wrap_content,那么动画有效。
// 如果设置button确切的dp值,那么无效,因为对应属性"width"的setWidth()方法就是 在wrap_content是才有效。
ObjectAnimator width1 = ObjectAnimator.ofInt(button, "width", 1000);
width1.setDuration(2000);
// width1.start();

//那么,想要在button原本有确切dp值时,要能对width动画,怎么做呢?
//方法一,包一层,然后用layoutParams
ViewWrapper wrapper = new ViewWrapper(button);
ObjectAnimator width2 = ObjectAnimator.ofInt(wrapper, "width", 1000);
width2.setDuration(2000);
// width2.start();

//方法二,使用ValueAnimator,每一帧自己显示宽度的变化
ValueAnimator valueAnimator = ValueAnimator.ofInt(button.getLayoutParams().width, 1000);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
int animatedValue = (Integer) animation.getAnimatedValue();
Log.i("hfy", "onAnimationUpdate: animatedValue=" + animatedValue);

// IntEvaluator intEvaluator = new IntEvaluator();
//// 获取属性值改变比例、计算属性值
// float animatedFraction = animation.getAnimatedFraction();
// Integer evaluate = intEvaluator.evaluate(animatedFraction, 300, 600);
// Log.i("hfy", "onAnimationUpdate: evaluate="+evaluate);


if (button != null) {
button.getLayoutParams().width = animatedValue;
button.requestLayout();
}
}
});

valueAnimator.setDuration(4000).start();

}

/**
* 包一层,提供对应属性的set、get方法
*/
private class ViewWrapper {

private final View mView;

public ViewWrapper(View view) {
mView = view;
}

public int getWidth() {
return mView.getLayoutParams().width;
}

public void setWidth(int width) {
ViewGroup.LayoutParams layoutParams = mView.getLayoutParams();
layoutParams.width = width;
mView.setLayoutParams(layoutParams);
mView.requestLayout();
}
}

属性动画的原理

属性动画,要求对象有这个属性的set方法,执行时会根据传入的 属性初始值、最终值,在每帧更新时调用set方法设置当前时刻的 属性值。随着时间推移,set的属性值会接近最终值,从而达到动画效果。如果没传入初始值,那么对象还要有get方法,用于获取初始值。

在获取初始值、set属性值时,都是使用 反射 的方式,进行 get、set方法的调用。 见PropertyValuesHolder的setupValue、setAnimatedValue方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
private void setupValue(Object target, Keyframe kf) {
if (mProperty != null) {
Object value = convertBack(mProperty.get(target));
kf.setValue(value);
} else {
try {
if (mGetter == null) {
Class targetClass = target.getClass();
setupGetter(targetClass);
if (mGetter == null) {
// Already logged the error - just return to avoid NPE
return;
}
}
Object value = convertBack(mGetter.invoke(target));
kf.setValue(value);
} catch (InvocationTargetException e) {
Log.e("PropertyValuesHolder", e.toString());
} catch (IllegalAccessException e) {
Log.e("PropertyValuesHolder", e.toString());
}
}
}
void setAnimatedValue(Object target) {
if (mProperty != null) {
mProperty.set(target, getAnimatedValue());
}
if (mSetter != null) {
try {
mTmpValueArray[0] = getAnimatedValue();
mSetter.invoke(target, mTmpValueArray);
} catch (InvocationTargetException e) {
Log.e("PropertyValuesHolder", e.toString());
} catch (IllegalAccessException e) {
Log.e("PropertyValuesHolder", e.toString());
}
}
}

以上效果图:

在这里插入图片描述

使用动画的注意事项

  1. 使用帧动画,避免OOM。因为图片多。

  2. 属性动画 如果有循环动画,在页面退出时要及时停止,避免内存泄漏。

  3. 使用View动画后,调用setVisibility(View.GONE)失效时,使用view.clearAnimation()可解决。

  4. 属性动画,可能会由于View属性变化导致频繁触发重新measure layout,注意性能

Lifecycle

ViewModel

简述:fragmentcomponentActivity实现了ViewModelStoreOwner接口,实现该接口方法getViewModelStore(),当调用ViewModelProvider(ViewModelStoreOwner owner).get(Class<T> modelClass)时,会使用工厂模式创建viewModel,之后将其以canonicalName(全限定名)为key存入mViewModelStore中,ViewModelStore内部是个HashMap<String, ViewModel>

当屏幕旋转或切换系统语言等配置修改的行为发生时,Activity 生命周期从销毁再重建,在销毁时(系统杀死或配置修改),如果判断系统配置没有变化(即!isChangingConfigurations)清空保存的ViewModel(即 getViewModelStore().clear();

如果发生变化则调用onRetainNonConfigurationInstance() 方法将 viewModelStore 保存起来,当Activity重建时则从getLastNonConfigurationInstance()中获取保存的mViewModelStore

ps:如果ViewModelProvider传入Activity,则取得是Activity的ViewModelStore,如果传入了fragment,则根据以下代码取ViewModelStore,即先取父fragment的FragmentManager的ViewModelStore,再取hostActivty的ViewModelStore,最后才是新建一个。

Read more

Design Pattrens

设计模式的六个原则

  1. 单一原则
    每个类或方法都应该只被一个原因影响
  2. 开闭原则
    类或方法应该对扩展开放, 对修改封闭
  3. 里氏替换原则
    子类出现的地方一定可以被父类替换
  4. 接口隔离原则
    接口的粒度要尽量的小
  5. 依赖倒置原则
    抽象依赖抽象, 具体依赖抽象
  6. 迪米特原则
    尽量无知, 朋友的朋友不是我的朋友
Read more

bidi 算法

逻辑顺序与视觉顺序

[I] 逻辑顺序:指人们阅读和从键盘上输入的文字顺序,文本在内存里也是以逻辑顺序存储的。
[II] 视觉顺序:则是文本在屏幕或是打印机中显示的顺序。

字符类型

[I] 阿拉伯文、希伯来文及其派生语言的本土字符为“强RTL字符”
[II] 我们通常见到的中文、英文等语言的本土字符为“强LTR字符”
[III] 欧洲数字、东方阿拉伯,数字,逗号,冒号,句号(即小数点)等为“弱字符”
[IV] 括号,中括号,尖括号,空格符,大多数标点符号为“中性字符”

image-20230818153319225

Read more

Tools

AOSP

Android 代码搜索 ( cs.android.com )

代码搜索文档

ClipBoard

Download the application apk and manually install application on your android device.

1
2
# am broadcast -a clipper.set -e text "this can be pasted now"
# am broadcast -a clipper.get

BookMark

ProcessOn

语法

API Levels | Android versions, SDK/API levels, version codes, codenames, and cumulative usage

QRCode生成

Android内存分析命令 - Gityuan博客 | 袁辉辉的技术博客

Color Converter - RGB, HEX, HSL, ARGB, RGBA

Unix Time Stamp - Epoch Converter

Time 洛杉矶: 01:04

chatGpt-New chat

Watching

Doc

doc

.bash_profile

#jdk
#jdk-11.0.12.jdk or jdk1.8.0_291.jdk
export JAVA_HOME=/Library/Java/JavaVirtualMachines/jdk1.8.0_291.jdk/Contents/Home
#export JAVA_HOME=/Library/Java/JavaVirtualMachines/jdk-11.0.12.jdk/Contents/Home
export PATH=$JAVA_HOME/bin:$PATH
export CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar
#android sdk
export ANDROID_HOME=/Users/crow/Library/Android/sdk
export PATH=${PATH}:${ANDROID_HOME}/tools
export PATH=${PATH}:${ANDROID_HOME}/platform-tools
#android ndk
export ANDROID_NDK_HOME=/Users/crow/Library/Android/sdk/ndk-bundle
export PATH=$PATH:$ANDROID_NDK_HOME

#alias
#screen shot phone
alias ss=’adb shell screencap /sdcard/screenshot.png && adb pull /sdcard/screenshot.png . && open ./screenshot.png’
alias screenShot=’adb shell screencap /sdcard/screenshot.png && adb pull /sdcard/screenshot.png . && open ./screenshot.png’

#screen record
alias sr=’adb shell screenrecord /sdcard/video.mp4’
alias startRecord=’adb shell screenrecord /sdcard/video.mp4’
#control + c to stop
alias showRecord=’adb pull /sdcard/video.mp4 . && open ./video.mp4’

#adb input text to phone
myinput() { adb shell input text “$1” }
alias input=’myinput ‘

#adb stop/start app
alias stopApp=’adb shell am force-stop com.alibaba.aliexpresshd’
alias startApp=’adb shell am start “com.alibaba.aliexpresshd/com.alibaba.aliexpresshd.home.ui.MainActivity”‘

#adb debug app
alias debugApp=’adb shell am start -D “com.alibaba.aliexpresshd/com.alibaba.aliexpresshd.home.ui.MainActivity”‘

#gradle
dependencies() { ./gradlew “$1”:dependencies –configuration debugRuntimeClasspath }
alias depen=’dependencies’

#incremental command
alias increment=’bash <(curl -s https://gitlab.alibaba-inc.com/chenzhong.cz/incremental_scripts/raw/dev_parallel_aliexpress_mtl4/incremental.sh)'

Throwable

Binder

为什么是Binder

img

具体见QA。

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

其内存流向是这样的:

  1. 首先 Binder 驱动在内核空间创建一个数据接收缓存区;
  2. 接着在内核空间开辟一块内核缓存区,建立内核缓存区内核中数据接收缓存区之间的映射关系,以及内核中数据接收缓存区接收进程用户空间地址的映射关系;
  3. 发送方进程通过系统调用 copy_from_user() 将数据 copy 到内核中的内核缓存区,由于内核缓存区和接收进程的用户空间存在内存映射,因此也就相当于把数据发送到了接收进程的用户空间,这样便完成了一次进程间的通信。

如下图:

nzNJUA.png

Read more

Git

节点

Q:每次commit,Git储存的是全新的文件快照还是储存文件的变更部分?

A: 全新的文件快照

Git储存的是全新的文件快照,而不是文件的变更记录。也就是说,就算你只是在文件中添加一行,Git也会新建一个全新的blob object。那这样子是不是很浪费空间呢?

这其实是Git在空间和时间上的一个取舍,思考一下你要checkout一个commit,或对比两个commit之间的差异。如果Git储存的是问卷的变更部分,那么为了拿到一个commit的内容,Git都只能从第一个commit开始,然后一直计算变更,直到目标commit,这会花费很长时间。而相反,Git采用的储存全新文件快照的方法能使这个操作变得很快,直接从快照里面拿取内容就行了。

当然,在涉及网络传输或者Git仓库真的体积很大的时候,Git会有垃圾回收机制gc,不仅会清除无用的object,还会把已有的相似object打包压缩。

Read more

Full-QA

一、基础篇

网络基础

TCP三次握手

三次握手过程:

客户端——发送带有SYN标志的数据包——服务端 一次握手 Client进入syn_sent状态

服务端——发送带有SYN/ACK标志的数据包——客户端 二次握手 服务端进入syn_rcvd

客户端——发送带有ACK标志的数据包——服务端 三次握手 连接就进入Established状态

Read more

TaskPerFrame

16ms 内都需要完成什么

from : https://juejin.cn/post/7062552765117136903

图片

// Vsync顶层

  1. Vsync 调度:硬件每隔16.6ms发出硬件Vsync信号,需要经过软件调度,类似注册,才能收到回调

  2. 消息调度:主要是 doframe 的消息调度,如果消息被阻塞,会直接造成卡顿;

  3. input 处理:触摸事件的处理;

  4. 动画处理:animator 动画执行和渲染;

  5. view 处理:主要是 view 相关的遍历和三大流程;

  6. measure、layout、draw:view 三大流程的执行;

    //Vsync底层

  7. DisplayList 更新:view 硬件加速后的 draw op;

  8. OpenGL 指令转换:canvas指令转换为 OpenGL 指令;

  9. 指令 buffer 交换:OpenGL 的指令交换到 GPU 内部执行;

  10. GPU 处理:GPU 对数据的处理过程;

  11. layer 合成:surface buffer 合成屏幕显示 buffer 的流程;

  12. 光栅化:将矢量图转换为位图;

  13. Display:显示控制;

  14. buffer 切换:切换屏幕显示的帧 buffer;

Read more

RenderBlock

链接:https://juejin.cn/post/7062552765117136903

https://developer.android.com/topic/performance/rendering/profile-gpu?hl=zh-cn

RenderThread的作用:

主线程的 draw 函数并没有真正的执行 drawCall ,而是把要 draw 的内容记录到 DIsplayList 里面(在 Measure、Layout、Draw 的 Draw 这个环节,Android 使用 DisplayList 进行绘制而非直接使用 CPU 绘制每一帧。),同步到 RenderThread 中,一旦同步完成,主线程就可以被释放出来做其他的事情,RenderThread 则继续进行渲染工作
image

2.3 生产者和消费者

图片

我们再回到 Vsync 的话题,消费 Vsync 的双方分别是 App 和 sf,其中 App 代表的是生产者,sf 代表的是消费者,两者交付的中间产物则是 surface buffer

再具体一点,生产者大致可以分为两类,一类是以 window 为代表的页面,也就是我们平时所看到的 view 树这一套;另一类是以视频流为代表的可以直接和 surface 完成数据交换的来源,比如相机预览等。

对于一般的生产者和消费者模式,我们知道会存在相互阻塞的问题。比如生产者速度快但是消费者速度慢,亦或是生产者速度慢消费者速度快,都会导致整体速度慢且造成资源浪费。所以 Vsync 的协同以及双缓冲甚至三缓冲的作用就体现出来了。

思考一个问题:是否缓冲的个数越多越好?过多的缓冲会造成什么问题?

答案是会造成另一个严重的问题:lag,响应延迟

这里结合 view 的一生,我们可以把两个流程合在一起,让我们的视角再高一层:

图片

我们一般都比较了解 view 渲染的三大流程,但是 view 的渲染远不止于此:

此处以一个通用的硬件加速流程来表征

图片

  1. Vsync 调度:很多同学的一个认知误区在于认为 vsync 是每 16ms 都会有的,但是其实 vsync 是需要调度的,没有调度就不会有回调;
  2. 消息调度:主要是 doframe 的消息调度,如果消息被阻塞,会直接造成卡顿;
  3. input 处理:触摸事件的处理;
  4. 动画处理:animator 动画执行和渲染;
  5. view 处理:主要是 view 相关的遍历和三大流程;
  6. measure、layout、draw:view 三大流程的执行;
  7. DisplayList 更新:view 硬件加速后的 draw op;
  8. OpenGL 指令转换:绘制指令转换为 OpenGL 指令;
  9. 指令 buffer 交换:OpenGL 的指令交换到 GPU 内部执行;
  10. GPU 处理:GPU 对数据的处理过程;
  11. layer 合成:surface buffer 合成屏幕显示 buffer 的流程;
  12. 光栅化:将矢量图转换为位图;
  13. Display:显示控制;
  14. buffer 切换:切换屏幕显示的帧 buffer;
Read more

LiveData

简述:

数据实时更新、安全更新:当liveData的setValue被调用时,会遍历自身所有observer并considerNotify,此时会判断observer的活跃状态(shouldBeActive)和内部版本,决定是否向其发送通知。

避免内存泄漏:传入lifeCycleOwner即走observe()的LiveData会在lifeCycleOwner回调onStateChanged为DESTROYED的时候移除掉observer防止内存泄露,而相对的observeForever则由于没有owner而无此特性。

解决Configuration Change问题、粘性数据:一般而言observer是在onCreate中调用的,liveData.observer方法在调用时也会立即被推送liveData中最后一次数据。(会导致所谓的数据倒灌)。Configuration Change时会ViewModel会从当前Activity或fragmentManager的viewModelStore的缓存中重新取出,而ViewModel中的liveData自然也会保留。

https://zhuanlan.zhihu.com/p/593472898

Featrue

  • UI和实时数据保持一致 因为LiveData采用的是观察者模式,这样一来就可以在数据发生改变时获得通知,更新UI。

  • 避免内存泄漏 观察者被绑定到组件的生命周期上,当被绑定的组件销毁(destroy)时,观察者会立刻自动清理自身的数据。

  • 不会再产生由于Activity处于stop状态而引起的崩溃,例如:当Activity处于后台状态时,是不会收到LiveData的任何事件的。

  • 不需要再解决生命周期带来的问题 LiveData可以感知被绑定的组件的生命周期,只有在活跃状态才会通知数据变化。

  • 实时数据刷新 当组件处于活跃状态或者从不活跃状态到活跃状态时总是能收到最新的数据。

  • 解决Configuration Change问题 在屏幕发生旋转或者被回收再次启动,立刻就能收到最新的数据。

Read more

Mmap

Binder | 内存拷贝的本质和变迁

芦半山

虚拟地址和数据的关系

所有的数据都存储在物理内存中,而进程访问内存只能通过虚拟地址。因此,若是想成功访问必须得有个前提:

虚拟地址和物理内存之间建立映射关系

若是这层映射关系不建立,则访问会出错。信号11(SIGSEGV)的MAPERR就是专门用来描述这种错误的。

虚拟地址和物理地址间建立映射关系通过mmap完成。这里我们不考虑file-back的mapping,只考虑anonymous mapping。当mmap被调用(flag=MAP_ANONYMOUS)时,实际上会做以下两件事:

  1. 分配一块连续的虚拟地址空间。
  2. 更新这些虚拟地址对应的PTE(Page Table Entry)。

mmap做完这两件事后,就会返回连续虚拟地址空间的起始地址。在mmap调用结束后,其实并不会立即分配物理页。如果此时不分配物理页,那么就会有如下两个问题:

  1. 没有新的物理页分配,那么PTE都更新了哪些内容?
  2. 如果后续使用mmap返回的虚拟地址访问内存,会有什么情况产生呢?

1.1.1 没有新的物理页分配,那么PTE都更新了些什么内容呢?

PTE也即页表的条目,它的内容反映了一个虚拟地址到物理地址之间的映射关系。如果没有新的物理页分配,那这些新的虚拟地址都和哪些物理地址之间建立了映射关系呢?答案是所有的虚拟地址都和同一个zero page(页内容全为0)建立了映射关系。

1.1.2 如果后续使用mmap返回的虚拟地址访问内存,会有什么情况产生呢?

拿到mmap返回的虚拟地址后,并不会有新的物理页分配。此时若是直接读取虚拟地址中的值,则会通过PTE追踪到刚刚建立映射关系的zero page,因此读取出来的值都是0。

如果此时往虚拟地址中写入数据,将会在page fault handler中触发一个正常的copy-on-write机制。需要写多少页,就会新分配多少物理页。所以我们可以看到,真实的物理页是符合lazy(on-demand) allocation原则的。这一点,极大地保证了物理资源的合理分配和使用。

PackageManager

img

PackageManger的核心作用:

  1. 在系统启动过程中,通过**PackageManagerService(PKMS)*对特定系统文件(如package.list, package.xml)进行扫描解析后,将所有app的信息整合存储,通过IPackageManger*对外暴露
  2. 通过PackageInstallerService提供Apk/Apex的安装、更新、卸载等操作(IPackageInstaller)
  3. 应用运行过程中的权限检查
Read more