Handler理解和使用

Handler的理解和使用

写在最前面。最近在面试找工作,发现好多知识点会用,但是你要是详细的问我一些内容我回答的可能就会有偏差。借这个机会整理一下这些知识点的同时,也把自己整理的内容分享出来。不能保证写的一定正确。如有错误的地方希望大家批评指正。

什么是Handler,以及Handler怎么使用。

什么是Handler?

让我们先看看Android官方API是怎么对Handler进行描述的

A Handler allows you to send and process Message and Runnable objects associated with a thread’s MessageQueue. Each Handler instance is associated with a single thread and that thread’s message queue. When you create a new Handler, it is bound to the thread / message queue of the thread that is creating it – from that point on, it will deliver messages and runnables to that message queue and execute them as they come out of the message queue.

There are two main uses for a Handler: (1) to schedule messages and runnables to be executed at some point in the future; and (2) to enqueue an action to be performed on a different thread than your own.

Scheduling messages is accomplished with the post(Runnable), postAtTime(Runnable, long), postDelayed(Runnable, Object, long), sendEmptyMessage(int), sendMessage(Message), sendMessageAtTime(Message, long), and sendMessageDelayed(Message, long) methods. The post versions allow you to enqueue Runnable objects to be called by the message queue when they are received; the sendMessage versions allow you to enqueue a Message object containing a bundle of data that will be processed by the Handler’s handleMessage(Message) method (requiring that you implement a subclass of Handler).

When posting or sending to a Handler, you can either allow the item to be processed as soon as the message queue is ready to do so, or specify a delay before it gets processed or absolute time for it to be processed. The latter two allow you to implement timeouts, ticks, and other timing-based behavior.

When a process is created for your application, its main thread is dedicated to running a message queue that takes care of managing the top-level application objects (activities, broadcast receivers, etc) and any windows they create. You can create your own threads, and communicate back with the main application thread through a Handler. This is done by calling the same post or sendMessage methods as before, but from your new thread. The given Runnable or Message will then be scheduled in the Handler’s message queue and processed when appropriate.

按照我个人的理解翻译一下上面这段话,不一定准确。大家凑合着看。
一个Handler允许您发送和处理与线程的MessageQueue相关联的Message和Runnable对象。每个Handler实例将单线程和该线程的消息队列相关联。从您创建一个新的Handler时起,它被绑定到创建它的线程和消息队列。他将发送Messages和Runnables到消息队列中,并且在它们从消息队列取出来的时候执行它们。
Handler有两个主要用途:

  1. 去安排Messages和Runnables在将来的某个时间节点被执行。
  2. 将一个活动列入到非当前线程的另一个线程去执行
    通过如下方法调用Message:
post(Runnable), postAtTime(Runnable, long), postDelayed(Runnable, Object, long), 
sendEmptyMessage(int), sendMessage(Message), sendMessageAtTime(Message, long), sendMessageDelayed(Message, long)

post版本允许您在接收消息队列时排序Runnable对象。sendMessage版本允许你将Handler的handleMessage(Message) 方法处理过的带有数据的Message对象进行排序(要求您实现Handler的子类)。
在传递或发送到 Handler 前, 您可以允许在消息队列准备就绪后立即处理该项, 或者在处理之前指定延迟, 或将其处理为绝对时间。 后两个允许您实现超时、定时和其他基于计时的行为。
当你的应用程序创建进程的时候,主线程会运行一个专用的消息队列,用于管理顶级的应用对象(Activites,broadcast receivers等)和它创建的其他窗口。你可以创建自己的线程,并通过Handler回调主线程。做这些操作和之前一样通过post或sentMessage方法,但是是从你的新线程里发送。然后Runnable和Message将被安排在Handler的消息队列中,并在适当的时间进行处理。

从官方描述中我们大概可以知道Handler是一个可以在线程间通讯的工具,同时也可以通过Handler进行一些延时操作。结合Android的特点禁止在子线程中更新UI(禁止不是代表不可以,有一种情况下在子线程中可以更新,文章最后会说)。所以在进行Android开发时,Handler主要被用于子线程更新UI使用。

Handler怎么用?

Handler主要是通过Looper和MessageQueue来更新UI的,我们也简单的介绍一下:

Message
我们先看一下Message对象的字段:

参数类型参数名称参数描述
intarg1如果您仅需要传递一个Ingeger值,使用arg1和arg2相比使用setDate()方法会消耗更少的资源。
intarg2同上.
Objectobj发送给接收者的一个人一的对象。
MessengerreplyTo将消息发送出去之后选择的接收Messenger。
intsendingUid选择发送uid消息的字段。
intwhat用户定义的消息码,以便接收者能够识别此条消息的内容。

在Message中,我们常用的字段是what,obj,arg1,arg2。replyTo和sendingUid则是用户进程间通讯使用(通过Handler+Messenger的形式,传递Message对象,在文章最后也说一下)。
从上面的表格不难看出每个字段存储信息的作用。因此选择合适的字段也能够帮助我们的App节省内存,比如在简单的Integer值中使用arg1和arg2来替代setData(Bundle)方法。

下面我们从源码中看一下Message的构造方法:

	/** Constructor (but the preferred way to get a Message is to call {@link #obtain() Message.obtain()}).
    */
    public Message() {
    }

Message只有一个空的构造方法来创建Message对象,但是在这个方法的注释上面,我们可以清晰的看到Android官方建议我们使用Message.obtain()方法来创建一个对象。obtain()创建的使用构造方法创建的有什么不一样呢?我们在看一下源码:

	/** @hide */
    public static final Object sPoolSync = new Object();
    private static Message sPool;
    private static int sPoolSize = 0;

    private static final int MAX_POOL_SIZE = 50;

    private static boolean gCheckRecycle = true;

    /**
     * Return a new Message instance from the global pool. Allows us to
     * avoid allocating new objects in many cases.
     */
    public static Message obtain() {
        synchronized (sPoolSync) {
            if (sPool != null) {
                Message m = sPool;
                sPool = m.next;
                m.next = null;
                m.flags = 0; // clear in-use flag
                sPoolSize--;
                return m;
            }
        }
        return new Message();
    }
	/**
     * Recycles a Message that may be in-use.
     * Used internally by the MessageQueue and Looper when disposing of queued Messages.
     */
    void recycleUnchecked() {
        // Mark the message as in use while it remains in the recycled object pool.
        // Clear out all other details.
        flags = FLAG_IN_USE;
        what = 0;
        arg1 = 0;
        arg2 = 0;
        obj = null;
        replyTo = null;
        sendingUid = -1;
        when = 0;
        target = null;
        callback = null;
        data = null;

        synchronized (sPoolSync) {
            if (sPoolSize < MAX_POOL_SIZE) {
                next = sPool;
                sPool = this;
                sPoolSize++;
            }
        }
    }

Looper类:

	public static void loop() {
        final Looper me = myLooper();
        final MessageQueue queue = me.mQueue;
        for (;;) {
            Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }
            msg.recycleUnchecked();
        }
    }

从上面我们可以看出obtain()方法在调用时如果sPool为null会返回一个new Message()对象,在Looper进行循环遍历MessageQueue时,在第一个Message()对象被取出使用时,会调用Message的recycleUnchecked方法,在recycleUnchecked方法中会创建一个大小为50的Message消息池。在sPoll不为null时候调用obtain时,会从消息池中获取。
这样通过Message.obtain()方法获取Message对象就能够合理的利用资源。比直接在程序中new Message()更节省内存。
最后说一下handler.obtainMessage();方法。我们还是看一下源码:

 	public final Message obtainMessage(){
        return Message.obtain(this);
    }f

该方法同样是调用Message.obtain方法,所以说Message.obtain()和Handler.obtainMessage()方法可以说是相同的。

Looper
Looper是一个循环器,去循环遍历MessageQueue。在Activity中系统会自动帮帮用户启动Looper对象,但是在用户自定义的类中,则需要用户手动去调用Looper.prepare()方法,才能正常启动Looper对象。
下面我们还是继续看一下源码:

	static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
	final MessageQueue mQueue;
    final Thread mThread;
    
    public static void prepare() {
        prepare(true);
    }

    private static void prepare(boolean quitAllowed) {
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper(quitAllowed));
    }
    private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }

在这段代码中我们会发现Looper的构造方法是私有的。所以我们是不能直接调用他的构造方法的,Looper提供了prepare方法给我们创建Looper使用。从prepare方法中我们可以看出在每个线程中,只能创建一个Looper对象。且Looper实际是一个ThreadLocal。何为ThreadLocal?请参考:Java并发编程:深入剖析ThreadLocal。而且在实例化Looper时会实例化一个MessageQueue,因此MessageQueue就不需要我们去创建了。而且MQ的构造方法MessageQueue(boolean quitAllowed)是没有被任何访问控制符修饰的。所以只能被当前包下的类访问,我们的类是不能访问的。

继续看源码:

	public static void loop() {
        final Looper me = myLooper();//获取当前线程的Looper
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
        final MessageQueue queue = me.mQueue;//获取当前线程的MessageQueue
        for (;;) {//开启死循环遍历
            Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }
            try {
                msg.target.dispatchMessage(msg);//重点。开始分发消息。
            } 
            
            msg.recycleUnchecked();
        }
    }

调用loop方法后,Looper就开始真正的工作了。他不断的从自己的MessageQueue中取出队头的消息执行。我们可以看一下msg.target.dispatchMessage(msg),msg.target其实是一个Handler对象,也就是说Message是包含Handler对象的。

Handler
Handler我们就直接写使用,在过程中出现的问题会对应的提到。
既然上面说过有post版本和send版本,那么我们就分别来看一下应该怎么写。首先我们先说一下xxxAtTime()和xxxDelayed()的区别,我们都用三个参数的方法来说:
xxxAtTime(Runnable/Message r/msg, Object token , long uptimeMillis)这个方法是指在固定的某一个时间节点执行Runnable或者Message。token是传入的一个用于识别当前任务的参数,可以用于取消使用。uptimeMillis是执行时间点的时间戳。
xxxDelayed(Runnable/Message r/msg, Object token , long uptimeMillis)这个方法是在间隔一个固定的时间执行Runnable或者Message。token是传入的一个用于识别当前任务的参数,可以用于取消使用。uptimeMillis是执行间隔时间的时间戳。
下面我就通过post(Runnable r)和sendMessage(Message msg)方法来写,另外的xxxAtTime和xxxDelayed就不在写。

post方式

先实现post方式的三种情况:子线程向主线程通讯,主线程向子线程通讯,子线程向子线程通讯。
1.子线程向主线程通讯
在主线程中创建Handler,在子线程中直接调用post方法,并重新Runnable的run()方法就可以了。

public class MainActivity extends Activity {
    private static final String TAG = "MainActivity";
    private Handler mainHandler = new Handler();
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        new Thread(){
            @Override
            public void run() {
                mainHandler.post(new Runnable() {
                    @Override
                    public void run() {
                        Log.i(TAG ,Thread.currentThread() + "");
                    }
                });
            }
        }.start();
    }
}

输出结果:
LogCat输出结果
2.主线程向子线程通讯
主线程向子线程通讯,其实就是将Handler创建给子线程,通过new Handler(Looper)的形式可以将Handler和Looper绑定,Looper所在的线程就是Handler所在的线程。

	public class MainActivity extends Activity {
    private static final String TAG = "MainActivity";
    
    Handler mHandler ;
    
    class MyThread extends Thread{
        private Looper looper;
        
        @Override
        public void run() {
            Looper.prepare();
            looper = Looper.myLooper();
            Looper.loop();
        }
    }
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        MyThread myThread = new MyThread();
        myThread.start();
        
        mHandler = new Handler(myThread.looper);
        mHandler.post(new Runnable() {
            @Override
            public void run() {
                Log.i(TAG ,Thread.currentThread() + "");
            }
        });
    }
}

运行这段代码你会发现将会报错,错误信息:
在这里插入图片描述
报空指针的原因是因为在Handler初始化的时候,thread.looper还没有初始化完成,所以就会报空指针,解决办法也不难,可以通过一个循环判断一下是否初始化完成,在实例化Handler。不过Android本身就给我们提供了一个方法解决这个问题,通过HandlerThread来创建一个子线程。因此我们将程序改为:

public class MainActivity extends Activity {
    private static final String TAG = "MainActivity";

    Handler mHandler ;

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

        HandlerThread myThread = new HandlerThread("my thread");
        myThread.start();

        mHandler = new Handler(myThread.getLooper());
        mHandler.post(new Runnable() {
            @Override
            public void run() {
                Log.i(TAG ,Thread.currentThread() + "");
            }
        });
    }
}

运行结果:

3.子线程向子线程通讯
在线程1中先调用Looper.prepare()方法,然后在将Handler绑定到当前的Looper,最后在执行Looper.loop()方法,在线程2中,通过Handler调用post()方法即可。

public class MainActivity extends Activity {
    private static final String TAG = "MainActivity";

    Handler mHandler ;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main)
        
        new Thread("接收消息线程"){
            @Override
            public void run() {
                Looper.prepare();
                mHandler = new Handler(Looper.myLooper());
                Looper.loop();
            }
        }.start();

        new Thread("发送消息线程") {
            @Override
            public void run() {
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                mHandler.post(new Runnable() {
                    @Override
                    public void run() {
                        Log.i(TAG ,"实际执行线程是:" + Thread.currentThread() );
                    }
                });
            }
        }.start();
    }
}

运行结果:

sendMessage()方式

实现sendMessage方式也通过上面三种方式实现:子线程向主线程通讯,主线程向子线程通讯,子线程向子线程通讯。
在实现之前有必要说一下通过sendMessage方式实现时,需要内部类,当出现内部类时就有可能会导致内存泄漏的发生。产生内存泄漏的原因是内部类会持有外部类的引用,当内部类没有及时释放持有的资源时,会导致外部类也无法被释放,这就会造成内存泄漏。这也就是为什么我们通过内部类创建一个Handler时,IDE工具会有警告的黄色。解决Handler的内存泄漏有两种办法。

  1. 通过程序逻辑进行维护。
    可以再外部类被销毁之前,调用Handler的removeCallbacksAndMessages (Object token),如果传入参数,具有和token相同obj的Message将被移除。如果token传入为null,则所有的callback和Message将被移除。
  2. 通过静态内部类+弱引用的形式解决问题。

1.子线程向主线程通讯

public HandlerActivity extends Activity{
	public static final int PRINT_THREAD_NAME_LOG = 0x00;
    Handler mHandler ;

    private static class MyHandler extends Handler {
        private  final WeakReference<SecondActivity> mWeakReference;
        public MyHandler(SecondActivity activity) {
            mWeakReference=new WeakReference<>(activity);
        }
        @Override
        public void handleMessage(Message msg) {
            final SecondActivity activity=mWeakReference.get();
            switch (msg.what) {
                case PRINT_THREAD_NAME_LOG:
                    if(activity!=null) {
                        Toast.makeText(activity, Thread.currentThread() + "", Toast.LENGTH_SHORT).show();
                    }
                    break;
                default:
                    break;
            }
        }
    }

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

        final SecondActivity activity = this;
        mHandler = new MyHandler(activity);
        new Thread(new Runnable() {
            @Override
            public void run() {
                mHandler.sendEmptyMessageDelayed(PRINT_THREAD_NAME_LOG,5000);
            }
        }).start();

    }
}

将Handler声明为静态类,这样Handler就不会持有外部类的引用,由于Handler不再持有外部类对象的引用,程序就不允许你在Handler中操作Activity的对象了,所以需要在Handler中增加一个对Activity的弱引用。

2.主线程向子线程通讯

public HandlerActivity extends Activity{
	public static final int PRINT_THREAD_NAME_LOG = 0x00;
    Handler mHandler ;

    private static class MyHandler extends Handler {
        private  final WeakReference<SecondActivity> mWeakReference;
        public MyHandler(SecondActivity activity) {
            mWeakReference=new WeakReference<>(activity);
        }
        public MyHandler(SecondActivity activity , Looper looper){
            super(looper);
            mWeakReference=new WeakReference<>(activity);
        }
        @Override
        public void handleMessage(Message msg) {
            final SecondActivity activity=mWeakReference.get();
            switch (msg.what) {
                case PRINT_THREAD_NAME_LOG:
                    if(activity!=null) {
                        Toast.makeText(activity, Thread.currentThread() + "", Toast.LENGTH_LONG).show();
                    }
                    break;
                default:
                    break;
            }
        }
    }

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

        final SecondActivity activity = this;

        HandlerThread thread = new HandlerThread("my thread");
        thread.start();

        mHandler = new MyHandler(activity, thread.getLooper());
        mHandler.sendEmptyMessageDelayed(PRINT_THREAD_NAME_LOG,1000);
    }
}

3.子线程和子线程通讯

public class HandlerActivity extends AppCompatActivity {

    public static final int PRINT_THREAD_NAME_LOG = 0x00;
    Handler mHandler ;

    private static class MyHandler extends Handler {
        private  final WeakReference<SecondActivity> mWeakReference;
        public MyHandler(SecondActivity activity) {
            mWeakReference=new WeakReference<>(activity);
        }
        public MyHandler(SecondActivity activity , Looper looper){
            super(looper);
            mWeakReference=new WeakReference<>(activity);
        }
        @Override
        public void handleMessage(Message msg) {
            final SecondActivity activity=mWeakReference.get();
            switch (msg.what) {
                case PRINT_THREAD_NAME_LOG:
                    if(activity!=null) {
                        Toast.makeText(activity, Thread.currentThread() + "", Toast.LENGTH_LONG).show();
                    }
                    break;
                default:
                    break;
            }
        }
    }




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

        final SecondActivity activity = this;

        new Thread("接收消息线程"){
            @Override
            public void run() {
                Looper.prepare();
                mHandler = new MyHandler(activity);
                Looper.loop();
            }
        }.start();

        new Thread("发送消息线程") {
            @Override
            public void run() {
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                mHandler.sendEmptyMessageDelayed(PRINT_THREAD_NAME_LOG,1000);
            }
        }.start();
    }
}

需要注意在子线程创建Handler一定要先确认Looper的prepare()和loop()的调用。在主线程不需要调用的原因在官方描述已经说了,主线程会自动创建。

最后说一下上面说到会提到的内容。
1.子线程真的不能更新UI么?
其实在特定情况下是可以的,但是不建议这么做。在onCreate()方法里直接创建子线程,通过子线程更新UI也是可以的,以为ViewRootImpl是在onResume方法中创建的,在调用checkThread方法之前,ViewRootImpl还没创建,不会检查当前线程是不是主线程,所以也不会报错,可以更新UI。
2.Messenger的使用
大家提到进程间通讯的时候大部分人第一个想到的会是AIDL其实通过Messenger也是可以完成进程间通讯的,而且还不用写AIDL文件,是不是感觉很爽?
这一块我暂时就不去写了给大家分享一篇我看过的Messenger使用与解析,说的听明白的。Android 进阶10:进程通信之Messenger使用与解析

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值