1.Handler(适用于多个异步任务的更新UI)

        采用生产者-消费者模型,Handler就是生产者,通过他可以生产需要执行的任务,Looper就是消费者,不断从MessageQueue中取出message进行消费。

异步通信机制,将工作线程中需更新UI的操作信息 传递到 UI主线程,从而实现 工作线程对UI的更新处理,最终实现异步消息的处理。Handler不仅仅能将子线程的数据传递给主线程,它能实现任意两个线程的数据传递。

(1)Message

Message 可以在线程之间传递消息。可以在它的内部携带少量数据,用于在不同线程之间进行数据交换。除了 what 字段,还可以使用 arg1 和 arg2 来携带整型数据,使用 obj 来携带 Object 数据。

(2) Handler

Handler 作为处理中心,用于发送(sendMessage 系列方法)与处理消息(handleMessage 方法)。

(3) MessageQueue

MessageQueue 用于存放所有通过 Handler 发送的消息。这部分消息会一直存放在消息队列中,直到被处理。每个线程中只会有一个 MessageQueue 对象

(4) Looper

Looper 用于管理 MessageQueue 队列,Looper对象通过loop()方法开启了一个死循环——for (;;){},不断地从looper内的MessageQueue中取出Message,并传递到 Handler 的 handleMessage() 方法中。每个线程中只会有一个 Looper 对象。

1.1 入队过程

(1)Handler对外提供了两种方式,post和sendMessage以及这两种方法对应的Delayed方法,

无论是post还是sendMessage都会调用sendMessageDelayed

而post是通过getPostMessage(r)将Runnable包装成一个Message对象

这个包装出来的 Message 将 callback 设置为了对应的 Runnable。

public final boolean post(@NonNull Runnable r) {return  sendMessageDelayed(getPostMessage(r), 0);
}public final boolean sendMessage(@NonNull Message msg) {return sendMessageDelayed(msg, 0);
}private static Message getPostMessage(Runnable r) {Message m = Message.obtain();m.callback = r;return m;
}

(2)sendMessageDelayed中,调用sendMessageAtTime

根据的时间是:

SystemClock.uptimeMillis() + delayMillis

指系统从开机到现在的时间,是一个相对时间。

public final boolean sendMessageDelayed(Message msg, long delayMillis)
{if (delayMillis < 0) {delayMillis = 0;}return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {MessageQueue queue = mQueue;if (queue == null) {RuntimeException e = new RuntimeException(this + " sendMessageAtTime() called with no mQueue");Log.w("Looper", e.getMessage(), e);return false;}return enqueueMessage(queue, msg, uptimeMillis);
}private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {msg.target = this;if (mAsynchronous) {msg.setAsynchronous(true);}return queue.enqueueMessage(msg, uptimeMillis);
}

(3)通过mQueue(mQueue 是在Handler构造函数中通过looper拿到的,mQueue = looper.mQueue;)拿到MessageQueue对象,然后调用enqueueMessage。

最后实际上是调用到了 MessageQueue 的 enqueueMessage 方法将这个消息传

入了 MessageQueue。

(4)以单链表的结构将Message入队

boolean enqueueMessage(Message msg, long when) {......synchronized (this) {......if (p == null || when == 0 || when < p.when) {// New head, wake up the event queue if blocked.msg.next = p;mMessages = msg;needWake = mBlocked;} else {......}// We can assume mPtr != 0 because mQuitting is false.if (needWake) {nativeWake(mPtr);}}return true;}

1.2 出队

使用空的 for 循环不断消费消息,通过Message的target发送到正确的线程和Handler

Message next() {......for (;;) {......nativePollOnce(ptr, nextPollTimeoutMillis);synchronized (this) {// Got a message.mBlocked = false;if (prevMsg != null) {prevMsg.next = msg.next;} else {mMessages = msg.next;}msg.next = null;if (DEBUG) Log.v(TAG, "Returning message: " + msg);msg.markInUse();return msg;}......}}

1.3 Looper休眠机制

Looper不会一直消耗系统资,当Looper的MessageQueue中没有消息时,或者定时消息没到执行时间时,当前持有Looper的线程就会进入阻塞状态。

阻塞实现

在消息出队的时候——enqueueMessage

Binder.flushPendingCommands();——进入阻塞之前跟内核线程发送消息,防止长时间持有某个对象

nativePollOnce(ptr, nextPollTimeoutMillis);——阻塞当前线程,进入休眠

唤醒:

在消息入队的时候——next

nativeWake(mPtr)——唤醒线程

1.4 两个子线程使用Handler通信

在UI线程创建的Handler默认可以使用looper,不需要调用Looper.parper。

两个子线程通信步骤:

1.在子线程A手动开启Looper.prepare()

2.在子线程A创建Handler

3.在子线程A手动开启循环Looper.loop()

4.在另一个线程使用这个Handler对象发送消息

Thread threadA  = new Thread(new Runnable() {@Overridepublic void run() {Looper.prepare();testHandler = new TestHandler(MainActivity.this);Looper.loop();}
});
threadA.start();Thread threadB  = new Thread(new Runnable() {@Overridepublic void run() {Message message = new Message();message.what = HANDLER_MESSAGE_TYPE1;message.obj = "hello";testHandler.sendMessage(message);}
});
threadB.start();/*** 静态内部类Handler*/
private static class TestHandler extends Handler {private final WeakReference<MainActivity> mActivity;public TestHandler(MainActivity activity) {this.mActivity = new WeakReference<>(activity);}@Overridepublic void handleMessage(@NonNull Message msg) {MainActivity activity = mActivity.get();if (activity!=null) {switch (msg.what) {case HANDLER_MESSAGE_TYPE1:Toast.makeText(activity, msg.obj.toString(), Toast.LENGTH_SHORT).show();break;default:break;}}}
}

2.AsyncTask(适用于单个异步任务的处理)

AsyncTask 是一种轻量级的任务异步类,可以在后台子线程执行任务,且将执行进度及执行结果传递给 UI 线程。

(1)onPreExecute()

在 UI 线程上工作,在任务执行 doInBackground() 之前调用。此步骤通常用于设置任务,例如在用户界面中显示进度条。

(2)doInBackground(Params... params)

在子线程中工作,在 onPreExecute() 方法结束后执行,这一步被用于在后台执行长时间的任务,Params 参数通过 execute(Params) 方法被传递到此方法中。任务执行结束后,将结果传递给 onPostExecute(Result) 方法,同时我们可以通过 publishProgress(Progress) 方法,将执行进度发送给 onProgressUpdate(Progress) 方法。

(3)onProgressUpdate(Progress... values)

在 UI 线程上工作,会在 doInBackground() 中调用 publishProgress(Progress) 方法后执行,此方法用于在后台计算仍在执行时(也就是 doInBackgound() 还在执行时)将计算执行进度通过 UI 显示出来。例如,可以通过动画进度条或显示文本字段中的日志,从而方便用户知道后台任务执行的进度。

(4)onPostExecute(Result result)

在 UI 线程上工作,在任务执行完毕(即 doInBackground(Result) 执行完毕)并将执行结果传过来的时候工作。

使用规则:

(1)AsyncTask 是个抽象类,所以要创建它的子类实现抽象方法

(1)AsyncTask 类必须是在 UI 线程中被加载,但在Android 4.1(API 16)开始,就能被自动加载完成。

(2)AsyncTask 类的实例对象必须在 UI 线程中被创建。

(3)execute() 方法必须是在 UI 线程中被调用。

(4)不要手动调用方法 onPreExecute()、onPostExecute()、doInBackground()、onProgressUpdate()

(5)任务只能执行一次(如果尝试第二次执行,将抛出异常)。即一个AsyncTask对象只能调用一次execute()方法。

原理:

其源码中原理还是 Thread 与 Handler 的实现,其包含 两个线程池,一个 Handler,如下所示:

名称

类型

作用

SERIAL_EXECUTOR

线程池

分发任务,串行分发,一次只分发一个任务

THREAD_POOL_EXECUTOR

线程池

执行任务,并行执行,执行的任务由 SERIAL_EXECUTOR 分发

InternalHandler

Handler

负责子线程与主线程的沟通,通知主线程做 UI 工作

3.ThreadPoolExector(适用于批处理任务)

一方面减少了每个并行任务独自建立线程的开销,另一方面可以管理多个并发线程的公共资源,从而提高了多线程的效率。所以ThreadPoolExecutor比较适合一组任务的执行。Executors利用工厂模式对ThreadPoolExecutor进行了封装。

Executors提供了四种创建ExecutorService的方法,他们的使用场景如下:

1. Executors.newFixedThreadPool()

创建一个定长的线程池,每提交一个任务就创建一个线程,直到达到池的最大长度,这时线程池会保持长度不再变化。

当线程处于空闲状态时,它们并不会被回收,除非线程池被关闭。当所有的线程都处于活动状态时,新任务都会处于等待状态,直到有线程空闲出来。

只有核心线程并且不会被回收,能够更加快速的响应外界的请求。

2. Executors.newCachedThreadPool()

创建一个可缓存的线程池,如果当前线程池的长度超过了处理的需要时,它可以灵活的回收空闲的线程,当需要增加时,它可以灵活的添加新的线程,而不会对池的长度作任何限制

线程数量不定的线程池,只有非核心线程,最大线程数为 Integer.MAX_VALUE。当线程池中的线程都处于活动状态时,线程池会创建新的线程来处理新任务,否则利用空闲的线程来处理新任务。线程池中的空闲线程具有超时机制,为 60s。

任务队列相当于一个空集合,导致任何任务都会立即被执行,适合执行大量耗时较少的任务。当整个线程池都处于限制状态时,线程池中的线程都会超时而被停止。

3. Executors.newScheduledThreadPool()

创建一个定长的线程池,而且支持定时的以及周期性的任务执行,类似于Timer。

非核心线程数没有限制,并且非核心线程闲置的时候立即回收,主要用于执行定时任务和具有固定周期的重复任务。

4. Executors.newSingleThreadExecutor()

创建一个单线程化的executor,它只创建唯一的worker线程来执行任务

只有一个核心线程,保证所有的任务都在一个线程中顺序执行,意义在于不需要处理线程同步的问题。

4. IntentService(适用于一个可以处理异步任务的简单Service)

一般用于执行后台耗时任务,当任务执行完成会自动停止;同时由于它是一个服务,优先级要远远高于线程,更不容易被系统杀死,因此比较适合执行一些高优先级的后台任务。

使用步骤:创建IntentService的子类,重写onHandleIntent方法,在onHandleIntent中执行耗时任务

原理:在源码实现上,IntentService封装了HandlerThread和Handler。onHandleIntent方法结束后会调用IntentService的stopSelf(int startId)方法尝试停止服务。

IntentService的内部是通过消息的方式请求HandlerThread执行任务,HandlerThread内部又是一种使用Handler的Thread,这就意味着IntentService和Looper一样是顺序执行后台任务的

(HandlerThread:封装了Handler + ThreadHandlerThread适合在有需要一个工作线程(非UI线程)+任务的等待队列的形式,优点是不会有堵塞,减少了对性能的消耗,缺点是不能同时进行多个任务的处理,需要等待进行处理。处理效率低,可以当成一个轻量级的线程池来用)

Android多线程的四种方式:Handler、AsyncTask、ThreadPoolExector、IntentService相关推荐

  1. android java 多线程,Android多线程的四种方式

    当我们启动一个App的时候,Android系统会启动一个Linux Process,该Process包含一个Thread,称为UI Thread或Main Thread.通常一个应用的所有组件都运行在 ...

  2. Java 实现多线程的四种方式 超详细

    Java 实现多线程的四种方式 文章目录 Java 实现多线程的四种方式 一.继承 Thread 类 二.实现 Runnable 接口 三.实现 Callable 接口 四.线程池 1,Executo ...

  3. java创建多线程的四种方式

    java多线程的创建方式是面试经常会被问到的一个问题,因此在这里我对java创建多线程的四种方式做一个简单的归纳与总结,便于复习. 一.继承Thread类创建多线程 ① 创建一个继承于Thread类的 ...

  4. 创建多线程的四种方式

    创建多线程的四种方式 方式一:继承于Thread类 创建一个继承于Thread类的子类 重写Thread类的run()->将此线程执行的操作声明在run()中 创建Thread类的子类的对象 通 ...

  5. Java 实现多线程的四种方式

    在 Java 中实现多线程一共有四种方式: 继承 Thread 类 实现 Runnable 接口 实现 Callable 接口 线程池 下面我将对这四种方式进行入门级的解析和演示. 一.继承 Thre ...

  6. java实现多线程抢单_JAVA实现多线程的四种方式

    JAVA多线程实现方式: 1.继承Thread类(无返回值) 2.实现Runnable接口(无返回值) 3.实现Callable接口,通过FutureTask包装器来创建Threak线程(有返回值) ...

  7. android:launchMode的四种方式

    Activity一共有以下四种launchMode: standard singleTop singleTask singleInstance 1.standard standard模式是默认的启动模 ...

  8. 创建多线程的几种方式

    Java中多线程的创建有几种方式? 答:四种. 创建多线程的四种方式: 继承Thread类,实现Runnable接口,jdk5.0以后又增加了两种方式,实现Callable接口和使用线程池. 方式一: ...

  9. android+定时器+动画,Android 实现定时器的四种方式总结及实现实例_Android_脚本之家...

    Android中实现定时器的四种方式 第一种方式利用Timer和TimerTask 1.继承关系 java.util.Timer 基本方法 schedule 例如: timer.schedule(ta ...

最新文章

  1. 网站访问慢解决思路详细图解
  2. 解决Linux中使用google chrome浏览器出现:ERR_PROXY_CONNECTION_FAILED 代理错误,导致不能够上网
  3. VS2013安装OpenCV4.1版本并搭建一个小程序
  4. Python学习:模块
  5. PL0编译器TurboPascal版再现时间:2009-07-20 17:24:49来源:网络 作者:未知 点击:52次
  6. 完成一个分析H264码流的工具
  7. 总结关于 Vue 框架面试题
  8. ES6语法实现数据的双向绑定
  9. 一文读懂APU/BPU/CPU/DPU/EPU/FPU/GPU等处理器
  10. js内置对象方法笔记
  11. THREE实战2_正交投影相机与透视相机
  12. c语言删除字符串中特定字符串,C语言删除字符串中指定字符的例子
  13. 将pdf转成图片时,文字没法显示
  14. 【分享】超级菜鸟另类玩swf反汇编
  15. arduino硬件虚拟键盘中对应键盘上的按键整理
  16. 干扰抑制 空时联合 matlab程序,空时联合自适应天线抗干扰的研究
  17. 生成式模型(VAE+GAN)
  18. HaaS轻应用(JavaScript)低功耗蓝牙案例
  19. 求生之路服务器参数配置
  20. c语言中换行符与回车符的区别,C语言中换行符与回车符的区别

热门文章

  1. Python解释器的下载与安装教程(Win11)
  2. 斯坦福大学深度学习开言
  3. C# 以GB2312编码保存数据
  4. 解决 Ubuntu 11.10 在 RTL8111/8168B 网卡下速度慢的问题
  5. 设计模式之----Java动态代理模式
  6. 可以永久清除您的隐私——MacCleanse for Mac v8.0特别版垃圾清理软件!
  7. Zabbix实现监控Kubernetes
  8. 用计算机一级考试考的照片要求,2020年全国计算机等级考试报名照片有哪些要求?【附各省标准】...
  9. 工业视觉系统相关知识和选型介绍(一):相机篇
  10. 终于有人总结了图神经网络!