Handler——深入理解android消息机制

android 重要核心知识点,怎么深刻理解都不为过,本篇博客从常用api ,Looper Hanldery以及HanlderTread源码角度解读

1 Handler使用

 public class MainActivity extends Activity {

    private final String TAG = "HandlerActivity.class";

    private Handler handler = new Handler() {
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case 1:
                    bt.setText("正在下载...");
                    break;
                case 2:
                    Bitmap bm = (Bitmap) msg.obj;
                    iv.setImageBitmap(bm);
                    break;
                case 3:
                    Bundle bundle = msg.getData();
                    String data = bundle.getString("text");
                    bt.setText(data);
                    break;
                case 4:
                    Log.d(TAG, "arg1------" + msg.arg1);
                    Log.d(TAG, "arg2------" + msg.arg2);
                    Person person = (Person) msg.obj;
                    if (person != null) {
                        Log.d(TAG, "person.age------" + person.getAge());
                        Log.d(TAG, "person.name------" + person.getName());
                    }
                    break;

            }
        }
    };


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

        new Thread(new Runnable() {
            @Override
            public void run() {
                /**
                 * 传递简单Integer类型消息
                 */
                Message message = new Message();
                message.arg1 = 1;
                message.arg2 = 2;

                /**
                 * 传递复杂消息
                 */
                Person person = new Person();
                person.setAge(555);
                person.setName("Liu");
                message.obj = person;
                mHandler.sendMessage(message);

                Message msg = new Message();
                msg.what = 1;//魔鬼数字,避免
                handler.sendMessage(msg);
//或者
                handler.sendMessageDelay(what);
//或者
                handler.sendEmptyMessage(what);
//如果要带一个obj
                Message msg = new Message();
                msg.what = 2;
                msg.obj = bm;
                handler.sendMessage(msg);
//带一个bundle
                Message msg = new Message();
                Bundle data = new Bundle();
                data.putString("text", "正在下载...");
                msg.what = 3;
                msg.setData(data);
                handler.sendMessage(msg);
//通过obtainMessage能避免重复Message创建对象
                handler.sendEmptyMessage(1);
                handler.sendEmptyMessageDelayed(1, 1000);
                handler.sendEmptyMessageAtTime(1, 1000);
                handler.sendMessage(Message.obtain());
                handler.sendMessageAtTime(Message.obtain(), 100);
                handler.sendMessageDelayed(Message.obtain(), 100);
//获取Message的另一种方式,看下源码就好,很简单
                Message message = handler.obtainMessage();
                message.sendToTarget();
            }
        }).start();
    }
}

post发送

mHandler.post(new Runnable()  
        {  
            @Override  
            public void run()  
            {  
                Log.e("TAG", Thread.currentThread().getName());  
                mTxt.setText("yoxi");  
            }  
        }); //并未创建线程,与handler同一个线程当然还有postDelayed方法

各种发送消息图解
在这里插入图片描述

2 原理分析

我们知道android UI线程(主线程)更新ui信息,为什么要这么做呢?

解决多线程并发问题,

  • 加入一个activity有多个线程去更新ui,都没有枷锁,会导致 更新界面错乱
  • 如果对更新ui的操作都进行加锁处理,会导致 性能下降
    基于以上考虑 android给我们提供了一套更新ui的机制,我们只需要遵守这样的机制就可以了。不用关系多线程问题,因为所有更新ui的操作, 都是主线程的消息队列当中通过轮询处理的。

2.1 Handler 机制工作原理

handler要想起作用有三个步骤

  1. 创建Looper
  2. 创建Handler
  3. 调用Looper的loop方法,循环消息

先普及一个知识,整个应用是通过AcitivityThread类启动的,在ActivityThread类当中,负责创建所有的Activity,并回调Acitivity中的生命周期方法,在ActivityThread类中,默认会去创建一个线程,整个线程叫做main线程,主线程,ui线程,

主线程的创建Looper和调用loop方法的工作,android sdk已经为我们做好了,平时用的时候创建Hander并发送消息即可。
从ActivityThread的main方法开始看起。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述sThreadLocal是一个ThreadLocal对象,**可以在一个线程中存储变量。**可以看到,将一个Looper的实例放入了ThreadLocal,并且判断了sThreadLocal是否为null,否则抛出异常。这也就说明了Looper.prepare()方法不能被调用两次,同时也保证了一个线程中只有一个Looper实例.

Looper构造器

private Looper(boolean quitAllowed) {  
        mQueue = new MessageQueue(quitAllowed);  
        mRun = true;  
        mThread = Thread.currentThread();  
} 

在构造方法中,创建了一个MessageQueue(消息队列)。也就是说默认的情况下,android为我们自动创建了主线程的Looper和MessageQuene。

那么Handler怎么和我们的MessageQuene消息队列联系在一起的那?因为之前不是说handler发出的消息是发送到消息队列中了吗?

原因还要看我们在创建Handler的时候做了那些事情,跟进Handler初始化源码发现最终调用的是下面这个构造器创建实例的

public Handler(Callback callback, boolean async) {
        if (FIND_POTENTIAL_LEAKS) {
            final Class<? extends Handler> klass = getClass();
            if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
                    (klass.getModifiers() & Modifier.STATIC) == 0) {
                Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
                    klass.getCanonicalName());
            }
        }

        mLooper = Looper.myLooper();//注意!!!!!!
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread that has not called Looper.prepare()");
        }
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }

跟进Looper的myLooper方法

    public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
    }

看到了什么?sThreadLocal是不是很熟悉,没错它就是ThreadLocal对象。默认情况下android为我们创建了主线程Looper对象并存储在sThreadLocal中,所以此处返回的就是主线程的Looper对象,也就是说我们在创建Handler的时候,它就和消息队列关联起来了。

那么当我们使用handler发送消息的时候,不管使用哪一种方法,一步一步跟进源码发现最终调用的都是Handler的sendMessageAtTime方法

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);
    }

看代码可知,在发送消息的时候消息队列不能为null,继续跟进enqueueMessage方法

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        msg.target = this;
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }

可以看到,消息最终发送到了消息队列当中。那么消息是怎么轮训的那?前面已经提过,是通过Looper的loop方法,那么来看看吧!!!
在这里插入图片描述可以看到loop方法里面的机制就是一个死循环,不断的从消息队列中取出消息,然后发送给target(handler对象)的dispatchMessage方法,跟进去!!!

在这里插入图片描述一般情况下我们发送消息的时候没有给Message的callback赋值,所以第一个if条件不满足。下面的mCallback是在我们初始化Handler的时候才被初始化,Handler初始化有一种方法Handler(Callback callback),此处的参数就是给mCallback赋值的。我们一般初始化Handler的时候使用的是空参数的构造器,所以导致mCallback未被初始化,所以会直接走handleMessage(msg)方法,也就是我们初始化Handler时重写的handleMessage方法。至此,Handler工作的机制就开始工作了,你、了解了吗?

下面让我们看看如果我们选择的是带Callback参数的初始化方式逻辑又会是什么样那,请看初始化代码:

Handler mHandler = new Handler(new Handler.Callback() {
        @Override
        public boolean handleMessage(Message msg) {
            Log.d(TAG,"callback参数-------handleMessage");
            return true;//此处的返回值会影响下面的handleMessage方法是否调用
            //false     调用
            //true      不调用
        } }){
        @Override
        public void handleMessage(Message msg) {
            Log.d(TAG,"重写handler的-------handleMessage方法");
            super.handleMessage(msg);

        }
    };

根据上面的源码分析我们知道此处Callback参数中的handleMessage方法的返回值会影响到下面第二个handleMessage方法是否调用。经过验证,return true 则不调用 ,return false则调用。

最会通过一张图,看一下Handler的原理:
在这里插入图片描述在这里插入图片描述

3 handler处理消息一定要在主线程吗

new Thread() {
            private Handler handler;

            public void run() {

                Looper.prepare();//注意此行代码

                handler = new Handler() {
                    public void handleMessage(android.os.Message msg) {
                        Log.e("TAG", Thread.currentThread().getName());
                    }
                };
            }
            Looper.loop();
        }.start();

直接new Handler()默认为当前线程,如果用于刷新ui,Handler handler = new Handler(Looper.getMainLooper());也是可以的,不过毫无意义。
上一章介绍了Hander机制的工作原理,默认情况下Activity为我们创建了主线程的Looper和消息队列,所以在主线程创建Handler,发送消息的时候,消息的轮询和handle都是在主线程进行的。这种情况属于子线程给主线程发消息,通知主线程更新ui,那么反过来,怎么才能让主线程给子线程发消息,通知子线程做一些耗时操作。
之前学习,android消息机制三个步骤

  • 创建当前线程Looper
  • 创建当前线程的Handler
  • 调用Looper的loop方法。

之所以强调当前线程,只因为上一章都是android为我们做好的,譬如创建主线程Looper,主线程消息队列。如果非主线程发送、处理消息应该怎么办?

public class ChildThreadHandlerActivity extends Activity {
    private MyThread childThread;

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

        childThread = new MyThread();
        childThread.start();

        Handler childHandler = new Handler(childThread.childLooper){//这样之后,childHandler和childLooper就关联起来了。
            public void handleMessage(Message msg) {
                
            };
        };
    }

    private class MyThread extends Thread{
        public Looper childLooper;

        @Override
        public void run() {
            Looper.prepare();//创建与当前线程相关的Looper
            childLooper = Looper.myLooper();//获取当前线程的Looper对象
            Looper.loop();//调用此方法,消息才会循环处理
        }
    }
}

代码如上,我们依然循序Android的三步走战略,完成了子线程Handler的创建,难道这样创建完了,就可以发消息了么?发的消息在什么线程处理?一系列的问题,怎么办?看代码!!!运行上述代码,我们发现一个问题,就是此代码一会崩溃、一会不崩溃,通过查看日志我们看到崩溃的原因是空指针。谁为空???查到是我们的Looper对象,怎么会那?我不是在子线程的run方法中初始化Looper对象了么?话是没错,但是你要知道,当你statr子线程的时候,虽然子线程的run方法得到执行,但是主线程中代码依然会向下执行,造成空指针的原因是当我们new Handler(childThread.childLooper)的时候,run方法中的Looper对象还没初始化。当然这种情况是随机的,所以造成偶现的崩溃。

那怎么办?难道我们不能创建子线程Handler ???No!!!No!!!No!!!,你能想到的Android早就为我们实现好了,HandlerThread类就是解决这个问题的关键所在,看代码!!!

public class HandlerThreadActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_handler);
        TextView textView = (TextView) findViewById(R.id.tv);
        textView.setText("HandlerThreadActivity.class");

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

        Handler mHandler = new Handler(handlerThread.getLooper()){
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                Log.d("HandlerThreadActivity.class","uiThread2------"+Thread.currentThread());//子线程
            }
        };

        Log.d("HandlerThreadActivity.class","uiThread1------"+Thread.currentThread());//主线程
        mHandler.sendEmptyMessage(1);
    }
}

创建HandlerThread对象的时候,有个参数,是指定线程名字的。上面的代码不管运行多少次都不会奔溃!!!并且这种方法创建的handler的handleMessage方法运行在子线程中。所以我们可以在这里处理一些耗时的逻辑。到此我们完成了主线程给子线程发通知,在子线程做耗时逻辑的操作。

下面我们去看看源码,看看为什么使用HandlerThread就可以避免空指针那?

HandlerThread的getLooper方法

 public Looper getLooper() {
        if (!isAlive()) {
            return null;
        }
        
        // If the thread has been started, wait until the looper has been created.
        synchronized (this) {
            while (isAlive() && mLooper == null) {
                try {
                    wait();
                } catch (InterruptedException e) {
                }
            }
        }
        return mLooper;
    }

HandlerThread类的getLooper方法如上,我们看到当我们获取当前线程Looper对象的时候,会先判断当前线程是否存活,然后还要判断Looper对象是否为空,都满足之后才会返回给我Looper对象,否则处于等待状态!!既然有等待,那就有唤醒的时候,在那里那???我们发现HandlerThread的run方法中,有如下代码:

@Override
    public void run() {
        mTid = Process.myTid();
        Looper.prepare();
        synchronized (this) {
            mLooper = Looper.myLooper();
            notifyAll();
        }
        Process.setThreadPriority(mPriority);
        onLooperPrepared();
        Looper.loop();
        mTid = -1;
    }

HandlerThead常用场景,定时更新ui

public class HandlerThreadActivity extends Activity {

    private TextView mTvServiceInfo;

    private HandlerThread mCheckMsgThread;
    private Handler mCheckMsgHandler;
    private boolean isUpdateInfo;

    private static final int MSG_UPDATE_INFO = 0x110;
    //与UI线程管理的handler
    private Handler mHandler = new Handler();


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_thread_handler);
        mTvServiceInfo = (TextView) findViewById(R.id.id_textview);

        mCheckMsgThread = new HandlerThread("check-message-coming");
        mCheckMsgThread.start();
        mCheckMsgHandler = new Handler(mCheckMsgThread.getLooper()) {
            @Override
            public void handleMessage(Message msg) {

                try {
                    //模拟耗时
                    Thread.sleep(1000);
                    mHandler.post(new Runnable() {
                        @Override
                        public void run() {
                            String result = "实时更新中,当前大盘指数:<font color='red'>%d</font>";
                            result = String.format(result, (int) (Math.random() * 3000 + 1000));
                            mTvServiceInfo.setText(Html.fromHtml(result));
                        }
                    });

                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                if (isUpdateInfo) {
                    mCheckMsgHandler.sendEmptyMessageDelayed(MSG_UPDATE_INFO, 1000);
                }
            }
        };
    }

    @Override
    protected void onResume() {
        super.onResume();
        //开始查询
        isUpdateInfo = true;
        mCheckMsgHandler.sendEmptyMessage(MSG_UPDATE_INFO);
    }

    @Override
    protected void onPause() {
        super.onPause();
        //停止查询
        isUpdateInfo = false;
        mCheckMsgHandler.removeMessages(MSG_UPDATE_INFO);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        //释放资源
        mCheckMsgThread.quit();
    }
}

4 handler造成内存泄漏

public class MemoryLeakActivity extends Activity {

    private MyHandler mHandler;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_handler);

        mHandler = new MyHandler();
        mHandler.sendEmptyMessage(1);
    }

    private class MyHandler extends Handler{
        
        public MyHandler(){
        }
        
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
        }
    }
}

handler对象作为成员变量保存在发送的消息msg中,即msg持有handler的引用,而handler是activity的内部类实例,即handler持有acitivity的引用,那么可以理解为msg间接持有acitivyt的引用,msg被发送到下线队列msgqueue中,然后等待Looper的轮询处理,(msgqueue和looper都是线程相关联的,msgqueue是looper的成员变量,looper保存在ThreadLocal中)acitivity退出后 ,msg可能仍然存在消息队列中未处理,或者正在处理,导致内存泄漏。

public abstract class WeakHandler<T> extends Handler {

    protected WeakReference<T> reference;

    //创建子线程Handler使用的构造器
    public WeakHandler(Looper looper, T reference) {
        super(looper);
        this.reference = new WeakReference<>(reference);
    }

    //创建主线程Handler使用的构造器
    public WeakHandler(T reference) {
        this.reference = new WeakReference<>(reference);
    }

    @Override
    public void handleMessage(Message msg) {
        super.handleMessage(msg);
        T t = reference.get();
        if (t == null)
            return;
        handleMessage(t, msg);
    }

    protected abstract void handleMessage(T t, Message message);

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值