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处理满了的任务。

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

Window

https://www.cnblogs.com/huan89/p/14111360.html

从来都没什么Window,有的只是一个个View树,每个窗口(activity/dialog/popupWindow)都是一个view树(代码中都是叫addView,直到WMS中才叫addWindow),在需要显示时被添加进WMS中,最后通过surfalceFlinger合成后渲染到屏幕中。

每个窗口都对应一个Token,一个应用只对应一个session。

从Window视角看ActivityStart

Activity

简述:首先回顾一下,activity是怎么显示出来的:

  1. 为activity创建PhoneWindow和WindowManager(WindowManagerImpl)对象

在handleLaunchActivity()被回调的时候,调用WindowManagerGlobal.initialize();初始化WindoWindowManagerGlobal,之后 Application app = r.packageInfo.makeApplication(false, mInstrumentation);创建Application(如果还没有创建过Application),然后调用activity.attach(),这里面 mWindow = new PhoneWindow(this, window, activityConfigCallback);初始化PhoneWindow并给它设置WindowManager

1
2
3
4
mWindow.setWindowManager(
(WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
mToken, mComponent.flattenToString(),
(info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);

Window.setWindowManager()中通过WMS创建了WindowManagerImpl(其实就是个一百多行代码的壳)。

从这里可以看到是利用系统服务的windowManager来创建新的windowManagerImpl,因而这个应用所有的WindowManagerImpl都是同个内核windowManager,而创建出来的仅仅是包了个壳。

  1. setContentView实际上掉的是getWindow()(也就是上面的PhoneWindow)的setContentView(),其中调用PhoneWindow.installDecor(),
  • 首先看decorView创建了没有,没有的话创建DecorView
  • 把布局加载到DecorView中(LayoutInflater加载预设模板布局,见下)

DecorView是在PhoneWindow中预设好的一个布局,这个布局长这样:

decorView

他是一个垂直排列的布局,上面是ActionBar,下面是ContentView,他是一个FrameLayout。我们的Activity布局就加载到ContentView里进行显示。所以Decorview是Activity布局最顶层的viewGroup。

// DecorView创建完成了,但还缺少了最重要的一步:把DecorView作为window添加到屏幕上。

  1. 在handleResumeActivity中,执行了最后的 wm.addView(mDecor, getWindow().getAttributes());

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,
    String reason) {
    // 调用Activity的onResume方法
    final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason);
    ...
    // 让decorView显示到屏幕上
    if (r.activity.mVisibleFromClient) {
    r.activity.makeVisible();
    }

    void makeVisible() {
    if (!mWindowAdded) {
    ViewManager wm = getWindowManager();
    wm.addView(mDecor, getWindow().getAttributes());
    mWindowAdded = true;
    }
    mDecor.setVisibility(View.VISIBLE);
    }

    直接调用WindowManagerImpl的addView方法来吧decorView添加到屏幕上,至此,我们的Activity界面就会显示在屏幕上了。

    进一步需要看WindowManagerImpl.addView之后是怎么将View(即Window)添加入屏幕的

    1. wm.addView(mDecor, getWindow().getAttributes());其中wm是WindowManagerImpl实例,WindowManagerImpl.addView其实是通过桥接,调用WindowManagerGlobal的全局单例的方法WindowManagerGlobal.addView,该方法中会新建一个ViewRootImpl,然后将入参的decorView、新建的ViewRootImp等加入自身维护的mViews、mRoots列表中,同时将DecorView注入ViewRootImplroot.setView(view, wparams, panelParentView)
    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
    WindowMangerGlobal.clss
    维护着WMS实例sWindowManagerService和以下列表

    //应用所有的decorView
    private final ArrayList<View> mViews = new ArrayList<View>();
    //应用所有的ViewRootImpl
    private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>();
    //应用所有的WindowManager.LayoutParams
    private final ArrayList<WindowManager.LayoutParams> mParams =
    new ArrayList<WindowManager.LayoutParams>();

    public void addView(View view, ViewGroup.LayoutParams params,
    Display display, Window parentWindow) {
    //... 主要是校验参数和调整子窗口的参数
    synchronized (mLock) {
    ...
    // 这里新建了一个viewRootImpl,并设置参数
    root = new ViewRootImpl(view.getContext(), display);
    view.setLayoutParams(wparams);

    // 添加到windowManagerGlobal的三个重要list中,每一个window所对应的这三个对象都会保存在这里,之后对window的一些操作就可以直接来这里取对象了。当window被删除的时候,这些对象也会被从list中移除。
    mViews.add(view);
    mRoots.add(root);
    mParams.add(wparams);

    // 最后通过viewRootImpl来添加window
    try {
    root.setView(view, wparams, panelParentView);
    }
    ...
    }
    }

​ 5. ViewRootImpl.setView()将调用到

1
2
3
4
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(), mWinFrame,
mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel);

这个mWindowSession来自于构造器的入参,是由WindowManagerGlobal.getWindowSession中来的,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public static IWindowSession getWindowSession() {
synchronized (WindowManagerGlobal.class) {
if (sWindowSession == null) {
try {
...
sWindowSession = windowManager.openSession(
new IWindowSessionCallback.Stub() {
...
});
}
...
}
return sWindowSession;
}
}

可以看出,这个session是一个单例,也就是整个应用的所有viewRootImpl的windowSession都是同一个,也就是一个应用只有一个windowSession

Session.addToDisplay实际上走的是WMS的addWindow方法,后面的逻辑就交给WMS去处理了,WMS就会创建window,然后结合参数计算window的高度等等,最后使用viewRootImpl进行绘制。

window的添加过程是通过PhoneWindow对应的WindowManagerImpl来添加window,内部会调用WindowManagerGlobal来实现。WindowManagerGlobal会使用viewRootImpl来进行跨进程通信让WMS执行创建window的业务。

每个应用都有一个windowSession,用于负责和WMS的通信,如ApplicationThread与AMS的通信。

Other Window

dialog和popupwindow的层级是10001999,在这个层级都属于子window而不是应用Window(199),子Window需要附属于父Window(Activity,也就是应用Window)才能显示,dialog虽然创建了一个PhoneWindow,但是popupWindow最终也创建了一个Window,只是它不是PhoneWindow而已,popupWindow和dialog的显示都需要依赖父Window的Token,其实两者都需要依赖于Activiy

那么,PopupWindow则是在构造器中时将入参的contentView直接执行 setContentView(contentView);,然后在showAtLocation()中调用了preparePopup() 创建它的decorView(PopupDecorView)之后invokePopup()调用执行mWindowManager.addView(decorView, p);

  • 根据参数构建popupDecorView
  • 把popupDecorView添加到屏幕上

dismiss()中调用mWindowManager.removeViewImmediate(decorView);

Dialog

dialog的创建过程Activity比较像:构造器创建PhoneWindow,setContentView初始化DecorView,show时候添加DecorView。

  • 构造函数中创建PhoneWindow,设置WindowManger(这里拿的是传入的context,实际上这context只能是actiivty,的WindowManager)
  • Dialog.setContentView时掉PhoneWindow.setContentView来初始化DecorView
  • show()时候调用了mWindowManager.addView(mDecor, l);
  • dismiss()时候调用mWindowManager.removeViewImmediate(mDecor);

总结

  • dialog和popupWindow不同,dialog创建了新的PhoneWindow,使用了PhoneWindow的DecorView模板。而popupWindow没有,popupWindow他也对应一个window,因为它也是通过windowManager添加上去的,不属于Activity的view树。
  • dialog的显示层级数更高,会直接显示在Activity上面,在dialog后添加的popUpWindow也会显示在dialog下
  • dialog的创建流程和activity非常像
Read more

Linux

进程切换

什么是 CPU 上下文

CPU 寄存器和程序计数器就是 CPU 上下文,因为它们都是 CPU 在运行任何任务前,必须的依赖环境。

  • CPU 寄存器是 CPU 内置的容量小、但速度极快的内存。
  • 程序计数器则是用来存储 CPU 正在执行的指令位置、或者即将执行的下一条指令位置。

什么是 CPU 上下文切换

就是先把前一个任务的 CPU 上下文(也就是 CPU 寄存器和程序计数器)保存起来,然后加载新任务的上下文到这些寄存器和程序计数器,最后再跳转到程序计数器所指的新位置,运行新任务。

而这些保存下来的上下文,会存储在系统内核中,并在任务重新调度执行时再次加载进来。这样就能保证任务原来的状态不受影响,让任务看起来还是连续运行。

Read more

HandlerEpoll

Looper的阻塞唤醒

简述:通过pipe/epoll机制,实现MessageQueue.next的无消息时阻塞,有消息时唤醒,pipe

原理

具体来说,当Looper在处理消息时,如果消息队列为空,那么它会调用MessageQueue的next方法来等待新的消息到来,通过Linux内核的epoll机制阻塞线程,等待新的消息到来。在阻塞期间,线程处于睡眠状态,不会占用CPU资源;当新的消息到来时,MessageQueue对象会将消息加入队列,并通知Looper对象,从而唤醒线程并继续执行消息的分发和处理。

epoll机制是Linux内核提供的一种高效的I/O多路复用机制,可以在多个文件描述符上等待,并在其中任何一个文件描述符有事件到达时立即返回,从而实现高效的I/O事件处理。

epoll机制主要分为以下三个步骤:

  1. 创建epoll句柄:在使用epoll机制之前,首先需要创建一个epoll句柄。这可以通过调用epoll_create函数来完成,它会返回一个整型的文件描述符,可以用于后续的epoll操作。
  2. 添加文件描述符到epoll:将需要监听的文件描述符添加到epoll中,可以通过调用epoll_ctl函数来完成。在添加文件描述符时,需要指定文件描述符的类型(例如管道、socket等)、事件类型(例如读事件、写事件等)以及回调函数等信息。
  3. 等待事件到达:等待事件到达是epoll机制的核心。可以通过调用epoll_wait函数来等待文件描述符上的事件。该函数会阻塞,直到有文件描述符上有事件到达或者超时时间到达。当有事件到达时,函数会立即返回,返回值为就绪文件描述符的个数,同时将就绪文件描述符的信息填充到一个事件数组中。

在处理就绪事件时,可以遍历事件数组,并根据每个事件的类型来进行相应的处理。例如,如果是读事件,可以使用read函数来读取文件描述符上的数据。

总之,通过使用epoll机制,可以高效地处理多个文件描述符上的I/O事件,并及时响应事件的到达。这种机制在Linux系统中得到了广泛的应用,包括网络编程、图形界面等领域。

Read more

EventAndNestedScroll

原理&流程

一. ns child会在收到DOWN事件时,找到自己祖上中最近的能与自己匹配的ns parent,与它进行绑定并关闭它的事件拦截机制

二. 然后ns child会在接下来的MOVE事件中判定出用户触发了滑动手势,并把事件流拦截下来给自己消费

三. 消费事件流时,对于每一次MOVE事件增加的滑动距离:

  1. ns child并不是直接自己消费,而是先把它交给ns parent,让ns parent可以在ns child之前消费滑动dispatch/onNestedPreScroll()
  2. 如果ns parent没有消费或是没有消费完,ns child再自己消费剩下的滑动dispatchNestedScroll()
  3. 如果ns child自己还是没有消费完这个滑动,会再把剩下的滑动交给ns parent消费onNestedScroll()
  4. 最后如果滑动还有剩余,ns child可以做最终的处理dispatchNestedScroll()

四. 同时在ns childcomputeScroll()方法中,ns child也会把自己因为用户fling操作引发的滑动,与上一条中用户滑动屏幕触发的滑动一样,使用「parent -> child -> parent -> child」的顺序进行消费

Read more

Java

final & finalize()

final

final修饰的类不可被继承,方法不可被覆盖(或者叫重写),对象不可被更改。

finalize()

finalize()是Object的protected方法,是用来给对象在Gc前一次行动的机会:首先,当对象变成(GC Roots)不可达时,GC会判断该对象是否覆盖了finalize方法,若未覆盖,则直接将其回收。否则,若对象未执行过finalize方法,将其放入F-Queue队列,由一低优先级线程执行该队列中对象的finalize方法。执行finalize方法完毕后,GC会再次判断该对象是否可达,若不可达,则进行回收,否则,对象“复活”。

  • System.gc()与System.runFinalization()方法增加了finalize方法执行的机会,但不可盲目依赖它们

  • Java语言规范并不保证finalize方法会被及时地执行、而且根本不会保证它们会被执行

  • finalize方法可能会带来性能问题。因为JVM通常在单独的低优先级线程中完成finalize的执行

  • 对象再生问题:finalize方法中,可将待回收对象赋值给GC Roots可达的对象引用,从而达到对象再生的目的

  • finalize方法至多由GC执行一次(用户当然可以手动调用对象的finalize方法,但并不影响GC对finalize的行为)

简述:finalize()方法会在对象回收前至多被调用一次,一般可以在这里做一次重新挂到GcRoot链上的保活操作,或者像Android6以前安卓在覆盖的finalize()方法中释放native内存一样。

Read more

DNS

简述:

浏览器中访问一个www.baidu.com,首先会访问浏览器缓存,如未命中则进一步访问操作系统缓存,如未命中则访问hosts文件。如未命中,则客户端想本地DNS服务器发起递归查询(本地DNS会将结果也就是ip直接返回),如本地DNS解析服务器未命中,则由本地DNS解析服务器向根域名服务器、顶级域名服务器、管理方域名服务器等依次进行迭代查询(每有一个未命中则返回下一个域名服务器地址给本地域名服务器,比如local dns server向根域名服务器查询未命中,则根域名服务器返回顶级域名服务器地址给local dns server,然后local dns server向收到的这个地址迭代发起查询)

迭代与递归查询区别:客户端向本地dns服务器发起的是递归查询,客户端拿到的ip是由本地dns服务器直接返回;本地dns服务器向外网查询时是用的迭代查询,即向根域名服务器查询未命中返回给本地dns服务器的是下一个域名服务器的地址而非根域名去访问下一个域名服务器返回结果ip。

这里还有一个权威性的问题:如果客户端向本地dns服务器发起递归查询时,本地dns存在缓存,并将结果返回客户端,则该结果不具权威性(即缓存可能已经失效);反之如果由本地dns服务器解析命中后返回,则具有权威性

Read more

Generics

Java泛型

简述:

理解的泛型,其实是这样的:假如没有泛型,出现在泛型类型位置的就会是Object类,而程序员在使用该类型的时候再手动将类型强转一下,不仅代码啰嗦、而且有类型不匹配的crash可能。

而java1.5提供泛型同时为了兼容旧版,本质上也是用了Object,只不过编译器将.java编译成.class时进行“泛型擦除”时会把泛型类型替代成Object上限类型(字节码中就不存在泛型了),再自动地使用处前加上原始类型的强转,之后再加载到JVM中。而在编译期存在的泛型可以借由IDE有效的进行类型检测可读的扩展,防止类型不匹配。

//java伪泛型的设计原因主要为了兼容老版本的java。真泛型并非做不到,而是因为如果用真泛型(即类型保留),老程序都需要修改。

//另外泛型类中基础数据类型需要使用Integer、Long等封装类的原因也是因为“擦除后会把泛型类型替代成Object上限类型

而kotlin的泛型是跟java一样的,在编译时会被擦除。但是kotlin提供了新的特性可以保留类型,就是**内联函数+reified(ˈriːɪfʌɪ/)**,泛型实化,可以说是真泛型:内联函数(inline)会把方法体copy到调用处(即不会创建新的虚拟机栈帧),

1
2
3
inline fun <reified C : Activity> Context.startActivityKtx() {
startActivity(Intent(this, C::class.java))
}

协变:①协变父子关系一致子类也可以作为参数传进来→java<? extends Entity>上界通配符→kotlin<out Entity>

逆变:②逆变父子关系颠倒父类也可以作为参数传进来→java<? super Article>下界通配符→kotlin<in Entiry>

不变:③不变:只能

无限通配符<?> == java <? extend Object> == kotlin<*> == kotlin<out Any>

Read more