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

VirtualMemory

img

简述:cpu以虚拟内存,通过MMU查询 页表, 映射 高4位(页号) 到页表中查询得到 页框号 和 有效位。

如果有效位 为1, 则直接将页框号和虚拟内存低12位(偏移量)组合返回即为物理地址

如果有效位为0(意味着该页表项不存在MMU中,即未向MMU注册或相关页未被加载如内存中),则产生缺页中断,系统处理中断,通过内存置换swap算法(LRU,OPT,FIFO),之后重走一遍以上逻辑

Read more

HighFrequencyIssue

操作系统:

(1)线程和进程的区别?

(2)线程之间怎么共享资源?

(3)进程之间怎么通信?

(4)进程池的原理是什么?

进程与线程的概念,以及为什么要有进程线程,其中有什么区别,他们各自又是怎么同步的?

1. 基本概念:

进程是对运行时程序的封装,是系统进行资源调度和分配的的基本单位,实现了操作系统的并发

线程是进程的子任务,是CPU调度和分派的基本单位用于保证程序的实时性,实现进程内部的并发;线程是操作系统可识别的最小执行和调度单位。每个线程都独自占用一个虚拟处理器:独自的寄存器组指令计数器和处理器状态。每个线程完成不同的任务,但是共享同一地址空间(也就是同样的动态内存,映射文件,目标代码等等),打开的文件队列和其他内核资源

Read more

HttpProtocol

Http 缓存

在请求一个静态文件的时候(图片,css,js)等,这些文件的特点是文件不经常变化,将这些不经常变化的文件存储起来,对客户端来说是一个优化用户浏览体验的方法。那么这个就是客户端缓存的意义了。

简述:

  • 强制缓存是根据上次响应header中的Cache-Control:Max-age或是Expries客户端直接判断缓存是否能用该资源缓存;

  • 协商缓存需要客户端用记录下来的上次响应header中的ETag或是Last-Modified,通过与向服务器的请求request的header中赋值If-None-Match或If-Modified-Since,由服务器判断资源是否更新,结果由code和是否存在body明确是否命中缓存;

同时出现的优先级排序: 强制缓存 > 协商缓存;ETag & If-None-Match > Last-Modified & If-Modified-Since ;Cache-Control > Expries;

Read more

sharePreference

SharedPreferences原理-xmind

SharedPreferences原理-xmind

SharedPreferences是系统提供的一种简易数据持久化的手段,适合单进程、小批量的数据存储与访问。以键值对的形式存储在xml文件中。
文件存储路径为data/data/package_name/shared_prefs/目录。

源码解析

源码解析

源码解析

Read more

Concurrent

https://www.wwwbuild.net/JavaAmazing/112187.html

并发操作:原子性、可见性、有序性

1、原子性

即一个操作或者多个操作,要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。

如java.util.concurrent.atomic包下的原子类,就是用CAS(Compare And Swap)保证原子性和可见性(内存屏障)

虽然 java.util.concurrent.atomic 提供了原子操作,但这些解决方案主要针对单一变量。对于涉及多个变量时的原子性操作,仍然需要使用高级同步机制(如 synchronized 块或 ReentrantLock)。

Java 内存模型(JMM)确保在使用原子类和 CAS 操作时,数值的更新对其他线程是可见的。这是通过内存屏障来实现的。

2、可见性

可见性是指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。

如volatile能保证被修饰变量的可见性、有序性;原子类可以保证原子性和可见性;

  • 可见性volatile 关键字确保变量的更新对所有线程立即可见,避免线程读取到变量的过期值。
  • 禁止指令重排序优化:编译器和运行时不会把 volatile 变量的写操作与之前的内存操作重排序,也不会把 volatile 变量的读操作与之后的内存操作重排序。

3、有序性

即程序执行的顺序按照代码的先后顺序执行。(指令编排可能会导致多线程下执行结果不一致)

如volatile能保证被修饰变量的可见性、有序性

synchronized关键字三者都能保证。

Volatile 和synchronized的区别:

java.util.concurrent.atomic包的原子类可以保证数据的原子性、可见性;volatile关键字能保证数据的可见性、有序性,但不能保证数据的原子性。synchronized关键字三者都能保证。

1:并发特性比较:

volatile关键字能保证数据的可见性、有序性,但不能保证数据的原子性(即volatile int x; x++ 是三步操作:一取x值,二加一,三赋值回x)。synchronized关键字两者都能保证。

有序性则volatile和synchronized都能保证,volatile关键字禁止JVM编译器已及处理器对其进行重排序,

synchronized保证顺序性是串行化的结果,但同步块里的语句是会发生指令从排。

2:volatile 的原理

1). 修改volatile变量时会强制将修改后的值刷新的主内存中。

2). 修改volatile变量后会导致其他线程工作内存中对应的变量值失效。因此,再读取该变量值的时候就需要重新从读取主内存中的值。

3). 禁止指令重排序优化:编译器和运行时不会把 volatile 变量的写操作与之前的内存操作重排序,也不会把 volatile 变量的读操作与之后的内存操作重排序。

(Intel 的MESI协议:当CPU写数据时,如果发现操作的变量是共享变量,即在其他CPU中也存在该变量的副本,会发出信号通知其他CPU将该变量的高速缓存置为无效状态,因此当其他CPU需要读取这个变量时,发现自己缓存中缓存该变量的缓存行是无效的,那么它就会从内存重新读取重新加载到高速缓存。)

3:阻塞与否

多线程访问volatile关键字不会发生阻塞(2所述原理),而synchronized关键字可能会发生阻塞(重量级锁时会阻塞)

4:性能

volatile关键字是线程同步的轻量级实现,所以volatile性能肯定比synchronized关键字要好。但是volatile关键字只能用于变量而synchronized关键字可以修饰方法以及代码块。synchronized关键字在JavaSE1.6之后进行了主要包括为了减少获得锁和释放锁带来的性能消耗而引入的偏向锁和轻量级锁以及其它各种优化之后执行效率有了显著提升,实际开发中使用synchronized关键字的场景还是更多一些。

Read more

tcp

简述:

Tcp是

面向链接的
面向字节流的
可靠的(几个点)

保证可靠的手段:

  • 数据分块——握手时协商确定MSS,大于MSS的tcp数据包分段(也就是拆包);序列号;校验和;确认ack包

  • 超时重传——发送方使用一个保守估计的时间作为收到数据包的确认的超时上限RTO。如果超过这个上限仍未收到确认包,发送方将重传这个数据包。每当发送方收到确认包后,会重置这个重传定时器。

    超时重传会触发拥塞控制之重置拥塞窗口为1个MSS,阈值减为当前cwnd一半,执行慢启动每轮往返拥塞倍增

  • 滑动窗口实现的流量控制;接收方在ack包中设置rwnd控制发送方发送速度。

  • 拥塞控制算法——小于阈值之前从1开始每轮往返拥塞窗口cwnd倍增(慢启动),拥塞窗口大于阈值后步长为一的递增(拥塞避免)。接收方收到失序报文段后立即发出重复确认ack包,发送方连续三次重复确认则直接发送缺乏ack的丢包(快速重传),同时把阈值减为cwnd/2并调整拥塞窗口为新阈值而后执行拥塞避免算法(快速恢复);

Ps:

超时重传会触发拥塞控制之重置拥塞窗口为1个MSS,阈值减为当前cwnd一半,执行慢启动每轮往返拥塞倍增;

三次重复确认会执行快速重传快速恢复,阈值减为当前cwnd一半,拥塞窗口为新阈值值,执行拥塞避免,每轮往返递增;

Read more

Socket

Socket

1 什么是Socket

  • 网络上的两个程序通过一个双向的通讯连接实现数据的交换,这个双向链路的一端称为一个Socket。Socket通常用来实现客户方和服务方的连接。Socket是TCP/IP协议的一个十分流行的编程界面,一个Socket由一个IP地址和一个端口号唯一确定。

  • 但是,Socket所支持的协议种类也不光TCP/IP、UDP,因此两者之间是没有必然联系的。在Java环境下,Socket编程主要是指基于TCP/IP协议的网络编程。

  • socket连接就是所谓的长连接,客户端和服务器需要互相连接,理论上客户端和服务器端一旦建立起连接将不会主动断掉的,但是有时候网络波动还是有可能的

  • Socket偏向于底层。一般很少直接使用Socket来编程,框架底层使用Socket比较多,

Read more

MVXArchitecture

MVC

View:XML布局文件。 Model:实体模型(数据的获取、存储、数据状态变化)。 Controller:对应于Activity,处理数据、业务和UI。

从上面这个结构来看,Android本身的设计还是符合MVC架构的,但是Android中纯粹作为View的XML视图功能太弱,我们大量处理View的逻辑只能写在Activity中,这样Activity就充当了View和Controller两个角色,直接导致Activity中的代码大爆炸。相信大多数Android开发者都遇到过一个Acitivty数以千行的代码情况吧!所以,更贴切的说法是,这个MVC结构最终其实只是一个Model-View(Activity:View&Controller)的结构。

Read more

ANR

机制描述:

图片

(//ps:AOSP源码中Service的前台Service的TIMEOUT时间是20s,后台Service的TIMEOUT时间是200s)

android-10

// How long we wait for a service to finish executing.

​ static final int SERVICE_TIMEOUT = 20*1000;

​ // How long we wait for a service to finish executing.

​ static final int SERVICE_BACKGROUND_TIMEOUT = SERVICE_TIMEOUT * 10;原理:

Service:

前台Service启动超过20s没有启动完成:在Service启动时发送一个延迟20s的消息(该消息内部即为 报ANR并分析ANR栈),之后在Service的启动完成时将这个消息remove掉。如果成功remove那就啥事没有,如果超过20s没有remove就消息触发,执行消息体内的ANR动作。(后台消息)

Read more

HashMap

常见Map类

简述:HashMap是非线程安全的,如需要线程安全的哈希隐射类,应使用实现了分段锁(1.8)的ConcurrentHashMap,而不建议使用遗留类HashTable(HashTable实现线程安全是依靠用synchronized关键字修饰put/get方法,效率较低)。

如果需要保存记录插入的顺序,可使用LinkHashMap(),其内部实现了一个双向链表,即每个节点本身记录了前后节点的引用。(btw:MessageQueue是单链表)

Read more

mmkv

MMKV 是基于 mmap 内存映射的 key-value 组件,底层序列化/反序列化使用 protobuf 实现,性能高,稳定性强。多进程同步实现是依靠文件锁

Android 存储优化 —— MMKV 集成与原理 - 掘金 (juejin.cn)

design · Tencent/MMKV Wiki (github.com)

android_ipc · Tencent/MMKV Wiki (github.com)

一些对比:

虽然 MMKV 一些场景下比 SP 稍慢(如: 首次实例化会进行数据的复写剔除重复数据, 比 SP 稍慢, 查询数据时存在 ProtocolBuffer 解码, 比 SP 稍慢), 但其逆天的数据写入速度、mmap Linux 内核保证数据的同步, 以及 ProtocolBuffer 编码带来的更小的本地存储空间占用等都是非常棒的闪光点

Read more

ClassLoading

1041642665584_.pic_hd_副本

类的生命周期:

classloadinglifecycle

简述:加载是字节码(.class)文件被ClassLoader装载进方法区并在堆中生成一个class对象引用;链接包括:校验二进制流是否符合JVM规范的验证、为各个变量分配内存赋值默认值的准备、将字符串表示的符号引用解析成直接将引用的解析;初始化则是static块、static变量初始化、类构造器执行的过程。

Read more

APM

主线程卡顿监控

方案一、Looper Printer监控每次 dispatchMessage 的执行耗时:

DoKit & BlockCanary & Matrix

滴滴的哆啦A梦的卡顿检测其实就是blockCanary,和Matrix 的EvilMethodTracer和AnrTracer (当然后来Matrix还增加了native的Signal信号监听)使用的 方案也就是Looper设置Printer监听卡顿

都是根据handler原理,通过给Looper.loop() 中设置printer(无论是通过反射替换Looper的mLogging还是通过setMessageLogging设置printer),监控超过 设定阈值(matrix700ms) 的主线程消息(超过5s报为ANR),printer 中判断start和end,来获取主线程dispatch该message的开始和结束时间,并判定该时间超过阈值为主线程卡慢发生,并 打印当时堆栈 + 方法耗时(matrix/dokit)

Read more

卡顿监控

https://mp.weixin.qq.com/s/3dubi2GVW_rVFZZztCpsKg

主线程卡顿监控

方案一、Looper Printer监控每次 dispatchMessage 的执行耗时:

DoKit & BlockCanary & Matrix

滴滴的哆啦A梦的卡顿检测其实就是blockCanary,和Matrix 的EvilMethodTracer和AnrTracer (当然后来Matrix还增加了native的Signal信号监听)使用的 方案也就是Looper设置Printer监听卡顿

都是根据handler原理,通过给Looper.loop() 中设置printer(无论是通过反射替换Looper的mLogging还是通过setMessageLogging设置printer),监控超过 设定阈值(matrix700ms) 的主线程消息(超过5s报为ANR),printer 中判断start和end,来获取主线程dispatch该message的开始和结束时间,并判定该时间超过阈值为主线程卡慢发生,并 打印当时堆栈 + 方法耗时(matrix/dokit)

Read more

recyclerView

缓存机制

简述:

一级缓存为屏内缓存scrapView,分为没有变化的可以直接复用的ViewHolder mAttachedScrap和因notifyXXX标记为需要重新绑定的ViewHolder mChangedScrap;(用position索引)

二级缓存为离屏2个的ViewHolder离屏缓存cacheView,直接复用;(用position索引)

三级缓存为自定义缓存ViewCacheExtension,较少用

四级缓存为超出上述缓存的需要重新绑定的ViewHolder缓存池RecycledViewPool; (用viewType索引ViewHolder,每种viewType最多5个)

Read more

Glide

Glide加载图片流程

Glide.with(context).load(String).into(xx)

这一行代码干了多少事,其完整的流程:

With()

Glide.with(context) 生成感知生命周期的requestManager;

1
2
3
1 with(context) 
调用getRetriever(activity).get(activity);
通过获取RequestManagerRetriever,拿到其fragmentManager(如果context是fragment的话拿其宿主Activity的fragmentManager),向其中塞一个隐性fragment,用于监听生命周期的变化,并在暂停、停止、销毁时执行对应的行为

Load()

RequestManager.load()确定解码类型并构建requestBuilder

1
2
3
4
5
6
7
2 load(string)多个重载,以String为例(没有手动调用asXXX()的情况下)
2.1 调用asDrawable().load(string),先asDrawable设定resourceClass类型为Drawable.class,再调用
load(String) {
this.model = model;
isModelSet = true;
}
设定load模型 (model)

Into()

image-20241017165028811

RequestBuilder.into()确定transformations、构建ViewTarget、主线程Handler的Executor后,构建SingleRequest对象,将request对象保存到ViewTarget中,之后调用requestManager.track(target, request)中执行request.begin()开始正式加载流程。

Into(ImageView)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
3 into(ImageView) 多个重载,以ImageView为例
3.0.1 clone一份当前的requestOption,根据scaleType(默认为FIT_CENTER)通过requestOptions.clone().optionalFitCenter()保存一些transformations和Options

3.0.2
into(
glideContext.buildImageViewTarget(view, transcodeClass), // transcodeClass为Drawable.class(默认),构建一个DrawableImageViewTarget对象(extends ImageViewTarget extends ViewTarget),ViewTarget会持有ImageView
/*targetListener=*/ null,
requestOptions, // 3.0.1中clone出来的requestOptions对象,其中保存了一些transformation配置
Executors.mainThreadExecutor()); // 主线程Handler的Executor,传递给engine使用,在job完成时callback回主线程

3.1 buildRequest(target, targetListener, options, callbackExecutor); //根据3.0.2构建的Target、requestOptions、targetListener为null、主线程回调的Executor,构建SingleRequest对象

3.2 target.setRequest(request); // target.setTag() 调用View.Tag,将3.1构建的request存到View中

3.3 requestManager.track(target, request); // 主要是调用requestTracker.runRequest(request) -> request.begin();开始加载流程

3.4 request.begin()
3.4.0 如果model为空,直接set error(优先)或者Placeholder返回
3.4.1 如果这时候能拿到target(ImageView)的宽高就走onSizeReady()开始engine.load()
3.4.2 如果这时候宽高还未测量完成,则走target.getSize(this)注册一个ViewTreeObserver,在addOnPreDrawListener回调回来后再掉onSizeReady()
3.4.3 置状态为RUNNING,status = Status.RUNNING;
3.4.4 engine.load()启动 尝试读取缓存、下载、解码等步骤,见4详解
3.5 target.onLoadStarted(getPlaceholderDrawable()); 加载占位图

Engine.load()

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
4 engine.load()

4.1 先读内存缓存
内存缓存:先读 activeResource,再读memoryCache

4.2 内存缓存都没有,就走Engine job启动decode job的流程:engineJob.start(decodeJob);
4.2.1 engineJob和decodeJob都是通过池化技术(实现Poolable接口)获取,减少创建带来的内存和时间消耗

4.3 engineJob.start会在磁盘线程池diskCacheExecutor中执行decodeJob的run()方法(允许磁盘缓存情况下RESOURCE_CACHE/DATA_CACHE)
4.3.1 run()->runWrapped()中通过runGenerators()执行状态机
将根据缓存策略依次执行
ResourceCacheGenerator (缓存策略为ALL、AUTOMATIC、RESOURCE,即该策略下decodeCachedResource()返回为true)
-> DataCacheGenerator (缓存策略为ALL、AUTOMATIC、DATA,即该策略下decodeCachedData()返回为true)
-> SourceGenerator (若当前禁止磁盘缓存或磁盘缓存不存在)

private void runGenerators() {
currentThread = Thread.currentThread();
startFetchTime = LogTime.getLogTime();
boolean isStarted = false;
while (!isCancelled
&& currentGenerator != null
&& !(isStarted = currentGenerator.startNext())) {
stage = getNextStage(stage);
currentGenerator = getNextGenerator();

if (stage == Stage.SOURCE) {
reschedule();
return;
}
}
// We've run out of stages and generators, give up.
if ((stage == Stage.FINISHED || isCancelled) && !isStarted) {
notifyFailed();
}

// Otherwise a generator started a new load and we expect to be called back in
// onDataFetcherReady.
}
private Stage getNextStage(Stage current) {
switch (current) {
case INITIALIZE:
return diskCacheStrategy.decodeCachedResource()
? Stage.RESOURCE_CACHE
: getNextStage(Stage.RESOURCE_CACHE);
case RESOURCE_CACHE:
return diskCacheStrategy.decodeCachedData()
? Stage.DATA_CACHE
: getNextStage(Stage.DATA_CACHE);
case DATA_CACHE:
// Skip loading from source if the user opted to only retrieve the resource from cache.
return onlyRetrieveFromCache ? Stage.FINISHED : Stage.SOURCE;
case SOURCE:
case FINISHED:
return Stage.FINISHED;
default:
throw new IllegalArgumentException("Unrecognized stage: " + current);
}
}
private DataFetcherGenerator getNextGenerator() {
switch (stage) {
case RESOURCE_CACHE:
return new ResourceCacheGenerator(decodeHelper, this);
case DATA_CACHE:
return new DataCacheGenerator(decodeHelper, this);
case SOURCE:
return new SourceGenerator(decodeHelper, this);
case FINISHED:
return null;
default:
throw new IllegalStateException("Unrecognized stage: " + stage);
}
}


4.3.2 DataFetcherGenerator的执行流程

没有缓存的情况举例:
4.3.2.1 通过状态机初始状态下,第一次进到DataCacheGenerator,由于不存在缓存,decodeJob会被重新加到sourceExecutor中执行,来到SourceGenerator.

4.3.2.2 SourceGenerator.startNext()中遍历当前注册表已注册的loader,glideContext.getRegistry().getModelLoaders,依次尝试寻找对应的ModelLoader:
匹配到的是GlideUrl.class对应的 OkHttpUrlLoader -> OkHttpStreamFetcher,调用okhttp3
(默认其实是 HttpGlideUrlLoader -> HttpUrlFetcher,调用HttpURLConnection的API)

4.3.2.2.1 请求接口返回回来之后,赋值变量 dataToCache 同时调用cb(DecodeJob).reschedule();

4.3.2.2.2 赋值runReason为SWITCH_TO_SOURCE_SERVICE 同时调用callback(EngineJob).reschedule(this)//decodeJob,将解析任务decodejob加入getActiveSourceExecutor中执行。

4.3.2.3 之后就又走到了SourceGenerator.startNext(),
区别是这次runWrapped()中runReason是SWITCH_TO_SOURCE_SERVICE同时SourceGenerator的dataToCache不为空。
于是又走SourceGenerator.startNext()中cacheData(data);的逻辑,将接口返回源数据后写入磁盘缓存

4.3.2.4 之后主动执行DataCacheGenerator.startNext()进行源数据磁盘缓存读取操作,此时通过匹配到的 ByteBufferFileLoader.loadData进行文件随机读取操作从磁盘文件中读取出数据。

4.3.2.5 读取到源数据之后通过DecodeJob.onDataFetcherReady将源数据回到到DecodeJob中执行Decode
解码操作
4.3.2.6 解码后又通过EngineJob的回调,执行activeResouce缓存写入和任务资源之类的回收操作
4.3.2.7 同时通过SingleRequest的回调,调用Target的setResource即最后调用ImageView.setImageDrawable

Generator具体执行流程

在DecodeJob的getNextGenerator被执行到时,Generator会被初始化,初始化时,Generator会通过调用decodeHelper.getCacheKeys -> decodeHelper.getLoadData -> glideContext.getRegistry().getModelLoaders(model);
对 MultiModelLoaderFactory 中对 Register 注册表中已注册的所有entries的遍历,寻找与model类型匹配的Entry(包含modelClass、dataClass、具体loader的factory类)。
//注:此处的Register中可以是自己注册 model -> factory 的映射,也可能是Glide默认注册的那些。默认的model为String,启用okhttp的情况下映射到的是 OkHttpUrlLoader 的factory类。最后走OkHttpStreamFetcher请求网络
//注: String类型的model 在 StringLoader 中会先被解析成Uri,然后包装成 GlideUrl 类然后重新寻找对应的loader,也就是OkHttpUrlLoader
//注: 注册表 Register中,注册时 Key 为model的类型,value为 ModelLoaderFactory,ModelLoaderFactory build -> ModelLoader buildLoadData -> LoadData

以磁盘缓存模式为DATA_CACHE为例:
generator的执行顺序是 ResourceCacheGenerator(若有) -> DataCacheGenerator(若有) -> SourceGenerator(若不存在磁盘缓存)

第一次 decodejob 执行是在 diskCacheExecutor 线程池中
此时,首次进入runWrapped -> case INITIALIZE -> runGenerators() 时,执行的是 DataCacheGenerator.startNext(),由于此时磁盘缓存还不存在(所以也没法helper.getModelLoaders(cacheFile)),startNext返回false,走到下一个stage和generator,也就是 stageStage.SOURCE 并currentGenerator为SourceGenerator,当 stage == stageStage.SOURCE ,走到decodeJob的 reschedule(RunReason.SWITCH_TO_SOURCE_SERVICE),此时decodejob会被加入到 sourceExecutor 线程池中重新执行。

第二次 decodejob 执行是在 sourceExecutor 线程池中
此时,重新进入runWrapped -> case SWITCH_TO_SOURCE_SERVICE -> runGenerators() 时,执行的是 SourceGenerator.startNext(),遍历当前符合的所有 ModelLoader.LoadData(其实就是一个,fetcher为OkHttpStreamFetcher的LoadData对象),最后执行loadData.fetcher.loadData() 走到 OkHttpStreamFetcher.loadData() 调用okhttp下载数据,并监听 onDataReady() 和 onLoadFaild() 回调。
onDataReady()下载完成后,数据赋值给 SourceGenerator.dataToCache ,之后又 reschedule(RunReason.SWITCH_TO_SOURCE_SERVICE) 到sourceExecutor线程池中重新执行。

第三次 decodejob 执行依然是在 sourceExecutor 线程池中
此时,依然是runWrapped -> case SWITCH_TO_SOURCE_SERVICE -> runGenerators() ,SourceGenerator.startNext(),由于此时dataToCache不为空,走 SourceGenerator.startNext() 中先Encode数据然后存储磁盘缓存数据的逻辑,即调用 SourceGenerator.cacheData() 将源数据通过decodeHelper.getDiskCache.put(key = new DataCacheKey(loadData.sourceKey, helper.getSignature(), value = new DataCacheWriter<>(encoder, data, helper.getOptions()))写入磁盘缓存。同时构建一个 DataCacheGenerator执行其 startNext()。
DataCacheGenerator.startNext()中,此时由于已经写过磁盘缓存了,cacheFile不为空,于是走到helper.getModelLoaders(cacheFile)得到缓存文件的读取modelLoaders(此时有四个ByteBufferFileLoader,FileLoader$StreamFactory、 FileLoader$FileDescriptorFactory、UnitModelLoader)
根据当前的resourceClass和transcodeClass,以及loadData的dataClass(ByteBuffer)确定为由ByteBufferFileLoader处理,执行ByteBufferFileLoader.loadData。之后借助ByteBufferUtil类随机读取的方式,从磁盘文件中读取源数据的ByteBuffer,之后数据回调到 DecodeJob.onDataFetcherReady(),然后执行 decodeFromRetrievedData()解码源数据 (注,此时还在sourceExecutor线程池中)。
通过 20241009145727 这一串调用之后,走到遍历 decodePaths 尝试解析的步骤,此时一般有(AnimatedImageDecoder, ByteBufferGifDecoder, BitmapDrawableDecoder以及自定义的比如AvifBufferBitmapDecoder),任何一个decoder解码成功后即结束这段逻辑(decode时会执行Downsampler类的逻辑进行采样缩放并且解码),

image-20241009174652194

重新走到 DecodeJob: decodeFromRetrievedData() -> notifyEncodeAndRelease() -> notifyComplete() ->

  1. EngineJob: onResourceReady() -> notifyCallbacksOfResult() -> onEngineJobComplete()
    此处执行活动缓存的写入逻辑,activeResources.activate(key, resource); 之后就是资源回收任务结束移除之类的收尾工作。

  2. CallResourceReady.run() -> engineResource.acquire();callCallbackOnResourceReady(cb); -> SingleRequest.onResourceReady() -> ImageViewTarget.onResourceReady() -> ImageViewTarget.setResourceInternal() -> DrawableImageViewTarget.setResource() -> view.setImageDrawable();
    此处是将解码后的图片资源直接通过view.setImageDrawable显示出来。

Tips

  • Glide是先将图片下载到磁盘,然后再从磁盘中读取。之后decode使用
  • 不存在磁盘缓存需要fetch的情况下:存储和读取磁盘数据,包括后续的磁盘取出后解码操作 是在sourceExecutor线程池中进行的(即是都在上述的第三步中完成)
  • 存在磁盘缓存的情况下:读取磁盘数据和解码数据 是在diskCacheExecutor线程池中进行的。

整体流程参考:

img

Glide缓存

三级缓存:

磁盘(DiskLruCache) -> LRUCache(不活跃资源) -> ActiveResources(使用中资源WeakReference)

内存缓存分为 ActiveResources弱引用 的和 LruCache ,其中正在使用的图片使用弱引用缓存,暂时不使用的图片用 LruCache缓存,这一点是通过 图片引用计数器(acquired变量)来实现的

内存缓存

内存缓存的key是由model, signature, width, height, transformations, resourceClass, transcodeClass, options等因素共同组成的,因此,不同的宽高、变换之类的因素会生成不同的内存缓存。

活动缓存

活动缓存(activeResources: Map<Key, ResourceWeakReference>)

key为EngineKey对象,value为弱引用的图片缓存ResourceWeakReference实例

正在使用的图片缓存(被ImageView或Activity引用的),在gc或所在Activity(fragment宿主activity)销毁时被移到内存缓存

(为什么这一层缓存要用弱引用缓存的方式:一个是这一层缓存因为没有限制缓存大小,用弱引用如果遇到gc即可降级;一个是)

  • 资源被包装成带引用计数的EngineResource,标记引用资源的次数(当引用数acquired不为0时阻止被回收或降级,降级即是存储到LruCache中)
  • 这一级缓存没有大小限制,所以使用了资源的弱引用
  • 存:每当下载资源后会在onEngineJobComplete()中存入ActiveResource,或者LruCache命中后,将资源从中LruCache移除并存入ActiveResource。
  • 取:每当资源释放时,会降级到LruCache中(请求对应的context onDestroy了或者被gc了)
  • 开一个后台线程,监听ReferenceQueue,不停地从中获取被gc的缓存,将其从ActiveResource中移除,并重新构建一个新资源将其降级为LruCache(这一步并不会影响页面展示,图片内存没有销毁,只是被降级到有大小限制的LruCache中)
  • ActiveResource是为了缓解LruCache中缓存造成压力,因为LruCache中没有命中的缓存只有等到容量超限时才会被清除,强引用即使内存吃紧也不会被gc,现在当LruCache命中后移到ActiveResource,弱引用持有,当内存吃紧时能被回收。

活动缓存是感知生命周期的

​ 当绑定的context销毁onDestory时,RequestManager会将该事件传递给RequestTracker,然后触发该请求Resource的clear,再调用Engine.release,将改activiteResource降级到LruCache

LRU缓存LRUCache

LRU缓存LRUCache(memoryCache: LruCache<Key, Resource<?>>)

key为EngineKey对象,value为图片缓存Resource实例

当图片不再显示时,图片会从活动缓存移到内存缓存。存储的是暂时不用的图片缓存(依靠图片引用计数器acquired变量实现,当acquired大于0时存在于活动缓存中,为0是移到内存缓存中

  • 使用 LinkedHashMap实现, 存储从活跃图片降级的资源,使用Lru算法淘汰最近最少使用的

    Map<T, Y> cache = new LinkedHashMap<>(100, 0.75f, true);

  • 存:从活跃图片降级的资源(退出当前界面,或者ActiveResource资源被回收)

  • 取:网络请求资源之前,从缓存中取,如没命中活动资源则取活动资源,没命中活动资源则取内存缓存LRUCache,若命中则直接从LruCache中移除了,没命中则走磁盘或网络。

  • 内存缓存最大空间(maxSize) = 每个进程可用的最大内存(activityManager.getMemoryClass() * 0.4

    (低配手机的话是: 每个进程可用的最大内存 * 0.33)

    activityManager.getMemoryClass值:

    • 低端设备可能在 16-32 MB 范围内。
    • 中档设备通常在 64-128 MB 范围内。
    • 高端设备和新款设备可能在 256 MB 或更高。//当设置largeHeap时,最多可申请512M

磁盘缓存(已证)

对于远程资源:DATA/AUTOMATIC 策略下的磁盘缓存的key只由url(和signature,但一般没有)决定。

RESOURCE 策略下的磁盘缓存的key决定因素还要包括 宽高、变换等多种因素

磁盘缓存(diskLruCache: LinkedHashMap<String, Entry>)

  • 缓存Key Value:

key为 经过Sha256算法对 DataCacheKey(GlidUrl和signature)或ResourceCacheKey(GlideUrl、singnature、width、

height、transformation等多个因素) 摘要后 的64位字符串,value为File实例

  1. 在内存中用LinkedHashMap实现的LruCache记录一组Entry,Entry内部包含一组文件,文件名即是key,并且有开启后台线程执行删除文件操作以控制磁盘缓存大小
  2. 写磁盘缓存即是触发Writer将数据写入磁盘,并在内存构建对应的File缓存在LinkedHashMap中
  3. 磁盘缓存的存储顺序记录在journal文件中,有时该文件过大会导致glide初始过慢。
  • 磁盘缓存大小默认250MB,根据缓存策略的不同可能存储原始图片或解码后的图片。

DiskCacheStrategy.ALL : //表示既缓存原始图片,也缓存decode过后的图片。

DiskCacheStrategy.NONE: //表示不缓存任何内容。

DiskCacheStrategy.RESOURCE: //表示只缓存decode过后的图片,依然编码为jpeg/png等格式存储

DiskCacheStrategy.DATA: //表示只缓存原图片(decode前)。 如webp/avif

DiskCacheStrategy.AUTOMATIC //(默认选项)Remote资源下走DiskCacheStrategy.DATA,Local资源下走DiskCacheStrategy.RESOURCE。

磁盘-RESOURCE

实际是内存中解码后的Bitmap对象,编码成磁盘需要的文件格式,jpeg/png,之后再存磁盘

解码后的资源的磁盘缓存经过转换,解压之后的体积较大,解码速度较快的,由bitmap直接编码成的JPEG/PNG等未压缩格式

此时存储的文件是文件头为ffd8 ffe0 0010 4a46 4946 0001 0100 0001的jpeg格式。

Resource 代表了解码后的资源,即已经从原始数据(如网络图片的字节流)解码并转换为可直接使用的数据形式,之后还是需要编码成文件格式jpeg/png等文件存储格式。Resource 封装了解码后的资源,并提供了一些管理功能,如引用计数和资源回收。

磁盘-DATA

原资源的直接数据缓存(未解码,转换,磁盘体积较小,解码速度较慢)(AVIF/WEBP等压缩格式)

此时存储的文件是文件头为0000 0020 6674 7970 6176 6966 0000 0000的avif格式。

DataCache 是指数据缓存,主要用于缓存原始数据(例如,通过网络请求获取的图片字节流)。数据缓存可以加速后续的解码过程,因为原始数据已经被缓存下来,不需要再次从网络或其他源获取。

磁盘-AUTOMATIC

Remote资源下走DiskCacheStrategy.DATA,Local资源下走DiskCacheStrategy.RESOURCE

Tips

  • 对于远程的资源,DATA和AUTOMATIC是一样的。

  • 想缩短解码时间可以考虑使用RESOURCE模式,本质上是用磁盘空间 换 解码时间:decode后的资源存储空间更大,但解码时间更短。

  • 对于webp,avif等模式,RESOURCE缓存的是.jpeg格式,其内容是decode后内存中使用的bitmap对象直接,不带压缩的二进制内容。DATA缓存的则是原始的图片格式

  • **那么对于.jpg文件,RESOURCE跟DATA模式的区别是啥:jpg本身也是有压缩的,RESOURCE模式下存储的文件格式仍为jpg,但是其内容是decode后的,尺寸更大而解码时间更短 **

  • 那么对于.gif文件,不管什么模式都一样,就是缓存gif图原始文件

  • 互联网早期图片现在看到很多事绿色且模糊,其实就是由于jpeg的压缩是有损的,每次存到磁盘中可能经过一次压缩,并且上传时会再压缩一次,重复多次后就会质量下降

Glide缓存Key

Glide生成key(两级内存缓存Key类型为同一个EngineKey对象,磁盘缓存Key类型为DataCacheKey或ResourceCacheKey摘要的哈希字符串)的方式涉及的参数有8种,其中都包括图片URL、签名。可能包括宽高、变换等

1
2
EngineKey key = keyFactory.buildKey(model, signature, width, height, transformations,
resourceClass, transcodeClass, options);

宽高一定是明确有值的。即使wrap_content也会用屏幕最长的一边作为兜底宽高。

Glide的缓存大小默认是多少

内存缓存大小

内存缓存最大空间(maxSize) = 每个进程可用的最大内存(activityManager.getMemoryClass() * 0.4

(低配手机的话是: 每个进程可用的最大内存 * 0.33)

activityManager.getMemoryClass值:

  • 低端设备可能在 16-32 MB 范围内。
  • 中档设备通常在 64-128 MB 范围内。
  • 高端设备和新款设备可能在 256 MB 或更高。//当设置largeHeap时,最多可申请512M

磁盘缓存大小

磁盘缓存大小: 默认250MB

磁盘缓存目录: 项目/cache/image_manager_disk_cache

image-20240815114741925

这里有个点:随着图片磁盘缓存的存取,由于每次存取磁盘图片glide都会将存取操作记录到日志文件(journal文件),日志文件会逐渐增大到可能几兆大小,导致glide初始化延迟(glide的初始化中,磁盘缓存初始化时会涉及到将日志文件读取到内存中操作)。

这里可以增加一个首页模块的磁盘缓存目录,比如项目/cache/image_manager_disk_home_cache,启动时先初始化首页的磁盘缓存(现为10m)再初始化主体的磁盘缓存,避免日志文件过大导致的初始化时间太长阻塞App启动。

Other

感知生命周期

通过链式调用中with(xxx)传入的activity或Fragment,Glide实现了对所属activity或者fragment生命周期监听:通过new一个隐形的fragment(SupportRequestManagerFragment.class),嵌入到所要监听的fragment或者activity所在的Activity中,这样当宿主Acitivity的生命周期变化时,可以通过嵌入的fragment监听回调,并在RequestManager中执行:

**onStart(): 继续请求resumeRequests() **

onStop(): 暂停请求pauseRequests()

onDestory(): 销毁请求、对应页面的活动缓存降级到内存缓存LRUCache,移除监听等操作

// ps:当 Activity时,Glide 会清理相关资源,移除 Fragment 不会立即触发 ActiveResources 缓存降级,只有宿主 Activity 销毁时才会。

//com/bumptech/glide/request/target/CustomViewTarget.java 中保留了一个没打开的接口clearOnDetach(),可以实现当ImageView detachedFromWindow的时候释放图片缓存。但由于考虑到太激进的释放可能导致缓存复用效率

glide是怎么拿到的width,height(view未测绘前拿不到宽高)

如果view的布局宽高有值,或view本身的宽高有值,会直接返回布局宽高或view宽高。如果没有:

ViewTarget会为所持有的View注册view树绘制回调,待到经过测量布局之后回调onSizeReady()了,才会发起engine.load。(如果wrap_content则会用getMaxDisplayLength()屏幕长的一边作为宽高返回)

addOnPreDrawListener(OnPreDrawListener listener)

image-20240709154704558

如果是Wrap_content?

那么会返回 屏幕高(长的一边) * 屏幕高,比如 1080 * 1920 屏幕会返回 1920 * 1920兜底,在后续流程比如内存缓存Key生成时候,也是用的这个兜底宽高。

具体见ViewTarget.getMaxDisplayLength()

感知内存吃紧

注册ComponentCallbacks2,实现细粒度内存管理:

  1. onLowMemory(){清除内存}
  2. onTrimMemory(){修剪内存}
1
2
3
memoryCache.trimMemory(level); // 内存缓存
bitmapPool.trimMemory(level); // bitmap池
arrayPool.trimMemory(level); // 字节数组池

可以设置在onTrimMemory时,取消所有正在进行的请求。

Glide线程池

源线程池sourceExecutor

(触发下载时存取磁盘和解析使用的线程池,非下载线程池,下载包给okhttp/httpurlconnection了):

定长为 cpu数量(最大4)(核心线程和工作线程数量都最大为4)的线程池

1
2
3
4
5
6
7
8
9
10
11
12
13
14
private GlideExecutor getActiveSourceExecutor() {
return useUnlimitedSourceGeneratorPool
? sourceUnlimitedExecutor
: (useAnimationPool ? animationExecutor : sourceExecutor);
}

//bestThreadCount = Math.min(MAXIMUM_AUTOMATIC_THREAD_COUNT, RuntimeCompat.availableProcessors());

public static GlideExecutor newSourceExecutor() {
return newSourceExecutor(
calculateBestThreadCount(),
DEFAULT_SOURCE_EXECUTOR_NAME,
UncaughtThrowableStrategy.DEFAULT);
}

磁盘缓存diskCacheExecutor

(从Resource或DataCache中加载图片)线程池:定长为1(核心线程和工作线程数量都为1)的线程池

    //diskCacheExecutor

    //DEFAULT_DISK_CACHE_EXECUTOR_THREADS = 1

    public static GlideExecutor newDiskCacheExecutor() {
    return newDiskCacheExecutor(
        DEFAULT_DISK_CACHE_EXECUTOR_THREADS,
        DEFAULT_DISK_CACHE_EXECUTOR_NAME,
        UncaughtThrowableStrategy.DEFAULT);
    }

加载Gif流程

读取流的前三个字节,若判断是gif,则会命中gif解码器-将资源解码成GifDrawable,它持有GifFrameLoader会将资源解码成一张张Bitmap并且传递给DelayTarget的对象,该对象每次资源加载完毕都会通过handler发送延迟消息回调 onFrameReady() 以触发GifDrawable.invalidataSelf()重绘。加载下一帧时会重新构建DelayTarget

QA

ActiveResources弱引用问题

Q: glide 当前页面的图片缓存是存在activiteResource的弱引用的,也就是说当gc时当前页面的图片缓存会被回收?那岂不是当前页面会空白

A: 不会啊,因为实际的资源被页面的view持有着,而ActiveResources只是一层弱引用而已。而已经不被view持有(没显示出来的图片)但仍在活动缓存中的弱引用会被降级到LRU内存缓存中。

活动资源缓存的存在以较小的代价减小Lru缓存的压力,提升Lru缓存的效率。

原因是活动资源缓存通过缓存的对象本身就是在内存中进行使用,缓存是只是建立一个弱引用关系。如果过没有活动资源缓存,每一次使用的资源都加入内存缓存,极有可能因为放入Lru缓存的数据过多,导致正在使用资源从Lru缓存中移除,等到下次来进行加载的时候因为没有对应的引用关系,找不到原来内存中正在使用的那个资源,从而需要再次从文件或者网络进行数据加载。这样同一份资源需要使用两处或者多处内存。大大的提高了内存消耗。总而言之,活动资源缓存以较小的代价提高了Lru缓存的使用效率,防止加载中的资源被lru回收。

字节数组的复用池

BitmapPool

  • BitmatPool 是 Glide 维护了一个图片复用池,LruBitmapPool 使用 Lru 算法保留最近使用的尺寸的 Bitmap。
  • api19 后使用bitmap的字节数和config作为key,而之前使用宽高和congif,所以19以后复用度更高
  • 用类似LinkedHashMap存储,键值对中的值是一组Bitmap,相同字节数的Bitmap 存在一个List中(这样设计的目的是,将Lru策略运用在Bitmap大小上,而不是单个Bitmap上),控制BitmapPool大小通过删除数据组中最后一个Bitmap。
  • BitmapPool 大部分用于Bitmap变换和gif加载时

ArrayPool

  • 是一个采用Lru策略的数组池,用于解码时候的字节数组的复用。
  • 清理内存意味着清理MemoryCache,BitmapPool,ArrayPool

相关源码

load可加载多种model的图片

image-20240815203503827

1
2
3
4
5
6
7
8
9
10
11
RequestBuilder.java
public RequestBuilder<TranscodeType> load(@Nullable Object model) {
return loadGeneric(model);
}

@NonNull
private RequestBuilder<TranscodeType> loadGeneric(@Nullable Object model) {
this.model = model;
isModelSet = true;
return this;
}

获得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
SingleRequest.java
public void onSizeReady(int width, int height) {
//...
//加载图片的View在viewTreeObserver的onPreDrawListener回调回来后拿到确定的View宽高
loadStatus =
engine.load(
glideContext,
model,
requestOptions.getSignature(),
this.width,
this.height,
requestOptions.getResourceClass(),
transcodeClass,
priority,
requestOptions.getDiskCacheStrategy(),
requestOptions.getTransformations(),
requestOptions.isTransformationRequired(),
requestOptions.isScaleOnlyOrNoTransform(),
requestOptions.getOptions(),
requestOptions.isMemoryCacheable(),
requestOptions.getUseUnlimitedSourceGeneratorsPool(),
requestOptions.getUseAnimationPool(),
requestOptions.getOnlyRetrieveFromCache(),
this,
callbackExecutor);
//...
}

内存缓存Key的生成

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
Engine.java
public <R> LoadStatus load(
GlideContext glideContext,
Object model,
Key signature,
int width,
int height,
Class<?> resourceClass,
Class<R> transcodeClass,
Priority priority,
DiskCacheStrategy diskCacheStrategy,
Map<Class<?>, Transformation<?>> transformations,
boolean isTransformationRequired,
boolean isScaleOnlyOrNoTransform,
Options options,
boolean isMemoryCacheable,
boolean useUnlimitedSourceExecutorPool,
boolean useAnimationPool,
boolean onlyRetrieveFromCache,
ResourceCallback cb,
Executor callbackExecutor) {

//内存缓存Key的生成:由model(所加载的数据的模型:Uri/String/Bitmap等)、singature(请求SingleRequest的签名、ImageView的宽高、请求变换(Transformations)、resourceClass(资源解码类型,asBitmap是Bitmap.class,asGif是GifDrawable.class,没有指定是Drawable.class),transcodeClass(链式中调用.transcode()传入,一般为Drawable.class,是最后ImageView实际),options(一组存储如压缩质量、压缩格式、网络超时时间等选项的类)
//这八者任意一个改变都会引起key的hashcode变化
EngineKey key =
keyFactory.buildKey(
model,
signature,
width,
height,
transformations,
resourceClass,
transcodeClass,
options);


EngineResource<?> memoryResource;
synchronized (this) {
//是否存在内存缓存(包括 activiteResource 和 cache)
memoryResource = loadFromMemory(key, isMemoryCacheable, startTime);

//没有内存缓存,就进行网络或者磁盘缓存解析
if (memoryResource == null) {
return waitForExistingOrStartNewJob(
glideContext,
model,
signature,
width,
height,
resourceClass,
transcodeClass,
priority,
diskCacheStrategy,
transformations,
isTransformationRequired,
isScaleOnlyOrNoTransform,
options,
isMemoryCacheable,
useUnlimitedSourceExecutorPool,
useAnimationPool,
onlyRetrieveFromCache,
cb,
callbackExecutor,
key,
startTime);
}
}

//只有命中内存缓存才从这里返回
cb.onResourceReady(memoryResource, DataSource.MEMORY_CACHE);
return null;
}

请求缓存流程

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
Engine.java {
@Nullable
private EngineResource<?> loadFromMemory(
EngineKey key, boolean isMemoryCacheable, long startTime) {
if (!isMemoryCacheable) {
return null;
}

//该缓存key存在于activiteResource,是弱引用存在于内存中的
EngineResource<?> active = loadFromActiveResources(key);
if (active != null) {
if (VERBOSE_IS_LOGGABLE) {
logWithTimeAndKey("Loaded resource from active resources", startTime, key);
}
return active;
}

//该缓存存在于cache
EngineResource<?> cached = loadFromCache(key);
if (cached != null) {
if (VERBOSE_IS_LOGGABLE) {
logWithTimeAndKey("Loaded resource from cache", startTime, key);
}
return cached;
}
return null;
}


//如果该缓存key存在于activiteResource,对应的对象引用加1
@Nullable
private EngineResource<?> loadFromActiveResources(Key key) {
EngineResource<?> active = activeResources.get(key);
if (active != null) {
active.acquire();
}

return active;
}

//如果该缓存存在于cache,将其中cache中移除并存到activiteResource中并引用计数加1
private EngineResource<?> loadFromCache(Key key) {
EngineResource<?> cached = getEngineResourceFromCache(key);
if (cached != null) {
cached.acquire();
activeResources.activate(key, cached);
}
return cached;
}

private EngineResource<?> getEngineResourceFromCache(Key key) {
Resource<?> cached = cache.remove(key);

final EngineResource<?> result;
if (cached == null) {
result = null;
} else if (cached instanceof EngineResource) {
//一般情况都是从缓存cache中直接返回
// Save an object allocation if we've cached an EngineResource (the typical case).
result = (EngineResource<?>) cached;
} else {
//兜底
result =
new EngineResource<>(
cached, /*isMemoryCacheable=*/ true, /*isRecyclable=*/ true, key, /*listener=*/ this);
}
return result;
}
}

相关概念

  1. Resource

Resource 代表了解码后的资源,即已经从原始数据(如网络图片的字节流,一般为AVIF、WEBP)解码并转换为可直接使用的数据形式(如 Bitmap、Drawable 等,如果存到磁盘会encode成jpeg/png格式)。Resource 封装了解码后的资源,并提供了一些管理功能,如引用计数和资源回收。

  1. DataCache

DataCache 是指数据缓存,主要用于缓存原始数据(例如,通过网络请求获取的图片字节流)。数据缓存可以加速后续的解码过程,因为原始数据已经被缓存下来,不需要再次从网络或其他源获取。

  1. Source

Source 代表数据的源头,即原始数据的来源。常见的源包括网络(主要)、文件系统、资源文件、内容提供者等。
网络源:从 URL 或网络地址加载数据。
文件源:从本地文件系统加载数据。
资源源:从应用的资源文件中加载数据。
内容提供者:通过内容提供者(ContentProvider)加载数据。

  • URL:主要用于标识网络上的资源。示例:https://example.com/image.jpg
  • URI:是一个更广泛的概念,可以标识任意一种资源,包括本地文件和网络资源。示例:file:///storage/emulated/0/Download/image.jpg 和 content://media/external/images/media/12345

resourceClass //图片以什么类型解码图片资源

resourceClass 是指图像加载过程中,Glide 从源(如网络或本地存储)加载并解码后的资源类型。它表示 Glide 获取图像数据后将其解码成什么类型的资源。

常见的 resourceClass 类型包括:

  • Bitmap:用于静态图像,如 JPEG、PNG。
  • GifDrawable:用于 GIF 动图。
  • Drawable:一个通用类型,可以表示 BitmapDrawable、GifDrawable 等。

指定 resourceClass 可以确保 Glide 以指定的方式解码和处理图像资源。例如:

1
2
3
4
5
6
java
Copy
Glide.with(context)
.asBitmap() // 指定resourceClass为 Bitmap。不指定时默认asDrawable为drawable
.load("https://example.com/image.jpg")
.into(imageView);

transcodeClass // 最终提供给 Target(如 ImageView)的数据类型

transcodeClass 是指从 resourceClass 类型转换后的最终数据类型,它表示图像加载和解码后,最终提供给 Target(如 ImageView)的数据类型。

常见的 transcodeClass 类型包括:

  • Drawable:Glide 默认使用 Drawable 作为最终显示的类型,因为它可以表示不同类型的图像资源。
  • Bitmap:如果你需要直接处理 Bitmap 对象,可以指定 transcodeClass 为 Bitmap。
  • 自定义类型:你可以定义自己的转换器,将资源转换为自定义类型。

通过自定义转换器(ResourceTranscoder),你可以将 resourceClass 类型转换为所需的 transcodeClass 类型。例如,如果你想将 GIF 转换为静态图像或者其他类型,可以使用自定义转换器。

总结

  • resourceClass:表示 Glide 从源加载并解码后的资源类型(如 Bitmap、GifDrawable)。
  • transcodeClass:表示从 resourceClass 类型转换后的最终数据类型,可以是 Drawable、Bitmap 或其他自定义类型。

相关参考https://juejin.cn/post/7129306281650683935#heading-92

empty

  • PackageManagerService

  • WindowManagerService(done?)

  • ActivityManagerService(done?)

  • ServiceManager(done?)

  • Binder
  • ANR

1、review

kernel bright spots

  • Apm监控
  • fresco深入优化
  • 性能优化工作/工具

skills pool

  • recyclerview缓存和优化

  • app、android启动流程

  • 编译流程

  • 编舞者以及屏幕刷新原理(与耗时方法监听之)

  • 插件化(代理和hook两种方式)

  • handler

  • https&http(http version)

  • hashmap

  • Matrix为主,Dokit、blockcanary等APM框架

  • Jmm与GC算法(深入理解java虚拟机-标记清除之类)

  • tcp滑动窗口之类(就找一篇文章like腾讯之前的那篇tcp ip问题彻底弄懂TCP协议:从三次握手说起 (qq.com)

  • leakCanary、retrofit、okhttp、glide、

  • threadLocal、Rv四级缓存

2、TODO

  • kotlin协程
  • 算法(1~2)
  • View测绘,事件分发(事件系列只能被整一个处理)
  • SurfaceView, TextureView及View的区别。SurfaceView怎么控制它处于的层级
  • SMTP了解下

3、Optimize Experience

  • 图片内存优化工作:fresco关于gif缓存问题修复,dokit图片闪烁缓存失效问题的修复、基于dokit框架(ASM)下尺寸过大图片识别实现。(基于业务做的一些图片加载的优化,如cdn链接统一域名,regex(String.replace)耗时200us)

  • 耗时方法,两种常见的监听方式:looper.printer 以及 编舞者回调监听

  • recyclerview优化

4、Other

  • h5图片缓存共用的优化思路
  • 阿里patron和Metrix做的native hook 减少32bit模式下app内存占用

Project Experience

从购物车重构到购物车预加载

ps:我并不想抽象的去将购物车的架构之类的东西,而是更具体的讲遇到的问题及解决方案。不过还是得简单讲一下设计

购物车代码十分屎山,难以维护。在23年时做了一次历经一个月的重构,边开车边换轮子。使用多数业务在用的协议架构奥创重构:先说收益:奥创加购以组件为单位,职责清晰,易于复用维护,端侧组件使用MVVM架构进一步解耦逻辑:

举个例子,请求在model层(共用的Repository,维护于Engine层),每个组件通过注册组件的ViewHolder获得独立的View,注册组件的Parser获得独立的ViewModel解析数据及进行业务逻辑处理。view与vm之间使用liveData进行数据更新后的通知view状态渲染。

遇到的问题:

  1. 奥创冗余数据及后端接口慢 -> 购物车本地数据缓存 + (detail/购物车进下单页)的数据预加载 + 购物车预加载的根据abtest下做的精细运营。
  2. LiveData的使用:购物车提供给MainActiivty的其他Tab由于时机问题导致的刷新问题
  3. 多国家配置不同组件 -> 问题:一些组件不同国家需要配置不同的样式和逻辑,但是下发的组件名是一致的。那么当时有两种做法:一种是直接在解析是篡改掉网络数据中的组件名,本地正常映射。但是这个评估完认为直接修改了数据,风险更不好控。于是我在注册组件名时根据依靠 同名Parser后注册的Parser优先匹配的机制 实现 的特性,在注册Parser时提供将多国家的parser后置接口,于是当解析完多国家组件parser后即不会再映射通用组件了,即不影响原数据,也实现了多国家不新增后端组件。

RTL adapte

  1. What: Ltr下不同币种导致的显示顺序错乱;
  2. How: 服务端配置下发特殊符号
  3. More:
  1. 做过印象比较深的事情 ,有挑战的事情

  2. 弱网环境优化:业务和技术的手段,有什么很技术的手段来做吗

  3. 当前团队的优劣点

  4. 遇到不合理需求你会怎么拒绝

  5. 有用过新的东西吗

  6. 线上问题怎么解决,因为不能用动态字节码技术

  7. 遇到啥有意思的问题吗,crash。window

    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
    java.lang.IllegalArgumentException: View=android.widget.PopupWindow$PopupDecorView{2a10009 V.E...... R.....I. 0,0-0,0} not attached to window manager
    at android.view.WindowManagerGlobal.findViewLocked(WindowManagerGlobal.java:544)
    at android.view.WindowManagerGlobal.updateViewLayout(WindowManagerGlobal.java:433)
    at android.view.WindowManagerImpl.updateViewLayout(WindowManagerImpl.java:162)
    at android.widget.PopupWindow.update(PopupWindow.java:2226)
    at android.widget.PopupWindow.update(PopupWindow.java:2347)
    at android.widget.PopupWindow.alignToAnchor(PopupWindow.java:2517)
    at android.widget.PopupWindow.-$$Nest$malignToAnchor(Unknown Source:0)
    at android.widget.PopupWindow$1.onViewAttachedToWindow(PopupWindow.java:243)
    at android.view.View.dispatchAttachedToWindow(View.java:21423)
    at android.view.ViewGroup.dispatchAttachedToWindow(ViewGroup.java:3502)
    at android.view.ViewGroup.dispatchAttachedToWindow(ViewGroup.java:3509)
    at android.view.ViewGroup.dispatchAttachedToWindow(ViewGroup.java:3509)
    at android.view.ViewGroup.dispatchAttachedToWindow(ViewGroup.java:3509)
    at android.view.ViewGroup.dispatchAttachedToWindow(ViewGroup.java:3509)
    at android.view.ViewGroup.dispatchAttachedToWindow(ViewGroup.java:3509)
    at android.view.ViewGroup.dispatchAttachedToWindow(ViewGroup.java:3509)
    at android.view.ViewGroup.dispatchAttachedToWindow(ViewGroup.java:3509)
    at android.view.ViewGroup.dispatchAttachedToWindow(ViewGroup.java:3509)
    at android.view.ViewGroup.dispatchAttachedToWindow(ViewGroup.java:3509)
    at android.view.ViewGroup.dispatchAttachedToWindow(ViewGroup.java:3509)
    at android.view.ViewGroup.dispatchAttachedToWindow(ViewGroup.java:3509)
    at android.view.ViewGroup.dispatchAttachedToWindow(ViewGroup.java:3509)
    at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:3011)
    at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:2518)
    at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:9389)
    at android.view.Choreographer$CallbackRecord.run(Choreographer.java:1451)
    at android.view.Choreographer$CallbackRecord.run(Choreographer.java:1459)
    at android.view.Choreographer.doCallbacks(Choreographer.java:1089)
    at android.view.Choreographer.doFrame(Choreographer.java:1003)
    at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:1431)
    at android.os.Handler.handleCallback(Handler.java:942)
    at android.os.Handler.dispatchMessage(Handler.java:99)
    at android.os.Looper.loopOnce(Looper.java:210)
    at android.os.Looper.loop(Looper.java:299)
    at android.app.ActivityThread.main(ActivityThread.java:8261)
    at java.lang.reflect.Method.invoke(Native Method)
    at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:559)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:954)

OOM的治理:crash率在一次架构组的升级后由万4涨到万6,其中新增了很多的OOM。1:修复了EventCenter的内存泄露;2:搜推服务造成的泄露;

内存兜底措施:2g设备直接改为RGB565,其他才开ARGB8888;同时降低起设备图片内存池大小;

Kotlin

kotlin data class

data class会自动生成以下方法:

  • equals()

  • hashCode()

    1
    2
    3
    4
    5
    public int hashCode() {
    int var10000 = Integer.hashCode(this.age) * 31;
    String var10001 = this.name;
    return var10000 + (var10001 != null ? var10001.hashCode() : 0);
    }
  • toString()

  • copy()

  • componentN()

    编译器会为数据类生成 组件函数(Component function), 有了这些组件函数, 就可以在 解构声明(destructuring declaration) 中使用数据类:

    1
    2
    3
    4
    val jane = User("Jane", 35)
    val (name, age) = jane
    println("$name, $age years of age")
    // 输出结果为 Jane, 35 years of age
  • 属性的get()/set()

    val的属性不会有setter

  • constructor()

    只有有参构造函数,没有无参构造函数。fastJson解析会抛该异常,需升级到高版本并引入kotlin-reflect依赖

Read more

Surface

  • SurfaceView: View 的子类,但不与宿主 Window 共享 Surface, 而是有自己独立的 Surface, 且可以在一个独立的线程中进行绘制,因此 SurfaceView 一般用来实现比较复杂的图像或动画/视频的显示。可以参考 Android双缓存与SurfaceView。由于其内容是绘制在一个独立的 Surface 上,因此无法用 scrollTo/By 等方法去移动操作 Canvas 里的内容,但是可对整个 View 进行平移,缩放,旋转等变换操作。
  • GLSurfaceView: 基于 SurfaceView 再次进行扩展,在 SurfaceView 基础上封装了 EGL 环境管理以及 Render 线程,专门为 OpenGl 显示渲染使用。参考 Android-OpenGL-ES笔记
  • TextrueView: Android 4.0 后引入 TextureView, 它将 SurfaceTexture 和 View 结合到了一起。与 SurfaceView 相比,它并没有创建一个单独的 Surface 来绘制,解决了 SurfaceView 无法在 Canvas 内容上做动画的问题。另外 TextureView 必须在硬件加速开启的窗口中使用。
Read more

SystemStartProcess

image-20210730142407367

简述:

开机后系统将Rom文件加载进Ram内存中,loader检查Ram,kernel启动Swapper进程和kthreadd进程(创建内核守护进程)。

NativeFramework中init.cpp运行后启动init进程,init进程解析init.rc文件后,孵化如installd、logd、adbd等用户守护进程、启动servicemanager、surfaceflinger、bootanim等服务、孵化出Zygote虚拟机进程(java进程)。

JavaFramework中Zygote注册ZygoteSocket加载虚拟机、预加载通用类、资源;之后孵化system_server进程,启动如AMS(startService可监听RootPhase)、WMSPKMS、PMS等服务。

App:由Zygote孵化的第一个App——Launcher(桌面),用户点击Launcher上的app图片,通过JNI调用AMS从Zygote进程中fork出新的App。

Read more