最近发现一个技术提升的平台,很多课程对于技术提升都多有益处,很多是实际上的项目实战并对底层原理讲解透彻。前几个月已经学习完了《Android 工程师进阶 34 讲》,现在重学一遍并做些总结记录加深印象。学习的过程要符合遗忘曲线,当一些所学的知识遗忘时就是该重新复学的开始,这样真正学到的知识才能刻进大脑里变成自己的知识,把书读薄在运用到的时候“下笔如有神”。

一.程序运行时内存的分配:

Java 虚拟机在执行 Java 程序的过程中,会把它所管理的内存划分为不同的数据区域。下面这张图描述了一个 HelloWorld.java 文件被 JVM 加载到内存中的过程:

  • 计数器:主要记录当前线程执行的位置;
  • 虚拟机栈:JVM 是基于栈的解释器执行的,DVM 是基于寄存器解释器执行的。虚拟机栈的初衷是用来描述 Java 方法执行的内存模型,每个方法被执行的时候,JVM 都会在虚拟机栈中创建一个栈帧。而栈帧于支持虚拟机进行方法调用和方法执行的数据结构,每一个线程在执行某个方法时,都会为这个方法创建一个栈帧。栈帧包含局部变量表/操作数栈/动态链接/返回地址;

局部变量表:是变量值的存储空间,调用方法时传递的参数,以及在方法内部创建的局部变量都保存在局部变量表中;

操作数栈:是一个后入先出(LIFO)的栈,当一个方法刚刚开始执行的时候,这个方法的操作数栈是空的。在方法执行的过程中,会有各种字节码指令被压入和弹出操作数栈;

动态链接:主要目的是为了支持方法调用过程中的动态连接;

返回地址:用来帮助当前方法恢复它的上层方法执行状态;

  • 本地方法栈:与虚拟栈类似,用的于本地(native)方法;
  • 堆:存放的是实例对象;
  • 方法区:主要是存储已经被 JVM 加载的类信息(版本、字段、方法、接口)、常量、静态变量、即时编译器编译后的代码和数据。

二.GC 回收机制与分代回收策略:

Java 语言开发者比 C 语言开发者幸福的地方就在于,我们不需要手动释放对象的内存,JVM 中的垃圾回收器(Garbage Collector)会为我们自动回收。

  • 可达性分析:可达性分析算法是从离散数学中的图论引入的,JVM 把内存中所有的对象之间的引用关系看作一张图,通过一组名为”GC Root"的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链,最后通过判断对象的引用链是否可达来决定对象是否可以被回收:

  • GC Root对象:在 Java 中,有以下几种对象可以作为 GC Root;

1.Java虚拟机栈(局部变量表)中引用的对象;

2.方法区中静态引用指向的对象;

3.仍处于存活状态的线程对象;

4.native方法中引用的JNI对象。

  • 触发GC回收的情况:

Allocation Failure:在堆内存中分配时,如果因为可用剩余空间不足导致对象内存分配失败,这时系统会触发一次 GC;

System.gc():在应用层,Java 开发工程师可以主动调用此 API 来请求一次 GC。

  • 垃圾回收算法:标记清除算法,复制算法,标记-压缩算法。
  • JVM分代回收策略:Java 虚拟机根据对象存活的周期不同,把堆内存划分为几块,一般分为新生代、老年代,这就是 JVM 的内存分代策略。对于新创建的对象会在新生代中分配内存,此区域的对象生命周期一般较短。如果经过多次回收仍然存活下来,则将它们转移到老年代中。
  • 对象引用:强引用(Strong Reference)、软引用(Soft Reference)、弱引用(Weak Reference)、虚引用(Phantom Reference);

三.字节码层面分析 class 类文件结构:

  • class文件的用处:Java 能够实现"一次编译,到处运行”,这其中 class 文件要占大部分功劳。为了让 Java 语言具有良好的跨平台能力,Java 独具匠心的提供了一种可以在所有平台上都能使用的一种中间代码——字节码类文件(.class文件)。
  • class文件的结构: class 文件中只存在无符号数和表这两种数据结构。而这些无符号数和表就组成了 class 中的各个结构;

四.编译插桩操纵字节码:

  • 编译插桩:所谓编译插桩就是在代码编译期间修改已有的代码或者生成新代码。实际上,我们项目中经常用到的 Dagger、ButterKnife 甚至是 Kotlin 语言,它们都用到了编译插桩的技术;

  • 编译插桩实现的功能:日志埋点/性能监控/动态权限控制/业务逻辑跳转时,校验是否已经登录/甚至调试。
  • 插桩工具:AspectJ和 ASM, 通过 ASM 可以修改现有的字节码文件,也可以动态生成字节码文件,并且它是一款完全以字节码层面来操纵字节码并分析字节码的框架;

五.ClassLoader 的加载机制:

在 Java 程序启动的时候,并不会一次性加载程序中所有的 .class 文件,而是在程序的运行过程中,动态地加载相应的类到内存中,Java程序中的.class文件会在调用类构造器和调用类中的静态(static)变量或者静态方法两种情形下被ClassLoader主动加载到内存中。

  • JVM中自带3种类型的类加载器:

启动类加载器 BootstrapClassLoader:BootstrapClassLoader 加载系统属性“sun.boot.class.path”配置下类文件,它并不是使用 Java 代码实现的,而是由 C/C++ 语言编写的,它本身属于虚拟机的一部分。因此我们无法在 Java 代码中直接获取它的引用;

扩展类加载器 ExtClassLoader (JDK 1.9 之后,改名为 PlatformClassLoader):ExtClassLoader 加载系统属性“java.ext.dirs”配置下类文件;

系统加载器 APPClassLoader:AppClassLoader 主要加载系统属性“java.class.path”配置下类文件,也就是环境变量 CLASS_PATH 配置的路径。因此 AppClassLoader 是面向用户的类加载器,我们自己编写的代码以及使用的第三方 jar 包通常都是由它来加载的。

  • 双亲委派模式(Parents Delegation Model):所谓双亲委派模式就是,当类加载器收到加载类或资源的请求时,通常都是先委托给父类加载器加载,也就是说,只有当父类加载器找不到指定类或资源时,自身才会执行实际的类加载过程。
  • Android中的ClassLoader:本质上,Android 和传统的 JVM 是一样的,也需要通过 ClassLoader 将目标类加载到内存,类加载器之间也符合双亲委派模型。但是在 Android 中, ClassLoader 的加载细节有略微的差别。在 Android 虚拟机里是无法直接运行 .class 文件的,Android 会将所有的 .class 文件转换成一个 .dex 文件,并且 Android 将加载 .dex 文件的实现封装在 BaseDexClassLoader 中,而我们一般只使用它的两个子类:PathClassLoader 和 DexClassLoader;
  • PathClassLoader:用来加载系统 apk 和被安装到手机中的 apk 内的 dex 文件;
  • DexClassLoader:对比 PathClassLoader 只能加载已经安装应用的 dex 或 apk 文件,DexClassLoader 则没有此限制,可以从 SD 卡上加载包含 class.dex 的 .jar 和 .apk 文件,这也是插件化和热修复的基础,在不需要安装应用的情况下,完成需要使用的 dex 的加载。

六.Class 对象在执行引擎中的初始化过程:

一个 class 文件被加载到内存中需要经过 3 大步:装载、链接、初始化。其中链接又可以细分为:验证、准备、解析 3 小步。因此用一张图来描述 class 文件加载到内存的步骤如下所示:

  • 装载:是指 Java 虚拟机查找 .class 文件并生成字节流,然后根据字节流创建 java.lang.Class 对象的过程;装载的三个过程:

1.ClassLoader 通过一个类的全限定名(包名 + 类名)来查找 .class 文件,并生成二进制字节流:其中 class 字节码文件的来源不一定是 .class 文件,也可以是 jar 包、zip 包,甚至是来源于网络的字节流;

2.把 .class 文件的各个部分分别解析(parse)为 JVM 内部特定的数据结构,并存储在方法区;

3.在内存中创建一个 java.lang.Class 类型的对象。

  • 链接:分为验证、准备、解析;

验证:验证是链接的第一步,目的是为了确保 .class 文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危及虚拟机本身的安全。有文件格式检验/元数据检验/字节码检验/符号引用检验。

准备:这一阶段的主要目的是为类中的静态变量分配内存,并为其设置“0值”;

解析:这一阶段的任务是把常量池中的符号引用转换为直接引用,也就是具体的内存地址。在这一阶段,JVM 会将常量池中的类、接口名、字段名、方法名等转换为具体的内存地址。

  • 初始化:这一阶段是执行类构造器<clinit>方法的过程,并真正初始化类变量。

触发class类初始化的情况:

1.虚拟机启动时,初始化包含 main 方法的主类;

2.遇到 new 指令创建对象实例时,如果目标对象类没有被初始化则进行初始化操作;

3.当遇到访问静态方法或者静态字段的指令时,如果目标对象类没有被初始化则进行初始化操作;

4.子类的初始化过程如果发现其父类还没有进行过初始化,则需要先触发其父类的初始化;

5.使用反射 API 进行反射调用时,如果类没有进行过初始化则需要先触发其初始化;

6.第一次调用 java.lang.invoke.MethodHandle 实例时,需要初始化 MethodHandle 指向方法所在的类。

初始化变量 :在初始化阶段,只会初始化与类相关的静态赋值语句和静态语句,也就是有 static 关键字修饰的信息,而没有 static 修饰的语句块在实例化对象的时候才会执行。

  • class 初始化和对象的创建顺序:静态变量/静态代码块 -> 普通代码块 -> 构造函数,顺序:父类静态变量和静态代码块->子类静态变量和静态代码块->父类普通成员变量和普通代码块->父类的构造函数->子类普通成员变量和普通代码块->子类的构造函数。

七.Java的内存模型与线程:

  • 内存模型:是一套共享内存系统中多线程读写操作行为的规范,这套规范屏蔽了底层各种硬件和操作系统的内存访问差异,解决了 CPU 多级缓存、CPU 优化、指令重排等导致的内存访问问题,从而保证 Java 程序(尤其是多线程程序)在各种平台下对内存的访问效果一致。

  • 缓存不一致的问题引入:在执行任务时线程是CPU执行的最小单元,而CPU执行的速度比操作内存要快这时引入高速缓存及会有多CPU的情形。就会出现多线程操作同一块的内存的情况出现;
  • 程序次序规则:在单线程内部,如果一段代码的字节码顺序也隐式符合 happens-before 原则,那么逻辑顺序靠前的字节码执行结果一定是对后续逻辑字节码可见,只是后续逻辑中不一定用到而已;
  • 锁定规则:无论是在单线程环境还是多线程环境,一个锁如果处于被锁定状态,那么必须先执行 unlock 操作后才能进行 lock 操作;
  • 变量规则:volatile 保证了线程可见性。通俗讲就是如果一个线程先写了一个 volatile 变量,然后另外一个线程去读这个变量,那么这个写操作一定是 happens-before 读操作的;
  • 线程启动规则:Thread 对象的 start() 方法先行发生于此线程的每一个动作。假定线程 A 在执行过程中,通过执行 ThreadB.start() 来启动线程 B,那么线程 A 对共享变量的修改在线程 B 开始执行后确保对线程 B 可见;
  • 线程中断规则:对线程 interrupt() 方法的调用先行发生于被中断线程的代码检测,直到中断事件的发生;
  • 线程终结规则:线程中所有的操作都发生在线程的终止检测之前,我们可以通过 Thread.join() 方法结束、Thread.isAlive() 的返回值等方法检测线程是否终止执行。假定线程 A 在执行的过程中,通过调用 ThreadB.join() 等待线程 B 终止,那么线程 B 在终止之前对共享变量的修改在线程 A 等待返回后可见;
  • 对象终结规则:一个对象的初始化完成发生在它的 finalize() 方法开始前;

八.Synchronized与 ReentrantLock:

  • Synchronized用来修饰三个层面:

修饰实例方法:锁对象是当前实例对象,因此只有同一个实例对象调用此方法才会产生互斥效果,不同实例对象之间不会有互斥效果;

修饰静态类方法:锁对象是当前类的 Class 对象。因此即使在不同线程中调用不同实例对象,也会有互斥效果;

修饰代码块:锁对象就是跟在后面括号中的对象。

  • ReentrantLock:加锁和解锁操作都需要手动完成,有lock和unlock两个方法。unlock 操作放在 finally 代码块中。这是因为 ReentrantLock 与 synchronized 不同,当异常发生时 synchronized 会自动释放锁,但是 ReentrantLock 并不会自动释放锁。因此好的方式是将 unlock 操作放在 finally 代码块中,保证任何时候锁都能够被正常释放掉;
  • 公平锁:synchronized 和 ReentrantLock 都是非公平锁,但是 ReentrantLock 可以通过传入 true 来创建一个公平锁。所谓公平锁就是通过同步队列来实现多个线程按照申请锁的顺序获取锁;
  • 读写锁(ReentrantReadWriteLock):使用 concurrent 包中的读写锁(ReentrantReadWriteLock)实现上述功能,就只需要在读操作时获取读锁,写操作时获取写锁即可。当写锁被获取到时,后续的读写锁都会被阻塞,写锁释放之后,所有操作继续执行,这种编程方式相对于使用等待通知机制的实现方式而言,变得简单明了;

九.Java 线程优化偏向锁/轻量级锁/重量级锁:

  • synchronized实现原理: Java 对象在内存中的布局分为 3 部分:对象头、实例数据、对齐填充,使用 new 创建一个对象的时候,JVM 会在堆中创建一个 instanceOopDesc 对象,这个对象中包含了对象头以及实例数据;

对象头:在instanceOopDesc 的基类为 oopDesc 类中 _mark 和 _metadata 一起组成了对象头;

Monitor:可以把它理解为一个同步工具,也可以描述为一种同步机制。实际上,它是一个保存在对象头中的一个对象;

  • 锁自旋:就是让该线程等待一段时间,不会被立即挂起,看当前持有锁的线程是否会很快释放锁。而所谓的等待就是执行一段无意义的循环即可(自旋);
  • 轻量级锁:对于一块同步代码,虽然有多个不同线程会去执行,但是这些线程是在不同的时间段交替请求这把锁对象,也就是不存在锁竞争的情况。在这种情况下,锁会保持在轻量级锁的状态,从而避免重量级锁的阻塞和唤醒操作;
  • 偏向锁:轻量级锁是在没有锁竞争情况下的锁状态,但是在有些时候锁不仅存在多线程的竞争,而且总是由同一个线程获得。因此为了让线程获得锁的代价更低引入了偏向锁的概念。偏向锁的意思是如果一个线程获得了一个偏向锁,如果在接下来的一段时间中没有其他线程来竞争锁,那么持有偏向锁的线程再次进入或者退出同一个同步代码块,不需要再次进行抢占锁和释放锁的操作

十. AQS 和 CAS:

  • AQS: 全称是 Abstract Queued Synchronizer,一般翻译为同步器。它是一套实现多线程同步功能的框架,由大名鼎鼎的 Doug Lea 操刀设计并开发实现的。AQS 在源码中被广泛使用,尤其是在 JUC(Java Util Concurrent)中,比如 ReentrantLock、Semaphore、CountDownLatch、ThreadPoolExecutor。理解 AQS 对我们理解 JUC 中其他组件至关重要,并且在实际开发中也可以通过自定义 AQS 来实现各种需求场景;
  • Node双端队列节点: 是一个先进先出的双端队列,并且是等待队列,当多线程争用资源被阻塞时会进入此队列。这个队列是 AQS 实现多线程同步的核心;
  • CAS :全称是 Compare And Swap,译为比较和替换,是一种通过硬件实现并发安全的常用技术,底层通过利用 CPU 的 CAS 指令对缓存加锁或总线加锁的方式来实现多处理器之间的原子操作;

十一.线程池:

线程的创建需要开辟虚拟机栈、本地方法栈、程序计数器等线程私有的内存空间,在线程销毁时需要回收这些系统资源,频繁地创建销毁线程会浪费大量资源,而通过复用已有线程可以更好地管理和协调线程的工作

可跳转至三年前写的博客《Android中线程的相关知识及线程管理工具类》来重新复习线程池相关知识。

十二.DVM 以及 ART 对 JVM 进行的优化:

  • Dalvik:是 Google 公司自己设计用于 Android 平台的 Java 虚拟机,Android 工程师编写的 Java 或者 Kotlin 代码最终都是在这台虚拟机中被执行的。在 Android 5.0 之前叫作 DVM,5.0 之后改为 ART(Android Runtime);
  • Dex 文件:传统 Class 文件是由一个 Java 源码文件生成的 .Class 文件,而 Android 是把所有 Class 文件进行合并优化,然后生成一个最终的 class.dex 文件。dex 文件去除了 class 文件中的冗余信息(比如重复字符常量),并且结构更加紧凑,因此在 dex 解析阶段,可以减少 I/O 操作,提高了类的查找速度;
  • 架构基于寄存器&基于栈堆结构:JVM 的指令集是基于栈结构来执行的,而 Android 却是基于寄存器的,不过这里不是直接操作硬件的寄存器,而是在内存中模拟一组寄存器;
  • Zygote进程:Android 系统的第一个 Dalvik 虚拟机是由 Zygote 进程创建的,而应用程序进程是由 Zygote 进程 fork 出来的。Zygote 进程是在系统启动时产生的,它会完成虚拟机的初始化,库的加载,预置类库的加载和初始化等操作,而在系统需要一个新的虚拟机实例时,Zygote 通过复制自身,最快速的提供一个进程;另外,对于一些只读的系统库,所有虚拟机实例都和 Zygote 共享一块内存区域,大大节省了内存开销;

十三.Activity 交互:

  • taskAffinity:通过设置不同的启动模式可以实现调配不同的 Task。但是 taskAffinity 在一定程度上也会影响任务栈的调配流程;
  • taskAffinity + allowTaskReparenting:allowTaskReparenting 赋予 Activity 在各个 Task 中间转移的特性。一个在后台任务栈中的 Activity A,当有其他任务进入前台,并且 taskAffinity 与 A 相同,则会自动将 A 添加到当前启动的任务栈中;

十四. Android touch 事件分发时序:

  • 当前 Group 和子 View 之间的逻辑关系:

1.当前 Group 是否需要拦截 touch 事件;

2.是否需要将 touch 事件继续分发给子 View;

3.如何将 touch 事件分发给子 View。

  • View 单纯的控件touch 事件处理:

1.是否存在 TouchListener;

2.是否自己接收处理 touch 事件(主要逻辑在 onTouchEvent 方法中)

  • dispatchTouchEvent:整个 View 之间的事件分发,实质上就是一个大的递归函数,而这个递归函数就是 dispatchTouchEvent 方法。在这个递归的过程中会适时调用 onInterceptTouchEvent 来拦截事件,或者调用 onTouchEvent 方法来处理事件;
  • DOWN 事件:一旦子 View 捕获DOWN事件成功,后续的 MOVE 和 UP 事件是通过遍历 mFirstTouchTarget 链表,查找之前接受 ACTION_DOWN 的子 View,并将触摸事件分配给这些子 View。也就是说后续的 MOVE、UP 等事件的分发交给谁,取决于它们的起始事件 Down 是由谁捕获的。
  • ACTION_CANCEL 事件的触发:当父视图的 onInterceptTouchEvent 先返回 false,然后在子 View 的 dispatchTouchEvent 中返回 true(表示子 View 捕获事件),关键步骤就是在接下来的 MOVE 的过程中,父视图的 onInterceptTouchEvent 又返回 true,intercepted 被重新置为 true,逻辑就会被触发,子控件就会收到 ACTION_CANCEL 的 touch 事件。

十五.Android自定义 View:

  • 继承现有控件:关于控件大小的测量、控件位置的摆放等相关的计算,在系统中都已经实现并封装好,开发人员只要在其基础上进行一些扩展,并按照自己的意图显示相应的 UI 元素;
  • 自定义属性:attrs.xml 中声明自定义属性;
  • 直接继承自 View 或者 ViewGroup:

1.如何根据相应的属性将 UI 元素绘制到界面;

2.自定义控件的大小,也就是宽和高分别设置多少;

3.如是 ViewGroup,如何合理安排其内部子 View 的摆放位置。

  • onDraw方法:可以理解为一个画布,在这块画布上可以绘制各种类型的 UI 元素。
  • onMeasure方法:Android 系统提供了 wrap_contetn 和 match_parent 属性来规范控件的显示规则。它们分别代表自适应大小和填充父视图的大小,但是这两个属性并没有指定具体的大小,因此我们需要在 onMeasure 方法中过滤出这两种情况,真正的测量出自定义 View 应该显示的宽高大小;
  • 三种测量模式:

1.EXACTLY:表示在 XML 布局文件中宽高使用 match_parent 或者固定大小的宽高;

2.AT_MOST:表示在 XML 布局文件中宽高使用 wrap_content;

3.UNSPECIFIED:父容器没有对当前 View 有任何限制,当前 View 可以取任意尺寸。

  • onLayout方法:每一个自定义 ViewGroup 都必须主动实现如何排布子 View,具体就是遍历每一个子 View,调用 child.(l, t, r, b) 方法来为每个子 View 设置具体的布局位置。四个参数分别代表左上右下的坐标位置;

十六:RecyclerView分析:

  • RecyclerView常用设置项:

1.setLayoutManager:必选项,设置 RV 的布局管理器,决定 RV 的显示风格。常用的有线性布局管理器(LinearLayoutManager)、网格布局管理器(GridLayoutManager)、瀑布流布局管理器(StaggeredGridLayoutManager);

2.setAdapter:必选项,设置 RV 的数据适配器。当数据发生改变时,以通知者的身份,通知 RV 数据改变进行列表刷新操作;

3.addItemDecoration:非必选项,设置 RV 中 Item 的装饰器,经常用来设置 Item 的分割线;

4.setItemAnimator:非必选项,设置 RV 中 Item 的动画。

  • 缓存复用原理 Recycler:缓存复用是 RV 中另一个非常重要的机制,这套机制主要实现了 ViewHolder 的缓存以及复用。核心代码是在 Recycler 中完成的,它是 RV 中的一个内部类,主要用来缓存屏幕内 ViewHolder 以及部分屏幕外 ViewHolder;
  • 各级缓存功能:

1.第一级缓存 mAttachedScrap&mChangedScrap:在原有的 ViewHolder 基础上进行重新绑定新的数据 data 即可,而这些旧的 ViewHolder 就是被保存在 mAttachedScrap 和 mChangedScrap 中。实际上当我们调用 RV 的 notifyXXX 方法时,就会向这两个列表进行填充,将旧 ViewHolder 缓存起来;

2.第二级缓存 mCachedViews:它用来缓存移除屏幕之外的 ViewHolder,默认情况下缓存个数是 2,不过可以通过 setViewCacheSize 方法来改变缓存的容量大小。如果 mCachedViews 的容量已满,则会根据 FIFO 的规则将旧 ViewHolder 抛弃,然后添加新的 ViewHolder;

3.第三级缓存 ViewCacheExtension:这是 RV 预留给开发人员的一个抽象类,在这个类中只有一个抽象方法。开发人员可以通过继承 ViewCacheExtension,并复写抽象方法 getViewForPositionAndType 来实现自己的缓存机制;

4.第四级缓存 RecycledViewPool:同样是用来缓存屏幕外的 ViewHolder,当 mCachedViews 中的个数已满(默认为 2),则从 mCachedViews 中淘汰出来的 ViewHolder 会先缓存到 RecycledViewPool 中。ViewHolder 在被缓存到 RecycledViewPool 时,会将内部的数据清理,因此从 RecycledViewPool 中取出来的 ViewHolder 需要重新调用 onBindViewHolder 绑定数据。

十七.OkHttp 详解:

OkHttp 是一套处理 HTTP 网络请求的依赖库,由 Square 公司设计研发并开源,目前可以在 Java 和 Kotlin 中使用。对于 Android App 来说,OkHttp 现在几乎已经占据了所有的网络请求操作,RetroFit + OkHttp 实现网络请求似乎成了一种标配。

  • OkHttpClient.newCall().enqueue() 方法:

1.newCall:这个方法会返回一个 RealCall 类型的对象,通过它将网络请求操作添加到请求队列中;

2.RealCall.enqueue:调用 Dispatcher 的入队方法,执行一个异步网络请求的操作。

  • Dispatcher:是 OkHttpClient 的调度器,是一种门户模式。主要用来实现执行、取消异步请求操作。本质上是内部维护了一个线程池去执行异步操作,并且在 Dispatcher 内部根据一定的策略,保证最大并发个数、同一 host 主机允许执行请求的线程个数等;
  • Interceptor拦截器:

1.BridgeInterceptor:主要对 Request 中的 Head 设置默认值,比如 Content-Type、Keep-Alive、Cookie 等;

2.CacheInterceptor:负责 HTTP 请求的缓存处理;

3.ConnectInterceptor:负责建立与服务器地址之间的连接,也就是 TCP 链接;

4.CallServerInterceptor:负责向服务器发送请求,并从服务器拿到远端数据结果。

  • Okio:在向服务端发送数据以及获取数据都是使用一个 Okio 的框架来实现的。Okio 是 Square 公司打造的另外一个轻量级 IO 库,它是 OkHttp 框架的基石;

十八: Bitmap 全面详解:

  • getAllocationByteCount :可以通过 Bitmap.getAllocationByteCount() 方法获取 Bitmap 占用的字节大小;
  • res/drawable-hdpi 等目录屏幕自适应:实际上 BitmapFactory 在解析图片的过程中,会根据当前设备屏幕密度和图片所在的 drawable 目录来做一个对比,根据这个对比值进行缩放操作;
  • assets 中的图片:加载 assets 目录中的图片,系统并不会对其进行缩放操作;
  • Bitmap 加载优化:

1.修改图片加载的 Config:修改占用空间少的存储方式可以快速有效降低图片占用内存;

2.Bitmap 复用:通过 BitmapFactory 创建一个新的 Bitmap 对象。当方法执行完毕后,这个 Bitmap 又会被 GC 回收,这就造成不断地创建和销毁比较大的内存对象,从而导致频繁 GC(或者叫内存抖动);

3.使用 Options.inBitmap 优化:实际上经过第一次显示之后,内存中已经存在了一个 Bitmap 对象。每次切换图片只是显示的内容不一样,我们可以重复利用已经占用内存的 Bitmap 空间,具体做法就是使用 Options.inBitmap 参数;

4.BitmapRegionDecoder 图片分片显示:

5.Bitmap 缓存:最常用的缓存方式就是 LruCache;

十九.apk包体积优化:

  • 安装包监控:

1.APK Analyser是 Android Studio 提供的一个 APK 检测工具,通过它可以查看一个 APK 文件内部各项内容所占的大小,并且按照大小排序显示。因此我们很容易观察到 APK 中哪一部分内容占用了最大空间;

2.Matrix中 的 ApkChecker:是腾讯开源框架 Matrix 的一部分,主要是用来对 Android 安装包进行分析检测,并输出较为详细的检测结果报告;

  • 安装包优化实践:

1.删除无用文件:使用 Lint 查看未引用资源。Lint 是一个静态扫描工具,它可以识别出项目中没有被任何代码所引用到的资源文件。具体使用也很简单,只要在 Android Studio 中点击 Analyze -> Inspect Code,然后选中整个项目即可;

2.文件优化:静态图片优化/动态图片优化/引入三方库只裁剪自己所需要的功能模块。

二十.Android 崩溃:

  • JVM 异常堆栈信息:Java 中异常(Exception)分两种:检查异常 checked Exception 和非检查异常 unchecked Exception;
  • 自定义异常处理类:自定义类实现 UncaughtExceptionHandler 接口,并实现 uncaughtException 方法

在 handlerException 方法中需要做的:

  1. 收集 crash 现场的相关信息,如下面方法获取当前 App 的版本信息,以及所有手机设备的相关信息;
  2. 日志的记录工作,将收集到的信息保存在本地。
  • native异常:当程序中的 native 代码发生崩溃时,系统会在 /data/tombstones/ 目录下保存一份详细的崩溃日志信息;
  • NDK 导入 BreakPad:在 Breakpad GitHub 官网上,有一个 README Android 的介绍文件。这个文件专门介绍了如何在 Android 项目中导入 BreakPad。我们可以直接使用 CMake 方式将其编译为一个静态库;
  • 线上崩溃日志获取:针对线上版本,没有必要自己实现一个抓取 log 的平台系统。最快速的实现方式就是集成第三方 SDK。目前比较成熟,采用也比较多的就是腾讯的 Bugly。Bugly 基本能够满足线上版本捕获 crash 的所有需求,包括 Java 层和 Native 层的 crash 都可以获取相应的日志。并且每天 Bugly 都会邮件通知上一天的崩溃日志,方便测试和开发统计 bug 的分布以及崩溃率;

二十一.内存泄露的优化:

  • Activity 内存泄漏预防: Activity 承担了与用户交互的职责,因此内部需要持有大量的资源引用以及与系统交互的 Context,这会导致一个 Activity 对象的 retained size 特别大。一旦 Activity 因为被外部系统所持有而导致发生内存泄漏,被牵连导致其他对象的内存泄漏也会非常多;
  • Activity 内存泄漏的场景:
  1. 将 Context 或者 View 置为 static;
  2. 未解注册各种 Listener;
  3. 非静态 Handler 导致 Activity 泄漏;
  4. 三方库使用 Context;
  • 内存泄漏检测:LeakCanary或者MAT;
  • LeakCanary的实现原理:Java 中的 WeakReference 是弱引用类型,每当发生 GC 时,它所持有的对象如果没有被其他强引用所持有,那么它所引用的对象就会被回收;
  • LeakCanary实现思路:对内存泄漏检测的核心原理就是基于 WeakReference 和 ReferenceQueue 实现的:
  1. 当一个 Activity 需要被回收时,就将其包装到一个 WeakReference 中,并且在 WeakReference 的构造器中传入自定义的 ReferenceQueue。
  2. 然后给包装后的 WeakReference 做一个标记 Key,并且在一个强引用 Set 中添加相应的 Key 记录;
  3. 最后主动触发 GC,遍历自定义 ReferenceQueue 中所有的记录,并根据获取的 Reference 对象将 Set 中的记录也删除
  4. 经过上面 3 步之后,还保留在 Set 中的就是:应当被 GC 回收,但是实际还保留在内存中的对象,也就是发生泄漏了的对象;

二十二:UI卡顿分析与解决:

  • Systrace的使用:对于 UI 性能分析,Systrace 是目前使用最广的工具。它能够帮助开发者分析多个模块的运行状态以及详细信息。比如 SurfaceFlinger、View 刷机机制等。通过 Android 提供的脚本 systrace.py,可以设置数据采集方式并收集相关程序运行数据,最终生成一个网页文件提供程序开发者分析程序性能问题;

Android工程师进阶34讲学习笔记相关推荐

  1. java 源码学习,Java源码剖析34讲学习笔记~4

    详解 ThreadPoolExecutor 的参数含义及源码执行流程 前言 在阿里巴巴的开发者手册中针对线程池有如下说明: [强制]线程池不允许使用 Executors 去创建,而是通过 Thread ...

  2. Android工程师进阶第五课 多线程锁,线程池和DVM/ART优化

    第09讲:Java 线程优化 偏向锁,轻量级锁.重量级锁 我目前所在的公司是一家跨国企业,总部在瑞典.前段时间公司新开发的一个应用准备发布到应用宝平台.但是在发布之前,需要准备一系列软著相关的证明材料 ...

  3. 视觉SLAM十四讲学习笔记-第二讲-开发环境搭建

    专栏系列文章如下: 视觉SLAM十四讲学习笔记-第一讲_goldqiu的博客-CSDN博客 视觉SLAM十四讲学习笔记-第二讲-初识SLAM_goldqiu的博客-CSDN博客 ​​​​​​​ lin ...

  4. Android 工程师进阶手册(8 年 Android 开发者的成长感悟)

    AndroidDeveloperAdvancedManual 项目地址:Skykai521/AndroidDeveloperAdvancedManual 简介: Android 工程师进阶手册(8 年 ...

  5. MySQL实战45讲学习笔记

    文章目录 MySQL实战45讲-学习笔记 01 基础架构:一条SQL查询语句是如何执行的? mysql逻辑架构 连接器 查询缓存 分析器 优化器 执行器 02 日志系统:一条SQL更新语句如何执行 r ...

  6. Android Jetpack Components of LiveData 学习笔记

    Android Jetpack Components of Lifecycle 学习笔记 Android Jetpack Components of LiveData 学习笔记 Android Jet ...

  7. Android开源项目SlidingMenu本学习笔记(两)

    我们已经出台SlidingMenu使用:Android开源项目SlidingMenu本学习笔记(一个),接下来再深入学习下.依据滑出项的Menu切换到相应的页面 文件夹结构: 点击Bluetooth能 ...

  8. 视觉SLAM十四讲学习笔记-第七讲-视觉里程计-三角测量和实践

     专栏汇总 视觉SLAM十四讲学习笔记-第一讲_goldqiu的博客-CSDN博客 视觉SLAM十四讲学习笔记-第二讲-初识SLAM_goldqiu的博客-CSDN博客 视觉SLAM十四讲学习笔记-第 ...

  9. 视觉SLAM十四讲学习笔记-第七讲-视觉里程计-对极几何和对极约束、本质矩阵、基础矩阵

    专栏系列文章如下:  专栏汇总 视觉SLAM十四讲学习笔记-第一讲_goldqiu的博客-CSDN博客 视觉SLAM十四讲学习笔记-第二讲-初识SLAM_goldqiu的博客-CSDN博客 视觉SLA ...

最新文章

  1. 2020暑期实习后台开发字节跳动笔试
  2. Matlab 读取文件夹里所有的文件
  3. linux中的定时,linux中的定时任务
  4. 字符串替换ant修改文件中的内容
  5. 听说你盗图都盗绿了?
  6. mybatis多条件批量查询_Mybatis【14】 Mybatis如何实现一对多查询?
  7. vb中line的用法[转]
  8. 《东周列国志》第五十九回 宠胥童晋国大乱 诛岸贾赵氏复兴
  9. 谷歌跟oracle_Google vs Oracle –判决临近,Oracle的知识产权案件步履蹒跚
  10. 【DDD落地实践系列】DDD领域驱动设计如何进行工程化落地
  11. Rust中的所有权和借用的关系图
  12. 原来Python自带了数据库,用起来真方便
  13. android手机来电自动报名字,读短信来电报姓名
  14. WIN7电脑防火墙如何设置?
  15. python制作物联网控制软件下载_基于Python和Django框架的物联网智能设备管理系统的设计与实现...
  16. Python——LeetCode刷题——【977. 有序数组的平方】
  17. 工业企业如何转型与升级?2022年我国6部门联合出台《工业能效提升行动计划》具体申报条件及认定好处如下
  18. 三线金叉选股公式,均线、成交量、MACD共振
  19. 如何获取淘宝商品评论 API接口
  20. f_GetErrorInfo()获取系统错误信息函数

热门文章

  1. 龙贝格求积分算法例题_数值分析实习作业之龙贝格求积
  2. 自定义圆形进度条的实现方式
  3. Taro——taro安装及taro创建项目相关命令
  4. Taro,小程序scroll-view 填满剩下的高度空间,关闭页面回跳(部分ios机型 滚动不到底部)
  5. 数据分析师8大经典问题
  6. WinHTTP 会话概览
  7. 使用JavaScript制作轮播
  8. StrConv 内码转换
  9. oracle •求几个列的平均值,并保留2位小数
  10. 计算机视觉领域推荐期刊和会议评分标准