Android消息机制中Handler切换线程的思考

ThreadLocal作用

在当前线程中存放属于该线程的数据

ThreadLocal存储算法记录

将当前线程的ThreadLocal作为key,将存放的值作为value,使用当前线程内部Value的一个对象数组table存放,key的index为ThreadLocal的引用的hash和当前线程内部Value对象的mask(mask:用于将hash转化为指数(indices))相与的结果,存放的值的index为key的index+1

为什么不用一个ThreadManager去管理比如维护一个ConcrrentHashMap?主要有两个问题

1、防止线程竞争所带来的效率问题 2、防止线程引用被强引用而在线程完成工作后不能立即被回收

其实主要是第一个问题,第二个问题可以用WeakReference解决,但第一个问题会随着时间越来越大

ThreadLocal+Value解决方案

ThreadLocal的内部存储结构是给每一个Thread创建一个Value,用来保存自己的数据,这个Value内部维护一个数组用来存放当前线程的数据

线程的Value如果为空则通过任意ThreadLocal创建,因为是每一个线程都有自己的ThreadLocal所以不用担心线程同步的问题,Threadlocal的set、remove、get方法都是间接调用到了Value内部的方法,由于ThreadLocal仅存放Value的临时变量所以不会泄露,Value利用当前的ThreadLocal作为key计算出其在table中的index,key就存放在这个Index上,而其值则存放在index+1的位置,这样就使得每一个线程都具有其自己的数据,而且避免了多线程的竞争和内存泄露的问题


Handler与Looper

在子线程中new一个Handler时,如果不在构造参数中传入一个Looper,则必须在new之前调用Looper#prepare()方法为该子线程创建一个Looper,该方法内部使用ThreadLocal存储Looper,这样可以让每个线程拥有自己的Looper,Handler在构造器初始化时会调用Looper#myLooper()方法,该方法会从ThreadLocal中获取一个Looper对象,你也可以在构造器中传入一个looper对象,如果是通过Looper#getMainLooper()得到的looper对象,不需要再调用looper#loop(),因为系统已经调用过了,系统的looper已经开始不断遍历MessageQueue了,只要有新的Message到来,looper就会调用该Message持有的(Handler发送的Message,Message会持有Handler引用)Handler#handleMessage(Message msg)方法


系统创建Looper部分代码ActivityThread#main()

public static void main(String[] args) {
    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");
    SamplingProfilerIntegration.start();

    // CloseGuard defaults to true and can be quite spammy.  We
    // disable it here, but selectively enable it later (via
    // StrictMode) on debug builds, but using DropBox, not logs.
    CloseGuard.setEnabled(false);

    Environment.initForCurrentUser();
............

   Looper.prepareMainLooper();    ActivityThread thread = new ActivityThread();
    thread.attach(false);

    if (sMainThreadHandler == null) {
        sMainThreadHandler = thread.getHandler();
    }

    if (false) {
        Looper.myLooper().setMessageLogging(new
                LogPrinter(Log.DEBUG, "ActivityThread"));
    }

    // End of event ActivityThreadMain.
    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
    Looper.loop();    throw new RuntimeException("Main thread loop unexpectedly exited");
}

从Looper.loop()被调用起,UI线程就进入了消息的不断轮询中,一旦有新的消息过来,系统就会执行,android应用程序就这样跑起来了,这也是为什么UI线程中创建Handler不需要调用Looper#prepare()的原因


切换线程

切换线程实际上就是调用位置的切换而已,Handler切换线程的机制最终原因是handleMessage方法的调用位置的切换,比如主线程。

handler将线程切换到主线程

handler将自己的引用间接被Looper持有,当Looper在主线程调用loop()方法时,该方法会取出handler并调用其handleMessage()方法,相当于切换到主线程


handler--Looper--MessageQueue的关系

这三者构成Handler通信机制

MessageQueue内部使用单链表来存储信息Message,Handler发送的Message全部都添加到了MessageQueue中,Looper#loop()方法通过调用MessageQueue#next()方法不断遍历链表中的Message,当取得符合的Message后,通过Message持有的Handler对象引用调用Handler#handleMessage方法,如此,Looper#loop()在哪个线程调用的,handleMessage方法就切换到哪个线程了。

简单代码模拟实现handler切换线程到主线程的思想

Handler:

public class MHandler {

    MLooper mLooper;

    public MHandler(MLooper mLooper) {
        this.mLooper = mLooper;
    }
     public void sendMessage(MMessage msg) {
        //让msg保持MHandler的引用,同时让mLooper保持msg的引用,并在mLooper获取msg中的handler回调handleMessage,
        // 这样就能在mLooper的线程中调用handleMessage,相当于切换了线程
        msg.mHandler = this;
        mLooper.msg = msg;
        //主线程中的looper调用loop()后会处于阻塞状态,获取到msg同时间接获取到handler,此时msg不为null,
        // looper#loop()程序继续进行调用handlerMessage
    }

    public void handleMessage(MMessage msg) {

    }

}

Message(这里只模拟一个消息,所以就用Message代替MessageQueue)

/**
 * Created by zz on 16/3/6.
 * 如果只是切换线程的话,只需要让Looper等待Handler的引用到来然后在ui线程调用就可以
 * ,这里是为了模拟Handler发送信息,所以添加了一个Message
 */ public class MMessage {
    MHandler mHandler;
    String msg;
}
Looper: 
public class MLooper {
    //相当于把MessageQueue简化成一个msg了
    MMessage msg;


    public void loop() {
        while(msg == null) {
            //如果looper没有获取到msg,就让它一直等待
        }
        msg.mHandler.handleMessage(msg);
    }  }

调用代码

@Override protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_2);
    tv = (TextView) findViewById(R.id.activity2_text);
    final MLooper looper = new MLooper();

    tv.setClickable(true);
    tv.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            new Thread() {
                @Override
                public void run() {
                    MHandler handler = new MHandler(looper) {
                        @Override
                        public void handleMessage(MMessage msg) {
                            super.handleMessage(msg);
                            //主线程回调
                            tv.setTextSize(64);
                        }
                    };
                    MMessage msg = new MMessage();
                    msg.msg = "msg";
                    //子线程发送
                    handler.sendMessage(msg);
                }
            }.start();
            //在主线程调用,切换了线程
            looper.loop();
        }


    });  }

效果就是将一个TextView的字体大小由32sp变大到64sp,就不演示了



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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值