安卓之异步消息处理机制
Handler
简介
- 为什么叫异步消息处理机制?
- 先来看同步消息机制,它是指发送方必须等待接收方处理完消息后才能继续执行,也就是顺序执行。在 Android 中,如果在主线程中执行耗时操作,就会导致主线程阻塞,应用无法响应用户交互,这就是同步阻塞的例子。因此,需要在子线程中执行耗时操作以避免主线程阻塞。而异步消息机制是指在发送和接收消息时,发送方和接收方不需要同时执行,它们之间是异步的,也就是并行执行。因此,我们需要在子线程中执行这些耗时操作,但是在执行完后需要将结果返回到主线程中更新 UI,这时就可以通过异步消息机制来实现。
- 不用它行不行?
- 如果你要在主线程中执行一些耗时操作可能会导致应用程序卡顿或崩溃,你可能会想到一个办法就是在子线程中执行这些操作,然后再通过回调机制将执行结果传递到主线程中进行处理,这样也没毛病。但是使用 Handler 有以下优势:
- ①Handler 内部已经帮我们实现了线程间通信的机制,我们只需要发送消息即可,很方便。
- ②Handler 可以进行消息的延时发送,定时发送等操作,非常灵活。
- ③Handler 可以通过消息队列实现消息的异步处理,避免了线程阻塞。
- ④Handler 可以通过 Looper 机制实现消息循环,处理消息的顺序非常有序。
- 如果你要在主线程中执行一些耗时操作可能会导致应用程序卡顿或崩溃,你可能会想到一个办法就是在子线程中执行这些操作,然后再通过回调机制将执行结果传递到主线程中进行处理,这样也没毛病。但是使用 Handler 有以下优势:
- 异步消息机制的实现方式有很多,其中比较常见的包括:
- 线程池:利用线程池中的线程来处理消息,避免了线程频繁创建和销毁的开销。
- Handler 和 Looper:Android 中经典的异步消息处理机制,发送方通过 Handler 发送消息,接收方通过 Looper 接收并处理消息。
- AsyncTask:Android 提供的一个异步任务框架,封装了线程池和 Handler,提供了方便的异步操作接口。
- RxJava:一个基于观察者模式的异步编程框架,提供了强大的异步操作能力和线程调度功能。
- Java NIO(非阻塞 I/O):提供了异步 I/O 操作,通过注册事件和回调机制实现异步通信。
- 线程池、Handler 都是基于线程实现的,而 AsyncTask 则是基于 Handler 实现的;而 RxJava 则是基于线程池和调度器的机制来实现异步任务的处理。
- 同步消息机制:
- CountDownLatch:计数器,等待某些操作完成后再继续执行。使用 await() 方法等待计数器变为 0,使用 countDown() 方法减少计数器的值。当计数器为 0 时,await() 方法将返回;
- CyclicBarrier:回环栅栏,等待一组线程全部完成后再继续执行。使用 await() 方法等待其他线程到达栅栏,当所有线程都到达栅栏时,栅栏将开放,所有线程可以继续执行;
- synchronized:同步锁,用于保护共享资源的访问。使用 synchronized 关键字锁定对象或方法,使得同一时刻只有一个线程可以访问共享资源。
组成
- 主要由4个部分组成:Message、Handler、MessageQueue、Looper。
- Message:
- 定义:它是在线程之间传递的信息。
- 作用:它可以携带少量信息,用于在不同线程之间传递数据。
- 常用参数:
- what:表示一个整数常量,用于标识消息类型。
- “arg1” 和 “arg2” :表示两个整数常量,用于传递消息的参数。
- obj:表示一个对象。
- Handler:
- 它负责接收并处理系统发来的消息。
- 常用方法:
- 发送消息:
- sendMessage(Message msg): 将消息发送到 MessageQueue 队列,等待处理;
- sendMessageAtTime(Message msg, long uptimeMillis): 将消息发送到 MessageQueue 队列,并指定处理时间;
- sendMessageDelayed(Message msg, long delayMillis): 将消息发送到 MessageQueue 队列,并延迟指定时间后处理。
- 处理消息:
- handleMessage(Message msg): 在 Handler 中处理消息的方法,需要子类进行实现。
- 其他方法:
- obtainMessage(): 获取一个空的 Message 对象;
- obtainMessage(int what): 获取一个 what 值为指定整数的 Message 对象;
- obtainMessage(int what, Object obj): 获取一个 what 和 obj 值的 Message 对象;
- obtainMessage(int what, int arg1, int arg2): 获取一个 what 和 arg1、arg2 值的 Message 对象;
- obtainMessage(int what, int arg1, int arg2, Object obj): 获取一个 what、arg1、arg2 和 obj 值的 Message 对象;
- post(Runnable r): 将 Runnable 对象发送到 MessageQueue 队列,等待处理;
- postDelayed(Runnable r, long delayMillis): 将 Runnable 对象发送到 MessageQueue 队列,并延迟指定时间后处理。
- 发送消息:
- MessageQueue:
- 它主要用于存放所有通过Handler发送的消息。
- 这些消息会一直存在于消息队列中,等待被处理。
- 每个线程只会有一个MessageQueue对象。
- Looper:
- Looper是每个线程中的MessageQueue的管家。
- 调用Looper.loop()后,就会进入一个无限循环中,每当MessageQueue中存在一条消息是,就会将它取出,并传递到Handler的handleMessage()中。
- 每个线程只会有一个Looper对象。
- Message:
使用步骤
- (1)创建一个Handler对象:在主线程中创建一个Handler对象,可以在Activity或者Fragment中创建。
-
// 在主线程中创建一个Handler对象 private Handler mHandler = new Handler();
-
- (2)实现Handler中的消息处理方法:在创建的Handler对象中实现消息处理方法,用于处理来自其他线程的消息。
-
// 实现消息处理方法 private Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { // 处理消息,更新UI等操作 } };
-
- (3)发送消息:在子线程中,通过Handler对象发送消息Message对象。这个消息会被添加到消息队列MessageQueue中等待被处理,Looper会一直尝试从MessageQueue中取出待处理的消息,最后分发回Handler的handleMessage()方法中。
-
// 在其他线程中发送消息 mHandler.sendEmptyMessage(MSG_WHAT);
-
- (4)处理消息:当Handler接收到消息时,调用消息处理方法中的代码进行处理。
内存泄露问题
- 一般都是使用静态内部类加上弱引用来实现,刚接触时一直没去仔细思考为什么要这样写?
- 拿Activity举例:
- 我们在Activity中创建一个Handler,这个Handler用来处理其他线程传来的消息或者处理自己线程的消息(不想写重复代码),像这样自定义地去处理消息就得写一个类去继承Handler,在Handler中,你大概率的会调用外部Activity类的方法,此时比如你用的是成员内部类,它会隐式地持有外部类的引用,你可以很方便的调用它的方法,但是这有一个问题,在Activity销毁时,Handler还持有Activity的引用,那么该Activity实例无法立即被回收,需要等待MessageQueue中的所有Message都被处理完成之后才能回收,这样就可能会导致内存泄漏问题。
- 其次,为了防止这种隐式地强引用,我们可以使用静态内部类,静态内部类并不会持有外部类的对象,同时也不能再直接调用外部类的成员方法,因此要想调用还得另想它法。我们可以把外部类的对象通过静态内部类的构造方法传入,再在内部创建一个Activity类型的变量引用它,这样不就可以了。但是这样直接传入再引用还是属于强引用,在Activity销毁时仍可能会发生内存泄漏,因此这里需要使用弱引用WeakReference持有它,这样即便Activity销毁时消息队列中还有消息,这个引用依然可以被回收。
- 拿Activity举例:
实例
- 1.创建Handler类:
-
private static class MyHandler extends Handler { private final WeakReference<MainActivity> mActivity; public MyHandler1(MainActivity activity) { mActivity = new WeakReference<>(activity); } @Override public void handleMessage(Message msg) { super.handleMessage(msg); MainActivity activity = mActivity.get(); if (activity != null) { switch (msg.what) { case 1: activity.test1(); break; case 2: activity.test2(); break; } } } }
-
- 2.创建Handler实例:
-
private MyHandler mHandler = new MyHandler(this);
-
- 3.发送消息:
-
mHandler.sendEmptyMessageAtTime(1, 0);
-
在子线程中创建Handler
Handler的使用
- 在子线程中,进行耗时操作,执行完操作后,发送消息,通知主线程更新UI。
-
public class Activity extends android.app.Activity { private Handler mHandler = new Handler(){ @Override public void handleMessage(Message msg) { super.handleMessage(msg); // 更新UI } }; @Override public void onCreate(Bundle savedInstanceState, PersistableBundle persistentState) { super.onCreate(savedInstanceState, persistentState); setContentView(R.layout.activity_main); new Thread(new Runnable() { @Override public void run() { // 执行耗时任务 ... // 任务执行完后,通知Handler更新UI Message message = Message.obtain(); message.what = 1; mHandler.sendMessage(message); } }).start(); } }
-
Handler架构
- Handler消息机制主要包括:MessageQueue、Handler、Looper这三大部分,以及Message。
- Message:需要传递的消息,可以传递数据;
- MessageQueue:消息队列,但是它的内部实现并不是用的队列,而是通过单链表的数据结构来维护消息列表,因为单链表在插入和删除上比较有优势。主要功能是向消息池投递消息(MessageQueue.enqueueMessage)和取走消息池的消息(MessageQueue.next)。
- Handler:消息辅助类,主要功能是向消息池发送各种消息事件(Handler.sendMessage)和处理相应消息事件(Handler.handleMessage);
-
Looper:消息控制器,不断循环执行(Looper.loop),从MessageQueue中读取消息,按分发机制将消息分发给目标处理者。
- 从图中可以看出:
- Looper有一个MessageQueue消息队列;
- MessageQueue有一组待处理的Message;
- Message中记录发送和处理消息的Handler;
- Handler中有Looper和MessageQueue。
- MessageQueue、Handler和Looper三者之间的关系:
-
每个线程中只能存在一个Looper,Looper是保存在ThreadLocal中的。
主线程(UI线程)已经创建了一个Looper,所以在主线程中不需要再创建Looper,但是在其他线程中需要创建Looper。
每个线程中可以有多个Handler,即一个Looper可以处理来自多个Handler的消息。
Looper中维护一个MessageQueue,来维护消息队列,消息队列中的Message可以来自不同的Handler。
-
Handler的运行流程
在子线程执行完耗时操作,当Handler发送消息时,将会调用MessageQueue.enqueueMessage,向消息队列中添加消息。
当通过Looper.loop开启循环后,会不断地从消息池中读取消息,即调用MessageQueue.next,
然后调用目标Handler(即发送该消息的Handler)的dispatchMessage方法传递消息,
然后返回到Handler所在线程,目标Handler收到消息,调用handleMessage方法,接收消息,处理消息。
文章来源:https://www.toymoban.com/news/detail-527534.html
总结
文章来源地址https://www.toymoban.com/news/detail-527534.html
方式一:基本方式
-
private Handler mHandler; //创建子线程handler private void createHandler(){ new Thread(new Runnable() { @Override public void run() { //当前子线程创建looper Looper.prepare(); //传入子线程Looper,也可以不传,默认也为该线程looper //Looper.myLooper():返回当前线程的Looper对象 mHandler = new Handler(Looper.myLooper()); //启动looper Looper.loop(); } }).start(); } //推送任务到消息队列 private void postRun(){ mHandler.post(new Runnable() { @Override public void run() { //执行耗时操作 SystemClock.sleep(5000); //更新UI MainActivity.this.runOnUiThread(new Runnable() { @Override public void run() { actTextReslt.setText("完成操作"); } }); } }); }
- 注:Looper.prepare() -> new Handler() -> Looper.loop()要按此顺序执行,由于子线程默认没有Looper而handler工作需要looper对象,直接创建handler对象会报java.lang.RuntimeException: Can’t create handler inside thread that has not called Looper.prepare()。
方式二:HandlerThread方式(HandlerThread内部创建消息队列,外部通过handler通知HandlerThread执行)
-
HandlerThread hanlerThread = new HandlerThread("子线程"); hanlerThread.start(); final Handler handler = new Handler(hanlerThread.getLooper()) { @Override public void handleMessage(Message msg) { super.handleMessage(msg); Log.d("----->", "线程:" + Thread.currentThread().getName()); } }; findViewById(R.id.bt).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { handler.sendEmptyMessage(100); } });
- 方式二是Android对方式一的封装,使用起来更简单。其中HandlerThread构造方法的第二个参数表示优先级(范围-20~19,越小优先级越高)。
- handler消息队列是串行的(即处理完当前消息,才进行处理下一个消息),背后只有一个线程,这里区别于线程池,对于并行需求考虑线程池解决。
方式三:子线程中创建主线程handler
-
new Thread(() -> { Handler handler = new Handler(Application.getContext().getMainLooper()); }).start();
AsyncTask
简介
- 从名称就能简单看出它是“异步任务”。
- 它封装了线程池和Handler 。
- AsyncTask是Android提供的一个轻量级异步框架,可以在后台执行耗时操作,同时也可以在主线程更新UI,简化了在Android中使用线程的复杂度。
- AsyncTask有四个泛型参数:
- Params:执行异步任务需要传入的参数类型。
- Progress:异步任务执行过程中,返回进度值的类型。
- Result:异步任务执行完成后,返回结果的类型。
- AsyncTask<Params, Progress, Result>:这里指的是AsyncTask本身,也就是说,它继承自Object类,同时也是一个泛型类,其中的三个泛型参数表示上述三个泛型参数。
- 注:
- AsyncTask通常被用于执行轻量级的操作,如果需要执行一些重量级的操作,例如,网络请求等,可以考虑使用其他的异步框架。
- AsyncTask在后台线程中执行,因此不应该在doInBackground()方法中访问UI元素,如果需要更新UI,应该在onProgressUpdate()方法和onPostExecute()方法中执行。
- AsyncTask在Android 4.0之后,使用的是单线程池,因此,如果需要同时执行多个异步任务,应该使用多线程池,或者使用其他的异步框架。
使用步骤:
- onPreExecute():在后台任务开始之前执行,通常进行一些预处理操作,例如,显示进度条等。
- doInBackground(params…):这个方法在异步线程(子线程)中执行,在这里执行任务。任务一旦完成可以通过return将执行结果返回,
- onProgressUpdate(progress…):在后台任务执行doInBackground()的过程中调用publishProgress(progress…):传入进度信息,然后在这个方法中更新UI。
- onPostExecute(result):在后台任务执行完成之后执行,通常在这里进行一些结果的处理,例如:更新UI,关闭进度条等。
实例
- 1.定义AsyncTask:
-
public class MyAsyncTask extends AsyncTask<Void, Void, String> { private WeakReference<Context> mContext; public MyAsyncTask(Context context) { this.mContext = new WeakReference<>(context); } @Override protected String doInBackground(Void... voids) { // 后台工作在这里执行,不可以更新UI String result = ""; // 模拟一个耗时的操作 try { Thread.sleep(2000); result = "Hello, AsyncTask!"; } catch (InterruptedException e) { e.printStackTrace(); } return result; } @Override protected void onPostExecute(String s) { super.onPostExecute(s); // 任务执行完成后,可以更新UI Context context = mContext.get(); if (context != null) { Toast.makeText(context, s, Toast.LENGTH_SHORT).show(); } } }
-
- 2.调用:
-
MyAsyncTask myAsyncTask = new MyAsyncTask(this); myAsyncTask.execute();
-
到了这里,关于安卓之异步消息处理机制的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!