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

来源:互联网 发布:苹果电脑安装软件 编辑:程序博客网 时间:2024/05/01 10:21

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
原创粉丝点击