苏格拉底曾说过:“学会了多线程,你就学会了压榨CPU,就好像资本家对无产阶级作的那事同样。”html
多线程是开发人员必不可少的技术点,也是初学者不太容易掌握好的一个难点。要想设计出优秀的程序,那必然须要合理的线程调度。今天就给你们细说下Android中与多线程相关的知识点,揭开多线程神秘的面纱。git
本篇文章仅介绍多线程的各类实现方式,不过多涉及深刻的基础原理探究,达到“所见即所学,所学便可用”的效果。关于各类多线程原理的深刻探究,有机会放在后面的专栏逐一介绍。编程
按照操做系统中的描述,线程是CPU调度的最小单元,同时线程是一种有限的系统资源。而进程通常指一个执行单元,在PC和移动设备上指一个程序或者一个应用。一个进程能够包含多个线程。bash
简单点理解,一个Android APP就是一个进程,一个APP里面有多个线程,咱们多线程编程的意义就是实现“一个APP多个线程”。网络
有杠精可能会问,那我可不能够一个APP多个进程?又可不能够一个进程只有一个线程?多线程
我告诉你,能够,均可以。并发
单线程的APP只包括Android的UI线程也是能运行的;一个APP多个进程也是能够达到的,实现方式涉及到Android的IPC机制,这里不细说。异步
这里杠精可能会说,那你单线程也能跑,我为啥还要整多线程?ide
我告诉你,首先这句话从Android开发的角度来说,近似于一个假命题。由于谷歌爸爸如今强制规定了不能在UI线程进行耗时操做,必须放到子线程里面去,除非你的程序不涉及耗时操做。究其缘由,是由于在UI线程进行耗时操做的话,给用户的使用体验就是界面“卡顿”。同时,若是UI线程被阻塞超过必定时间会触发ANR(Application Not Responding)错误。oop
从底层的角度来说,多线程可使得整个环境可以异步执行,这有助于防止浪费CPU时钟周期从而提升效率。换言之,多线程能更充分的利用CPU资源,从而提升程序的运行效率。
要想定义一个线程只须要新建一个类继承自Thread,而后重写父类的run方法便可
class MyThread extends Thread {
@Override
public void run() {
doSomething();
}
}
//在须要的时候启动线程
new MyThread().start();
复制代码
优化一下?
咱们能够不必继承整个Thread类,只实现Runnable接口就行了
class MyThread implements Runnable {
@Override
public void run() {
doSomething()
}
}
//启动线程
MyThread myThread = new MyThread();
new Thread(myThread).start();
复制代码
那我不想专门再写一个线程类怎么办?可使用匿名类
new Thread(new Runnable() {
@Override
public void run() {
doSomething();
}
}).start();
复制代码
既然我都会用Runnable接口来建立线程了,还要线程池干啥?其实否则,随意建立线程的操做在实际开发中是极为不推荐的。为啥?由于线程也是一种资源,反复的建立和销毁线程会带来必定性能上的额外开销。与其相比,线程池主要有如下几个优势:
一个完整的线程池应该有这么几个组成部分
当咱们经过线程池执行异步任务的时候,实际上是依次进行了下面的流程
下面手搓一个线程池的实现
//CPU核心数
private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
//核心线程数
private static final int CORE_POOL_SIZE = CPU_COUNT + 1;
//最大线程数
private static final int MAX_POOL_SIZE = CPU_COUNT * 2 + 1;
//非核心线程闲置的超时时间
private static final int KEEP_ALIVE_TIME = 1;
//任务队列
private static final BlockingQueue<Runnable> sPoolWorkQueue =
new LinkedBlockingQueue<Runnable>(128);
//线程池
private ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(CORE_POOL_SIZE,
MAX_POOL_SIZE, KEEP_ALIVE_TIME, TimeUnit.SECONDS, sPoolWorkQueue);
private void fun(){
Runnable runnable = new Runnable() {
@Override
public void run() {
//子线程处理耗时操做
doSomething();
}
};
poolExecutor.execute(runnable);
}
复制代码
这样咱们就实现了一个简单的线程池,核心线程数为CPU数量+1,非核心线程数为CPU数量*2+1,非核心线程的闲置时间为1秒,任务队列的大小为128。
线程池还有具体的好几种分类和相应不一样的实现方式,这里再也不细说。
有朋友可能会说,你讲的这些都是Java多线程里面的东西,能不能整点咱Android特有的?OK,如今进入专业时间。
Handler是Android提供的一种异步消息处理机制,要学会使用Handler咱们首先来了解下消息处理四兄弟:
Handler能够帮助咱们实如今不一样的线程之间传递消息,这里的Message就是消息本体,也就是咱们想要传递的那个东西。
Handler在这里扮演的角色是消息处理者,它的主要做用是发送和处理消息。
MessageQueue是一个消息队列,Handler发送过来的消息会放在这个队列里面,每一个线程只会有一个MessageQueue对象。
Looper是线程中消息队列的管家,它会无限循环运行,每发现MessageQueue中存在一条消息,它就会把消息取出而后发送给Handler。每个线程也只能有一个Looper对象。
好了,基本原理已经了解,如今咱们来反手搓一个Handler
private static final int FLAG = 1;
private Handler mHandler = new Handler(){
@Override
public void handleMessage(@NonNull Message msg) {
if (FLAG == msg.what){
//这里已经回到主线程了
doSomething();
}
}
};
private void fun(){
new Thread(new Runnable() {
@Override
public void run() {
//子线程发送消息
Message message = new Message();
message.what = FLAG;
mHandler.sendMessage(message);
}
}).start();
}
复制代码
除了Handler之外,谷歌爸爸还给咱们提供AsyncTask来进行线程的切换。AsyncTask是一种轻量级的异步任务,它能够在线程池中执行后台任务,而后把执行的进度和最终结果传递给主线程。从实现原理上来说,AsyncTask是对Thread和Handle的再次封装。
AsyncTask自己是一个抽象的泛型类,有四个亲儿子:
最早执行的是方法是onPreExecute()方法,位于主线程中,通常用来作一些准备工做。
而后执行doInBackground()方法,位于线程池中,用来执行异步任务,params表示异步任务的输入参数。这个方法须要返回结果给onPostExecute()方法。
onProgressUpdate()方法在主线程中执行,当后台任务的执行进度发生变化时这个方法会被调用。
onPostExecute()方法在最后异步任务完成以后会被调用,位于主线程中,result参数是后台任务的返回值,即doInBackground()的返回值。
OK,基本原理已经了解了,如今咱们来手搓一个AsyncTask
class DownloadTask extends AsyncTask<Void,Integer,Boolean> {
@Override
protected void onPreExecute() {
//这里咱们使用了一个显示进度的Dialog,具体实现不表
progressDialog.show();
}
@Override
protected Boolean doInBackground(Void... voids) {
try {
while (true){
//调用咱们的doDownload下载方法,具体实现不表
int downloadPercent = doDownload();
//使用publishProgress方法来更新执行的进度
publishProgress(downloadPercent);
if (downloadPercent >= 100)
break;
}
} catch (Exception e) {
e.printStackTrace();
}
return true;
}
@Override
protected void onProgressUpdate(Integer... values) {
//更新下载进度
progressDialog.setMessage("Download "+values[0]+"%");
}
@Override
protected void onPostExecute(Boolean aBoolean) {
//下载完成
progressDialog.dismiss();
}
}
复制代码
这里咱们建立了一个Download类继承自AsyncTask,有三个泛型,void表示不须要给后台任务传入参数,Integer表示用整数类型来做为进度显示的单位,Boolean表示用布尔类型来反馈后台任务的执行结果。
要让咱们的这个AsyncTask跑起来也很简单,只须要执行:
new DownloadTask().execute();
IntentService是一种特殊的Service,它继承了Service而且是一个抽象类,咱们能够建立它的子类来使用。IntentService也能够用于执行后台的耗时任务,而且当任务执行完毕以后它会自动中止。
IntentService由于是服务的缘由,因此和单纯的线程相比它的优先级要高不少,从而更不容易被系统杀死。
IntentService的内部实现是封装了HandlerThread和Handler,使用的话要遵循Service的使用方法,这里先略事后面有机会在Service的专栏里面再详细介绍。
有杠精可能会说,你讲的这些方法,一个比一个长,一个比一个复杂,就不能整个简单又粗暴的东西?
这个时候就须要祭出神兵利器RxJava了。
其实网络上RxJava的入门文章多如过江之鲫,这里不打算过多的深刻介绍。RxJava是一种响应式编程,你们不是很明白的话能够粗暴的理解为更优雅的多线程实现便可。
先手搓一个RxJava的普通实现方式
private void fun(){
Observable<Integer> observable = Observable.create(new ObservableOnSubscribe<Integer>() {
@Override
public void subscribe(ObservableEmitter<Integer> emitter) throws Exception {
emitter.onNext(1);
}
});
observable.subscribeOn(Schedulers.io()) //表示在io线程执行订阅
.observeOn(AndroidSchedulers.mainThread()) //表示在主线程接收订阅
.subscribe(new Observer<Integer>() {
@Override
public void onSubscribe(Disposable d) {
//接收订阅以前调用
}
@Override
public void onNext(Integer integer) {
//接收订阅成功调用
doSomething();
}
@Override
public void onError(Throwable e) {
//接收订阅出错调用
}
@Override
public void onComplete() {
//接收订阅完成调用
}
});
}
复制代码
emmmmm看起来好像仍是挺复杂的啊,能不能再整简单点?
OK,链式调用加lambda安排上
private void fun() {
Observable.create(emitter -> emitter.onNext(1))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(integer -> {
//接收订阅成功
doSomething();
}, throwable -> {});
}
复制代码
嗯......有内味了。
这串代码咱们是发送了一个Integer类型的数据;
subscribeOn()指定了咱们发送的线程是在后台的io线程,就能够理解为一个子线程;
observeOn指定了咱们接收的线程为主线程;
subscribe只接收成功的消息,至关于上面的OnNext()方法,本质上是咱们在这里建立了一个Comsumer对象来接收;
throwable在接收失败的时候调用,至关于上面的onError()方法。
RxJava有多达几十种的操做符,灵活运用能实现各类不一样的异步任务,这里就再也不花大量的篇幅详细介绍了,有兴趣的朋友能够去查看ReactiveX中文文档
RxKotlin能够理解为RxJava在Kotlin上的一个变种,原理都是同样的,只是操做语言变成了Kotlin,而后封装了一下使得能够更优雅的调用,这里给你们一个具体的实现案例,再也不过多讲解。
private fun test() {
Observable.create<Int> { 1 }
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribeBy(
onNext = {},
onError = {}
)
}
复制代码
协程其实和上面所说的线程并非一个概念,协程是什么?根据官方文档的描述,协程本质上是轻量级的线程。既然是轻量,那说明协程的资源消耗和性能等方面和线程比起来应该是有优点的。那这样看来咱们之前使用多线程实现的异步功能,如今基本上均可以用协程来替代了。
协程是一个全新的东西,介于篇幅这里就不展开讲解了,后面会专门写介绍协程的文章。
今天总结了Android平台上实现多线程的几种方式,但愿能给到须要的朋友一些帮助。
有不对之处还望各位同僚雅正。