Android学习笔记18-聊聊Handler

今天我们来聊聊Android中的Handler机制
1、为什么使用Handler:

Handler是android中的一种异步消息处理机制
我们都知道,在android中子线程是不能刷新UI的,我们的解决方法是在子线程通过Handler发送消息
来更新UI界面(progressBar可以在子线程更新哦)。
我们一般的用法:
        public class MainActivity extends Activity {
            @Override
            protected void onCreate(Bundle savedInstanceState) {
                super.onCreate(savedInstanceState);
                setContentView(R.layout.activity_main);
                new Thread(){
                    @Override
                    public void run() {
                        Message message = new Message();
                        message.what = 0;
                        handler.sendMessage(message);//发送消息
                    }
                }.start();

            }

            //这里我们采用匿名内部类了,正常我们new一个对象是 new Handler();
            //后面加了{}就是匿名内部类,为方便。当然你也可以创建一个class
            public Handler handler = new Handler(){
                @Override
                public void handleMessage(Message msg) {
                    switch (msg.what) {
                    case 0:
                        System.out.println("接受到了消息");
                        break;
                    default:
                        break;
                    }
                }
            };
        }
这样写没有问题,但是我们eclipse会报一个警告错误:
This Handler class should be static or leaks might occur (com.example.testhandler.MainActivity.1)
提示我们最好定义为静态的,否则可能导致内存泄漏。

这里写图片描述

特别注意:Handler里面的handMessage方法是运行在主线程的,你在这个里面做耗时操作也会导致ANR异常的。

2、Handler为什么会导致内存泄漏呢?

java内存回收机制:
    这个涉及到java的内存回收机制(GC)。
    如果GC发现一个或一组对象为不可到达状态,则将该对象从内存中回收。
    也就是说,一个对象不被任何引用所指向,则该对象会在被GC发现的时候被回收;
    另外,如果一组对象中只包含互相的引用,而没有来自它们外部的引用
    (例如有两个对象A和B互相持有引用,但没有任何外部对象持有指向A或B的引用),这仍然属于不可到达,同样会被GC回收。

分析Handler为什么导致
    当使用内部类(包括匿名类)来创建Handler的时候,Handler对象会隐式地持有一个外部类对象
    (通常是一个Activity)的引用(不然你怎么可能通过Handler来操作Activity中的View?)。
第一种情况:
    Handler里面有可能会伴随着一个耗时的后台线程(例如从网络拉取图片)一起出现,
    这个后台线程在任务执行完毕(例如图片下载完毕)之后,通过消息机制通知Handler,然后Handler把图片更新到界面。
    如果用户在网络请求过程中关闭了Activity,正常情况下,Activity不再被使用,它就有可能在GC检查时被回收掉,但由于这时线程尚未执行完,
    而该线程持有Handler的引用(不然它怎么发消息给Handler?),这个Handler又持有Activity的引用,
    就导致该Activity无法被回收(即内存泄露)。直到网络请求结束(例如图片下载完毕)。
第二种情况:
    如果你执行了Handler的postDelayed()方法,该方法会将你的Handler装入一个Message,并把这条Message推到MessageQueue中,
    那么在你设定的delay到达之前,会有一条MessageQueue -> Message -> Handler -> Activity 的链,导致你的Activity被持有引用而无法被回收。

总结:导致内存泄漏说白了就是handler持有activity的应用,activity被finish掉了,里面的资源得不到释放,导致内存泄漏。
我们把handler定义为静态的,这个handler就不会持有activity的引用了,就不存在什么内存泄漏。

3、我们把Handler定义为静态的:

    static class MyHandler extends Handler {  
        @Override  
        public void handleMessage(Message msg) {  
            tv.setText("hello");//必须是静态的
        }  
    } 
发现一个问题,我们在handler中所有的变量都必须是static静态的,这个我们不会将我们所用到的变量都定义为static
这个时候我们就出现了弱引用概念。

4、什么是弱引用:

WeakReference弱引用,与强引用(即我们常说的引用)相对。
它的特点是,GC在回收时会忽略掉弱引用,即就算有弱引用指向某对象,但只要该对象没有被强引用指向
(实际上多数时候还要求没有软引用,但此处软引用的概念可以忽略),该对象就会在被GC检查到时回收掉。

对于上面的代码,用户在关闭Activity之后,就算后台线程还没结束,但由于仅有一条来自Handler的弱引用指向Activity,
所以GC仍然会在检查的时候把Activity回收掉。这样,内存泄露的问题就不会出现了。

例如我们改造后的代码:
        public class TestHanlderActivity extends Activity {
            private TextView tv;

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

                tv = (TextView) findViewById(R.id.tv);
                new Thread(){
                    @Override
                    public void run() {
                        Message message = new Message();
                        message.what = 0;
                        handler.sendMessage(message);//发送消息
                    }
                }.start();
            }

            public Handler handler = new MyHandler(this) ;//定义handler,将activity传进去
            static class MyHandler extends Handler{
                final WeakReference<TestHanlderActivity> mActivity;

                MyHandler(TestHanlderActivity activity){
                    //使用传进来的activity初始化这个弱引用
                    mActivity = new WeakReference<TestHanlderActivity>(activity);
                }

                @Override
                public void handleMessage(Message msg) {
                    //拿到activity,
                    TestHanlderActivity activity = mActivity.get();
                    if(null != activity){
                        switch (msg.what) {
                        case 0:
                            //可以使用activity中的非静态的变量啦
                            activity.tv.setText("这个是静态的Handler");
                            break;
                        default:
                            break;
                        }
                    }
                }
            }
        }
总结:
    a.我们在主线程去new一个静态的Handler,不管我们new多少个,都是指向同一个Handler。因为static内存中只保留一份。
        如果我们不定义static,你每次new一个Handler就会在堆内存中创建一个对象,也是耗费内存的。
    b.我们一个线程中只能有一个Looper和一个MessageQueue,所以不管你new多少个handler,都是把消息发送到同一个MessageQueue消息队列中
        Looper也只是从这个消息队列中取消息。

到这里我们完美解决了handler使用的问题。下面我们从源码分析下Handler的运行机制

5、源码分析Handler的运行机制:

a.应用的入口函数
以前一直都说Activity的人口是onCreate方法。其实Android上一个应用的入口,应该是ActivityThread。和普通的Java类一样,入口是一个main方法。
    public static final void main(String[] args) {
        SamplingProfilerIntegration.start();
       ……
        Looper.prepareMainLooper();
        if (sMainThreadHandler == null) {
            sMainThreadHandler = new Handler();
        }
        ActivityThread thread = new ActivityThread();
        thread.attach(false);
       ……
        Looper.loop();
       ……
        thread.detach();
        ……
        Slog.i(TAG, "Main thread of " + name + " is now exiting");
    }
我们的应用入口,先调用了下prepareMainLooper,然后就调用了Looper.loop()进入了无限循环。
所以只要我们应用不关闭,Looper就会一直从消息队列中拿消息来处理。
这个是运行在主线程中的,别人可能会疑惑,为什么什么在主线程中loop不会导致主线程阻塞呢?
    你想一下,如果主线程没有loop,执行完就完了,你的应用不就挂了吗,又何来阻塞一说。

b.主线程中默认就创建了个Looper 和MessageQueue
    那么我们创建了Handler做了什么呢?
    我们new Handler(); 调用了Handler的无参构造方法
    android.os.Handler.class里面的代码:(我查看的是API19 android4.4)

    Handler中的源码
            /**
             * Default constructor associates this handler with the queue for the
             * current thread.
             *
             * If there isn't one, this handler won't be able to receive messages.
             */
            public Handler() {
                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();//这里我们拿到Looper对象,赋值给了final Looper mLooper; 就相当于我们拿到了主线程的Looper
                if (mLooper == null) {
                    throw new RuntimeException(
                        "Can't create handler inside thread that has not called Looper.prepare()");
                }
                mQueue = mLooper.mQueue;//我们拿到Looper中的消息队列 final MessageQueue mQueue;
                mCallback = null;//我们没有传Callback
            }
    mLooper = Looper.myLooper();我们看看干了什么

    android.os.Looper源码:
            static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
            //sThreadLocal是一个ThreadLocal对象,可以在一个线程中存储变量。(我们可以理解一个线程的局部变量,里面保存了Looper)
            final MessageQueue mQueue;
            public static Looper myLooper() {
                return sThreadLocal.get();//拿到Looper对象
            }
c.我们进入应用就会调用prepare方法
        private Looper() {
            mQueue = new MessageQueue();
            mRun = true;
            mThread = Thread.currentThread();
        }
    在构造方法中,创建了一个MessageQueue(消息队列)。
        public static void prepareMainLooper() {
            prepare();
            setMainLooper(myLooper());
            myLooper().mQueue.mQuitAllowed = false;
        }
        public static void prepare() {
            if (sThreadLocal.get() != null) {
                throw new RuntimeException("Only one Looper may be created per thread");
            }
            sThreadLocal.set(new Looper());
        }
    我们看loop()方法:
        public static void loop() {
            Looper me = myLooper();//拿到存储的Looper实例,如果me为null则抛出异常,也就是说looper方法必须在prepare方法之后运行
            if (me == null) {
                throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
            }
            MessageQueue queue = me.mQueue;//拿到该looper实例中的mQueue(消息队列)

            // Make sure the identity of this thread is that of the local process,
            // and keep track of what that identity token actually is.
            Binder.clearCallingIdentity();
            final long ident = Binder.clearCallingIdentity();

            while (true) {//就进入了我们所说的无限循环
                Message msg = queue.next(); // might block
                if (msg != null) {
                    if (msg.target == null) {
                        // No target is a magic identifier for the quit message.
                        return;
                    }

                    long wallStart = 0;
                    long threadStart = 0;

                    // This must be in a local variable, in case a UI event sets the logger
                    Printer logging = me.mLogging;
                    if (logging != null) {
                        logging.println(">>>>> Dispatching to " + msg.target + " " +
                                msg.callback + ": " + msg.what);
                        wallStart = SystemClock.currentTimeMicro();
                        threadStart = SystemClock.currentThreadTimeMicro();
                    }

                    msg.target.dispatchMessage(msg);//把消息交给msg的target的dispatchMessage方法去处理

                    if (logging != null) {
                        long wallTime = SystemClock.currentTimeMicro() - wallStart;
                        long threadTime = SystemClock.currentThreadTimeMicro() - threadStart;

                        logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
                        if (logging instanceof Profiler) {
                            ((Profiler) logging).profile(msg, wallStart, wallTime,
                                    threadStart, threadTime);
                        }
                    }

                    // Make sure that during the course of dispatching the
                    // identity of the thread wasn't corrupted.
                    final long newIdent = Binder.clearCallingIdentity();
                    if (ident != newIdent) {
                        Log.wtf(TAG, "Thread identity changed from 0x"
                                + Long.toHexString(ident) + " to 0x"
                                + Long.toHexString(newIdent) + " while dispatching to "
                                + msg.target.getClass().getName() + " "
                                + msg.callback + " what=" + msg.what);
                    }

                    msg.recycle();//释放消息占据的资源
                }
            }
        }
    所以:loop()方法,不断从MessageQueue中去取消息,交给消息的target属性的dispatchMessage去处理。

我们看看怎么取的消息:Message msg = queue.next(); // might block
这个代码在MessageQueue中:
        Message next() {
          for (;;) {
            Message prevMsg = null;
            Message msg = mMessages;

            if (prevMsg != null) {    
              prevMsg.next = msg.next;
            } else {    
              mMessages = msg.next;
            }
            msg.next = null;//这个的目的就是为了释放上一个message的引用便于垃圾回收
            return msg;
          }
        }
示意图

这里写图片描述

d.现在我们看看handler.sendMessage(message);//发送消息

        public final boolean sendMessage(Message msg)
        {
            return sendMessageDelayed(msg, 0);
        }

        public final boolean sendMessageDelayed(Message msg, long delayMillis)
        {
            if (delayMillis < 0) {
                delayMillis = 0;
            }
            return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
        }

        public boolean sendMessageAtTime(Message msg, long uptimeMillis)
        {
            boolean sent = false;
            MessageQueue queue = mQueue;
            if (queue != null) {
                msg.target = this;
                sent = queue.enqueueMessage(msg, uptimeMillis);
            }
            else {
                RuntimeException e = new RuntimeException(
                    this + " sendMessageAtTime() called with no mQueue");
                Log.w("Looper", e.getMessage(), e);
            }
            return sent;
        }
    我们可以看到最终调用到了sent = queue.enqueueMessage(msg, uptimeMillis);
    把消息放到消息队列中.
         final boolean enqueueMessage(Message msg, long when) {
            if (msg.isInUse()) {
                throw new AndroidRuntimeException(msg
                        + " This message is already in use.");
            }
            if (msg.target == null && !mQuitAllowed) {
                throw new RuntimeException("Main thread not allowed to quit");
            }
            final boolean needWake;
            synchronized (this) {
                if (mQuiting) {
                    RuntimeException e = new RuntimeException(
                        msg.target + " sending message to a Handler on a dead thread");
                    Log.w("MessageQueue", e.getMessage(), e);
                    return false;
                } else if (msg.target == null) {
                    mQuiting = true;
                }

                msg.when = when;
                //Log.d("MessageQueue", "Enqueing: " + msg);
                Message p = mMessages;
                if (p == null || when == 0 || when < p.when) {
                    msg.next = p;
                    mMessages = msg;
                    needWake = mBlocked; // new head, might need to wake up
                } else {
                    Message prev = null;
                    while (p != null && p.when <= when) {
                        prev = p;
                        p = p.next;
                    }
                    msg.next = prev.next;
                    prev.next = msg;
                    needWake = false; // still waiting on head, no need to wake up
                }
            }
            if (needWake) {
                nativeWake(mPtr);
            }
            return true;
        }
    主要是这个代码把消息放到消息队列中
    Message p = mMessages;
    msg.next = p;
    mMessages = msg;
    我们画图示意一下
    为什么要Message p = mMessages; 然后再把p赋值给msg.next呢?
    经过我自己代码测试,p和mMessages都是个内存地址,一样的。只是可能为了在后面用到方便点,不用写这么长吧。
    msg.next = mMessages;
    mMessages = msg;
    //效果是一样

这里写图片描述

    msg.target = this;//关键的一句代码,把msg的target设置为当前的handler,在loop中我们就回调对应的handler的handMessage方法。

6、handler在子线程中使用:

我们在子线程中使用Handler可以,但是你必须手动调用prepare和loop方法
因为主线程在main函数调用了,所以不需要。
    class MyThread extends Thread {
        public Handler mHandler;
        @Override
        public void run() {

            Looper.prepare();
            mHandler = new Handler() {
                public void handleMessage(Message msg) {
                    System.out.println("hello handler");
                }
            };
            System.out.println("即将开启loop");
            for (int i = 0; i < 10; i++) {
                System.out.println(i + "");
            }
            Looper.loop();
            System.out.println("已经开启loop");
        }
    }
特别注意:
你在loop之后的代码是不会执行到了,因为loop了,就相当你的子进程一直阻塞在这里了。
所以我们一般没必要在子线程中取定义Handler
没有什么应用场景。

一个网上摘下的Handler流程图
发消息
这里写图片描述

主线程处理消息
这里写图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值