0 Android进阶之光-目录

2020/01/14 posted in  Android进阶之光

01 Android新特性

2019/03/27 posted in  Android进阶之光

02 Material Design

2019/03/27 posted in  Android进阶之光

03 View体系与自定义View

3.1 View与ViewGroup

3.2 坐标系

3.2.1 Android坐标系

3.2.2 View坐标系

3.3 View的滑动

3.3.1 layout()方法

3.3.2 offsetLeftAndRight()与offsetTopAndBottom()

3.3.3 LayoutParams(改变布局参数)

3.3.4 动画

3.3.5 scrollTo与scollBy

3.3.6 Scroller

3.4 属性动画

3.5 解析Scroller

3.6 View的事件分发机制

3.6.1 源码解析Activity的构成

3.6.2 源码解析View的事件分发机制

3.7 View的工作流程

3.7.1 View的工作流程入口

3.7.2 理解MeasureSpec

3.7.3 View的measure流程

3.7.4 View的layout流程

3.7.5 View的draw流程

3.8 自定义View

3.8.1 继承系统控件的自定义View

3.8.2 继承View的自定义View

3.8.3 自定义组合控件

3.8.4 自定义ViewGroup

3.9 本章小结

2019/12/11 posted in  Android进阶之光

04 多线程编程

4.1 线程基础

4.1.1 进程与线程

1.什么是进程
进程是操作系统结构的基础,是程序在一个数据集合上运行的过程,是系统进行资源分配和调度的基本单位。进程可以被看作程序的实体,同样,它也是线程的容器。进程就是程序的实体,是受操作系统管理的基本运行单元。
2.什么是线程
线程是操作系统调度的最小单元,也叫作轻量级进程。在一个进程中可以创建多个线程,这些线程都拥有各自的计数器、堆栈和局部变量等属性,并且能够访问共享的内存变量。
3.为何要使用多线程
在操作系统级别上来看主要有以下几个方面:

  • 使用多线程可以减少程序的响应时间,使程序具备了更好的交互性。
  • 与进程相比,线程创建和切换开销更小,同时多线程在数据共享方面效率非常高。
  • 避免多CPU或者多核计算机的资源浪费,提高CPU的利用率。
  • 使用多线程能简化程序的结构,使程序便于理解和维护。

4.1.2 线程的状态

Java线程在运行的声明周期中可能会处于6种不同的状态,这6种线程状态分别为如下所示。

  • New:新创建状态。线程被创建,还没有调用 start 方法,在线程运行之前还有一些基础工作要做。
  • Runnable:可运行状态。一旦调用start方法,线程就处于Runnable状态。一个可运行的线程可能正在 运行也可能没有运行,这取决于操作系统给线程提供运行的时间。
  • Blocked:阻塞状态。表示线程被锁阻塞,它暂时不活动。
  • Waiting:等待状态。线程暂时不活动,并且不运行任何代码,这消耗最少的资源,直到线程调度器重新激活它。
  • Timed waiting:超时等待状态。和等待状态不同的是,它是可以在指定的时间自行返回的。
  • Terminated:终止状态。表示当前线程已经执行完毕。导致线程终止有两种情况:第一种就是run方法执行完毕正常退出;第二种就是因为一个没有捕获的异常而终止了run方法,导致线程进入终止状态。

4.1.3 创建线程

多线程的实现一般有以下3种方法,其中前两种为最常用的方法。

  1. 继承Thread类,重写run()方法
  2. 实现Runnable接口,并实现该接口的run()方法
  3. 实现Callable接口,重写call()方法

Callable接口实际是属于Executor框架中的功能类,Callable接口与Runnable接口的功能类似,但提供了比Runnable更强大的功能,主要表现为以下3点:

  1. Callable可以在任务接受后提供一个返回值;
  2. Callable中的call()方法可以抛出异常;
  3. 运行Callable可以拿到一个Future对象,Future对象表示异步计算的结果,它提供了检查计算是否完成的方法。由于线程属于异步计算模型,因此无法从别的线程中得到函数的返回值,在这种情况下就可 以使用 Future 来监视目标线程调用 call()方法的情况。但调用 Future的get()方法以获取结果时,当前线程就会阻塞,直到call()方法返回结果。

4.1.4 理解中断

当线程的run方法执行完毕,或者在方法中出现没有捕获的异常时,线程将终止。

  • interrupt()方法可以用来请求中断线程。当一个线程调用 interrupt 方法时,线程的中断标识位将被置位(中断标识位为true),线程会不时地检测这个中断标识位,以判断线程是否应该被中断。
  • Thread.currentThread().isInterrupted() 判断线程是否被置位
  • Thread.interrupted(),对中断标识位进行复位。

但是如果一个线程被阻塞,就无法检测中断状态。如果一个线程处于阻塞状态,线程在检查中断标识位时如果发现中断标识位为true,则会在阻塞方法调用处抛出InterruptedException异常,并且在抛出异常前将线程的中断标识位复位,即重新设置为false。需要注意的是被中断的线程不一定会终止,中断线程是为了引起线程的注意,被中断的线程可以决定如何 去响应中断。如果是比较重要的线程则不会理会中断,而大部分情况则是线程会将中断作为一个终止的请求。

抛出InterruptedException异常后合理的处理方式:

  1. 在catch子句中,调用Thread.currentThread.interrupt()来设置中断状态(因为抛出异常后中断标识位会复位),让外界通过判断Thread.currentThread().isInterrupted()来决定是否终止线程还是继续下去。

    void my Task() {
        try {
    sleep(50)
    } catch(InterruptedException) {
    Thread.currentThread().interrupted();
    //...
    }
    }
  2. 更好的做法就是,不使用try来捕获这样的异常,让方法直接抛出,这样调用者可以捕获这个异常。

    void myTask() throw InterruptedException {
        sleep(50)
    }

4.1.5 安全地终止线程

4.2 同步

在多线程应用中,两个或者两个以上的线程需要共享对同一个数据的存取。如果两个线程存取相同的对象,并且每一个线程都调用了修改该对象的方法, 这种情况通常被称为竞争条件。比如很多人同时买火车票,如果不使用同步保证其原子性,有可能出现两个人买同一张票的情况。解决方法如下:当一个线程要使用火车票这个资源时,我们就交给它一把锁,等它把事情做完后再把锁给另一个要用这个资源的线程。这样就不会出现上述情况了。

4.2.1 重入锁与条件对象

synchronized 关键字自动提供了锁以及相关的条件。重入锁ReentrantLock是 Java SE 5.0引入的,就是支持重进入的锁,它表示该锁能够支持一个线程对资源的重复加锁。用 ReentrantLock保护代码块的结构如下所示:

Lock mLock = new ReentrantLock();
mLock.lock() ;
try {
//...
} finally {
mLock.unlock() ;
}

这一结构确保任何时刻只有一个线程进入临界区,临界区就是在同一时刻只能有一个任务访问的代码区。一旦一个线程封锁了锁对象,其他任何线程都无法进入Lock语句。把解锁的操作放在finally中是十分必 要的。如果在临界区发生了异常,锁是必须要释放的,否则其他线程将会永远被阻塞。进入临界区时,却 发现在某一个条件满足之后,它才能执行。这时可以使用一个条件对象来管理那些已经获得了一个锁但是 却不能做有用工作的线程,条件对象又被称作条件变量

一个锁对象拥有多个相关的条件对象,可以用newCondition方法获得一个条件对象Condition,我们得到条件对象后调用await方法,当前线程就被阻塞了并放弃了锁。一旦一个线程调用 await 方法,它就会进入该条件的等待集并处于阻塞状态,直到另一个线程调用了同 一个条件的signalAll方法时为止。调用signalAll方法时并不是立即激活一个等待线程,它仅仅解除了等待线程的阻塞,以便这些线程能 够在当前线程退出同步方法后,通过竞争实现对对象的访问。还有一个方法是signal,它则是随机解除某个线程的阻塞。如果该线程仍然不能运行,则再次被阻塞。如果没有其他线程再次调用signal,那么系统就死锁了。

4.2.2 同步方法

如果一个方法用 synchronized 关键字声明,那么对象的锁将保护整个方法。

public synchronized void method() {
}

等价于:

Lock mLock = new ReentrantLock();
public void method() {
    mLock.lock();
    try {
    } finally {
        mLock.unlock();
    }
}

4.2.3 同步代码块

其获得了obj的锁,obj指的是一个对象。

synchronized(obj) {
}

同步代码块是非常脆弱的, 通常不推荐使用。一般实现同步最好用java.util.concurrent包下提供的类,比如阻塞队列。

4.2.4 volatile

有时仅仅为了读写一个或者两个实例域就使用同步的话,显得开销过大;而volatile关键字为实例域的同步访问提供了免锁的机制。如果声明一个域为volatile,那么编译器和虚拟机就知道该域是可能被另一个 线程并发更新的。再讲到volatile关键字之前,我们需要了解一下内存模型的相关概念以及并发编程中的3个 特性:原子性、可见性和有序性。

1.Java内存模型

Java中的堆内存用来存储对象实例,堆内存是被所有线程共享的运行时内存区域,因此,它存在内存可见性的问题。而局部变量、方法定义的参数则不会在线程之间共享,它们不会有内存可见性问题,也不受内存模型的影响。Java 内存模型定义了线程和主存之间的抽象关系:线程之间的共享变量存储在主存中, 每个线程都有一个私有的本地内存,本地内存中存储了该线程共享变量的副本。需要注意的是本地内存是 Java内存模型的一个抽象概念,其并不真实存在,它涵盖了缓存、写缓冲区、寄存器等区域。Java内存模型控制线程之间的通信,它决定一个线程对主存共享变量的写入何时对另一个线程可见。

线程A与线程B之间若要通信的话,必须要经历下面两个步骤:

  1. 线程A把线程A本地内存中更新过的共享变量刷新到主存中去。
  2. 线程B到主存中去读取线程A之前已更新过的共享变量。

2.原子性、可见性和有序性

  • 原子性
    • 对基本数据类型变量的读取和赋值操作是原子性操作,即这些操作是不可被中断的,要么执行完毕, 要么就不执行。一个语句含有多个操作时,就不是原子性操作。
    • java.util.concurrent.atomic 包中有 很多类使用了很高效的机器级指令(而不是使用锁)来保证其他操作的原子性。例如 AtomicInteger 类提供 了方法incrementAndGet和decrementAndGet,它们分别以原子方式将一个整数自增和自减。可以安全地使用 AtomicInteger类作为共享计数器而无须同步。另外这个包还包含AtomicBoolean、AtomicLong和 AtomicReference这些原子类,这仅供开发并发工具的系统程序员使用,应用程序员不应该使用这些类。
  • 可见性
    • 可见性,是指线程之间的可见性,一个线程修改的状态对另一个线程是可见的。也就是一个线程修改的结果,另一个线程马上就能看到。
    • 当一个共享变量被volatile修饰时,它会保证修改的值立即被更新到主存,所以对其他线程是可见的。当有其他线程需要读取该值时,其他线程会去主存中读取新值。
    • 普通的共享变量不能保证可见性,因为普通共享变量被修改之后,并不会立即被写入主存,何时被写入主存也是不确定的。当其他线程去读取该值时,此时主存中可能还是原来的旧值,这样就无法保证可见性。
  • 有序性
    • Java内存模型中允许编译器和处理器对指令进行重排序,虽然重排序过程不会影响到单线程执行的正确性,但是会影响到多线程并发执行的正确性。
    • 可以通过volatile来保证有序性;
    • 还可以通 过synchronized和Lock来保证有序性。我们知道,synchronized和Lock保证每个时刻只有一个线程执行同步代码,这相当于是让线程顺序执行同步代码,从而保证了有序性。

3.volatile关键字

当一个共享变量被volatile修饰之后,其就具备了两个含义,一个是线程修改了变量的值时,变量的新值对其他线程是立即可见的,即不同线程对这个变量进行操作时具有可见性。另一个含义是禁止使用指令重排序。

什么是重排序呢?重排序通常是编译器或运行时环境为了优化程序性能而采取的对指令进行重新排序执行的一种手段。重排序分为两类:编译期重排序和运行期重排序,分别对应编译时和运行时环境。

volatile不保证操作原子性
volatile保证有序性

4.正确使用volatile关键字

synchronized关键字可防止多个线程同时执行一段代码,那么这就会很影响程序执行效率。而volatile关键字在某些情况下的性能要优于synchronized。但是要注意volatile关键字是无法替代synchronized关键字的, 因为volatile关键字无法保证操作的原子性。通常来说,使用volatile必须具备以下两个条件:

  1. 对变量的写操作不会依赖于当前值。
  2. 该变量没有包含在具有其他变量的不变式中。

使用volatile有很多种场景,这里介绍其中的两种。

  1. 状态标志

    volatile boolean shutdownRequested;
    public void shutdown () {
    shutdownRequested = true;
    }
    public void doWork() {
    while (!shutdownRequested) {
    }
    }
  2. 双重检查模式(DCL)

4.3 阻塞队列

4.3.1 阻塞队列简介

阻塞队列常用于生产者和消费者的场景,生产者是往队列里添加元素的线程,消费者是从队列里拿元 素的线程。阻塞队列就是生产者存放元素的容器,而消费者也只从容器里拿元素。

1.常见阻塞场景

阻塞队列有两个常见的阻塞场景,它们分别是:

  1. 当队列中没有数据的情况下,消费者端的所有线程都会被自动阻塞(挂起),直到有数据放入队列。队列空,消费者堵塞。
  2. 当队列中填满数据的情况下,生产者端的所有线程都会被自动阻塞(挂起),直到队列中有空的位置,线程被自动唤醒。队列满,生产者堵塞。

支持以上两种阻塞场景的队列被称为阻塞队列。

2.BlockingQueue的核心方法

  • 放入数据
    • offer(anObject):表示如果可能的话,将anObject加到BlockingQueue里。即如果BlockingQueue可以容纳,则返回true,否则返回false。(本方法不阻塞当前执行方法的线程。)
    • offer(E o,long timeout,TimeUnit unit):可以设定等待的时间。如果在指定的时间内还不能往队列中加入BlockingQueue,则返回失败。
    • put(anObject):将anObject加到BlockingQueue里。如果BlockQueue没有空间,则调用此方法的线程被阻断,直到BlockingQueue里面有空间再继续。
  • 获取数据:
    • poll(time):取走BlockingQueue 里排在首位的对象。若不能立即取出,则可以等 time参数规定的时间。取不到时返回null。
    • poll(long timeout,TimeUnit unit):从BlockingQueue中取出一个队首的对象。如果在指定时间内,队列一旦有数据可取,则立即返回队列中的数据;否则直到时间超时还没有数据可取,返回失败。
    • take():取走BlockingQueue里排在首位的对象。若BlockingQueue为空,则阻断进入等待状态,直到 BlockingQueue有新的数据被加入。
    • drainTo():一次性从BlockingQueue获取所有可用的数据对象(还可以指定获取数据的个数)。通过该方法,可以提升获取数据的效率;无须多次分批加锁或释放锁。

4.3.2 Java中的阻塞队列

在Java中提供了7个阻塞队列,它们分别如下所示。

  • ArrayBlockingQueue:由数组结构组成的有界阻塞队列。
  • LinkedBlockingQueue:由链表结构组成的有界阻塞队列。
  • PriorityBlockingQueue:支持优先级排序的无界阻塞队列。
  • DelayQueue:使用优先级队列实现的无界阻塞队列。
  • SynchronousQueue:不存储元素的阻塞队列。
  • LinkedTransferQueue:由链表结构组成的无界阻塞队列。
  • LinkedBlockingDeque:由链表结构组成的双向阻塞队列。

4.3.3 阻塞队列的实现原理

4.3.4 阻塞队列的使用场景

4.4 线程池

在编程中经常会使用线程来异步处理任务,但是每个线程的创建和销毁都需要一定的开销。如果每次 执行一个任务都需要开一个新线程去执行,则这些线程的创建和销毁将消耗大量的资源;并且线程都是“各 自为政”的,很难对其进行控制,更何况有一堆的线程在执行。这时就需要线程池来对线程进行管理。在 Java 1.5中提供了Executor框架用于把任务的提交和执行解耦,任务的提交交给Runnable或者Callable,而 Executor框架用来处理任务。Executor框架中最核心的成员就是 ThreadPoolExecutor,它是线程池的核心实现 类。

4.4.1 ThreadPoolExecutor

ThreadPoolExecutor构造方法:

public ThreadPoolExecutor(
    int corePoolSize,   //核心线程数
    int maximumPoolSize,//最大线程数
    long keepAliveTime,//非核心线程超时时间
    TimeUnit unit,//keepAliveTime参数的时间单位
    BlockingQueue<Runnable> workQueue,//任务队列
    ThreadFactory threadFactory,//线程工厂,可创建新线程
    RejectedExecutionHandler handler//饱和策略
)

4.4.2 线程池的处理流程和原理

4.4.3 线程池的种类

通过直接或者间接地配置ThreadPoolExecutor的参数可以创建不同类型的ThreadPoolExecutor,其中有 4 种线程池比较常用,它们分别是 FixedThreadPool、CachedThreadPool、SingleThreadExecutor和 ScheduledThreadPool。下面分别介绍这4种线程池。

1.FixedThreadPool

FixedThreadPool 是可重用固定线程数的线程池。

FixedThreadPool就是一个有固定数量核心线程的线程池,并且这些核心线程不会被回收。当线程数超过 corePoolSize 时,就将任务存储在任务队列中;当线程池有空闲线程时,则从任务队列中去取任务执行。

2.CachedThreadPool

CachedThreadPool是一个根据需要创建线程的线程池。CachedThreadPool 比较适于大量的需要立即处理并且耗时较少的任务。

3.SingleThreadExecutor

SingleThreadExecutor是使用单个工作线程的线程池。能确保所有的任务在一个线程中按照顺序逐一执行。

4.ScheduledThreadPool

ScheduledThreadPool是一个能实现定时和周期性任务的线程池。

4.5 AsyncTask的原理

4.6 本章小结

2019/03/27 posted in  Android进阶之光

05 网络编程与网络框架

5.1 网络分层

网络分层就是将网络节点所要完成的数据的发送或转发、打包或拆包,以及控制信息的加载或拆出等 工作,分别由不同的硬件和软件模块来完成。这样可以将通信和网络互联这一复杂的问题变得较为简单。 网络分层有不同的模型,有的模型分7层,有的模型分5层。这里介绍分5层的,因为它更好理解。网络分层 的每一层都是为了完成一种功能而设的。为了实现这些功能,就需要遵守共同的规则,这个规则叫作“协议”。

如图5-1所示,网络分层从上到下分别是应用层、传输层、网络层、数据链路层和物理层。越靠下的层越接近硬件。接下来我们从下而上来分别了解这些分层。

  1. 物理层
    该层负责比特流在节点间的传输,即负责物理传输。该层的协议既与链路有关,也与传输介质有关。 其通俗来讲就是把计算机连接起来的物理手段。
  2. 数据链路层
    该层控制网络层与物理层之间的通信,其主要功能是如何在不可靠的物理线路上进行数据的可靠传递。为了保证传输,从网络层接收到的数据被分割成特定的可被物理层传输的帧。帧是用来移动数据的结 构包,它不仅包括原始数据,还包括发送方和接收方的物理地址以及纠错和控制信息。其中的地址确定了 帧将发送到何处,而纠错和控制信息则确保帧无差错到达。如果在传送数据时,接收点检测到所传数据中 有差错,就要通知发送方重发这一帧。
  3. 网络层
    该层决定如何将数据从发送方路由到接收方。网络层通过综合考虑发送优先权、网络拥塞程度、服务质量以及可选路由的花费来决定从一个网络中的节点 A 到另一个网络中节点 B 的最佳路径。
  4. 传输层
    该层为两台主机上的应用程序提供端到端的通信。相比之下,网络层的功能是建立主机到主机的通 信。传输层有两个传输协议:TCP(传输控制协议)和UDP(用户数据报协议)。其中,TCP是一个可靠的 面向连接的协议,UDP是不可靠的或者说无连接的协议。
  5. 应用层
    应用程序收到传输层的数据后,接下来就要进行解读。解读必须事先规定好格式,而应用层就是规定 应用程序的数据格式的。它的主要协议有HTTP、FTP、Telnet、SMTP、POP3等。

5.2 TCP的三次握手与四次挥手

通常我们进行HTTP连接网络的时候会进行TCP的三次握手,然后传输数据,之后再释放连接。

TCP三次握手的过程如下。

  • 第一次握手:建立连接。客户端发送连接请求报文段,将SYN设置为1、Sequence Number (seq)为x;接下来客户端进入SYN_SENT状态,等待服务端的确认。
  • 第二次握手:服务器收到客户端的SYN报文段,对SYN报文段进行确认,设置AcknowledgmentNumber (ACK)为x+1 (seq+1) ;同时自己还要发送SYN请求信息,将SYN设置为1、seq为y。 服务端将上述所有信息放到SYN+ACK报文段中,一并发送给客户端,此时服务端进入SYN_RCVD状态。
  • 第三次握手:客户端收到服务端的SYN+ACK报文段;然后将ACK设置为y+1,向服务端发送ACK报文段,这个报文段发送完毕后,客户端和服务端都进入ESTABLISHED (TCP连 接成功)状态,完成TCP的三次握手。

当客户端和服务端通过三次握手建立了TCP连接以后,当数据传送完毕,断开连接时就需要进行TCP的四次挥手。其四次挥手如下所示。

  • 第一次挥手:客户端设置seq和ACK, 向服务端发送一个FIN报文段。此时,客户端进入FIN_WAIT_1状态,表示客户端没有数据要发送给服务端了。
  • 第二次挥手:服务端收到了客户端发送的FIN报文段,向客户端回了一个ACK报文段。
  • 第三次挥手:服务端向客户端发送FIN报文段,请求关闭连接,同时服务端进入LAST_ACK状态。
  • 第四次挥手:客户端收到服务端发送的FIN报文段,向服务端发送ACK报文段,然后客户端进入TIME_WAIT状态。服务端收到客户端的ACK报文段以后,就关闭连接。此时,客户端等待2MSL (最大报文段生存时间)后依然没有收到回复,则说明服务端已正常关闭,这样客户端也可以关闭连接了。

如果有大量的连接,每次在连接、关闭时都要经历三次握手、四次挥手,这很显然会造成性能低下。 因此,HTTP有一种叫作keepalive connections的机制,它可以在传输数据后仍然保持连接,当客户端需要再次获取数据时,直接使用刚刚空闲下来的连接而无须再次握手。

5.3 HTTP协议原理

5.3.1 HTTP简介

HTTP 是一个属于应用层的面向对象的协议,由于其简捷、快速的方式,适用于分布式超媒体信息系统。

1.HTTP的历史版本

  • HTTP 0.9:1991年发布的第一个版本,只有一个命令GET,服务器只能回应HTML格式的字符串。
  • HTTP 1.0:1996年发布的版本,内容量大大增加。除了GET命令外,还引入了POST命令和HEAD命 令。HTTP请求和回应的格式除了数据部分,每次通信都必须包括头信息,用来描述一些元数据。
  • HTTP 1.1:1997发布的版本,进一步完善了HTTP协议,直到现在还是最流行的版本。
  • SPDY协议:2009年谷歌为了解决 HTTP 1.1效率不高的问题而自行研发的协议。
  • HTTP 2:2015年新发布的版本,SPDY 协议的主要特性也在此版本中。

2.HTTP协议的主要特点

  • 支持C/S(客户/服务器)模式。
  • 简单快速:客户向服务器请求服务时,只需传送请求方法和路径。请求方法常用的有GET、HEAD、 POST,每种方法规定了客户与服务器联系的类型不同。由于 HTTP 协议简单,使得HTTP服务器的程序规模小,因而通信速度很快。
  • 灵活:HTTP允许传输任意类型的数据对象。正在传输的类型由Content-Type加以标记。
  • 无连接:无连接的含义是限制每次连接只处理一个请求。服务器处理完客户的请求,并收到客户的应答后,即断开连接。采用这种方式可以节省传输时间。
  • 无状态:HTTP协议是无状态协议,无状态是指协议对于事务处理没有记忆能力。缺少状态意味着如 果后续处理需要前面的信息,则它必须重传,这样可能导致每次连接传送的数据量增大;而另一方面,在服务器不需要先前信息时它的应答速度就较快。

HTTP URL的格式如下所示:http://host[":"port][abs_path]

  • http表示要通过HTTP协议来定位网络资源;
  • host表示合法的Internet主机域名或者IP地址;
  • port指定一个 端口号,为空则使用默认端口80;
  • abs_path指定请求资源的URI(Web上任意的可用资源)。

HTTP有两种报 文,分别是请求报文和响应报文,下面先来查看请求报文。

5.3.2 HTTP请求报文

HTTP 报文是面向文本的,报文中的每一个字段都是一些ASCII码串,各个字段的长度是不确定的。一 般一个HTTP请求报文由请求行、请求报头、空行和请求数据4个部分组成。

  1. 请求行
    请求行由请求方法、URL字段和HTTP协议的版本组成,格式如下:
    Method Request-URI HTTP-Version CRLF
    其中 Method表示请求方法;Request-URI是一个统一资源标识符;HTTP-Version表示请求的HTTP协议版本;CRLF表示回车和换行(除了作为结尾的CRLF外,不允许出现单独的CR或LF字符)。
    HTTP请求方法有8种,分别是GET、POST、HEAD、PUT、DELETE、TRACE、CONNECT、 OPTIONS。对于移动开发最常用的就是GET和POST了。

  2. 请求报头
    在请求行之后会有0个或者多个请求报头,每个请求报头都包含一个名字和一个值,它们之间用英文冒 号“:”分割。关于请求报头,我们会在后面做统一解释。

  3. 请求数据
    请求数据不在GET方法中使用,而在POST方法中使用。POST方法适用于需要客户填写表单的场合,与请求数据相关的最常用的请求报头是Content-Type和Content-Length。

5.3.3 HTTP响应报文

响应报文的一般格式:

HTTP 的响应报文由状态行、响应报头、空行、响应正文组成。响应正文是服务器返回的资源的内容。我们先来看看状态行。
状态行格式如下所示:
HTTP-Version Status-Code Reason-Phrase CRLF
HTTP-Version表示服务器HTTP协议的版本;Status-Code表示服务器发回的响应状态码;Reason- Phrase表示状态码的文本描述。状态码由3位数字组成,第一个数字定义了响应的类别,且有以下5种可能取值。

  • 100~199:指示信息,收到请求,需要请求者继续执行操作。 * 200~299:请求成功,请求已被成功接收并处理。
  • 300~399:重定向,要完成请求必须进行更进一步的操作。
  • 400~499:客户端错误,请求有语法错误或请求无法实现。
  • 500~599:服务器错误,服务器不能实现合法的请求。

常见的状态码如下。

  • 200 OK:客户端请求成功。
  • 400 Bad Request:客户端请求有语法错误,服务器无法理解。
  • 401 Unauthorized:请求未经授权,这个状态码必须和WWW-Authenticate报头域一起使用。
  • 403 Forbidden:服务器收到请求,但是拒绝提供服务。
  • 500 Internal Server Error:服务器内部错误,无法完成请求。
  • 503 Server Unavailable:服务器当前不能处理客户端的请求,一段时间后可能恢复正常。

5.3.4 HTTP的消息报头

消息报头分为通用报头、请求报头、响应报头、实体报头等。消息报头由键值对组成,每行一对,关键字和值用英文冒号“:”分隔。

  1. 通用报头
    它既可以出现在请求报头,也可以出现在响应报头中,如下所示。
    • Date:表示消息产生的日期和时间。
    • Connection:允许发送指定连接的选项。例如指定连接是连续的;或者指定“close”选项,通知服务器,在响应完成后,关闭连接。
    • Cache-Control:用于指定缓存指令,缓存指令是单向的(响应中出现的缓存指令在请求中未必会出现),且是独立的(一个消息的缓存指令不会影响另一个消息处理的缓存机制)。
  2. 请求报头
    请求报头通知服务器关于客户端请求的信息。典型的请求报头如下所示。
    • Host:请求的主机名,允许多个域名同处一个IP地址,即虚拟主机。
    • User-Agent:发送请求的浏览器类型、操作系统等信息。
    • Accept:客户端可识别的内容类型列表,用于指定客户端接收哪些类型的信息。 * Accept-Encoding:客户端可识别的数据编码。
    • Accept-Language:表示浏览器所支持的语言类型。
    • Connection:允许客户端和服务器指定与请求/响应连接有关的选项。例如,这时为Keep-Alive则表示
      保持连接。
    • Transfer-Encoding:告知接收端为了保证报文的可靠传输,对报文采用了什么编码方式。
  3. 响应报头
    用于服务器传递自身信息的响应。常见的响应报头如下所示。
    • Location:用于重定向接收者到一个新的位置,常用在更换域名的时候。
    • Server:包含服务器用来处理请求的系统信息,与User-Agent请求报头是相对应的。
  4. 实体报头
    实体报头用来定义被传送资源的信息,其既可用于请求也可用于响应。请求和响应消息都可以传送一 个实体。常见的实体报头如下所示。
    • Content-Type:发送给接收者的实体正文的媒体类型。
    • Content-Lenght:实体正文的长度。
    • Content-Language:描述资源所用的自然语言。
    • Content-Encoding:实体报头被用作媒体类型的修饰符。它的值指示了已经被应用到实体正文的附加 内容的编码,因而要获得Content-Type报头域中所引用的媒体类型,必须采用相应的解码机制。
    • Last-Modified:实体报头用于指示资源的最后修改日期和时间。 * Expires:实体报头给出响应过期的日期和时间。

5.3.5 抓包应用举例

5.4 HttpClient与HttpURLConnection

5.4.1 HttpClient

5.4.2 HttpURLConnection

5.5 解析Volley

适合进行数据量不大但通信频繁的网络操作。

5.5.1 Volley基本用法

1.Volley网络请求队列
2.StringRequest的用法
3.JsonRequest的用法
4.使用lmageRequest加载图片
5.使用lmageLoader加载图片
6.使用NetworklmageView加载图片
7.NetworklmageView

5.5.2 源码解析Volley

1.从RequestQueue入手
2.CacheDispatcher缓存调度线程
3.NetworkDispatcher网络调度线程

5.6 解析OkHttp

5.6.1 OkHttp基本用法

基本步骤就是创建OkHttpClient、Request和Call,最后调用Call的异步方法enqueue()或同步方法execute()。

5.6.2 源码解析OkHttp

1. OkHttp的请求网络流程

  1. 从请求处理开始分析
  2. Dispatcher任务调度
  3. Interceptor拦截器
  4. 缓存策略
  5. 失败重连

2. OkHttp的复用连接池

  1. 主要变量与构造方法
  2. 缓存操作
  3. 自动回收连接
  4. 引用计数
  5. (5)小结
    可以看出连接池复用的核心就是用Deque来存储连接,通过 put、get、connectionBecameIdle和evictAll几个操作来对Deque进行操作,另外通过判断连接中的计数对象 StreamAllocation来进行自动回收连接。

5.7 解析Retrofit

Retrofit底层是基于OkHttp实现的,使用运行时注解的方式提供功能。

5.7.1 Retrofit基本用法

1.使用前的准备工作
2.Retrofit的注解分类
3.GET请求访问网络
4.POST请求访问网络
5.消息报头Header

5.7.2 源码解析Retrofit

1.Retrofit的创建过程
2.Call的创建过程
3.Call的enqueue方法

5.8 本章小结

2019/03/27 posted in  Android进阶之光

06 设计模式

6.1 设计模式六大原则

设计模式的六大原则,它们分别是单一职责原则、开放封闭原
则、里氏替换原则、依赖倒置原则、迪米特原则和接口隔离原则。

  • S 单一职责原则定义:就一个类而言,应该仅有一个引起它变化的原因。
  • O 开放封闭原则定义:类、模块、函数等应该是对于拓展是开放,对于修改是封闭。
  • L 里氏替换原则定义:所有引用基类(父类)的地方必须能透明地使用其子类的对象。
  • I 接口隔离原则定义:一个类对另一个类的依赖应该建立在最小的接口上。
  • D 依赖倒置原则定义:高层模块不应该依赖低层模块,两者都应该依赖于抽象。抽象不应该依赖于细节,细节应该依赖于抽象。
  • 迪米特原则定义:一个软件实体应当尽可能少地与其他实体发生相互作用。

6.2 设计模式分类

GoF提出的设计模式总共有23种,根据目的准则分类,分为三大类。

  • 创建型设计模式,共5种:单例模式、工厂方法模式、抽象工厂模式、建造者模式、原型模式。
  • 结构型设计模式,共7种:适配器模式、装饰模式、代理模式、外观模式、桥接模式、组合模式、享 元模式。
  • 行为型设计模式,共11种:策略模式、模板方法模式、观察者模式、迭代器模式、责任链模式、命令 模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。

6.3 创建型设计模式

6.3.1 单例模式

6.3.2 简单工厂模式

6.3.3 工厂方法模式

6.3.4 建造者模式

6.4 结构型设计模式

6.4.1 代理模式

6.4.2 装饰模式

6.4.3 外观模式

6.4.4 享元模式

6.5 行为型设计模式

6.5.1 策略模式

6.5.2 模板方法模式

6.5.3 观察者模式

6.6 本章小结

2019/03/27 posted in  Android进阶之光

07 事件总线

为了简化并且更加高质量地在Activity、Fragment、Thread和Service等之间的通信,同时解决组件之间 高耦合的同时仍能继续高效地通信,事件总线设计出现了。

7.1 解析EventBus

EventBus是一款针对Android优化的发布-订阅事件总线。它简化了应用程序内各组件间、组件与后台线 程间的通信。其优点是开销小,代码更优雅,以及将发送者和接收者解耦。

7.1.1 使用EventBus

EventBus的三要素如下。

  • Event:事件。
    • 可以是任意类型的对象。
  • Subscriber:事件订阅者。
    • 在 EventBus 3.0 之前消息处理的方法只能限定于 onEvent、 onEventMainThread、onEventBackgroundThread和onEventAsync,它们分别代表4种线程模型。
    • 而在EventBus 3.0之后,事件处理的方法可以随便取名,但是需要添加一个注解@Subscribe,并且要指定线程模型(默认 为POSTING)。4种线程模型下面会讲到。
  • Publisher:事件发布者。
    • 可以在任意线程任意位置发送事件, 直接调用 EventBus 的post(Object)方法。可以自己实例化EventBus对象,但一般使用 EventBus.getDefault()就可以。根据post函数参数的类型,会自动调用订阅相应类型事件的函数。

EventBus的4种ThreadMode(线程模型)如下。

  • POSTING(默认):
    • 如果使用事件处理函数指定了线程模型为POSTING,那么该事件是在哪个线程 发布出来的,事件处理函数就会在哪个线程中运行,也就是说发布事件和接收事件在同一个线程中。在线 程模型为POSTING的事件处理函数中尽量避免执行耗时操作,因为它会阻塞事件的传递,甚至有可能会引 起ANR。
  • MAIN:
    • 事件的处理会在UI线程中执行。事件处理的时间不能太长,长了会导致ANR。
  • BACKGROUND:
    • 如果事件是在UI线程中发布出来的,那么该事件处理函数就会在新的线程中运行; 如果事件本来就是在子线程中发布出来的,那么该事件处理函数直接在发布事件的线程中执行。在此事件 处理函数中禁止进行UI更新操作。
  • ASYNC:
    • 无论事件在哪个线程中发布,该事件处理函数都会在新建的子线程中执行;同样,此事件 处理函数中禁止进行UI更新操作。

EventBus基本用法
(1)自定义一个事件类
(2)在需要订阅事件的地方注册事件
EventBus.getDefault().register(this);
(3)发送事件
EventBus.getDefault().post(messageEvent);
(4)处理事件
(5)取消事件订阅
EventBus.getDefault().unregister(this);

EventBus的黏性事件
除了上面讲的普通事件外,EventBus还支持发送黏性事件,就是在发送事件之后再订阅该事件也能收到该事件,这跟黏性广播类似。
(1)订阅者处理黏性事件 sticky = true
(2)发送黏性事件
EventBus.getDefault().postSticky(new MessageEvent ("黏性事件")) ;

7.1.2 源码解析EventBus

7.2 解析otto

7.2.1 使用otto

7.2.2 源码解析otto

2019/03/27 posted in  Android进阶之光

08 函数响应式编程

函数式编程是一种编程范式。我们常见的编程范式有命令式编程、函数式编程和逻辑式编程。我们常见的面向对象编程是一种命令式编程。命令式编程是面向计算机硬件的抽象,有变量、赋值语句、表达式和控制语句。而函数式编程是面向数学的抽象,将计算描述为一种表达式求值,函数可以在任何地方定 义,并且可以对函数进行组合。
响应式编程是一种面向数据流和变化传播的编程范式,数据更新是相关联的。把函数式编程里的一套思路和响应式编程合起来就是函数响应式编程。
函数响应式编程可以极大地简化项目,特别是处理嵌套回调的异步事件、复杂的列表过滤和变换或者 时间相关问题。在 Android 开发中使用函数响应式编程的主要有两大框架:一个是RxJava,另一个是Goodle 推出的Agera。

8.1 RxJava基本用法

8.1.1 RxJava概述

1.ReactiveX与RxJava

RxJava是ReactiveX的一种Java实现。 Rx是一个函数库,让开发者可以利用可观察序列和LINQ风格查询操作符来编写异步和基于事件的程序。开发者可以用Observables表示异步数据流,用LINQ操作符查询异步数据流,用Schedulers参数化异步数据流的并发处理。

2.为何要用RxJava

RxJava的原理就是创建一个Observable对象,然后使用各种操作符建立起来的链式操作,就如同流水线一样,把你想要处理的数据一步一步地加工成你想要的成品,然后发射给Subscriber处理。

3.RxJava与观察者模式

RxJava的异步操作是通过扩展的观察者模式来实现的。RxJava有4个角色Observable、Observer、Subscriber和Suject。Observable和 Observer 通过subscribe方法实现订阅关系,Observable就可以在需要的时候通知Observer。

8.1.2 RxJava基本实现

RxJava的基本用法分为如下3个步骤。

  1. 创建Observer(观察者) onCompleted onError onNext
  2. 创建 Observable(被观察者) create just from
  3. Subscribe(订阅)

8.1.3 RxJava的不完整定义回调

8.2 RxJava的Subject

Subject 既可以是一个 Observer 也可以是一个 Observerable,它是连接 Observer 和Observerable的桥梁。

  1. PublishSubject
  2. BehaviorSubject
  3. ReplaySubject
  4. AsyncSubject

8.3 RxJava操作符入门

8.3.1 创建操作符

  1. interval 按固定时间间隔发射整数序列
  2. range 发射指定范围的整数序列
  3. repeat 重复发射特定数据

8.3.2 变换操作符

变换操作符的作用是对Observable发射的数据按照一定规则做一些变换操作,然后将变换后的数据发射出去。

  1. map 变换
  2. flatMap 集合变换
  3. cast 转换
  4. concatMap 连续变换
  5. flatMapIterable
  6. buffer 缓存x个
  7. groupBy 分组

8.3.3 过滤操作符

过滤操作符用于过滤和选择Observable发射的数据序列,让Observable只返回满足我们条件的数据。

  1. filter 过滤
  2. elementAt 第x个
  3. distinct 去重
  4. skip 跳过
  5. take 只取x个
  6. ignoreElements 忽略
  7. throttleFirst 发射第一个
  8. throttleWithTimeOut 超时限流

8.3.4 组合操作符

组合操作符可以同时处理多个Observable来创建我们所需要的Observable。

  1. startWith 插入数据
  2. merge 合并
  3. concat 顺序合并
  4. zip 合并
  5. combineLastest

8.3.5 辅助操作符

  1. delay 延迟
  2. Do 添加回调
    • doOnEach
    • doOnNext
    • doOnSubscribe
    • doOnUnsubscribe
    • doOnCompleted
    • doOnError
    • doOnTerminate
    • finallyDo
  3. subscribeOn observeOn 指定线程
  4. timeout 超时

8.3.6 错误处理操作符

RxJava在错误出现的时候就会调用Subscriber的onError方法将错误分发出去,由Subscriber自己来处理错 误。

  1. catch
    • onErrorReturn
    • onErrorResumeNext
    • onExceptionResumeNext
  2. retry

8.3.7 条件操作符和布尔操作符

条件操作符和布尔操作符可用于根据条件发射或变换Observable,或者对它们做布尔运算。

  • 条件操作符ambdefaultIfEmptyskipUntilskipWhiletakeUntiltakeWhile等;
  • 布尔操作符allcontainsisEmptyexistssequenceEqual

8.3.8 转换操作符

转换操作符用来将 Observable 转换为另一个对象或数据结构。转换操作符有 toListtoSortedListtoMap、toMultiMap、getIterator和nest等。

8.4 RxJava的线程控制

1.内置的Scheduler
如果我们不指定线程,默认是在调用subscribe方法的线程上进行回调的。如果我们想切换线程,就需要使用Scheduler。RxJava 已经内置了如下5个Scheduler。

  • Schedulers.immediate():直接在当前线程运行,它是timeout、timeInterval和timestamp操作符的默认调度器。
  • Schedulers.newThread():总是启用新线程,并在新线程执行操作。
  • Schedulers.io():I/O操作(读写文件、读写数据库、网络信息交互等)所使用的 Scheduler。
  • Schedulers.computation():计算所使用的 Scheduler,例如图形的计算。
  • Schedulers.trampoline():当我们想在当前线程执行一个任务时,并不是立即时,可以用trampoline()将它入队。
  • AndroidSchedulers.mainThread():RxAndroid库中提供的Scheduler,它指定的操作在主线程中运行。

2.控制线程
在RxJava中用subscribeOn和observeOn操作符来控制线程。

8.5 RxJava的使用场景

8.5.1 RxJava结合OkHttp访问网络

8.5.2 RxJava结合Retrofit访问网络

8.5.3 用RxJava实现RxBus

8.6 RxJava源码解析

8.6.1 RxJava的订阅过程

8.6.2 RxJava的变换过程

8.6.3 RxJava的线程切换过程

8.7 本章小结

2019/03/27 posted in  Android进阶之光

09 注入与依赖注入框架

9.1 注解

从JDK 5开始,Java增加了注解,注解是代码里的特殊标记,这些标记可以在编译、类加载、运行时被读取,并执行相应的处理。通过 使用注解,开发人员可以在不改变原有逻辑的情况下,在源文件中嵌入一些补充的信息。代码分析工具、 开发工具和部署工具可以通过这些补充信息进行验证、处理或者进行部署。

9.1.1 注解分类

注解分为标准注解和元注解。

1.标准注解

标准注解有以下4种:

  • @Override:
    • 对覆盖超类中的方法进行标记,如果被标记的方法并没有实际覆盖超类中的方法,则编 译器会发出错误警告。
  • @Deprecated:
    • 对不鼓励使用或者已过时的方法添加注解,当编程人员使用这些方法时,将会在编译 时显示提示信息。
  • @SuppressWarnings:
    • 选择性地取消特定代码段中的警告。
  • @SafeVarargs:J
    • DK 7新增,用来声明使用了可变长度参数的方法,其在与泛型类一起使用时不会出现类型安全问题。

2.元注解

元注解,它用来注解其他注解,从而创建新的注解。元注解有以下几种。

  • @Targe:
    • 注解所修饰的对象范围。
  • @Inherited:
    • 表示注解可以被继承。
  • @Documented:
    • 表示这个注解应该被JavaDoc工具记录。 * @Retention:用来声明注解的保留策略。
  • @Repeatable:
    • JDK 8 新增,允许一个注解在同一声明类型(类、属性或方法)上多次使用。

其中@Targe注解取值是一个ElementType类型的数组,其中有以下几种取值,对应不同的对象范围。

  • ElementType.TYPE:能修饰类、接口或枚举类型。
  • ElementType.FIELD:能修饰成员变量。
  • ElementType.METHOD:能修饰方法。
  • ElementType.PARAMETER:能修饰参数。
  • ElementType.CONSTRUCTOR:能修饰构造方法。
  • ElementType.LOCAL_VARIABLE:能修饰局部变量。
  • ElementType.ANNOTATION_TYPE:能修饰注解。
  • ElementType.PACKAGE:能修饰包。
  • ElementType.TYPE_PARAMETER:类型参数声明。
  • ElementType.TYPE_USE:使用类型。

其中@Retention注解有3种类型,分别表示不同级别的保留策略。

  • RetentionPolicy.SOURCE:源码级注解。
    • 注解信息只会保留在.java源码中,源码在编译后,注解信息被丢弃,不会保留在.class中。
  • RetentionPolicy.CLASS:编译时注解。
    • 注解信息会保留在.java 源码以及.class 中。当运行Java程序时, JVM会丢弃该注解信息,不会保留在JVM中。
  • RetentionPolicy.RUNTIME:运行时注解。
    • 当运行Java程序时,JVM也会保留该注解信息,可以通过反射获取该注解信息。

9.1.2 定义注解

1)基本定义

定义新的注解类型使用@interface关键字

//定义
public @interface Swordsman{}

//使用
@Swordsman
public class AnnotationTest{}

2)定义成员变量

注解只有成员变量,没有方法。注解的成员变量在注解定义中以“无形参的方法”形式来声明,其“方法名”定义了该成员变量的名字,其返回值定义了该成员变量的类型。还可以使用default关键字为其指定默认值。

//定义注解和成员变量
public @interface Swordsman{
    String name();
    int age();
}
//使用
public class AnnotationTest{
    @Swordsman(name="张无忌",age=23)
    public void fighting(){ }
}
//default指定默认值
public @interface Swordsman{
    String name() default "张无忌";
    int age() default 23;
}

3)定义运行时/编译时注解

可以用@Retention来设定注解的保留策略,这 3个策略的生命周期长度为 SOURCE <CLASS< RUNTIME。生命周期短的能起作用的地方,生命周期长的一定也能起作用。

  • 一般如果需要在运行时去动态获取注解信息,那只能用RetentionPolicy.RUNTIME;
  • 如果要在编译时进行一些预处理操作,比如生成一些辅助代码,就用 RetentionPolicy.CLASS;
  • 如果只是做一些检查性的操作,比如@Override 和 @SuppressWarnings,则可选用RetentionPolicy.SOURCE。当设定为RetentionPolicy.RUNTIME时,这个注解就是运行时注解。

9.1.3 注解处理器

对于不同的注解有不同的注解处理器。 虽然注解处理器的编写会千变万化,但是其也有处理标准,比如:针对运行时注解会采用反射机制处理, 针对编译时注解会采用 AbstractProcessor 来处理。

1.运行时注解处理器

处理运行时注解需要用到反射机制。通过反射获得Field、Method等信息,调用getAnnotation()方法即可获得注解信息。

2.编译时注解处理器

  1. 定义注解
  2. 编写注解处理器
    1. 注解处理器ClassProcessor,它继承AbstractProcessor
    2. 方法有init、process、getSupportedAnnotationTypes、getSupportedSourceVersion等。
  3. 注册注解处理器
    1. 使用Google 开源的AutoService
    2. 在ClassProcessor中添加@AutoService(Processor.class)
  4. 应用注解
  5. 使用android-apt插件
    1. 仅仅在编译时期去依赖注解处理器所在的函数库并进行工作,但不会打包到APK中。
    2. 为注解处理器生成的代码设置好路径,以便Android Studio能够找到它。

9.2 依赖注入的原理

9.2.1 控制反转与依赖注入

1.控制反转

IoC是Inversion of Control的缩写,即控制反转,借助于“第三方”实现具有依赖关系的对象之间的解耦。

  1. 引入IoC容器之前:对象A依赖于对象B,那么对象A在初始化的时候,自己必须主动去创建对象B或者使用已经创建的对象B。无论是创建对象B还是使用对象B,控制权都在自己手上。
  2. 引入IoC容器之后:由于IoC容器的加入,对象A与对象B之间失去了直接联系,所以,当对象A运行到需要对象B的时候,IoC容器会主动创建一个对象B注入到对象A需要的地方。
  3. 通过引入Ioc容器前后的对比,可以看出:对象A获得依赖对象B的过程,由主动行为变为被动行为,控制权颠倒过来了,这就是控制反转这个名称的由来。

2.依赖注入

Martin Fowler提问:
控制反转是“哪些方面的控制被反转了呢?”
答:“获得依赖对象的过程被反转了”。

控制被反转之后,获得依赖对象的过程由自身管理变为由IoC容器主动注入。于是,他给控制反转取了一个更合适的名字,叫作依赖注入(Dependency Injection),简称DI。所谓依赖注入,是指由IoC容器在运行期间,动态地将某种依赖关系注入到对象中。

9.2.2 依赖注入的实现方式

这里举一个汽车的例子,汽车类Car包含了引擎Engine等组件:

public class Car{
    private Engine mEngine;
    public Car(){
        mEngine = new PetrolEngine();
    }
}
  1. 构造方法注入

    public class Car{
        private Engine mEngine;
    public Car(Engine mEngine){
    this.mEngine = mEngine;
    }
    }
  2. Setter方法注入

    public class Car{
        private Engine mEngine;
    public void set(Engine mEngine){
    this.mEngine = mEngine;
    }
    }
  3. 接口注入

    public interface ICar{
        public void setEngine(Engine engine);
    }
    public class Car implements ICar{
    private Engine mEngine;
    @Override
    public void setEngine(Engine engine){
    this.mEngine = engine;
    }
    }

9.3 依赖注入框架

9.3.1 为何使用依赖注入框架

9.3.2 解析ButterKnife

1.ButterKnife的注解使用方法

  1. 添加依赖库
  2. 绑定控件
    • @BindView
  3. 绑定资源
    • @BindString、@BindArray、@BindBool、@BindColor、@BindDimen、@BindDrawable和@BindBitmap
  4. 绑定监听
    • @OnClick、@OnLongClick、@OnTextChanged、@OnTouch、@OnItemClick
  5. 可选绑定
    • @Nullable防止找不到资源

2. ButterKnife原理解析

  1. ButterKnifeProcessor源码分析
    1. ButterKnifeProcessor#process#findAndParseTargets:查找所有注解并解析
    2. brewJava:方法将使用注解的类生成一个JavaFile
  2. ButterKnife的bind方法
    1. 得到Activity的 DecorView,findBindingConstructorForClass方法获得构造器并生成实例
  3. 生成的辅助类分析
    1. DecorView传入这个类中,通过findViewById将View返回。

9.3.3 解析Dagger2

Dagger2是一个基于JSR-330(Java依赖注入)标准的依赖注入框架,在编译期间自动生成代码,负责依赖对象的创建。

1.注解使用方法

  1. 添加依赖库
  2. @Inject和@Component

    1. @Inject:用于标记需要注入的依赖
    2. @Component:注入器,可以编译生成类,调用注入方法
    @Component
    public interface MainActivityComponent {
    void inject (MainActivity activity) ;
    }
    public class MainActivity extends AppCompatActivity {
    @Inject
    Watch watch;
    @Override
    protected void onCreate (Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    Dagger MainActivityComponent.create().inject(this);
    watch.work();
    }
    }
  3. @Module和@Provides

    1. 如果项目中使用了第三方的类库,或需要注入的类是抽象类,可以采用@Module和@Provides提供注入器。
    @Module
    public class GsonModule {
    @Provides
    public Gson provideGson() {
    return new Gson();
    }
    }
    @Component (modules = GsonModule.class)
    public interface MainActivityComponent {
    void inject (MainActivity activity);
    }
  4. @Named和@Qualifier

    1. @Qualifier 是限定符,@Named 则是@Qualifier 的一种实现。
    2. 当有两个相同的依赖时,它们都继承同一个父类或者均实现同一个接口。当它们被提供给高层时,Component 就不知道我们到底要提供哪一个依赖 对象了,因为它找到了两个。
    3. 或者通过自定义注解来实现。
    @Module
    public class Eng ineModule {
    @Provides
    @Named("Gasoline")
    public Engine provideGasoline() {
    return new GasolineEngine() ;
    }
    @Provides
    @Named("Diesel")
    public Engine provideDiesel() {
    return new DieselEngine() ;
    }
    }
    public class Car {
    private Engine engine;
    @Inject
    public Car(@Named("Diesel") Engine engine) {
    this.engine = engine;
    }
    }
    @Qualifier
    @Retention(RUNTIME)
    public dinterface Gasoline {}
    @Qualifier
    @Retention(RUNTIME)
    public dinterface Diesel {}
    @Module
    public class EngineModule {
    @Provides
    @Gasoline
    public Engine provideGasoline() {
    return new Gasol ineEngine();
    }
    @Provides
    @Diesel
    public Engine provideDiesel() {
    return new DieselEngine();
    }
    }
    public class Car {
    private Engine engine;
    @Inject
    public Car(@Gasoline Engine engine) {
    this.engine = engine;
    }
    public String run() {
    return engine.work();
    }
    }
  5. @Singleton和@Scope

    1. @Scope是用来自定义注解的,而@Singleton则是用来配合实现局部单例和全局单例的。@Singleton本身不具备创建单例的能力。
    2. 如果想实现全局单例,我们可以用@Scope结合Application来实现。
  6. @Component的dependencies

    1. @Component也可以用dependencies依赖于其他Component。

2.懒加载
Dagger2提供了懒加载模式,在@Inject的时候不初始化,而是使用的时候,调用get方法来获取实例。
3.Dagger2原理解析

  1. WatchModule_ProvideWatchFactory 用来生成 Watch 实例;
  2. Dagger2Activity_MemberInject 将 Watch 实例赋值 给 MainActivity 的成员变量 Watch;
  3. DaggerActivityComponent则作为程序入口和桥梁,负责初始化 WatchModule_ProvideWatchFactory和Dagger2Activity_MemberInject,并将它们串联起来。

9.4 本章小结

2019/03/27 posted in  Android进阶之光

10 应用架构设计

2019/03/27 posted in  Android进阶之光

11 系统架构与MediaPlayer框架

2019/03/27 posted in  Android进阶之光