欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Android多线程----异步消息处理机制之Handler使用详解

程序员文章站 2022-07-14 14:33:33
...

Handler使用(异步消息处理机制)

关于Android的多线程知识,上一篇博客说明了AsyncTask的使用方法,今天就来说说另一种Handler。

在安卓开发中,我们经常会遇到更新UI状态的需求。比如更新TextView的文字时,如果直接在子线程中操作,那么就会出现报错的异常,提示我们需要在UI线程中进行更新。而解决办法很简单:创建一个Message对象,使用handler去处理,通过handler的sendMessage方法是更新。大概操作步骤如下:

//首先实例化一个Textview控件和Button按钮,通过点击button按钮改变textview的文字
private TextView handler_txt1;
private Button handler_bt1;

handler_bt1 = findViewById(R.id.handler_bt1);
handler_txt1 = findViewById(R.id.handler_txt1);


handler_bt1.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                //这里获取message时,建议通过handler.obtainMessage()或者handler.obtain()来获取
                //这样的话,可以由系统自己负责message的创建和销毁
                Message message = handler.obtainMessage();
                handler.sendMessage(message);
            }
        }){}.start();
    }
});

/**
* 通过handler接收指令并修改
*/
Handler handler = new Handler(){
   @Override
   public void handleMessage(Message msg) {
       handler_bt1.setText("修改完毕");
       handler_txt1.setText("我变成了2");
   }
};

这样的话就可以完成对textview的文字修改啦


当然,在子线程中并非不能修改UI,参考下面的写法就可以在子线程中修改UI啦
//方法一:
new Thread() {
    public void run() {
        //这儿是耗时操作,完成之后更新UI;
        runOnUiThread(new Runnable(){

            @Override
            public void run() {
                //更新UI
                handler_txt1.setText("我变成了2");
            }
        });
    }
}.start();

//方法二:
/**
* 方法二:通过handler接收指令并修改
*/
public void onClick(View v) {
    new Thread(new Runnable() {
        public void run() {
            mImageView.post(new Runnable() {
                public void run() {
                     //更新UI
                     handler_txt1.setText("我变成了2");
                }
            });
        }
    }).start();
}

说完了更改UI的操作之后,我们来看看自定义与线程相关的handler该如何创建使用。

在这里插入代码片
//首先定义线程
MyThread myThread;

/**
 * 创建一个MyThread线程
 */
class MyThread extends Thread{
    //通过handler去打印一个ID
    Handler handler;
    @Override
    public void run() {
        //创建一个Looper,与handler绑定
        Looper.prepare();
        handler = new Handler(){
            @Override
            public void handleMessage(Message msg) {
                System.out.println("Thread----->" + Thread.currentThread());
            }
        };
        //循环处理消息
        Looper.loop();
    }
}

/**
* 主线程handler
*/
Handler handler = new Handler(){
   @Override
   public void handleMessage(Message msg) {
       super.handleMessage(msg);
       System.out.println("UI----->" + Thread.currentThread());
   }
};

//在onCreate()方法中调用
myThread = new MyThread();
myThread.start();
try {
    Thread.sleep(1000);
} catch (InterruptedException e) {
    e.printStackTrace();
}
//发送给myThread里面的handler消息
myThread.handler.sendEmptyMessage(1);
//发送消息给主线程handler
handler.sendEmptyMessage(1);

Android多线程----异步消息处理机制之Handler使用详解
从打印的log来看,myThread的handler位于Thread-7822中是在子线程中执行的,而Handler里面的方法是在主线程中执行的,所以需要注意的是,我需要处理一些耗时操作的时候,不要在主线程中去处理,因为这样会造成线程堵塞,从而界面卡顿。

其实Handler的用法有好多种,这里只说一点比较简单的用法。接下来我们看看handlerTread。在有关线程的开发中,我们会经常遇到并发问题,而Google官方提供的handlerTread类就很好的提供了解决办法,我们只需要简单调用就好。

//定义handler
Handler handler;

//定义handlerthread
HandlerThread handlerThread;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    TextView textView = new TextView(this);
    textView.setText("我是HandlerThreadActivity");
    setContentView(textView);

    //实现一个HandlerThead,并起名为caicai,与定义的handler进行绑定
    handlerThread = new HandlerThread("caicai");
    handlerThread.start();

    //使用handlerthread的looper
    handler = new Handler(handlerThread.getLooper()){
        @Override
        public void handleMessage(Message msg) {
            System.out.println("Log----->" + Thread.currentThread());
        }
    };

    //发送消息给handler
    handler.sendEmptyMessage(1);
}

Android多线程----异步消息处理机制之Handler使用详解
从打印的日志来看,可以看出这个handlermessage方法是一个在命名为caicai的子线程的线程中去处理任务的,所以在开发中,也可以通过handlerThread去处理异步任务,和AsyncTask一样去处理一个异步任务机制。

主线程也有了,子线程也有了,那么问题来了,主线程和子线程之间如何发送呢?往下看

//定义两个按钮用来发送和停止消息
Button handlerthread_bt1, handlerthread_bt2;

/**
 * 定义一个主线程的handler
 */
Handler handler = new Handler(){
    @Override
    public void handleMessage(Message msg) {
        super.handleMessage(msg);
        Message message = handler.obtainMessage();

        //向子线程发送一个消息
        threadHandler.sendMessageDelayed(message, 1000);

        System.out.println("主线程----->" + Thread.currentThread());
    }
};


//作为子线程去处理handler
HandlerThread handlerThread;
Handler threadHandler;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_handler_thread2);

    //实例化按钮
    handlerthread_bt1 = findViewById(R.id.handlerthread_bt1);
    handlerthread_bt2 = findViewById(R.id.handlerthread_bt2);
    handlerthread_bt1.setOnClickListener(this);
    handlerthread_bt2.setOnClickListener(this);


    //创建一个子线程
    handlerThread = new HandlerThread("你最好看");
    handlerThread.start();

    //创建一个子线程的handler
    threadHandler = new Handler(handlerThread.getLooper()){
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            Message message = threadHandler.obtainMessage();

            //向主线程发送一个消息
            handler.sendMessageDelayed(message, 1000);

            System.out.println("子线程----->" + Thread.currentThread());
        }
    };

}

@Override
public void onClick(View v) {
    int temdId = v.getId();
    if(temdId == R.id.handlerthread_bt1){
        //开始发送
        handler.sendEmptyMessage(1);
    } else if(temdId == R.id.handlerthread_bt2) {
        //任务停止
        handler.removeMessages(0);
    }
}

下来我们稍微的整理一下

Handler

handler是什么?
handler是可以用来更新UI界面的机制,也可以用来处理异步消息的机制。

为什么要有Handler?
Android在设计的时候,封装了一套消息创建、传递、处理机制,如果不遵循这样的机制就没办法更新UI信息,就会抛出异常。

handler怎么用?
1.post(Runnable)
2.postDelayed(Runnable ,long)
3.sentMessage
4.sentMessageDelayed

Android为什么要设置只能通过Handler机制更新UI?
为了解决多线程所产生的并发的问题。
假设如果在一个Activity中,有多个线程去更新UI,并且都没有加锁机制,就会产生更新界面混乱;
而如果对更新UI 的操作都加锁处理的话就会造成性能下降 。

handler的原理是什么?
1.handler封装消息的发送(主要包括消息发送给谁)
2.Looper——消息封装的载体。内部包含一个MessageQueue,所有的Handler发送的消息都走向这个消息队列;Looper.Looper方法,就是一个死循环,不断地从MessageQueue取消息,如果有消息就处理消息,没有消息就阻塞。
3.MessageQueue,一个消息队列,添加消息,处理消息
4.handler内部与Looper关联,handler->Looper->MessageQueue,handler发送消息就是向MessageQueue队列发送消息。
总结:handler负责发送消息,Looper负责接收handler发送的消息,并把消息回传给handler自己。
MessageQueue存储消息的容器。

HandlerThread的作用是什么
HandlerThread thread=new HandlerThread(“handler thread”);自动含等待机制,等Looper创建好了,才创建Handler,避免出现空指针异常。

Android更新UI的方式
1.runOnUIThread
2.handler post
3.handler sendMessage
4.view post

非UI线程真的不能更新UI吗
不一定,之所以子线程不能更新界面,是因为Android在线程的方法里面采用checkThread进行判断是否是主线程,而这个方法是在ViewRootImpl中的,这个类是在onResume里面才生成的,因此,如果这个时候子线程在onCreate方法里面生成更新UI,而且没有做阻塞,就是耗时多的操作,还是可以更新UI的。

使用Handler遇到的问题
比如说子线程更新UI,是因为触发了checkThread方法检查是否在主线程更新UI,还有就是子线程中没有Looper,这个原因是因为Handler的机制引起的,因为Handler发送Message的时候,需要将Message放到MessageQueue里面,而这个时候如果没有Looper的话,就无法循环输出MessageQueue了,这个时候就会报Looper为空的错误。

主线程怎么通知子线程
请参考最后一个代码块。可以利用HandlerThread进行生成一个子线程的Handler,并且实现handlerMessage方法,然后在主线程里面也生成一个Handler,然后通过调用sendMessage方法进行通知子线程。同样,子线程里面也可以调用sendMessage方法进行通知主线程。这样做的好处比如有些图片的加载啊,网络的访问啊可能会比较耗时,所以放到子线程里面做是比较合适的。

至此END

q:486789970
email:[email protected]

如果有什么问题,欢迎大家指导。并相互联系,希望能够通过文章互相学习。

												---财财亲笔