一、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相比,在使用上更加简单。