Android-基础

一、Preferences

Preferences的类型基本可以理解为Map型

Preferences对象可以通过PreferenceManager和getDefaultSharePreferences(context)获取它,创建它后可以查看它保存的数据

通过preferences对象保存的数据都是保存在某个文件中,而且这个文件是xml格式的,通过以下步骤我们可以查询该文件内容:

1.adb shell(进入设备根目录)

2.cd data/data(执行该命令后会显示该应用的所有包名,找到你的应用)

3.cd 应用包名/share_prefs

4.执行ls你就会看到该文件

5.如果因为权限问题进入无法查看,可通过:(run-as 包名,可以不通过root就能进入该包下)进入

 

某应用的有关文件都保存在data/包名/下,包括database数据库等文件。

 二、AppWidget

AppWidget表示是桌面小部件,也叫桌面控件,就是能直接显示在桌面上的控件,例如天气框,桌面头部的谷歌搜索栏。

一些使用较频繁的可以做成appWidget。

如何开发AppWidget

AppWidget是通过BroadcastReceiver的形式进行控制,开发AppWidget桌面小部件的主要类为AppWidgetProvider,该类继承自

BroadCastReceiver。为实现桌面小部件,开发者只要开发一个继承自AppWidgetProvider的子类,并重写它的onUpdate()方法即可。

重写该方法。

widget不支持recyclerview,所以只能用listview了,另外也不支持原生view和自定义view。所以appwidget也就不那么炫酷了

小部件可支持以下控件:

FrameLayout
LinearLayout
RelativeLayout
GridLayout
And the following widget classes:
AnalogClock
Button
Chronometer
ImageButton
ImageView
ProgressBar
TextView
ViewFlipper
ListView
GridView
StackView
AdapterViewFlipper

 

2.1、小部件配置信息

小部件源码:

git clone -b widget https://gitee.com/a18307884396/custom-view.git

 

三、Handler的使用 

handler是子线程Runalbe和主线程交互的桥梁,举个例子如果你想在子线程中更新视图组件,就需要通过handler去更新,当然子线程也可通过Loop等一系列操作实现更新的效果,不过handler是一种高效的实现方式。

 

四、PopupWindow的使用

 这个组件的作用是悬浮在别的窗口之上,你可以叫他悬浮框

 

五、SQLiteDataBase的使用

 

六、AndroidLocation的使用

Location定位是Android开发中常用的,例如通过经纬度获取天气,根据定位Location获取所在地区详细Address(比如Google Map)。

而在Android中通过LocationManager(位置管理器)来获取Location。要想获取Location需要打开GPS,WIFI获取。

 

七、笔记

1.在安卓中什么时候才执行到onResume()方法?其实在程序执行完setContentView的时候就开始才执行onResume。

2.安卓的触发事件是通过WindowMangerService来管理的。

3.activity交互是通过window类来实现的,在安卓移动手机中是通过window的实现类PhoneWindow来实现,如何平板手机、手表等就不是PhoneWindow了。

4.而PhoneWindow一般是通过DecorView来渲染页面,DecorView又分为两部分,分别是头部(TitleView)的ActionBar和身体(contentView)FameLayout这个布局的id是content。

5.上面我们说到执行setContentView的时候就渲染页面了,所以想管理ActionBar就只能在这个方法之前,执行requestWindowFeature可以管理ActionBar。

6.ViewGroup通常不需要绘制,需要绘制的只有子View。ViewGroup需要绘制的情况只有设置background。

7.虽然ViewGroup通常需要绘制,但它管理着子ViewGroup的绘制,通过dispatchDraw()方法来实现,该方法的原来是遍历所有子View,然后调用子View的onDraw。

8.在安卓中xml文件实际上有点类似web项目的web.xml,里面的每一个标签都代表着一个类,只不过通过xml的方式体现处理而已,所有在实际开发过程中通过尽量少的标签实现页面的开发。

9.View中通常比较重要的回调是:

*onFinishInflatc():从XML加载组件完成后回调。

*onSizeChanged():组件大小改变时回调。

*onMeasure():回调该方法来进行测量。

*onLayout():确定显示的位置。

*onTouchEvent():监听触摸事件。

通常情况下有以下五种方法实现自定义View:

(1).对现有View控件进行扩展,例如TextView等。

(2)实现原生View。

(3)对现有ViewGroup进行扩展,例如FameLayout等。

(4)实现原生Viewgroup。

(5)组合控件

 

10.滑动一个View,本质上移动一个view。手机屏幕左上角是坐标原点,横向是x轴,纵向是Y轴。

11.触摸事件——MotionEvent

12.屏幕的绘图机制

Android的绘图机制是android最核心的内容之一,不管是什么样的功能,最终都要以图像的形式呈现给用户。

(1)屏幕的尺寸信息

一块屏幕有以下信息

屏幕大小。指对角线的长度,通常用寸来度量。

分辨率。指手机屏幕的像素点个数

PPI。又称DPI,是怎么算出来的呢?对角线的密度除以屏幕的尺寸得到的,而对角线的密度是横向密度的平方加纵向密度的平方再开方得到的。

独立像素密度dp

(2)2D绘制基础

系统通过提供的Canvas对象来提供绘图方法。它提供了各种绘制图像的API,如drawPoint(点)、drawLine(线)、drawRect(矩形)、drawVertices(多边形)、drawCircle(圆)

Paint作为一个非常重要的元素,功能也是很强大的。

setAntiAlias()

setColor()

setARGB()

setAlpha()

setTextSize()

setStyle()

setStrokeWidth()

(3)Android XML绘图

xml在android系统中可不仅仅是Java中的一个布局文件、配置列表,它还可以是一张画、一幅图。

Bitmap,在XML文件中可以把图片转换为bitmap使用。

Shape,通过Shape可以在XML中绘制各种形状,可以设置圆角、渐变、边框、大小、填充、边框

(4)Layer

layer是Photoshop中非常常用的功能且很类似。

(5)selector

Selector的作用在于帮助开发者实现静态绘图中事件反馈,通过不同的事件设置不同的图像。

 

(6)绘图技巧

Canvas作为绘制图形的直接对象,提供了以下几个非常有用的方法:

Canvas.save():保存画布

Canvas.restore():合并图层

Canvas.translate():平移画布

Canvas.rotate():画布翻转

 

(7)Android图形处理之色彩特效处理

Android对于图片的处理,最常使用到的数据结构是位图Bitmap,它包含了一张图片所有数据。整个图片是有点阵和颜色组成的,所谓点阵就是一个包含像素的矩阵,每一张对应着图片的一个像素。而颜色值——ARGB,分别对应透明度、红、绿、蓝这四个通道分量它们决定着每个像素点显示的颜色。

色彩矩阵分析

通常使用以下三个角度来描绘一个图像

色调——物体传播的颜色

饱和度——颜色的纯度,从0到100%(饱和)来进行描述

亮度——颜色的相对明暗程度

在安卓中使用一个颜色矩阵——ColorMatrix,来处理图像的这些色彩效果。Android中颜色矩阵是一个4*5的数字矩阵。每一个像素点都由一个颜色矩阵用来保存颜色的RGBA值。

ColorMatrix hueMatrix = new ColorMatrix();

色调的设置

ColorMatrix hueMatrix = new ColorMatrix();

hueMatrix.setRotate(0,hue0);

hueMatrix.setRotate(1,hue1);

hueMatrix.setRotate(2,hue2);

饱和度的设置

setStaturation(saturation);

亮度的设置

setScale(lum,lum,lum,1);

 

Android apk反编译

apktool

 

16.性能优化篇

16.1布局优化

Android UI的渲染机制

人的视觉能感觉到流畅的是40帧到60帧的画面,所有渲染画面就要尽量在1秒(1000ms)中显示60帧,1000/60=16ms,所有显示一帧的画面,

就要在16毫秒以内。

在Android系统中,View是一种主动渲染的机制,VSYNC信号触发对UI的渲染、重绘,系统每隔16ms就发送这样的一个通知。

如果某一次绘制耗时20ms,那么在16ms系统发出的VSYNC信号时就无法绘制。等待下车信号才开始绘制。

 

避免Overdraw

Overdraw过度绘制会浪费很多的CPU、GPU资源,例如系统默认会绘制Activity的背景

如何要绘制背景的话,那就不绘制Window的背景,去掉DecorView的背景是在setContentView()之后设置getWindow().setBackgoundDrawable(null);

尽量增大蓝色的区域,减少红色的区域。

 

优化布局层级

在Androi中,系统对View进行测量,布局和绘制是,都是通过对View数的遍历进行操作的。如果一个View树的高度太高,就会严重影响测量、布局和绘制的速度,因此优化布局的第一个方法就是降低View树的高度,Google也在其API文档中建议View树的高度不宜超过10层。

 

避免嵌套过度无用布局

 

使用<include>标签重用Layout

使用<ViewStub>实现View的延迟加载

<ViewStub>是一个轻量级的组件,它不仅可视,而且大小为0.

 

Hierachy Viewer

一个布局优化工具

 

16.2内存优化

由于Android应用的沙箱机制,每隔应用分配的内存大小是有限的,内存太低就会触发LMK LOWMORY killer机制。什么是内存呢?

通常所说的内存是手机的RAM,RAM包括以下部分:

寄存器

速度最快的存储场所,因为寄存器位于处理器内部,在程序中无法控制。

 

栈(Stack)

存放基本类型的数据和对象的引用,但对象本身不存放在栈里,而是存放在堆中

 

堆内存用来存放由new创建的对象和数组,在堆中分配的内存,由Java虚拟机的自动垃圾回收机制器来管理。

 

静态存储局域

静态存储局域就是指在固定的位置存放应用程序运行是一直存在的数据,Java在内存中专门划分

了一个静态内存局域来管理一些特殊的数据变量,如静态的数据变量。

 

常量池

JVM虚拟机必须为每个被装载的类型维护一个常量池。常量池就是该类型所用到的一个有序集合

 

Bitmap的优化

在列表中显示图片时,可以使用缩略图thumbnails,查看详情时就用高清图。在图像要求不高的地方可以降低像素。

Bitmap是造成内存占用过高甚至是OOM的最大威胁。

 

使用图片缓存,通过内存缓存LruCache和硬盘缓存DiskLruCache可以更好地使用Bitmap

 

代码优化

任何Java类,都将占用大约500字节左右的内存空间。创建一个类的实例实例会消耗大约15字节的内存。从代码上的优化如下:

对常量使用static修饰符

使用静态方法,静态方法会比普通方法提供15%左右的访问速度。

减少不必要的成员变量,这点在Android Lint工具上已经集成检测了,如果一个变量可以定义为局部变量就不用定义为成员变量。

减少不必要的对象,使用基础类型会比对象更加节省空间,同时更应该避免繁琐创建短作用域的变量。

尽量不要用枚举、迭代器

对Cursor、Receiver、Sensor、File等对象,要非常主要对它们的创建,回收与注册、解注册。

避免使用IOC框架,大量的使用反射会降低性能的下级。

使用RenderScript、OpenGL来进行非常复杂的绘图操作。

使用SurfaceView来代替View进行大量、繁琐的绘图操作。

尽量使用视图缓存,而不是每次都执行inflace()方法解析视图。

Lint工具是Android Studio中集成的一个Android代码提示工具,它可以给你的布局、代码提供非常强大的帮助。

 

17.塔建云端服务器

移动后端服务介绍

 

移动后端即服务——Backcnd as a Service也叫Baas,说白了,Bass就是帮我们把服务器端的东西全部打包了,做服务端的人不用在考虑写服务端了。

Activity的过渡动画

进入和退出效果如下:

explode(分解)——从屏幕中间进或出,移动视图

slide(滑动)——从屏幕编译进或出,移动视图

fade(淡出)——通过改变屏幕上视图的不透明达到添加或者移除视图

共享元素

changeBounds——改变目标视图的布局边界

changeClipBounds——裁剪目标视图边界

changeTransform——改变

 

 

18.多线程编程

Android沿用了Java的线程模型,一个Android应用在创建的时候会开启一个线程,我们叫它主线程或者UI线程。

如果我们想要访问网络线程或者数据库等耗时的操作时,都会开启子线程去处理。从Android3开始,系统邀请网络访问必须在子线程中进行,否则会抛出异常。

也就是为了避免主线程被耗时操作阻塞从而产生ANR。

 

18.1线程基础

进程与线程

进程是操作系统结构的基础,是程序运行在数据集合上运行的过程,是系统进行资源分配和调度的基本单位。进程可以被看作程序结构的实体,同样,它也是线程的容器。

 

线程是进程的子任务,是操作系统调度的最小单位

一个进程可以创建多个线程,这些线程都拥有各自的计数器、堆栈和局部变量等属性并且能够访问共享的内存变量

 

为什么要使用多线程

使用多线程可以减少程序的响应时间。使用多线程的原因是某个操作耗时太久,使用多线程可以实现更好的交互性。

线程的状态:

New:新建状态。线程被创建,还没有调用start方法,在线程运行之前海雅一些基础工作要做。

Runnable:可以运行状态。

Blocked:阻塞状态。

Waiting:等待状态。

Timed waiting:超时等待状态。

Terminated:终止状态。

 

创建线程:

(1)继承Thread类

(2)实现Runnable接口

(3)实现Callable接口

Callable接口于Runnable接口的功能类似,但提供了比Runnable更加强大的功能,主要表示为以下3点:

Callable可以在任务接受后提供一个返回值,Runnable无法提供这个功能。

Callable中的Call()方法可以抛出异常,而Runnable的run方法不能抛出异常

运行Callable可以拿到一个Future对象,future对象表示异步计算的结果,它提供了检查计算是否完成的方法。

 

理解中断

当run方法执行完毕后或者方法中没有捕获的异常是,线程终止。早期有stop方法,其他线程可以通过它来终止线程,但是这个方法被弃用。

interrupt方法用来请求中断线程。

 

安全的中断线程

Thread.currentThread().isInterrupted()

 

18.2同步

同步一直是Java多线程的难点,两个线程存取相同的对象时就会出现同步的问题。

 

 重入锁与条件对象

synchronized关键字自动关键字提供了锁以及相关的条件。大多数需要显式锁的情况使用synchronized非常方便。

ReentrantLock是Java SE 5引入的就是支持重入的锁。它表示该锁能够支持一个线程对资源的重复加锁。

调用reentrantLock.signalAll方法并不是立即激活一个等待的线程,它仅仅解除了等待线程的阻塞,以便这些线程能够在当前线程退出同步方法后。

 

同步方法

Lock和Condition接口为程序设计人员提供了高度的锁定控制,然而大多数情况下,并不需要那样的控制,并且可以使用一种嵌入式到Java语言内部的机制。

 

同步代码块

每一个Java对象都有一个锁,线程可以调用同步方法来获得锁。还有另一种机制可以获得锁,那就是使用一个同步代码块。

 

19.反编译

(1)使用apktool反编译资源文件

apktool d -f apkname.apk

 

(2)使用d2j-dex2jar.sh 或d2j-dex2jar.bat反编译出java代码

使用d2j-dex2jar文件是把.dex变成.jar文件,而jd-gui是把.jar文件变成javar源代码。

步骤如下:

d2j-dex2jar.sh classes.dex

然后通过jd-gui文件打卡jar文件

 

apktool、d2j-dex2jar、jd-gui就是人们常说的反编译三件套。

 

20.多线程

1.volatile

volatile关键字为实例域的同步访问提供了面锁的机制。如果声明一个域为volatile,那么编译器和虚拟机就知道该域是可能被另一个线程并发更新的。也就是说,声明一个属性为volatile之后,该volatile能被实时获取到新值。在此之前我们要了解以下内存模型的相关概念以及并发编程中的3个特性:原子性、可见性、有序性。

 

2.Java内存模型

java中的堆内存用来存储对象实例,堆内存是被所有线程共享的实例。因此它存在内存可见性的问题。而局部变量不会在线程间共享。

线程之间的共享变量存储在主存中,每一个线程都有一个私有的本地内存,本地内存存储了该线程共享变量的副本。需要主要的是本地内存是Java内存模型的一个抽象概念。

Java内存模型控制着线程之间的通信,它决定一个线程对主存共享变量的写入何时对另一个线程可见。

 

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

(1)线程A把线程A本地内存中更新过的共享变量刷新到主内存中去。

(2)线程B到主存中去读取线程A之前已更新过的共享变量。

例如:

int i = 3;

执行线程必须先在自己的工作线程中对变量i所在的缓存进行赋值操作,然后再写入主存当中,而不是直接将数值3血入主存当中。

 

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

(1)原子性

对基本数据类型变量的读取和赋值操作是原子性操作,即这些操作是不可被中断的,要么执行完毕,要么就不执行。例如:

x = 3;

y = x;

x++;

上面3个语句中,只有语句1是原子性操作,其他两个语句都不是原子性操作。java.util.concurrent.atomic包有很多类使用了很高效的机器级命令来保证其他操作的原子性。

例如AtomicInteger类提供了方法incrementAndGet和decrementAndGet,它们分别以原子方法将一个整数自增和自减。

(2)可见性

可见性,是指线程之间的可见性,一个线程修改的状态对另一个线程是可见的。也就是一个线程修改的结果,另一个线程马上就能看到。volatile修饰的变量保证了线程修改后能立刻更新到主存中。普通的共享变量不能保证可见性,普通的共享变量被修改后,并不会立即写入到主存中,何时写入到主存中也是不确定的。

(3)有序性

Java内存模型中允许编译器和处理器对指令进行重排序,虽然重排序过程不会影响到单线程执行的正确性,但是会影响到多线程并发执行的正确性。这时可以通过volatile来保证有序性,除了volatile,也可以通过synchronized和Lock来保证有序性。syschronized和lock保证每个时刻只有一个线程执行同步代码,这相当于是让线程顺序执行同步代码,从而保证有序性。

 

volatile关键字

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

 

什么是重排序?

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

线程1执行

boolean stop = false;

while(!stop){

}

 

线程2执行

stop = true;

 

很多人可能会采用这种方法中断线程。但是这段代码不一定会将线程中断。虽说无法中断线程这个情况出现的概率很小,但一旦发生,就会造成死循环。

为什么会无法中断呢?

当线程2修改stop的值,但是stop修改的值会先存放到私有内存中,而如果线程如果比较忙碌的话stop就不会更新的主存中,并且何时更新也不确定,所以线程1可能会永远运行下去。

 

volatile不保证原子性

假如线程1对变量进行自增的操作,自增的操作有3个步骤读取值、加1、保存到内存

 

volatile保证有序性

volatile能禁止执行重排序,因此volatile能保证有序性。volatile关键字禁止指令重排序有两个含义:

一、一个是当程序执行到volatile变量的操作时,在其面前的操作已经全部完成。

二、vaolatile变量之后的语句也不能在volatile变量前执行。

 

使用volatile必须具备以下两个条件:

(1)对变量的写操作不会依赖于当前值

(2)该变量没有包含在具有其他变量的不变式中。

第一个条件就是不能是自增、自减等操作,上文已经提到volatile不保证原子性。关于第二个条件,我们来举个例子,它包含了一个不变式。下界总是小于或等于上界。

 

4.阻塞队列

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

(1)场景阻塞场景

当队列中没有数据的情况下,消费者端的所有线程都会被自动阻塞(挂起),直到有数据放入队列中 。

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

 

(2)BlockingQueue的核心方法

放入数据:

offer(anObject):表示如果可能的话,将anObject加到BlockingQueue里。即如果BlocKingQueue可以容纳,则返回true,否则返回false.

offer(E o,long timeout,TimeUnit unit):可以设定等待的时间,如果在指定的时间内还不能往队列中加入BlockingQueue,则返回失败。

put(anObject):将anObject加到BlockingQueue里。如果BlockQueue没有控件,则调用此方法的线程被阻断,直到BlockingQueue里面于空间在继续。

poll:取走BlockingQueue里面排在首位的对象。若不能立即取出,则可以等time参数规定的时间。取不到时返回null。

take():取走BlockingQueue里排在首位的对象。若BlockingQueue为空,则阻断进入等待状态,直到BlockingQueue有新的数据被加入。

drainTo:一次性从BlocKingQueue获取所有可用的数据对象。通过该方法,可以提升获取数据的效率;无须多次分批加锁或释放锁。

 

(3)Java中的阻塞队列

ArrayBlockingQueue:由数组结构组成的有界阻塞队列。

LinkedBlockQueue:由链表结构组成的有界阻塞队列。

PriorityBlockingQueue:支持优先级排序的无界阻塞队列。

DelayQueue:使用优先级队列实现的无界阻塞队列。

SynchronouusQueue:不存储元素的阻塞队列

LinkedTransferQueue:由链表结构组成的无界阻塞队列。

LinkedBlockingDeque:由链表结构组成的双向阻塞链表。

 

ArrayBlockingQueue

它是用数组实现的有界阻塞队列,并按照先进先出的原则对元素进行排序。默认情况下不保证线程公平的访问队列。

 

LinkedBlockingQueue

它之所以能够高效地处理并发数据,还因为其对于生产这端和消费端分别采用了独立的锁来控制数据同步。

这意味这在高并发的情况下生产者和消费这可以并行地操作队列中的数据,以此来提高整个队列的并发性能。

如果构造一个LinkedBlockingQueue对像,而没有指定其容量大小,LinkedBlockingQueue会默认一个类似无限大小的容量。(Integer.MAX_VALUE)

 

5.阻塞队列的实现原理

public void put(E e) throws InterruptedExeption {

checkNotNull(e);

final ReentrantLock lock = this.lock;

lock.lockInterruptibly();

try{

while(count == items.lenght) notFull.await();

enqueue(e);

} finally {

lock.unlock();

}

}

 

6.线程池

在编程中经常会使用线程来处理异步任务,但是每个线程的创建和销毁都需要一定的开销。如果每次执行一个任务都需要开一个新线程去执行任务,则这些线程的创建和销毁将消耗大量的资源。

6.1ThreadPoolExecutor线程池

可以通过ThreadPoolExecutor来创建一个线程池,ThreadPoolExecutor类一共有4个构造方法。

public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutinonHandler handle){}

corePoolSize:核心线程数

maximumPoolSize:线程池允许创建的最大线程数

keepAliveTime:非核心线程闲置的超时时间

TimeUnit:keepAliveTime参数的时间单位

workQueue:任务队列

ThreadFactory:线程工厂

RejectedExecutionHandler:饱和策略

 

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

 

7.AsyncTask的原理

当我们通过线程去执行耗时的任务,并且在操作完之后可能还有更新UI时,通常还会用到Handler来更新UI线程。虽然其实现起来简单,但是如果有多个任务同时则会显得代码臃肿。

AsyncTask是一个抽象的泛型,他有3个泛型参数,分别为Params,Progress和Result,其中Params为参数类型,Progress为后台任务执行进度的类型,Result为返回结果的类型。如果不需要某个参数,可以将其设置为Void类型。AsyncTask中有4个核心方法。

(1)onPreExecute:在主线程中执行,一般在任务执行前做准备工作,比如对UI做一些标记。

(2)doInBackground:在线程池中执行。 在onPreExecute方法执行后运行,用来执行较为耗时的操作。在执行过程中可以调用publicProgress(Progree values)来更新进度信息。

(3)onProgressUpdate(Progress..values):在主线程中执行,当调用publishProgress(Progress...values)时,此方法会将进度更新到UI组件上。

(4)onPostExecute(Result result):在主干线程中执行。当后台任务执行完成后,它会被执行。doInBackground方法得到的结果就是返回的result的值。

 

Android7.0版本的AsyncTask

AsyncTask的属性变量WorkerRunnable实现了Callable接口,并实现了它的call方法,在call方法中用了doInBackground来处理任务并得到结果,最终是调用哦postRessult将结果投递出去。FutureTask是一个可管理的异步任务,它实现了Runable和Futrue这两个接口。因此,它可以包装Runnable和Callable,并提供给Executor执行。

 

8.网络编程与网络框架

在学习这一节之前,我们首先要了解网络分层、Http协议原理、HttpClient与HttpURLConnection的使用等这些网络编程的基础知识,然后学习Volley、OkHttp和Retrofit这些开源网络框架的使用以及原理分析。

8.1网络分层

每一层都是为了完成一种功能而设置的,为了实现这些功能,就需要共同遵守共同的规则,这个规则叫作“协议”。

8.2HttpClient与HttpURLConnection

前面我们了解Http协议原理,本节讲讲Apache的HttpClient和Java的HttpURLConnection,它们都是我们平常请求网络会用到的。无论我们是自己封装的网络请求类还是第三方的网络请求框架,都离不开这两个库。

(1)HttpURLConnection

在Android2.2版本之前,HttpURLConnection一直存在着一些令人厌烦的bug。比如说对一个可读的InputStream调用close方法时,就有可能会导致链接池失效。我们通常的解决办法就是直接禁用链接池的功能。

所以在Android2.2版本其之前的版本使用HttpClient是较好的选择,而在Android2.3版本之后,HttpURLConnection则是最佳的选择,它的API简单,体积较小,因而非常适用于Android项目。HttpURLConnection的压缩和缓存机制可以有效的减少网络访问的流量。在Android6.0之后HttpClient被移除了。

 

(2)HttpURLConnection的POST请求

 

8.3解析Volley

在2013年Google I/O大会上推出了一个新的网络通信框架Volley。Volley既可以访问网络取得数据,也可以加载图片。并且在性能方法进行了大幅度的调整。它的设计目标就是适合进行数据量不大但通信频繁的网络操作。而对于大数据量的网络操作,比如下载文件等,Volley的表现却非常糟糕。

(1)Volley基本用法

在使用Volley前请下载Volley库且放在libs目录下并add到工程中。

 

Volley网络请求队列

Volley请求网络都是基于请求队列的,开发这只要把请求放在请求队列中就可以了,请求队列会依次进行请求。一般请求下,一个应用程序如果网络请求不是特别频繁,完全可以只有一个请求队列;如果网络请求非常对或有其他情况,则可以是一个Activity对应一个网络请求队列。

ReQuestQueue queue = Volley.newRequestQueue(getApplicationContext());

StringRequest的用法

StringRequest返回的数据是String类型的。

 

JsonRequest的用法

创建一个Java实体类来接受返回的数据。

 

ImageRequest加载图片

ImageRequest已经是过时的办法了,其和StringRequest、JsonRequest的用法类似,代码如下所示:

RequestQueue queue = Volley.newRequestQueue(getApplicationContext());

ImageRequest imageRequest = new ImageRequest("http://img.my.csdn.net/uploads/201603/26/1458988468_5804.jpg",

new Response.Listener<Bitmap>(){

public void onResponse(Bitmap response){

iv_image.setImageBitmap(response);

},0,0,Bitmap.Config.RGB_565,new Response.ErrorListener(){

public void onErrorResponse(VolleyError error){

iv_image.setImageResource(R.drawable.ico_default);

}

});

queue.add(imageRequest);

 

21.设计模式

在讲到常用的设计模式之前,首先介绍设计模式的六大原则,它们分别是单一设计原则、开放封闭原则、

里式替换原则、依赖倒置原创、迪米特原创和接口隔离原则。

单一职责原则定义:就一个类而言,应该仅有一个引起它变化的原因。也就是说不能让一个类过于臃肿。

例如在Activity中写Adapter、Bean文件、网络请求等。

 

开放封闭原则:类、模块、函数等应该是可以拓展的,但是不可以修改。

开发封闭有两个含义:一个是对拓展开发,另一个是对修改封闭。

 

里式替换原则:所有的引用基类的地方必须能透明地使用其子类的对象

 

22.函数响应式编程

函数响应式编程是一种编程范式,数据更新是相关的。把函数编程里的一套思路和响应式编程合起来就是函数响应式编程。

我们常见的面向对象编程是一种命令式编程。命令式编程是面向计算机硬件的抽象,有变量、赋值语句、表达式和控制语句。

而函数式编程是面向数学的抽象,将计算描述为一种表达式值,函数可以在任何地方定义,并且可以对函数进行组合。

 

响应式编程是一种面向数据流变化传播的编程范式,数据更新是相关联的。把函数式编程里的一套思路和响应式编程合起来就是函数响应式编程。

 

函数响应式编程可以极大地简化项目,特别是处理嵌套回调的异步事件、复杂的列表过滤和变换或者时间相关问题。

在Android开发中使用函数响应式编程的主要有两个框架:一个是RxJava,另一个是Goodle退出的Agera。

 

22.1RxJava概述

22.1.1ReactiveX与RxJava

在讲到RxJava之前我们首先要了解什么是ReactiveX,因为RxJava是ReactiveX的一种Java实现。

ReactiveX是Reactive Extensions的缩写,一般简写为Rx。微软给的定义是,Rx是一个函数库。

让开发者可以利用可观察序列和LINQ风格查询操作符来编写。

RxJava总共有4个角色Observable、Observer、Subscriber和Suject。

Observable和Observer通过subscribe方法实现订阅关系,Observable就可以需要的时候通知Observer。

 

其中RxAndroid是RxJava在Android平台的扩展

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

1.创建Observer(观察者)

它决定事件触发的时候将来有怎样的行为,代码如下所示:

Subscriber subscriber = new Subscriber<String>()

public void onCompleted()

public void onError()

public void onNext()

public void onStart()

 

其中onCompleted、onError和onNext是必须要实现的方法,其含义如下。

onCompleted:事件队列完结。RxJava不仅把每个事件单独处理,其还会把它们看作一个队列。

onError:事件队列异常

onNext:普通的事件。

onStart:它会在事件未发送之前调用。

 

2.被观察者Observable(被观察者)

它决定什么时候触发事件以及触发怎么的事件。RxJava使用create方法来创建一个Observable,并为它

定义事件触发规则,如下所示:

 

23.RXJava

23.1响应式编程

在计算机中,响应式编程是一种面向数据流和变化传播的编程范式。这意味着可以着可以在编程语言中很方便的表达静态或动态的数据流,而相关的计算模型会自动将变化的值通过数据流进行传播。

传统的编程方式是顺序执行,需要等待直至完成上一个任务之后才会执行下一个任务。无论是提升机器的性能还是代码的性能,本质上都需要依赖上一个任务测完成。如果需要响应迅速,就得把同步执行的方式换成异步执行。

响应式编程有以下几个特点:

异步编程:提供了合适的异步编程模型,能够挖掘多核CPU的能力、提高效率、降低延迟和阻塞等。

数据流:基于数据流 模型,响应式编程提供一套统一的Stream风格的数据处理接口。与Java8中的Stream相比,响应式编程除了支持静态数据流,还支持动态数据流,并允许服用和同时接入多个订阅者。

传播变化:简单来说就是以一个数据流为输入,经过一连串操作为另一个数据流,然后分发给各个订阅者的过程。这就有点箱函数式编程中的组合函数,将多个函数串联起来。把一组输入数据转化为相同格式的数据。

 

 

23.2RxJava简介

RxJava产生的由来

RxJava是Reactive Extensions的Java实现,用于通过使用Observable/Flowable序列来构建异步和基于事件的程序的库。

RxJava扩展观察这模式以支持数据/事件序列,并添加允许你以声明方式组合序列的操作符,同时提取对低优先级的线程、同步、线程安全性和并发数据结构等问题的隐藏。

 

什么是Rx

ReactiveX是Reactive Extensions的缩写,一般简写为Rx,最初是LINQ的一个扩展。

 

RxJava能干什么

RxBinding它是对AndroidView事件的扩展,可以对View事件使用RxJava的各种操作。

对应Android的AsyncTask,也可完全使用RxJava来替代。当然不只是Android App开发,在服务端领域的开发中也可以使用RxJava。

 

23.3RxJava基础知识

Observable

RxJava的使用通常需要三步走

创建Observable

Observable的字面意思是被观察者,使用RxJava时需要创建一个被观察者,它会决定什么时候触发事件以及触发怎样的事件。有点类似上游发送命令。

 

创建Observer

Observer即观察者,它可以在不同的线程中执行任务。这种模式可以极大地简化并发操作,因为它创建了一个处于待命状态的观察这哨兵,可以在未来某个时刻响应Observable的通知,而不需要阻塞等待Observable发射数据。

 

使用subscribe进行订阅

创建Observable和Observer之后,我们还需要使用subscribe方法将它们连接起来。

 

如上述的just()方法,它是RxJava的创建操作符,用于创建一个Observable。consumer是消费者,用于接收单个值。它与Consumer类似,RxJava的命名规范参照Java8.

subscribe有多个重载的方法。

subscribe(onNext)

subscribe(onNext,onError)

subscribe(onNext,onError,onComplete)

subscribe(onNext,onError,onComplete,onSubscribe)

 

Action:无参数类型

Consumer:单一参数类型

 

在RxJava中,被观察者、观察者、subscribe方法缺一不可,只有使用了subscribe,观察者才会开始发送数据。

RxJava2中,Observable不再支持订阅Subscriber,而是需要使用Observable作为观察者。

 

RxJava2.x的五种观察者模式

Observable和Observer:能够发射0或n个数据,并以成功或错误事件终止

Flowable和Subscriber:能够发射0或n个数据,并以成功或错误事件终止。支持背压,可以控制数据源发射速度。

Single和SingleObserver:只发射单个数据或错误事件

Completable和CompletableObserver:从来不发射数据,只处理onComplete和onError事件

Maybe和MaybeObserver:能够发射0或者1个数据,要么成功,要么失败。有点类似于optional

 

do操作符

do操作符可以给Observable的生命周期的各个阶段加上一些列的回调监听,当Observable执行到这个阶段时,这些回调就会被触发。

在RxJava中包含了很多的doXXX操作符。

doOnNext:它产生的Obserbable每发射一项数据就会调用它一次,它的Consumber接受发射的数据项。一般用于在subsrcibe之前对数据进处理。在onNext执行前执行

doAfterNext:在onNext执行后执行

doOnComplete:它产生的Observable在正常终止调用onComplete时会被调用。

doOnSubscribe:一旦定义这订阅,就执行它

doAfterTerminate:注册一个Action,当Observable调用onComplete或onError时触发

doFinally:当它产生的Obserable终止之后会被调用,无论是正常终止还是异常终止。doFinally优先于doAfterTerminate的调用

doOnEach:

doOnLifecycle:可以在观察这订阅后,设置是否取消订阅

 

23.3、Hot Observable和Cold Observable

Observable的分类

在RxJava中,Observable有Hot和Cold之分。

Hot Observable无论有没有观察者进行订阅,事件始终都会发生。当Hot Observable有多个订阅者时,Hot Observable与订阅者们的关系是一对多的关系。

Cold Observable是只有观察者订阅了,才开始执行发射数据流的代码。并且Cold Observable和Observable只能是一对一的关系。

把Hot Observable想象成一个广播电台,所有听众都能听到它

把Cold Observable当做一张音乐CD,人们可以独立购买并听取它。

 

Cold Observable

Observable的just、create、range、fromXXX等操作符都能生成Cold Observable。

尽管Cold Observable很好,但是对应某些事件不确定何时发生及不确定Observable发射的元素数量的情况,还需要使用Hot Observable

比如,UI交互的事件、网络环境的变化、地理位置的变化、服务推送小箱的达到等。

 

3.Cold Observable如何转换成Hot Observable

(1)使用publish,生成ConnectableObservable

使用publish操作符,可以让Cold Observable转换成Hot Observable,它将原先的Observable转换成ConnectableObservable。

ConnectableObservable只有调用了connect方法才会执行。

多个subscriber共享一个事件,这里的ConnectableObservable是线程安全的。

 

(2)使用Subject/Processor

Subject和Processor的作用相同。Processor是RxJava2.x新增的类,继承自Flowable,支持背压控制,而Subject则不支持背压控制。

 

23.4、Single、Completable、Maybe

如果想要使用RxJava首先会想到的是Observable,如果考虑到背压的情况,首先会想到Flowable,因为RxJava2.x后Observable就不支持背压了。

(1)Single

从SingleEmitter的源码可以看出,Single只有onSuccess和onError事件。

onSuccess用于发射数据(Observable/Flowable中使用onNext来发射数据),而且只能发射一个数据,后面即使再发射数据也不会做任何处理。

single的SingleObserver中只有onSuccess和onError并没有onComplete。这也是Single与其他4种观察者之间的最大区别。

single可以通过toXXX方法转换为Observable、Flowable、Completable及Maybe

 

(2)Completable

Completable在创建后,不会发射任何数据。

从源码上看:

Completable只有onComplete和onError事件,同时Completable并没有map、flatMap等操作符,它的操作符比起Observable和Flowable要少得多。

我们可以通过fromXXX操作符来创建一个Completable。

在网络操作中,如果遇到更新的情况,也就是Restful架构中的put操作,一般要么返回原先的对象,要么只提示更新成功。下面两个接口使用了Retrofit架构,分别用于获取短信验证码和更新用户信息。

 

(3)Maybe

Maybe是RxJava2.x之后才有的新类型,可以看出是Single和Completable的结合。

Maybe创建之后,MaybeEmitter和SingleEmitter一样,并没有onNext方法,同样需要通过onSuccess方法来发射数据。

Maybe.create(new MaybeOnSubscribe<String>(){

public void subscribe(MaybeEmitter e) throws Exception {

e.onSuccess("testA");

}

}).subscribe(new Consumer<String>(){

public void accept(String s) throws Exception){

System.out.println("s="+s);

}

}

 

Maybe也只能发射0或者1个数据,即使发射多个数据,后面发射的数据也不会处理。

如何MaybeEmitter先调用了onComplete,即使后面再调用onSuccess,也不会发射任何数据。

Maybe.create(new MaybeOnSubscribe<String>(){

public void subscribe(MaybeEmittter<String e) throws Exception {

e.onComplete();

e.onSuccess(“testA”);

}

}).subscribe(new Consumer<String>(){

public void accept(String s) throws Exception{

System.out.println("s="+s);

}

}

 

23.5、Subject和Processor

Subject是一种特殊的存在

我们曾介绍过Subject既是Observable,又是Observer。官网称可以将Subject看作一个桥梁或者代理。

1.Subject的分类

Subject包括4种类型,分别是AsyncSubject、BehaviorSubject、ReplaySubject和PublishSubject

 

AsyncSubject

Observer会接收AsyncSubject的onComplete之前的最后一个数据

执行subject.onComplete()必须要调用才会开始接收数据,否则观察者将不接收任何数据。

 

BehaviorSubject

Observer会先接收到BehaviorSubject被订阅之前的最后一个数据,再接收订阅之后发射过来的数据。如果BehaviorSubject被订阅之前没有发送任何数据,则会发送一个默认数据。

 

ReplaySubject

ReplaySubject会发射所有来着原始Observable的数据给观察者,无论它们是何时订阅的。

ReplaySubject<String

 

使用PublishSubject实现简化的RxBus

事件总线是基于发布/订阅模式实现的,如果某一事件在多个Activity/Fragment中被订阅,则在App的任意地方一旦发布该事件,多个订阅的地方均能收到这一事件,这很符合Hot Observable的特性。

所以,我们使用PublishSubject,考虑到多线程的情况,还需要使用到Subject的toSeriabled方法。

 

(7)预加载BehaviorSubject实现预加载

预加载可以很好地提高程序的用户体验。每当用户处于弱网络时,打开一个App很可能会出现一片空白或者一直在loading,此时用户一定很烦躁。而如果能够预先加载一些数据,例如上一次打开App时保存的数据,那么一定会有效提升App的用户体验。

 

2.Processor

Processor和Subject的作用相同。Processor是RxJava2.0新增的功能,它是一个接口,继承自Subscriber、Publisher,能够支持背压控制。这是Processor和Subject的最大区别。

其实Publisher和Processor都是Reactive Streams的接口,Reactive Streams项目提供了一个非阻塞异步流处理的背压标准。RxJava2.0已经基于Reactive Streams库进行重写,包括Single、Completable,都是基于Reactive Streams规范重写的,Flowable实现Reactive Streams的 Publisher接口等。

Reactive Streams的目标是管制流数据在跨越异步边界进行流数据交换,可以认为将元素传递到另一个线程或线程池,同时确保在接受端不是被迫缓冲任意数量的数据。换句话说,背压是该模型的重要组成部分,

通过设置协调线程之间的队列大小进行限制。当各异步模型之间采用同步通信时会削弱异步代理的好处,因此必须采取谨慎措施,强制完全无阻塞反应流能在系统的各个方面都做到异步实施。

 

Reactive Streams规范的主要目标:

通过异步边界来解耦系统组件。解耦的先决条件,分离事件/数据流的发送和接收方的资源使用。

通过异步边界来解耦系统组件。解耦的先决条件,分离事件/数据流的发送方式和接收的资源使用。

为背压处理定义一种模型。流处理的理想范式是将数据从发布这推送到订阅这,这样发布这就可以快速发布数据,同时通过压力来确保速度更快的发布这不会对速度较慢的订阅这造成过载。背压处理通过流控制来确保操作的稳定性并能实现优雅降级,从而提供弹性能力。

Reactive Streams JVM接口由以下四个接口组成。

publisher:消息发布者

Subscriber:消息订阅者

Subscription:一个订阅

Processor:Publisher+Subscriber的结合体

 

23.6创建操作符

操作符是RxJava的重要组成部分。

RxJava的操作符大致可以按照图3-1来进行分类:

链接操作符

工具操作符

又可分为:

创建操作符

变换操作符

过滤操作符

条件操作符

布尔操作符

合并操作符

 

(1)RxJava的创建操作符主要包括以下内容:

just():将一个或多个对象转换成发射这个或这些对象的一个Observable

from():将一个iterable、一个Future或者一个数组转换成一个Observable

create():使用一个函数从头创建Observable

defer():只有当订阅者订阅才创建Observable,为每个订阅创建一个新的Observable。

range():创建一个发射指定范围的整个序列的Observable。

interval():创建一个按照给定的时间间隔发射整个序列的Observable。

timer():创建一个在给定的延时之后发射单个数据的Obserable。

empty():创建一个什么都不做直接通知完成的Observable

error():创建一个什么都不做直接通知错误的Observable

never():创建一个什么不发射任何数据的Observable

 

23.6.、create、Just、from

(1)create

使用一个函数从头开始创建一个Observable

create->onNext->onNext->onComplete

 

我们可以使用create操作符从头开始创建一个Observable,给这个操作符传递一个接受观察者作为参数的函数,编写这个函数让它的行为表现为一个Observable

适当地调用观察者的onNext、onError和onComplete方法。一个形式正确的有限Observable必须尝试调用观察者的onComplete一次或者它的onError一次,而且此后不能再调用观察者的任何其他方法。

 

RxJava建议我们咋传递给create方法的函数时,先检查一下观察者的isDisposed状态,以便在没有观察者的时候,让我们的Observable停止发射数据,防止运行昂贵的运算。

 

(2)just

创建一个发射指定值的Observable

Observable.just("hello world").subscribe(new Consumer<String>{

public void accpt(String s) throws Exception{

System.out.println(s);

}

});

 

just类似于from,但是from会将数组或Iterable的数据取出然后逐个发射,而just只是简单地原样发射,将数组或Iterable当作单个数据。

它可以接受一至十个参数,返回一个按参数列表顺序发射这些数据的Observable

 

(3)repeat

创建一个发射特定数据重复多次的Observable

repeat会重复发射数据,某些实现运行我们重复发射某个数据序列,还有一些运行我们限制重复的次数。

repeat不是创建一个Observable,而是重复发射原始Observable的数据序列,这个序列或者是无限的,或者是通过repeat(n)指定的重复次数。

 

repeatWhen

repeatWhen不是缓存和重放原始Observable的数据序列,而是有条件地重新订阅和发射原来的Observable

将原始Observable的终止通知(完成或者错误)当作一个void数据传递给一个通知处理器,是否重新发射数据由这个通知处理器来管理。

 

repeatUnit

repeatUntil是RxJava2.x新增的操作符,表示直到某个条件就不重复发射数据。当BooleanSupplier的getAsBoolean()返回false时,表示重复发射上游的Observable;

当getAsBoolean为true时,表示中止重复发射上游的Observable。

 

(3)defer、interval、timer

defer

直到观察者订阅时才创建Observable,并且为每个观察者创建一个全新的Observable

defer操作符会一直等待直到观察者订阅它,然后它使用Observable工厂方法生成Observable。

它对每个观察者都这样做,因此尽管每个订阅者都以为自己订阅的是同一个Observable,但事实上每个订阅者获取的是它们自己单独的数据序列

在某些情况下,直到最后一分钟(订阅发生时)才生成Observable,以确保Observable包含最新的数据。

 

interval

创建一个按固定时间间隔发射整数序列的Observable

interval操作符返回一个Observable,它按固定的时间间隔发射一个无限递增的整数序列。

interval接受一个表示时间间隔的参数和一个表示时间单位的参数。interval默认在computation调度器上执行。

 

timer

创建一个Observalbe,它在一个给定的延迟后发射一个特殊的值。timer操作符创建一个给定的时间段之后返回一个特殊值的Observable

timer返回一个Observable,它在延迟一段给定的时间后发射一个简单的数字0。timer操作符默认在computation调度器上执行。

 

23.7RxJava的线程操作

23.7.1调度器种类

RxJava线程介绍

RxJava是一个为异步编程而实现的库,异步是其重要特色,合理地利用异步编程能够提高系统的处理速度。但是异步也会带来线程的安全问题,而且异步并不等于并发,与异步概念相对应的是同步。

在默认情况下,RxJava只在当前线程中运行,它是单线程。此时Observable用于发射数据流,Observable用于接受和响应数据流,各种操作符用于加工数据流,它们都在同一个线程中运行,实现出来的是一个同步的函数响应式。然而,函数响应式的实际应用是大部分操作都在后台处理,前台响应的是一个同步的函数响应式。Observable生成发射数据流,Operators加工数据流在后台线程中运行,Observer在前台线程中接收并响应数据流,Operators加工数据流在后台线程中进行,Observable在前台线程中接收并响应数据。此时会涉及使用多线程来操作RxJava,我们可以使用RxJava的调度器(Scheduler)来实现。

 

Scheduler

Scheduler是RxJava对线程控制器的一个抽象,RxJava内置了多个Scheduler的实现,它们基本满足绝大多数使用场景

single:使用定长为1的线程池

newThread:每次都启用新线程,并在新线程中执行操作

computation:使用固定的线程池,大小为CPU核数,适用于CPU密集型计算

io:适合I/O操作所使用的Scheduler。行为模式和newThread差不多,区别在于io的内部实现是用一个无数量上限的线程池,可以重用闲置的线程,因此多数情况下,io比newThread更有效率

trampoline:直接在当前线程运行,如果当前线程有其他任务正在执行,则会先暂停其他任务。

Schedulers.from:将java.util.concurrent.Executor转换成一个调度器实例,即可以自定义一个Executor来作为调度器。

如果内置的Scheduler不能满足业务需求,那么可以自定义的Executor作为调度器,以满足个性化需求。

 

RxJava切换线程比较简单

 

23.7.2RxJava线程模型

RxJava的被观察者们在使用操作符时可以利用线程调度器——Scheduler来切换线程

 

线程调度器

Schedulers是一个静态工厂类,通分析Schedulers的源码可以看到它有多种不同类型的Scheduler。

 

computation

computation用于CPU密集型的计算任务,但并适合I/O操作

 

io用于I/O密集型任务,支持异步阻塞I/O操作,这个调度器的线程池会根据需要增长,对于普通的计算任务,请使用Schedulers.computation

 

trampoline

在RxJava2中与在RxJava中作用不同。在RxJava2中表示立即执行,如果当前线程有任务在执行,则会将其暂停,等插入进来的新任务执行完成之后,在接着执行原先未完成的任务。在RxJava1中,表示当前线程中等待其他任务完成之后,再执行其他任务。

 

newThread

newThread为每个任务创建一个新线程

 

single

single()拥有一个线程单例,所有的任务都在这一个线程中执行,当此线程中有任务执行时,它的任务将会按照先进先出的顺序依次执行

 

除此之外,还支持自定义的Executor来作为调度器

 

Scheduler是RxJava的线程任务调度器,Worker是线程任务的具体执行者。从Scheduler源码可以看到,Scheduler在schedulerDirect、schedulePeriodicallyDirect方法中创建了Worker,然后会分别调用worker的scheduler、schedulerPeriodicallyDirect方法中创建了Worker,然后会分别调用worker的schedule、schedulePeriodically来执行任务。

 

ComputationScheduler使用FixedSchedulerPool作为线程池,并且FixedSchedulerpool被AtomicReference包装一下

从ComputationScheduler的源码中可以看出,MAX_THREADS是CPU的数目。FixedSchedulerPool可以理解为拥有固定数量的线程池,数量为MAX_THREADS

 

IoScheduler

IoScheduler使用CachedWorkerPool作为线程池,并且CachedWorkerPool也被AtomicReference包装一下

 

2.线程调度

默认情况下不做任何线程处理,Observable和Observer处于同一线程中,如果想要切换线程,则可以使用subscribeOn和observeOn

 

(1)subscriveOn

subscriveOn通过接收一个Scheduler参数,来指定对数据的处理运行在特定的线程调度器Scheduler上。

若多次执行subscriberOn,则只有一次起作用

单击subscribeOn的源码可以看到,每次调用subscribeOn都会创建一个ObservableSubscribeOn对象

 

(2)observeOn

observeOn同样接收一个Scheduler参数,用来指定下游操作运行在特定的线程调度器Scheduler上。

若多次执行observeOn,则每次都起作用,线程会一起切换。

 

23.8变换操作符和过滤操作符

RxJava的变换操作符主要包括以下几种

map:对序列的每一项都用一个函数来变换Observable发射的数据序列。

flatMap、concatMap、flatMapIterable:将Observable发射的数据集合变换为Observables集合,然后将这些Observable发射的数据平坦化地放进一个单位的Observable中。

switchMap:将Observable发射的数据集合变换为Observables集合,然后只发射这些Observables最近发射过的数据。

scan:对Observable发射的每一项数据应用一个函数,然后按顺序依次发射每一个恶值。

groupBy:将Observable拆分为Observable集合,将原始Observable发射的数据按Key分组,每一个Observable发射过一组不同的数据。

buffer:定期从Observable收集数据到一个集合,然后把这些数据集合打包发射,而不是一次发射一次。

window:定期将来自Observable的数据拆分成一些Observable窗口,然后发射这些窗口,而不是每次发射一项。

cast:在发射之前强制将Observable发射的所有数据转换为指定类型。

 

RxJava的过滤操作符主要包括以下几种:

filter:过滤数据

takeLast:只发射最后的N项数据

last:只发射最后一项数据

 

23.8.1、Map和FlatMap

1.map操作符

对Observable发射的每一项数据应用一个函数,执行变换操作。

map操作符对原始Observable发射的每一项数据应用一个你选择的函数,然后返回一个发射这些结果的Observable

RxJava将这个操作符实现为map函数,这个操作符默认不在任何特定的调度上执行

 

2.flatmap操作符

flatMap将一个发射数据的Observable变换为多个Observables,然后将它们发射的数据合并后放进一个单独的Observable

 

23.9RxJava背压

背压

在RxJava中会遇到被观察这发送消息太快以致于它的操作符或者订阅者不能及时处理相关的消息,这就是典型的背压场景

是指在异步场景下,被观察者发送事件速度远快于观察这处理速度,从而导致下游的buffer溢出,这种现象叫做背压。

 

首先背压必须是在异步的场景下才会出现,即被观察者和观察者处于不同线程中。

其次,RxJava是基于Push模型的。对于Pull模型而言,当消费者请求数据的时候,如果生产者比较慢,则消费者会阻塞等待。如果生产者比较快,则生产者会等待消费者处理完后再产生新的数据。

 

RxJava2.x的背压策略

在RxJava2.x中,Observable不在支持背压,而是改用Flowable来专门支持背压。默认队列大小为128,并且邀请所有的操作符强制支持背压。

 

从BackpressureStrategy的源码可以看到,Flowable一共有5种背压策略。

 

1.MISSINGE

此策略表示,通过create方法创建的flowable没有指定背压策略,不会对通过onNext发射的数据做缓存或丢弃处理,需要下游通过背压操作符,指定背压策略。

 

2.ERROR

此策略表示,如果放入Flowable的异步缓存池中的数据超限了,则会抛出MissingBackpressureException异常。

 

3.BUFFER

此策略表示,Flowable的异步缓存池同Observable的一样,没有固定大小,可以无限制天津数据,不会抛出MissingBackpressureException异常,

但会导致OOM

 

4.DROP

此策略表示,如果Flowable的异步缓存池满了,则会丢弃掉将要放入缓存池的数据。

 

5.LATEST

此策略表示,如果缓存池满了,会丢掉将要放入缓存池的数据。这一点与DROP策略一样,不同的是,

不管缓存池的状态如何,LATEST策略会将最后一条数据强行放入缓存中。

 

23.9、Disposable和Transformer的使用

9.1 Disposable

在RxJava2.x中可以通过Disposable取消订阅。取消订阅后会停止整个调用链。则可以用Suscription.isUnsubscribed检查Subscription是否是unsubscribed。如果是unsubscribed,则调用Subscription.unsubscribe,RxJava会取消订阅并通知给Subscriber,并运行垃圾回收机制是否对象,防止任何RxJava造成内存泄露。

 

9.2 RxLifecycle和AutoDispose

在Android开发中,可以使用Dispoable来管理一个订阅或者使用CompositeDisposable来管理多个订阅,防止由于没有及时取消,导致Activity/Fragment无法销毁而引起内存泄露。然而,也有一些比较成熟的库可以做这些事情。

 

1.RxLifecycle

RxLifecycle是配合Activity/Fragment生命周期来管理订阅的,由于RxJava Observable订阅后,一般会在后台线程执行一些操作(比如访问网络请求数据),

当后台操作返回后,调用Observer的onNext等函数,然后再更新UI状态。但是后台线程请求是需要时间的,如果用户单击刷新按钮请求新的微博信息,在刷新还没有完成时,如果用户退出了当前的界面,而这个时候刷新的Observable又未取消订阅,就会导致之前的Activity无法被JVM回收,从而导致内存泄露。这就是在Android开发中值得注意的地方,RxLifecycle就是专门用来做这件事的。

 

当涉及绑定Observable到Activity或Fragment的生命周期时,要么指定Observable应终止的生命周期事件,要么让RxLifecycle库决定何时终止Observable序列。

 

2.AutoDispose

AutoDispose是Uber开源的库,它与RxLifecycle的区别是,不仅仅可以在Android平台上使用,还可以在Java平台上使用,适用的范围更加宽广。

 

9.3、Transformer在RxJava中的使用

1.Transformer的用途

Transformer是转换器的意思。在RxJava2.x版本中有ObservableTransformer、SingleTransformer、CompletableTransformer、FlowableTransformer和MaybeTransformer。其中FlowableTransformer和MaybeTransformer是新增的。由于RxJava2将Observable拆分为Observable和Flowable,所以有了FlowableTransformer。同样,Maybe也是RxJava2新增的一个类型,所以有MaybeTransformer。

Transformer能够将一个Observable/Flowable/Single/Completable/Mayble对象转换成另一个Observable/Flowable/Single/Completable/Maybe对象

 

2.与compose操作符结合使用

compose操作符能够从数据中得到原始的被观察者。当创建被观察者时,compose操作符会立即执行,而不像其他的操作符需要在onNext调用后才能执行。

 

23.10、RxJava的并行编程

被观察者发射的数据流可以经历各种线程切换,但是数据流的各个元素之间不会产生并行执行的效果。并行不是并发,也不是同步,更不是异步。

并发是指一个处理器同时处理多个任务。并行是多个处理器或者多核的处理器同时处理多个不同任务。并行是同时发生的多个并发事件,具有并发的含义,而并发则不一定是并行。

 

Java8新增了并行流来实现并行的效果,只需在集合上调用paralleStream方法即可

从执行结果中可以看到,Java8借助了JDK的fork/join框架来实现并行编程。

 

1.借助flatMap实现并行

在RxJava中可以借助flatMap操作符来实现类似于Java8的并行执行效果

FlatMap操作符的原理是将这个Observable转化为多个以原Observable发射的数据作为源数据Observable,然后再将这多个Observable发射的数据整合发射出来。

flatMap会对原始Observable发射的每一项数据执行变换操作。在这里,生成的每个Observable使用线程池(指定computation作为Scheduler)并发地执行。

当然,我们还可以使用ExecutorService来创建一个Scheduler,对刚才的代码稍微做一些改动。

 

 

10.2 ParalleFlowable

RxJava2.0.5版本新增了ParalleFlowable API,它运行并行地执行一些操作符,例如map、filter、concatMap、flatmap、collect、reduce等。

ParallelFlowable是并行的Flowable版本,而不是新增的被观察者类型。在ParallelFlowable中,很多典型的操作符是不可用的。

RxJava中并没有ParallelObservable,因为在RxJava2之后,Observable不在支持背压。然而在并行处理中背压是必不可少的,否则会淹没在并行操作符内部队列中。

 

1.ParallelFlowable实现并行

类似于Java8的并行流,在相应的操作符上调用Flowable的parallel就会返回ParallelFlowable。

 

2.ParallelFlowable与Scheduler

ParallelFlowable遵循与Flowable相同的异步原理,因此parallel本省并不引入顺序源的异步消耗,只准备并行流,但是可以通过runOn操作符定义异步。这点与Flowable有很大不同,Flowable使用subscribeOn、observeOn操作符。

 

23.11RxBinding的使用

11.1RxBinding简介

RxBinding是Jake Wharton大神写的框架,它的API能够把Android平台和兼容包内的UI控件变为Observable对象,这样就可以把UI控件的事件当作RxJava中的数据流来使用了。比如View的onClick事件,使用RxView.click(view)即可获取一个Observable对象,每当用户单击这个View的时候,该Observable对象就会发射一个事件,Observable的观察者就可以通过onNext回调知道用户单击了View。

 

RxBinding的优点

它是对Android View事件的扩展,它使得开发者可以对View事件使用RxJava的各种操作。

它提供了与RxJava一致的回调,使得代码简洁明了,尤其是页面上充斥着大量的监听事件,以及各种各样的匿名内部类。

几乎支持所有的常用控件及事件,另外每个库还有对应的Kotlin支持库。

 

2.响应式的Android UI

对UI事件的响应几乎是Android App开发的基础部分,但是Android SDK对UI事件的处理有些复杂,我们通常需要使用各种listeners、handlers、TextWatches和其他控件等组合来响应UI事件。这些组件中的每一个都需要编写大量的样板代码,更为槽糕的是,实现这些不同组件的方式并不一致。

例如RxView.click(button)

RxTextView.textChanges(edittext)

 

11.2RxBinging使用场景

RxBinding可以应用于整个App的所有UI事件。

例如:

1.点击事件onclick

RxView.clicks(text1)

 

2.长点击事件

RxView.longClicks(text2)

 

3.防止重复点击

RxView.clicks(text3)

.compose(RxUtils.useRxViewTransformer(MainActivity.this))

.subscribe();

 

这里使用了compose操作符,它封装了两个操作:一个是防止UI事件的重复点击,另一个是绑定Activity的生命周期,防止内存泄露。

 

4.表单的验证

App内常见的表单验证是用户登录页面,我们需要对用户名、密码做一些校验。对于检验,有些是服务端做的,例如,用户名是否存在、用户名密码是否正确等。而有些校验需要客户端来做,例如,用户名是否输入正确、输入的用户名是否规范、密码是否输入正确等。

 

5.验证码倒计时

long MAX_COUNT_TIME = 60;

RxView.clicks()

.throttleFirst(MAX_COUNT_TIME,TimeUnit.SECONDS)

.flatMap();

 

6.对RecyclerView的支持

RxBinding提供了一个rxbinding-recyclerview-v7的库,专门用于对RecyclerView的支持。

其中,RxRecyclerView提供了几个状态的观察:

scrollStateChanges观察者RecyclerView的滚动状态

scrollEvents观察者RecyclerView的滚动事件

childAttachStateChangeEvents观察者child View的detached状态,当LayoutManager或者RecyclerView认为不在需要一个child view时,就会调用这个方法。如果child view占用资源,则应当释放资源。

 

在Adapter的onBindViewHolder中,可以使用clicks来绑定itemView的点击事件

 

7.对UI控件进行多次监听

可以利用RxJava的操作符,例如public、share、replay,实现对UI控件的多次监听。

 

8.RxBinding结合RxPermissons的使用

Android6.0之后权限的改变

Android6.0带来一个很大变化就是权限机制的改变,特别是运行时权限。

Android6.0添加时的运行时权限可分为两种:

Normal Permissons:这类权限不涉及个人隐私,不需要用户授权,比如手机震动、访问网络等。

Dangerous Permissons:这类权限涉及个人隐私,需要用户授权,比如读取SD卡,访问通信录等。

危险权限是有分组的,如果用户申请了某个危险权限,而该用户已经授权了一个与他现在申请的是同一组的危险权限,那么系统会自动授权,无线用户再次授权。

 

RxPermission的介绍

在处理运行时权限时,通常需要两步:

(1)申请权限

(2)处理权限回调,根据授权的情况进行回调

RxPermissions的出现可以简化这些步骤,它是基于RxJava开发的Android框架,旨在帮助Android6之后处理运行时权限的监测。

11.3.3RxBinding结合RxPermissions

举一个拨打电话的离职,CALL_PHONE在Android6.0之后是一个Dangerous Permissions,第一次使用时需要动态申请该权限,只有得到允许才能完成后面打电话的动作。

 

2.RxBinding结合compose使用RxPermissions

对上述的代码稍作修改,RxBinding可以结合compose操作符来使用RxPermissions。本书9.3节曾介绍过compose操作符。

RxView.clicks(text2)

.compose(rxPermissons.ensure(Mainfest.permission.CALL_PHONE))

 

3.使用多个权限的用法

RxPermissons也支持申请多个权限。

RxView.clicks(text3).compose(rxPermissions.ensure(Manifest.permissions.CAMERA,Manifest.permissions.READ_CONTACTS))

 

11.4使用RxBinding使用的注意点

使用useRxViewTransformer方法

public static ObservableTransformer useRxViewTransformer(final AppCompatActivity activity){

return new ObservableTransformer(){

public ObservableSource apply(Observable upstream){

return upstream.compose(rxJavaUtils.preventDuplicateClicksTransformer()).compose(RxLifecycle.bind(activity).toLifecycleTransformer));

}}

 

这个方法封装了两个操作:一个防止UI事件的重复点击,另一个是绑定Activity的生命周期,防止内存泄露。

在Android App中使用RxJava存在者一个最大的缺点,即不完整的订阅会导致内存泄露。当Android系统尝试销毁正在运行的observable的Activity/Fragment时,会发生内存泄露。由于Observable正在运行,其观察者仍会持有该Activity/Fragment的引用。

因此,Trello开发出了大名鼎鼎的RxLifecycle,它可以通过绑定生命周期的方式,来解决内存泄露的问题。

后来,知乎也开发了一款类似的RxLifecycle,它运行你仅用一句话绑定你的Observable到Activity/Fragment的生命周期上。知乎的RxLifecycle与最初的Trello开发的RxLifecycle相比,在使用上更加简单。

 

RxAndroid 2.x 和Retrofit的使用

12.1RxAndroid2.x简介

1.介绍

近几年RxJava逐渐成为Android开发的新宠,越来越多的Android开发者正在使用或者即将使用RxJava。想要在Android上使用RxJava,RxAndroid必不可少。

RxAndroid也隶属于ReactiveX组织,它是RxJava在Android上的一个扩展。RxAndroid不能单独使用,它只有依赖RxJava才能使用。

选择所依赖的RxJava版本时最好用最新的版本,因为新版本会修复之前版本的Bug并且提供一些新的特性。

 

2.使用

(1)AndroidSchedulers.mainThread()

在Android中使用时需要新增一个调度器,用于将指定的操作切换到Android的主线程中运行,方便做一些更新UI的操作。RxAndroid就提供了这样的调度器。

下面的例子展示了从网上获取图片然后将图片转换成Bitmap,最后显示到ImageView上的过程。通过subscribeOn、observeOn不断地切换线程来到达目的。

 

(2)AndroidSchedulers.from(Looper looper)

RxAndroid除了可以切换到主线程,还可以使用任意指定的Looper。

 

12.2 Retrofit简介

Retrofit是一个在Android开发中非常流行的网络框架,底层依赖OkHttp。Retrofit和OkHttp都出自Square的技术团队。

应用程序通过Retrofit请求网络,实际上是使用Retrofit接口封装请求参数、Header、URL等信息,之后OkHttp完成后续的请求操作,在服务器返回数据后,OkHttp将原始的结果交给Retrofit,Retrofit再根据用户的需求对结果进行解析的过程。

Retrofit支持大多数的Http方法。

Retrofit的特点如下:

(1)Retrofit是可插拔的,允许不同的执行机制及其库用于执行http调用。允许API请求,与应用程序其余部分中任何现有线程模型和任何框架组合。

Retrofit为常见的框架提供了适配器:

(2)允许不同的序列化格式及其库,用于将Java类型转换为其http表示形式,并将http实体解析为Java类型。

Retrofit为常见的序列化格式提供了转换器:

Gson

Jackson

Moshi

 

12.3Retrofit与RxJava的完美配合

Retrofit是一个网络框架,如果想尝试响应方式,则可以结合RxJava一起使用。Retrofit对RxJava1和RxJava2 都提供了Adapter

多个网络请求嵌套使用,可以考虑使用flatMap操作符

 

重试机制

对应一些重要的接口,需要采用重试机制。因为有些时候用户的网络环境比较差,第一次请求接口超时了,那么再一次请求可能就会成功。虽然有一定的延迟,但至少返回了数据,保证了用户体验。

 

apiService.loadContent(param)

.retryWhen(new RetryWithDelay(3,1000))

.compose(RxLifecycle.bind(fragment).toLifecycle.Transformer())

 

23.13开发EventBus

13.1传统的EventBus

EventBus是事件总线框架,它是一种消息发布——订阅的模型。

 

24.Framework架构

1.1深入认识Android

1.1.1Android的系统架构

要深入学习Android,首先要学习Android的系统架构。Android的系统架构和其操作系统一样,采用分层的架构,层次非常清晰,因此要掌握它的架构并不难。

Android分为4层,从高层到底层分别是应用程序层、应用程序框架层、系统运行库层、内核层。

1.应用程序层

Android会与核心应用程序包一起发布,该应用程序包包括图1为大家展示的主屏、E-mail客户端、SMS/MMS短消息程序、日历、地图、浏览器、联系人管理程序等。所有的应用程序都是使用Java语言编写的,通过调用应用程序框架层所提供的API来完成。当然,你也可以使用Java通过JNI的方式,配合Android NDK来开发原生的应用程序,这样可以提供应用程序的效率,但是难度也大大增加——你需要精通C和C++等语言,并且Android NDK所提供的为啥不多的功能很深的认识。因为Android NDK提供的功能不是太多,为了避免你做了很久之后才发现——原来NDK不支持某项功能,大家可以根据自己的需求来选择是否采用NDK开发原生程序。

 

2.应用程序框架层

应用程序框架层为开发人员提供了可以完全访问核心应用程序所使用的API框架。该应用程序的架构设计简化了组件的重用,任何一个应用程序都可以发布自己的功能模块。同样,该应用程序重用机制也使用用户可以方便地替换程序组件。

活动管理器(Activity Manager):管理应用程序生命周期并提供常用的导航回退功能。比如:开启应用程序需要的资源和退出应用程序时需要释放的资源。

窗口管理器:(Window Manager):管理所有开启的窗口程序。

内容提供器(Content Providers):使应用程序可以访问另一个应用程序的数据(如联系人数据库),或者共享它们自己的数据

视图系统(View System):可以用来构建应用程序,它包括列表、网格、按钮、图形绘制等,以及可嵌入的web浏览器。

通知管理器(Notification Manager):使应用程序可以在状态栏中显示自定义的提示信息。

包管理器(Package Manger):管理所有安装在Android系统中的应用程序。比如:信息查看和卸载应用程序等。

资源管理器:提供各种资源供应应用程序使用。比如:字符串资源、图像资源、音频资源等。

硬件服务(Hardware Services):

Telephony Manager:电话拨打和接听等相关服务功能。

Location Manager:管理地图服务的相关功能。

Blutooth Service:有关蓝牙服务的相关功能。

WIFI Service:WIFI服务相关功能。

USB Service:USB服务相关功能。

Sensor Service:传感器服务相关功能。

 

3.系统运行库层

系统运行库层包括程序库和Android运行库两部分,下面分别来介绍这两个部分。

(1)程序库

Android包含一些c/c++库,这些库能被Android系统中的不同组件使用,它们通过应用程序框架为开发者提供服务。它们通过应用程序框架为开发者提供服务。

系统运行库层包含的核心库及其功能描述:

Surface Manager: 对显示子系统进行管理,并且为多个应用程序提供了2D和3D图层的无缝融合

Media Framework:基于PacketVideo OpenCORE,该库支持多种格式的音视频的回放和录制,同时支持静态图像文件。编码格式包括MPEG4、H.264等等。

SQLite:对于所有应用程序可用、功能强劲的轻型关系型数据库引擎。

Openg1 ES:支持OpenGL ES1.x和OpenGL ES2.0,该库可用使用硬件3D加速或者使用高度优化的3D软加速底层的2D图形引擎。

SGL:底层的2D图形引擎

FreeType:位图和矢量字体显示

webkit:最新的Web流量器引擎

SSL:安全通信相关技术处理,为网络通信提供安全及数据完整性的一种安全协议。

Libc:Android并没有采用glibc作为c库

 

(2)Android运行时库

Android运行时库又分为核心库和Dalvik虚拟机两部分。核心库提供了Java语言核心库的大多数功能,这里主要通过JNI的方式应用程序框架层提供调用底层程序库的接口。Dalvik虚拟机是为了能同时高效地运行多个VMS而实现的。Dalvik虚拟机执行.dex的Dalvik可执行文件,该格式的文件针对最小内存使用做了优化。Dalvik虚拟机是基于寄存器的,所有的类都经有Java汇编器编译,然后通过SDK中的dx工具转换为.dex格式并有虚拟机执行。Dalvik虚拟机依赖Linux的一些功能,比如线程机制和底层的内存管理机制。每一个Android应用程序都在它自己的进程中运行,都拥有一个独立的Dalvik虚拟机实例。

 

4.Linux内核层

Android依赖于Linux2.6版内核提供的核心系统服务,例如安全、内存管理、进程管理、网络栈、驱动模块等。内核作为一个抽象层,存在于软件栈层和硬件层之间,Android对内核进行了增强。主要表现在以下几个方面:

硬件时钟Alarm

内存分配与共享Ashmem

低内存管理器Low Memory Killer

Kernel调试Kernel Debugger

日志设备Logger

Android IPC机制Binder

电源管理Power Management

 

1.1.2 Android的初始化流程

上一节分析了Android的系统架构,让大家对Android系统有了一个深入的认识;但是,Android系统本身非常庞大,在深入分析每一个模块的具体功能之前,有必要对其启动过程进行分析。

Android系统在启动时会首先会启动Linux基础系统,然后引导加载Linux kernel内核并启动初始化进程。

接着,启动Linux守护进程(daemons)。这个过程主要需要启动以下内容。

1.启动USB守护进程来管理USB连接。

2.启动Android Debug Bride守护进程来管理管理adb连接

3.启动Debug守护进程来管理电视进程的请求

4.启动无线接口守护进程来管理无线通信

 

在启动Linux守护进程的同时还需要启动Zygote进程,它主要包括以下需要启动和注册的内容:

初始化一个Dalvik虚拟机实例

装载Socket请求所需的类和监听

创建虚拟机实例来管理应用程序的进程

 

再接着,需要初始化runtime进程。在这个过程中需要处理以下操作:

初始化服务管理器

注册服务管理器,以它作为默认Binder服务的Context管理器

runtime进程初始化之后,runtime进程将发送一个请求到Zygote,开始启动系统服务,这时Zygote将为系统服务进程建立一个虚拟机实例。

紧接着,系统服务将启动原生系统服务,主要包括surface Flinger和Audio Flinger。这些本地系统服务将注册到服务管理器作为IPC服务的目标

 

系统服务将启动Android管理服务,Android管理服务将都注册到服务管理器作。

Android管理服务包括以下:

Content Manger

window Manager

Telephony Service

Activity Manger

Bluetooth Service

Package Manger

Connectivity Service

Power Manager

Location Manager

 

最后系统加载完所有的服务后会处于等待状态,等待程序运行。但是每一个应用程序都将启动一个单独的进程。那么各个进程之间如何进程交互呢?这就需要使用IPC机制了。

 

1.1.3 各个层次之间的相互关系

在Android中运行的应用程序都是通过以下三种方式来层层深入的:

App——>Runtime Service——>Lib

App——>Runtime Service——>Native Service——>Lib

App——>Runtime Service——>Native Daemon——>Lib

下面就分别来分析这三种方式,我们还是采用流程图的方式来为大家展示。

App——>Runtime Service——>Lib方式对应的流程图

 

通过上图我们可以看出,在Android平台上,应用程序首先是在应用程序层通过Binder IPC调用应用框架层的Runtime Service,然后再通JNI与运行库中原生服务绑定,并动态的加载Hal库,进而调用Linux内核层Kernel Driver。

 

下面我们再来看一下第二种方式(App——>Runtime Service——>Native Service——>Lib)是如何调用的。这种方式通过被Android原生服务采用。

与第一种方式只多了一个通过IPC机制调用原生服务并进行动态装载的过程。

第三种只是把第二的IPC机制改为Socket

 

1.1.4 Android系统开发(移植)和应用开发

因为Android是开源的操作系统,所有可以选择的开发方式主要有以下几种:

Android系统开发

Android应用开发

 

1.Android系统开发(移植)

Android系统开发属于底层的开发工作,主要是针对Android系统本身进行完善和将其移植到其他的硬件平台,因此我们需要掌握Android系统架构中的Linux内核层和系统运行库层。

Android系统开发主要涉及Libraries和Android Runtime这两部分内容,一般就是在本地编写c/c++代码,然后通过JNI向上提供调用接口,主要是为应用开发提供服务等工作。

Android系统移植则主要涉及硬件抽象和Linux内核层,移植的主要工作是将驱动移植到不同的硬件上,使其完美运行Android系统。这些驱动又主要包括设备驱动和Android专用驱动。

 

无论是系统开发还是系统移植,都是由底层直接操作Android的源代码,因此在开发之前,我们首先就需要准备Android的源码。

2.Android应用开发

Android应用开发主要是根据Android系统所提供的API来完成各种应用程序的编写,所使用的API属于Android的应用框架层。如果Android系统没有提供某些API,那么只能通过在Android系统底层编写C/C++代码来开发这些API向Android应用框架层提供接口。

 

1.2获取和编译Android的源码

Android的源码非常庞大,所以需要分别为两部分来获取,分别是Android源码和Android内核

1.获取Android源码的步骤

如果要获取Android的完整源码,则需要以下步骤:

(1)在用户目录

 

2.Android的内核机制和结构解析

本章主要内容:

Android为什么要采用Linux内核?

设备在睡眠状态时如何确保系统事件保持运行,以及如何从睡眠状态被唤醒?

如何管理内存和内存共享

当内存过低时怎么办

内核调试器的原理是什么

如何抓取Android系统的各种日志

第一章在宏观上介绍了Android的机构和原理,同时,塔建了后面我们将要使用的开发环境。

2.Linux与Android的关系

虽然Android基于Linux内核,但是它与Linux之间还是有着很大的差别,比如Android在linux内核的基础上添加了自己所特有的驱动程序。

 

2.1.1为什么会选择Linux

成熟的操作系统有很多,但是Android为什么选择采取Linux内核呢?这就与Linux的一些特性有关了,比如:

强大的内存管理和进程管理方案

基于权限的安全模式

支持共享库

警告认证的驱动模型

Linux本身就是开源项目

 

2.1.2Android不是Linux

看到这个标题大家可能会有些迷惑,前面 不是一直说Android是基于Linux内核的吗,怎么现在又是Linux了?迷惑也是正常的,请先看下面几个要点,然后我们将对每个要点进行分析,看完后你就会觉得Android不是Linux了。

它没有本地窗口系统

它没有glibc的支持

它并不包括一套标准的Linux使用程序

它增强了Linux以支持其特有的驱动

 

1.它没有本地窗口系统

什么是本地窗口系统呢?本地窗口系统是指GNU/Linux上的X窗口系统,或者Mac OXX的Quartz等。不同的操作系统的窗口系统可能不一样,Android并没有使用Linuxde X窗口系统,这是Android不是Linux的一个基本原因。

 

2.它没有glibc支持

由于Android最初用于一些便携的移动设备上,所有可能出于效率等方面的考虑,Android并没有采用glibc作为c库,而是Google自己开发了一套Bionic Libc来代替glibc

 

3.它并不包括一套标准的Linux使用程序

Android并没有完全照搬Linux系统的内核,除了修正部分Linux的Bug外,还增加了不少内容。

 

4.Android专有的驱动程序

除了上面这些不同点之外,Android还对Linux设备驱动进行了增强,主要如下所示。

(1)Android Binder基于OpenBinder框架的一个驱动,用于提供Android平台的进程间通信功能。源代码位于drivers/staging/android/binder.c。

(2)Android电源管理(PM)一个基于:

 (3)低内存管理器:比如Linux的标准的OOM机制更加灵活,它可以根据需要杀死进程以释放需要的内存。

(4)匿名共享内存:为进程间提供大块共享内存,同时为内核提供回收和管理这个内存的机制。

(5)Android PMEM:用于向用户空间提供连续的物理内存局域,DSP和某些设备只能工作在连续的物理内存上。

(6)Android Logger:一个经量级的日志设备,用于抓取Android系统的各种日志。源代码位于drivers/staging/android.logger.c

(7)Android Alam提供了一个定时器,用于把设备从设备从睡眠状态唤醒,同时它还提供了一个即使在设备睡眠也会运行的时钟基准。源代码位于drivers/rtc/alarm.c

(8)USB Gadget驱动:一个基于Linux USB gadget驱动框架的设备驱动,Android的USB驱动是基于gaeget框架

(9)Androi Ram Console为了提供调试功能,Android运行将调试日志信息写入一个被称为RAM Console的设备里,它是一个基于RAM的Buffer。

(10)Android timed device提供了对设备进行定时控制的功能,目前支持vibrator和LED设备。

(11)Yaffs2文件系统  Android采用Yaffs2作为MTD nand flash文件系统,源代码位于fs/yaffs2/目录下。

 

2.2 Android 对Linux内核的改动

Android从多个方面对Linux内核进行了改动和增强,下面将对此进行详细介绍和分析

 

2.2.1 Goldfish

Android模拟器通过运行一个Goldfish的虚拟CPU.Goldfish来运行arm926t指令集并且仿真了输入/输出,比如键盘输入和LCD输出。这个模拟器其实是在qemu之上开发的,输入/输出是基于libSDL的。既然Goldfish是被模拟器运行的虚拟CPU,那么当Android在真实的硬件设备上运行时,我们就需要去掉它,因此,只有知道Google对Goldfish做了哪些集体改动之后才能正确地去掉。

 

2.2.2 YAFFS2

不同于PC机,手机使用FLASH作为存储介质。HTC的G1使用的是NANDFLASH这种存储目前已经相当普及了,而且种类也颇多存储密度也越来越高,价格也越来越低。

YAFFS2是专门用在FLASH上的文件系统,YAFFS2是“Yet Another Flash File System,2nd edition”的缩写。YAFFS2为Linux内核提供了一个高效访问NANDFLASH的接口。但是NANDFLASH的支持并不包含在标准的2.6.25内核中,所以Google在其中添加了对NANDFLASH的支持。

 

2.2.3蓝牙

在蓝牙通信协议栈里Google修改了10个。这些改动修复了一些与蓝牙耳机相关的明显的Bug,以及一些与蓝牙耳机的明显Bug,以及一些与蓝牙调式和访问控制相关的函数。

 

2.2.4调度器

Android内核还修改了与进程调度和时钟相关的策略。只改动了5个文件。

 

2.2.5 Android新增驱动

Android在linux的基础上新增了许多特有的驱动。

(1)IPC Binder 一种IPC机制。它的进程能够为其他进程提供服务——通过标准的Linux系统调用API。IPC Binder的概念起源于一家Be.Inc的公司,在Google之前就已经被Palm软件采用了。

(2)Low Memory Killer 其实内核里已经有一个类似的功能,名称为oom killer。当内存不够的时候,该策略会试图结束一个进程。

(3)Ashmem匿名共享内存。该功能使得进程间能够共享大块的内存。比如说,系统可以使用Ashmem保存一种回收这些使用完的共享内存块的方法,如果某个进程试图访问这些已经被回收的内存,它将会得到错误的返回值,以便它重新进程内存块分配和数据初始化。

(4)RAM Console and Log Device 为了调试方便,Android添加了一个功能,使调试信息可以输入到一个内存块中。此外,Android还添加了一个独立的日志模块,这样用户空间的进程就能够读写日志消息,以及调试打印信息等。

(5)Android Debug Bridge 嵌入式设备的调试的确比起麻烦,为了便于调试。

(6)电源管理 电源管理对应移动设备来说相当重要,也就是最为复杂和开发难度最高的一个功能。Google添加了一个新的电源管理系统。

 

2.2.7杂项

除了上述改动之外。

posted @ 2021-10-27 17:18  周千  阅读(178)  评论(0编辑  收藏  举报