多线程
常见的耗时的操作如下:
- 网络请求
- 文件读写
- 创建、删除、更新数据库中的内容
- SharedPreferences读写
- 图片处理
- 文本解析
对耗时任务其实并没有一个清晰固定的定义,但是只要应用中出现了卡顿的情况,比如按钮反应慢,动画卡壳,这些都算是
耗时任务。特别是,在UI线程中对动画效果的计算比点击按钮更敏感
运行环境
一个Android应用中的线程使用java.lang.Thread来表示。它是Android中最基础的运行环境。一个线程存活的时间取决于任务
的长短。线程支持实现了java.lang.Runnable接口的任务的执行。
private class MyTask implements Runnable{
public void run(){
int i = 0; // Stored on the thread local task
}
}
run()方法中的所有本地变量都将会被存储在这个线程的内存栈中。当初始化和开始了一个线程之后,这个任务就开始执行
了。
Thread myThread = new Thread(new MyTask()); myThread.start();
在操作系统层面,一个线程有一个指令指针和一个栈指针。指令指针指向下一条要执行的指令,栈指针指向了一块私有的内存区域——对其他线程是不可用的——这里存储了线程的本地数据。线程本地数据从代码上来看,其实就是定义在run方法里的变量。
CPU在某一刻只能执行从一个线程中执行代码,但是一个系统通常有多个要求同时执行的线程,比如一个同时运行多个应用的系统。从用户的感受来说,应用可以并行执行,CPU必须将他的处理时间在多个应用线程之间进行分享。调度器用来处理对CPU执行时间的分配。调度策略可以有多种实现方式,但是它主要是基于线程优先级:具有较高优先级的线程会先于较低
优先级的线程获得CPU。在java中,线程的优先级被设置为1——10,但是,如果不明确设定优先级,默认的优先级是5
myThread.setPriority(8);
然而,如果调度过程只依赖于优先级的高低,那么优先级较低的线程可能无法获得足够的运行时间来完成任务。因此,调度器也应该在改变执行线程的时候将每个线程的运行时间考虑在内。运行线程的切换被称为contextswitch(上下文切换)。当运
行中的线程的状态被存储完成后,context switch开始,这使得随后可以恢复这个线程的状态,然后,这个线程进入wait(等
待/挂起)状态。接下来调度器将会恢复一个等待中的线程开始执行。
单线程应用
每个应用至少有一个线程。如果没有其他的线程,那么所有的代码都会运行在同一个执行序列中,每条指令都必须等待在它之前的所有指令都运行完成后,它自己才能运行。
优点:
- 单线程执行环境是一种比较简单的编程模型。
缺点:
- 但是在很多情况下,单线程模型并不能满足需要,因为某些指令的执行时机可能会由于之前的指令而被明显的推迟,即使这些指令的执行并不依赖与前面的指令。降低了应用的性能和响应性。
解决方案:
- 为了解决这些问题,应用需要把代码查分到多个执行路径中,也就是多个线程。
多线程应用
- 在多线程环境中,应用程序代码可以被拆分到多个执行路径中,所以看起来好像多个操作在同时运行。
- 如果需要执行的线程数量超出了可执行的数量,真正的并发不会被实现,但是,调度器可以迅速地在不同线程之间进行切换,以至于每个代码路径都可以分到一定的执行时间。
- 多线程是必须的,提高性能的同时也带来了一些问题——编程复杂性提高,内存消耗增大,不确定的程序运行顺序。资源消耗增大
- 每个线程需要拥有一块私有的内存空间,主要用来存储自己的本地变量,
多线程的方式
- 当我们启动一个App的时候,Android系统会启动一个Linux Process,该Process包含一个Thread,称为UI Thread或Main Thread。通常一个应用的所有组件都运行在这一个Process中,当一个组件在启动的时候,如果该process已经存在了,那么该组件就直接通过这个process被启动起来,并且运行在这个process的UI Thread中。
- UI Thread中运行着许多重要的逻辑,如系统事件处理,用户输入事件处理,UI绘制,Service,Alarm等,如下图:
Android提供常见的操作多线程的方式
1. Handler+Thread
2. AsyncTask
3. Rxjava
各个方式的优缺点:
handler sendMessage用法
图解
优缺点:
1. Handler用法简单明了,可以将多个异步任务更新UI的代码放在一起,清晰明了
2. 处理单个异步任务代码略显多
适用范围:
多个异步任务的更新UI
AsyncTask
优缺点:
1. 处理单个异步任务简单,可以获取到异步任务的进度
2. 可以通过cancel方法取消还没执行完的AsyncTask
3. 处理多个异步任务代码显得较多
适用范围:
单个异步任务的处理
ThreadPoolExecutor
Executors提供了四种创建ExecutorService的方法,他们的使用场景如下:
1. Executors.newCachedThreadPool()
创建一个定长的线程池,每提交一个任务就创建一个线程,直到达到池的最大长度,这时线程池会保持长度不再变化
2. Executors.newFixedThreadPool()
创建一个可缓存的线程池,如果当前线程池的长度超过了处理的需要时,它可以灵活的回收空闲的线程,当需要增加时,
它可以灵活的添加新的线程,而不会对池的长度作任何限制
3. Executors.newScheduledThreadPool()
创建一个定长的线程池,而且支持定时的以及周期性的任务执行,类似于Timer
4. Executors.newSingleThreadExecutor()
创建一个单线程化的executor,它只创建唯一的worker线程来执行任务
适用范围
批处理任务
Rxjava
1. Rx的全称:
Reactive Extensions(响应式扩展)。Rx基于观察者模式,目的是提供一致的编程接口,帮助开发者更方便的处理异步数据流。ReactiveX结合了观察者模式、迭代器模式和函数式编程的精华。而Rxjava是针对java语言的一个异步的响应式编程库。
2. 怎么使用Rxjava
1863579-b959455ac1f04ec3.png
基础:
- Rxjava最核心的两个东西Observables(被观察者,事件源)和Subscribers(观察者)。Observables发出一系列事件,Subscribers处理这些事情。这些事情可以是触摸事件、web接口调用数据...
- 一个Observable可以发出零个或者多个事件,直到结束或出错,没发出一个事件,就会调用Subscriber的
onNext()
方法, - Rxjava看起来像设计模式中的观察者模式,但有一点不同,如果一个Observable没有任何的Subscriber,那么这个Observable就不会发出任何事件。
- Hello World
Observable<String> observable=Observable.create(new Observable.OnSubscribe<String>() {
@Override
public void call(Subscriber<? super String> subscriber) {
subscriber.onNext("Hello World");
subscriber.onCompleted();
}
});
Subscriber<String> subscriber=new Subscriber<String>() {
@Override
public void onCompleted() {
}
@Override
public void onError(Throwable e) {
}
@Override
public void onNext(String s) {
System.out.print(s);
}
};
observable.subscribe(subscriber);
}
lambda可以使代码变得简洁: > ```Observable.just("Hello World")
.subscribe(s->System.out.println(s));
`
1863579-433c53337adbaf31.png
操作符(operators)
- 操作符就是为了解决对Observable对象的变换的问题,操作符用于在Observable和最终的Subscriber之间修改Observable发出的事件。RxJava提供了很多很有用的操作符。
比如map操作符,就是用来把把一个事件转换为另一个事件的。
Observable.just("Hello, world!")
.map(new Func1<String, String>() { //string->string
@Override
public String call(String s) {
return s + " -Dan";
}
})
.subscribe(s -> System.out.println(s));
Observable.just("Hello World")
.map(s -> s.hashCode()) //string -> integer
.subscribe(s->System.out.println(Integer.toString(s)));
操作符
假如有这样一个方法:
根据输入的字符返回一个网站的url列表类似于搜索引擎
public static Observable<List<String>> query(String s){
List<String> urls=new ArrayList<>();
urls.add("www");
urls.add("http");
urls.add("https");
Observable<List<String>> observable=Observable.create(new Observable.OnSubscribe<List<String>>() {
@Override
public void call(Subscriber<? super List<String>> subscriber) {
subscriber.onNext(urls);
}
});
return observable;
}
query(u).subscribe(urls-> {
for (String url:urls)
System.out.println(url);
});
query(u).subscribe(urls->{
Observable.from(urls)
.subscribe(url->System.out.println(url));
});
Observable.from()方法他接收一个集合作为输入,然后每次输出一个元素给subscriber;(Observable.from也是Observable(被观察者对象))
query(u).subscribe(urls->{
Observable.from(urls)
.subscribe(url->System.out.println(url));
});/*虽然去掉了for each循环,但是代码依然看起来很乱。多个嵌套的subscription不仅看起来很丑,难以修改,更严重的是它会破坏某些RxJava的特性。*/
改进——flatMap()
Observable.flatMap()接收一个Observable的输出作为输入,输出另一个Observable。
query("Hello World")
.flatMap(new Func1<List<String>, Observable<String>>() {
@Override
public Observable<String> call(List<String> list) {
return Observable.from(list);
}
}).subscribe(url->System.out.println(url));
lambda简化代码
query("s")
.flatMap(urls->Observable.from(urls))
.subscribe(url->System.out.println(url));
flatMap()也可以返回任何它想返回的对象
丰富的操作符
- filter过滤
- take()输出最多指定数量的结果
- doOnNext()允许我们在每次输出一个元素之前做一些额外的事情
代码实例:
query("Hello, world!")
.flatMap(urls -> Observable.from(urls))
.flatMap(url -> getTitle(url))
.filter(title -> title != null)
.take(5)
.doOnNext(title -> saveTitle(title))
.subscribe(title -> System.out.println(title));
上述示例的功能过滤掉返回的title列表种null的值
输出最多5个结果
在println之前把每个标题保存到磁盘上
错误处理
-
onComplete()
和onError()
函数 - 伪代码
Observable.just("Hello, world!")
.map(s -> potentialException(s))
.map(s -> anotherPotentialException(s))
.subscribe(new Subscriber<String>() {
@Override
public void onNext(String s) { System.out.println(s); }
@Override
public void onCompleted() { System.out.println("Completed!"); }
@Override
public void onError(Throwable e) { System.out.println("error!"); }
});
代码中的potentialException() 和 anotherPotentialException()有可能会抛出异常。每一个Observerable对象在终结的时候都会调用onCompleted()或者onError()方法,所以Demo中会打印”Completed!”或者"error!”。
- 优点:
- 只要有异常发生onError()一定会被调用这极大的简化了错误处理。只需要在一个地方处理错误即可以。
- 操作符不需要处理异常将异常处理交给订阅者来做,Observerable的操作符调用链中一旦有一个抛出了异常,就会直接执行onError()方法。
- 你能够知道什么时候订阅者已经接收了全部的数据。
调度器
多线程的实现——subscribeOn()指定观察者代码运行的线程,使用observerOn()指定订阅者运行的线程:
myObservableServices.retrieveImage(url)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(bitmap -> myImageView.setImageBitmap(bitmap));
1863579-9b47a29c4e93f43b.png
多种线程:
• Schedulers.immediate,默认,当前线程,立即执行
• Schedulers.trampoline,当前线程,要等其他任务先执行
• Schedulers.newThread,启用一个新线程
• Schedulers.io,擅长读写文件、数据库、网络信息,一个无数量上限的线程池
• Schedulers.computation,擅长cpu密集型计算,固定线程池,不要做io操作,会浪费cpu
• AndroidSchedulers.mainThread,Android主线程
相比其他多线程的方式,rxjava只需要添加两个操作符其他代码保持不变就可以实现多线程。而AsyncTask等需要找出需要并发的部分
灵活地切换线程
1863579-ab448f99561e79c5.png•subscribeOn只能定义一次,除非是在定义doOnSubscribe
•observeOn可以定义多次,决定后续代码所在的线程
订阅(subscriptions)
Observable.subscribe(),会返回一个SubScription对象,代表了被观察者和订阅者之间的联系。
在后面可以通过调用unsubscribe()
取消订阅,从而达到避免内存泄露的目的
Subscription s = query("s")
.flatMap(urls -> Observable.from(urls))
.subscribe(url -> System.out.println(url));
//ctrl+Alt+V快速得到返回对象
s.unsubscribe();//取消订阅
Android中使用Rxjava——RxAndroid
AndroidSchedulers提供了针对Android的线程系统的调度器
在UI线程执行某些代码只需要调用AndroidSchedulers.mainThread();
网友评论