Android中的异步消息处理

0. 前言

  整理一下有关Android中异步消息处理机制相关的知识。
  在Android中基本的异步处理有 Handler、Asynctask、HandlerThread 和 IntentService。用法各不相同,但最终都能达到异步处理的效果,下面分点整理一下。

1. Handler

1. 什么是Handler

  在Android中有一个主线程又叫UI线程,从名字中就可以看出这个线程是来处理UI相关的操作,在这个主线程中我们不能进行耗时操作,否则就会引起ANR,导致程序崩溃,如果我要执行一个耗时操作,而耗时操作的结果我又要显示在UI上,那该怎么办呢?
  Handler 出现了,它可以让我们在子线程中执行耗时操作,然后把更新UI的操作放在主线程,这个结束耗时操作后提醒UI更新的过程靠 Handler 来实现。Handler 通过发送和处理 Message 或者是 Runnable 对象 来实现异步,它还可以设置延迟处理消息。

2. Handler 的使用

  Handler 有两种使用方法,一种是 sendMessage(Message) ,另一种是 post(Runnable)。用代码来展示一下。

  sendMessage(Message):

        //子线程中进行耗时操作
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Toast.makeText(mContext,"下载开始",Toast.LENGTH_LONG).show();
                    Thread.sleep(1000);
                    Toast.makeText(mContext,"下载结束",Toast.LENGTH_LONG).show();

                    //耗时操作完成之后,用handler发送message,在主线程中更新UI
                    Message msg = new Message();
                    msg.what = 1;
                    mHandler.sendMessage(msg);

                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();

//handler里处理message信息
    private Handler mHandler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            switch (msg.what){
                case 1:
                    //更新UI
                    Main2Activity.this.mtvText.setText("UI更新");
                    break;
                default:
                    break;
            }
        }
    };

  post(Runnable):

        //定义一个Runnable对象,在完成子线程耗时操作后由handler调用
        final Runnable runnable = new Runnable() {
            @Override
            public void run() {
                //更新UI
                MainActivity.this.mtvText.setText("UI更新");
            }
        };

        //子线程耗时操作
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Toast.makeText(mContext,"下载开始",Toast.LENGTH_LONG).show();
                    Thread.sleep(1000);
                    Toast.makeText(mContext,"下载结束",Toast.LENGTH_LONG).show();
                    //耗时操作完成后,使用handler的post更新UI
                    mHandler.post(runnable);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();

  看源码可知,post 最后还是通过调用 sendMessage 来实现的。

/**
     * Causes the Runnable r to be added to the message queue.
     * The runnable will be run on the thread to which this handler is 
     * attached. 
     *  
     * @param r The Runnable that will be executed.
     * 
     * @return Returns true if the Runnable was successfully placed in to the 
     *         message queue.  Returns false on failure, usually because the
     *         looper processing the message queue is exiting.
     */
    public final boolean post(Runnable r)
    {
       return  sendMessageDelayed(getPostMessage(r), 0);
    }
3. Handler机制原理

  handler机制主要靠 Handler、Looper、MessageQueue 和 Message 来实现,简单的来说。
* Handler :负责发送消息以及处理消息
* Looper :负责从 MessageQueue 中取出消息,交由 Handler 处理
* MessaQueue :存放消息对象的队列
* Message :被传递、被处理的消息

可以看一下这张图,逻辑比较清晰(网上找的,侵删)
handler机制.png

  Looper是每个线程都独有的,它有个 loop() 方法,来从MessageQueue 中取出待处理的消息,读到消息后,把消息发送给 Handler 来处理;
  MessageQueue 是一个消息队列,以先进先出的方式来管理 Message ,MessageQueue 是 Looper 的一个成员变量,因此,在创建 Looper 的时候,MessageQueue 就被创建了,两者就已经关联到了一起;
  Message 是被 MessageQueue 存放的对象;
  Handler 中有一个 MessageQueue 对象和一个 Looper 对象,用 sendMessage 的方式向 MessageQueue 中放入消息,当Looper 获取到消息后,调用 dispatchMessage() 方法来对消息进行处理。
  再放一张图。(侵删)
Handler.png
  如果按着关系图和流程图去读一下源码,其实思路挺清晰的。
  要知道的是,每个线程都有 Looper ,但是如果你要在这个线程中使用 Handler 来处理异步消息,你在使用之前需要调用 Looper.prepare() 和 Looper.loop() 方法,这样才能使用 Handler,不然会报错。但是有人会问了,我在 Activity 中使用 Handler 并没有写 Looper.prepare() 和 Looper.loop() 啊?因为 Activity 的UI线程在开启的时候就调用了这两个方法,所以不用我们再写,直接使用 Handler 就行了,因为是在UI线程开启,所以在Handler中可以对UI的进行更新。

4. Handler内存泄漏及其解决办法

  Java 中非静态内部类会隐匿地持有外部类的引用,如果一个 Activity 有一个非静态的 Handler ,当这个 Activity 要回收的时候,Handler 可能仍在执行耗时操作,此时 Handler 不能被释放,其对 Activity 的引用也就不能被释放,导致 Activity 不能被回收,停留在内存中,造成内存泄漏。
  对于这个问题,有两种解决方法,一种是将 Handler 设置成静态内部类,同时让它持有 Activity 的弱引用;第二种是在 Activity 的生命周期 onDestroy() 中调用Handler的removeCallback()方法,撤销 Handler 在 Activity 上的回调,让Activity 能成功被释放,避免内存泄漏。

  • 方法一
    private MyHandler handler = new MyHandler(this);

    static class MyHandler extends Handler{
        WeakReference<Activity> mWeakReference;
        Activity mActivity;

        public MyHandler(Activity activity) {
            mWeakReference = new WeakReference<>(activity);
            mActivity = mWeakReference.get();
        }

        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            if (mActivity != null){
                switch (msg.what){
                    case 1:
                        //更新UI
                        break;
                    default:
                        break;
                }
            }
        }
    }
  • 方法二
@Override
    protected void onDestroy() {
        super.onDestroy();
        mHandler.removeCallbacks(null);
    }

  以上是Handler相关。

2. AsyncTask

1. 什么是AsyncTask

  AsyncTask 本质上是一个封装了线程池和 Handler 的轻量级异步类,在使用时创建自己的类来继承 AsyncTask类(AsyncTask 是个抽象类),可以在类中实现异步操作,并且可以向UI线程反馈异步操作的进度。
  线程池的引入可以避免重复创建新线程带来的消耗,提高性能;Handler 则实现了工作线程和UI线程之间操作的灵活切换。

2. AsyncTask的使用方法
1) 三个参数
public abstract class AsyncTask<Params, Progress, Result>

  在自己定义的异步AsyncTask类时继承 AsyncTask 可以定义三个泛型参数,第一个表示在工作线程开始时所传入的参数,第二个表示在UI线程更新工作线程进度的参数,第三个表示异步线程完成之后所返回的参数。

public class MyAsyncTask extends AsyncTask<Void, Integer, String>

  我这边定义了一个 MyAsyncTask 继承 AsyncTask ,三个泛型分别定义为 Void(表示我们异步线程开始时不需要传参)、Integer(表示在更新工作线程进度的时候向UI线程传输参数的类型)、String(表示异步线程执行完后返回的参数为String类型)。

2) 五个方法

  五个方法分别为 onPreExecute 、doInBackground 、publishProgress 、onProgressUpdate 和 onPostExecute ,介绍可以看我在下面方法名上的注释。

    /**
     * 在异步线程开始前的会被调用,一般进行一些初始化操作,如进度条的初始化
     * 运行在UI线程
     */
    @Override
    protected void onPreExecute() {
        super.onPreExecute();
    }

    /**
     * AsyncTask必须实现的方法,在这个方法里进行耗时操作
     * 运行在工作线程,在 onPreExecute() 调用完成之后调用
     * 可以调用 publishProgress(Integer) 方法将工作线程的进度传给UI线程
     * @param voids 可设置传入参数,三个参数中的第一个(这里定义了Void,也就是没有参数)
     * @return 运行结果,会被传给 onPostExecute()
     */
    @Override
    protected String doInBackground(Void... voids) {
        //将进度传给 onProgressUpdate() ,更新进度
        publishProgress(50);

        return null;
    }

    /**
     * 更新UI线程的进度
     * 在 doInBackground 中调用 publishProgress()后被调用
     * 运行在UI线程
     * @param values 进度的值,其类型由第二个参数决定,这里定义为Integer
     */
    @Override
    protected void onProgressUpdate(Integer... values) {
        super.onProgressUpdate(values);
    }

    /**
     * 在工作线程完成后被调用,参数为 doInBackground() 所返回的参数
     * 参数的类型由第三个参数决定,这里是String
     * 运行在UI线程
     * @param s 计算的结果
     */
    @Override
    protected void onPostExecute(String s) {
        super.onPostExecute(s);
    }
3. AsyncTask内部原理
  • AsyncTask 的本质是一个静态的线程池,AsyncTask 派生出的子类可以实现不同的异步任务,这些任务都是提交到静态的线程池中执行。
  • 线程池中的工作线程执行 doInBackground 方法执行异步任务。
  • 当任务状态改变后,工作线程会向UI线程发送消息,AsyncTask 内部的 InternalHandler 响应这些消息,并调用相关的回调函数。
4. AsyncTask的注意事项
1) 内存泄漏

  原理和解决方法可以参考 Handler 内存泄漏。

2) 生命周期

  AsyncTask 不会随着 Activity 的 销毁而自动销毁,而我们又常常在 AsyncTask 中定义耗时操作完成之后对UI的更新操作,如果在 Activity 被销毁之后,AsyncTask 才执行玩耗时操作,此时去调用更新UI的方法必然会报错,所以我们需要在 Activity 的 onDestroy() 中调用 AsyncTask 的 cancel() 方法来避免这种错误的发生。

3)结果丢失

  当一个 AsyncTask 被开启后,如果它所对应的 Activity 因为一些原因被销毁(如屏幕旋转后,Activity 的销毁与重新创建),此时 AsyncTask 所引用的 Activity 并不存在了,所以我们应当做好结果保存的操作。

4)并行与串行

  在android1.6之前 AsyncTask 都为串行,在1.6到2.3的版本 AsyncTask 被改为并行,在2.3之后为了系统的稳定性,又把它改为了串行,但仍然保留并行的方法,可以调用 executeOnExecutor() 方法进行并行运算。

  以上是 AsyncTask 相关。

3. HandlerThread

1. HandlerThread是什么

  HandlerThread 本质上就是一个 Thread 线程,和普通的线程不一样的是 HandlerThread 内部开启了 looper,这样就可以使用 handler 机制进行异步操作。
  之前在 handler 相关中讲到,如果在一个线程中要使用 handler,就要调用 looper.prepare() 和 looper.loop() 两个方法开启循环,否则就会报错,而 HandlerThread 就是将这个操作封装了一下,方便我们的使用。
  通过获取 HandlerThread 的 looper 对象传递给 Handler 对象,可以在 handlerMessage 方法中执行异步任务。优点是不会有堵塞,减少了对性能的消耗,但缺点是不能同时进行多任务的处理,HandlerThread是一个串行队列,需要等待进行处理,处理效率较低。

2. HandlerThread使用方法

  先创建一个HandlerThread,由于它的本质是Thread,因此我们将它 start 起来。

        private HandlerThread mHandlerThread;

        mHandlerThread = new HandlerThread("mHandlerThread");
        mHandlerThread.start();

  然后创建一个 Handler,便于区分,我们将它取名为 mWorkHandler,表示他是工作线程的 Handler,可以发现我们在创建的时候传入了一个参数 mHandlerThread.getLooper() ,调用了我们之前创建的 HandlerThread 的方法,这个参数,使得我们的 mWorkHandler 是去管理 mHandlerThread 这个线程上的消息,因此下面重写的 handleMessage() 里的操作就是跑在 mHandlerThread 这个线程上,由此达到异步操作的目的。

mWorkHandler = new Handler(mHandlerThread.getLooper()){
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                switch (msg.what){
                    case MSG_START_WORK:
                        try {
                            //------模拟耗时操作开始-------
                            Thread.sleep(1000);
                            //------模拟耗时操作结束-------
                            Message msg_work_finished = new Message();
                            msg_work_finished.what = MSG_WORK_FINISHED;
                            mUIHandler.sendMessage(msg_work_finished);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        break;
                    default:
                        break;
                }
            }
        };

  我们在工作线程的 handleMessage 方法里模拟了耗时操作,操作结束后,由 mUIHandler 发送消息至 UI 线程,进行UI更新操作。
  下面是 mUIHandler 的代码,在收到更新UI的消息之后,对UI进行更新。

mUIHandler = new Handler(){
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                switch (msg.what){
                    case MSG_WORK_FINISHED:
                        //主线程更新UI操作
                        updateUI();
                        break;
                    default:
                        break;
                }
            }
        };

贴上整体的代码,方便理解使用。

    private HandlerThread mHandlerThread;
    private Handler mWorkHandler;
    private Handler mUIHandler;

    private static final int MSG_START_WORK = 1;
    private static final int MSG_WORK_FINISHED = 2;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        mHandlerThread = new HandlerThread("mHandlerThread");
        mHandlerThread.start();

        mWorkHandler = new Handler(mHandlerThread.getLooper()){
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                switch (msg.what){
                    case MSG_START_WORK:
                        try {
                            //------模拟耗时操作开始-------
                            Thread.sleep(1000);
                            //------模拟耗时操作结束-------
                            Message msg_work_finished = new Message();
                            msg_work_finished.what = MSG_WORK_FINISHED;
                            mUIHandler.sendMessage(msg_work_finished);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        break;
                    default:
                        break;
                }
            }
        };

        mUIHandler = new Handler(){
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                switch (msg.what){
                    case MSG_WORK_FINISHED:
                        //主线程更新UI操作
                        updateUI();
                        break;
                    default:
                        break;
                }
            }
        };

        Message msg_start_work = new Message();
        msg_start_work.what = MSG_START_WORK;
        mWorkHandler.sendMessage(msg_start_work);

    }

  其实自我感觉还是 Handler 使用起来方便一点,但是 HanlerThread 自己管理 Looper,相当于给UI线程的 Looper 减轻了压力,这也是一个优点。如果需要跑多个异步任务的话,一种方法是创建多个新线程跑,还有方法是用 HandlerThread 将这些任务串行处理,这样就可以节省系统的资源,当然也要考虑到串行与并行的优缺点,得看情况选择:)。
  以上是 HandlerThread 相关。

4. IntentService

1. IntentService是什么

  IntentService 是一个特殊的 Service,它继承自 Service,同时又在内部封装了 Handler 和 HandlerThread。
  它的启动方式和 Service 一样,当它的异步任务执行完成后,IntentService 会自动停止,不需要手动操作,同一个 IntentService 可以被启动多次,每一次被启动的耗时操作会被放在一个队列里,在 IntentService 的 onHandleIntent (这是写耗时操作代码的地方)回调方法中执行,以串行的方式,一次只执行一个耗时操作,执行完一个后再执行下一个,直至没有下一个后,自动停止。

2. IntentService使用方法

  IntentService 的用法很简单,首先创建一个类继承 IntentService,必须有一个带 String 参数的构造函数,然后重写其 onHandleIntent 方法,所需的耗时操作就写在 onHandleIntent 里,我这里用 LocalBroadcast 来实现耗时操作完成之后,向 Activity 发送更新UI的消息。

public class MyIntentService extends IntentService {

    public static final String UI_UPDATE_ACTION = "UI_UPDATE_ACTION";

    public MyIntentService(String name) {
        super(name);
    }

    @Override
    protected void onHandleIntent(@Nullable Intent intent) {

        assert intent != null;
        intent.getStringExtra("arg1");

        try {
            //-----模拟耗时操作开始-----
            Thread.sleep(1000);
            //-----模拟耗时操作结束-----
            LocalBroadcastManager.getInstance(this).sendBroadcast(new Intent(UI_UPDATE_ACTION));

        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }

}

  来看一下 Activity 里的代码。


public class MainActivity extends AppCompatActivity {

    public static final String UI_UPDATE_ACTION = "UI_UPDATE_ACTION";
    private MyBroadcastRecevier myBroadcastRecevier;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //注册BroadcastRecevier来接收UI更新的消息
        myBroadcastRecevier = new MyBroadcastRecevier();
        IntentFilter intentFilter = new IntentFilter(UI_UPDATE_ACTION);
        LocalBroadcastManager.getInstance(this).registerReceiver(myBroadcastRecevier,intentFilter);
        //启动IntentService进行耗时操作
        Intent intent = new Intent(this,MyIntentService.class);
        intent.putExtra("arg1","参数1");
        startService(intent);
    }

    class MyBroadcastRecevier extends BroadcastReceiver{

        @Override
        public void onReceive(Context context, Intent intent) {
            //更新UI的操作
        }
    }
}

  IntentService 的启动方式与 Service 相同,因为 IntentService 是继承自 Service 所以本质上差不多。我们这里还注册了 BroadcastRecevier 来接收耗时操作完成之后发送的消息,收到消息后进行 BroadcastReceiver 的 onReceive 中的更新UI操作。

3. IntentService原理

  和 Service 一样,当多次被启动后,只会有一个对象,但其耗时操作会被放入一个队列中串行执行,当对队列里没有耗时操作时,IntentService 会自动停止,不用手动调用。为什么会这样呢?其实 IntentService 中真正实现异步操作的是 HandlerThread 和 Handler 。

    @Override
    public void onCreate() {
        // TODO: It would be nice to have an option to hold a partial wakelock
        // during processing, and to have a static startService(Context, Intent)
        // method that would launch the service & hand off a wakelock.

        super.onCreate();
        HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
        thread.start();

        mServiceLooper = thread.getLooper();
        mServiceHandler = new ServiceHandler(mServiceLooper);
    }

  可以在 IntentService 的源码中看到,在它的 onCreate 方法中创建了一个 HandlerThread 和一个 ServiceHandler,而 ServiceHandler是啥?

 private final class ServiceHandler extends Handler {
        public ServiceHandler(Looper looper) {
            super(looper);
        }

        @Override
        public void handleMessage(Message msg) {
            onHandleIntent((Intent)msg.obj);
            stopSelf(msg.arg1);
        }
    }

  ServiceHandler 其实就是个 Handler,在其 handleMessage 方法中执行我们在 IntentService 中定义的 onHandleIntent 耗时操作。
  是不是很眼熟,在上面的 HandlerThread 使用介绍中,我们在 Activity 中创建了 HandlerThread 和 Handler 来执行异步操作,两者是异曲同工的,因为Service 和 Activity 一样,不能在主线程中进行耗时操作。所以在了解 HandlerThread使用方法的基础之上,就很容易搞懂 IntentService 的原理。
  以上是 HandlerThread 相关。

5. 最后

  在 Android 的开发过程中我们常常会碰到需要异步操作的情况,以上几种基本异步操作方法给了我们很多的选择,在了解其原理的情况下,我们也可以更好地去使用。
  除了这些之外,RxJava 也可以用来处理异步操作,而且很方便很好用!之前写过一篇 RxJava 使用介绍博客,感兴趣的朋友可以了解一下 Android RxJava2学习实践
  之后会不定期更新Android相关的知识整理,感兴趣的可以关注一波 ;)

以上。

猜你喜欢

转载自blog.csdn.net/weixin_42247440/article/details/81296306