android学习笔记----Handler的使用、内存泄漏、源码分析等一系列问题

记录一下自己参考的文章,方便回忆复习。

学习路径,先看慕课网视频:https://www.imooc.com/learn/267

本篇笔记将深入理解Android的Handler机制,并结合源码实例,讲解Looper、MessageqQueue、Handler之间的关系,和Handler的内存泄漏问题和解决办法。

因为是初学者,就先从慕课网上看这个视频,发现视频讲解比较精简,看了一半到分析Handler、Looper,MessageQueue三者之间的关系的时候有点蒙,然后去查看其它资料了,发现资料博客讲的详细,接着综合别人的讲解和自己去翻看源码理解就比较深入了。

目录

深入Android的消息机制源码详解~Handler,MessageQueue与Looper关系

自定义与线程相关的Handler代码示例:

问题代码:Handler和Looper深入思考~~~~为何不报错??

Handler内存泄漏详解及其解决方案

扩展知识阅读:关于强引用、软引用、弱引用、虚引用的概念


 

 

笔记关联: android学习笔记----HandlerThread学习

 

深入Android的消息机制源码详解~Handler,MessageQueue与Looper关系

https://blog.csdn.net/javazejian/article/details/50791598

备注:这篇文章从源码的角度分析了Handler、MessageQueue、Looper,跟着看了几遍收获很大。就是文章字体有点小,放大即可。

看完后再看一篇Android源码分析-消息队列和Looper

https://blog.csdn.net/singwhatiwanna/article/details/17361775

备注:这篇文章是《Android开发艺术探索》的作者任玉刚的博客中的一篇,适合把握整体大纲。

接着来一篇补充:

https://blog.csdn.net/u011240877/article/details/72892321

备注:这篇文章的亮点在于message源码的讲解过程。

批注:消息队列和Looper的工作机制
一个Handler会有一个Looper,一个Looper会有一个消息队列,Looper的作用就是循环的遍历消息队列,如果有新消息,就把新消息交给它的目标处理。每当我们用Handler来发送消息,消息就会被放入消息队列中,然后Looper就会取出消息发送给它的目标target。一般情况,一个消息的target是发送这个消息的Handler,这么一来,Looper就会把消息交给Handler处理,这个时候Handler的dispatchMessage方法就会被调用,一般情况最终会调用Handler的handleMessage来处理消息,用handleMessage来处理消息是我们常用的方式。

现在到了自问自答环节:

为什么Android设计只能UI线程更新UI? 

      ①android的UI控件不是线程安全的,多线程并发访问可能导致UI控件处于不可预期状态,即UI界面更新混乱。

      ②如果加上锁机制,缺点有两个,一是加上锁会让UI访问逻辑变得复杂,二是锁机制会降低UI访问效率,锁会阻塞某些线程的执行。

       综上,采用单线程模型来处理UI操作,用Handler切换到指定线程执行。

消息队列MessageQueue对象在什么时候创建?

       在Looper的构造方法中创建,只要new Looper对象,就会附带new MessgaeQueue对象。

那么Looper对象是在什么时候创建的呢?

       调用静态方法Looper.prepare()方法的时候创建的,如下:

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

Handler和Looper公用一个消息队列吗? 

       是的,在Handler的构造方法中,一定会将Handler对象里的引用指向Looper对象里的消息队列对象,所以共用一个消息队列对象。

Handler和Looper关联吗?那他们是怎么关联的?

       Handler里面有Looper引用,Handler一定与Looper关联,在Handler构造方法中,mLooper = Looper.myLooper();会从ThreadLocal中get获取Looper对象,此时就将Handler对象与Looper对象相关联。

Looper.loop()是怎么工作的?

       loop()方法它里面有一个死循环for(;;),然后这里Message msg = queue.next(); // might block

       没有消息的时候会阻塞,一旦有消息就会从消息队列中取出消息。然后会调用msg.target.dispatchMessage(msg);这里target一般是handler对象。

handler.sendMessage方法和handler.post方法在干嘛?

       不管是sendMessage方法还是post的一些方法,最终都会执行到sendMessageAtTime方法,在这个方法里,会有消息进队列的操作,而Looper.loop()不断的尝试从消息队列取出消息执行,每取出一条消息,就会尝试执msg.target.dispatchMessage(msg)

总结:用post发送消息时,Handler对象绑定了哪个Looper,那么post参数传入的Runnable对象就在Looper属于的那个线程执行,直接调用的run()而不是start()。用handler.sendMessage方法发送消息时也是看Looper在哪个线程,handleMessage这个回调方法就在哪个线程执行。所以不管是sendMessage和post方法,均要看对应的Handler对象各自绑定的Looper属于哪个线程对应的方法就在哪个线程执行

 

那么dispatchMessage是怎么运行的?


    public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            // 这个方法很简单,直接调用msg.callback.run();这个callback就是传进来的Runnable对象
            // 比如handler.post(myRunnable);这个myRunnable实现了Runnable接口,run()方法执行想做的操作
            handleCallback(msg);
        } else {
            //如果我们设置了mCallback会由mCallback来处理消息
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) { // 如果返回true,拦截,直接return
                    return;
                } // 返回false,不拦截,执行下面的handleMessage(msg)
            }
            //否则消息就由这里来处理,这是我们最常用的处理方式
            handleMessage(msg);
        }
    }

        如果调用的是handler的post方法,那么会调用handleCallback方法,里面会执行message.callback.run();这个callback就是传入进来的Runnable对象,见源码,handler.post是传入Runnable对象,将这个对象封装在一个Message对象里,而这个Message是在handler的getPostMessage方法创建的局部变量,然后Message对象的callback = r(Runnale对象),post开头的方法都会调用sendMessage开头的方法,传入之前都会调用getPostMessage方法。

    public final boolean post(Runnable r)
    {
       return  sendMessageDelayed(getPostMessage(r), 0);
    }    
    public final boolean postDelayed(Runnable r, long delayMillis)
    {
        return sendMessageDelayed(getPostMessage(r), delayMillis);
    }
    private static Message getPostMessage(Runnable r) {
        Message m = Message.obtain();
        m.callback = r;  // ===============就是这里
        return m;
    }

注意,这里执行的是message.callback.run(),是run方法而不是start方法,也就是要在当前调用run()的对象的线程运行,也就是消息队列所在的线程,也就是Looper所在的线程。这里的示例是main线程。

       如果调用的是handler的sendMessage系列方法,则需要看创建Handler的时候,构造方法是否有参数,如果有参数并且是Handler的内部接口Callback匿名内部类实现的实例对象,会执行dispatchMessage方法里的mCallback.handleMessage(msg)。

而这个mCallback就是这里的匿名对象new Handler.Callback(){...}

    private Handler handler = new Handler(new Handler.Callback() {
        @Override
        public boolean handleMessage(Message msg) {
            Toast.makeText(MainActivity.this, "拦截了吗", Toast.LENGTH_SHORT).show();
            return true;
        }
    }) {
        @Override
        public void handleMessage(Message msg) {
            // textView.setText(msg.arg1 + "---" + msg.arg2);
            Toast.makeText(MainActivity.this, "没拦截到,我出来显示", Toast.LENGTH_SHORT).show();
            //textView.setText(msg.obj.toString());
        }
    };

 那么需要重写的public boolean handleMessage方法,如果返回ture,就会拦截底下的public void handleMessage方法。返回false不拦截。当然如果不传这个接口的实现对象和false一样,不拦截,因为mCallback是null,进不了判断条件。

消息message创建的时候推荐用obtain,为什么?

       message就说平时写单链表里面node结点,只不过这个结点里面有了更多的属性,Message对象的内部实现是链表,最大长度是50(private static final int MAX_POOL_SIZE = 50;)用于缓存消息对象,达到重复利用消息对象的目的,以减少消息对象的创建,所以通常我们要使用handler.obtainMessage方法(最终会调用Message里面的obtain()方法)来获取消息对象。

       message.obtain目前有8个重载方法,我们选取其中一个查看,如下

    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,消息入队的时候会判断这个标志位
                //如果没清空,则throw new IllegalStateException(msg + " This message is already in use.");
                sPoolSize--;
                return m;
            }
        }
        return new Message();
    }

如果这个Message的引用sPool不为null,就复用原来的message对象,否则new一个Message()对象,这个sPool在消息回收的时候会被赋值,先来看看消息回收

    /**
     * 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; // 将sPool赋值对这个消息对象
                sPoolSize++;
            }
        }
    }

可以看到recycleUnchecked方法将当前 Message 标志为 FLAG_IN_USE,这样如果这个方法在被入队,就会报错。此外它还清除了其他数据,然后把这个消息加入了回收消息的链表中。

这个recycleUnchecked方法在MessageQueue.removeMessages()和Looper.loop()方法中都会有调用。

一个消息在被 Looper 处理时或者移出队列时会被标识为 FLAG_IN_USE,然后会被加入回收的消息链表,这样我们调用 Message.obtain() 方法时就可以从回收的消息池中获取一个旧的消息,从而节约成本。

 

 

自定义与线程相关的Handler代码示例:

import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;

public class SecondActivity extends AppCompatActivity {
    private static final String TAG = "SecondActivity";
    public Handler handler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            Log.d(TAG, "======主线程handleMessage: " + Thread.currentThread().getName());
        }
    };

    static class myThread extends Thread {
        public Handler handler;

        @Override
        public void run() {
            Looper.prepare();
            handler = new Handler() { // 必须先有looper对象,否则在handler构造方法报错
                @Override
                public void handleMessage(Message msg) {
                    super.handleMessage(msg);
                    Log.d(TAG, "=======子线程handleMessage: " + Thread.currentThread().getName());
                }
            };
            Looper.loop();
        }
    }

    private myThread myThread;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //setContentView就省略了,这里不分析布局
        myThread myThread = new myThread();
        myThread.start();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // 这里睡眠为了保证handler实例化
        myThread.handler.sendEmptyMessage(1);
        // 睡眠后看main线程
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        handler.sendEmptyMessage(2);
    }
}

运行结果:

 

 

问题代码:Handler和Looper深入思考~~~~为何不报错??

import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;

public class SecondActivity extends AppCompatActivity {
    private MyThread myThread;
    // 实例化mHandler的时候就绑定main线程的Looper,所以mHandler是主线程的Handler对象
    private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            Log.i("main", "======UI.Thread: " + Thread.currentThread());
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        myThread = new MyThread();
        myThread.start();
        mHandler.sendEmptyMessage(0);
        myThread.mHandler.sendEmptyMessage(0);

    }

    /**
     * Create MyThread
     */
    class MyThread extends Thread {
        // 因为子线程的Looper.prepare还没开始,此时ThreadLocal.get出来的还是main线程的Looper对象
        // 所以MyThread里的mHandler绑定了main线程的Looper,mHandler相当于还是属于主线程。
        private Handler mHandler = new Handler(new Handler.Callback() {
            @Override
            public boolean handleMessage(Message msg) {
                Log.i("main", "======Callback MyThread,currentThread: " + Thread.currentThread());
                return false;
            }
        }) {
            @Override
            public void handleMessage(Message msg) {
                Log.i("main", "======MyThread.currentThread: " + Thread.currentThread());
            }
        };

        @Override
        public void run() {
//            Create looper
            Looper.prepare();
            Log.i("main", "======Thread:" + Thread.currentThread());
            Looper.loop();
        }
    }
}

运行结果:

这里为什么会是main线程,奇怪了,我明明是在子线程实例化handler的,run方法还没开始执行,子线程的Looper对象还没被创建,那么handler在构造方法中怎么不报错呢?注意了,这个handler其实仍然属于主线程。

那也应该会执行

throw new RuntimeException( "Can't create handler inside thread " + Thread.currentThread() + " that has not called Looper.prepare()");

别忘了这个可是有条件的。

mLooper = Looper.myLooper();

if (mLooper == null) { .. 抛异常}

这个Looper.myLooper()方法的执行尝试返回Looper对象return sThreadLocal.get();

而在主线程创建的时候就已经Looper.prepare了,执行了sThreadLocal.set(new Looper(quitAllowed));

所以这个sThreadLocal里面是有main线程的Looper在里面的,这里其实就是将这里的mHandler和主线程的Looper绑定了。mHandler里面的消息队列引用也指向了主线程的消息队列。mQueue = mLooper.mQueue;

所以这里即使子线程还没初始化Looper对象也不报错,打印log就是main线程。(Handler对象绑定的Looper对象属于哪个线程,Handler对象就属于哪个线程,执行的一系列操作比如回调方法handleMessage方法就是在对应的线程

 

 

Handler内存泄漏详解及其解决方案

https://blog.csdn.net/javazejian/article/details/50839443

备注:阅读这篇文章时,让自己对内部类有了新的认识,扩展文章如下:

非静态内部类、非静态匿名内部类会持有外部对象的引用:https://blog.csdn.net/moon_nife/article/details/54975983

非静态内部类持有外部类的引用 使用不慎会造成内存溢出:https://blog.csdn.net/mafei852213034/article/details/78262490

java 内部类(inner class)详解:https://blog.csdn.net/suifeng3051/article/details/51791812


批注:

Handler内存泄漏解决方案:

将Handler声明为静态内部类

静态类不持有外部类的对象,所以Activity可以随意被回收

public class MainActivity extends AppCompatActivity {  

    ......


    static class MyHandler extends Handler {
        @Override
        public void handleMessage(Message msg) {
            // 错误示范
            Toast.makeText(MainActivity.this, "我出现了", Toast.LENGTH_SHORT).show();
        }
    }


    ......

}

但是没那么简单,静态类已经不持有外部类对象了,所以MainActivity.this会报语法错误,那怎么办呢?所以需要在Handler中增加一个对Activity的弱引用(WeakReference):

    static class MyHandler extends Handler {
        WeakReference<Activity> weakReference;
        public MyHandler(Activity activity) {
            weakReference = new WeakReference<Activity>(activity);
        }

        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            Toast.makeText(weakReference.get(), "我出现了", Toast.LENGTH_SHORT).show();
        }
    }
    private MyHandler myHandler = new MyHandler(this);

这样基本就完成了,但是当Activity finish后 handler对象还是在Message中排队。 还是会处理消息,这些处理有必要? 正常Activitiy finish后,已经没有必要对消息处理,那需要怎么做呢? 解决方案也很简单,在Activity onStop或者onDestroy的时候,取消掉该Handler对象的Message和Runnable。 通过查看Handler的API,它有几个方法:removeCallbacks(Runnable r)和removeMessages(int what)等。

比如在Handler把消息处理完了后,但是页面销毁了,这个时候可能Handler会更新UI,但是比如TextView、ImageView之类的资源引用不见了,就会抛出异常。处理如下:

    // Remove any pending posts of callbacks and sent messages whose obj is token.
    // If token is null, all callbacks and messages will be removed.
    @Override
    protected void onDestroy() {
        super.onDestroy();
        myHandler.removeCallbacksAndMessages(null);
    }

这样就可以了,总结3步

①改为handler静态内部类

②加上弱引用

③处理onDestroy()时removeCallbacksAndMessages(null)

某Demo部分测试代码:


import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;

import java.lang.ref.WeakReference;


class Person {
    public String name;
    public int age;

    @Override
    public String toString() {
        return "name: " + name + "age: " + age;
    }
}

public class MainActivity extends AppCompatActivity {
    int[] images = new int[]{R.drawable.image1, R.drawable.image2, R.drawable.image3};
    int index;
    private TextView textView;
    private static final String TAG = "MainActivity";

    static class MyHandler extends Handler {
        WeakReference<Activity> weakReference;

        public MyHandler(Activity activity) {
            super(new Callback() {
                @Override
                public boolean handleMessage(Message msg) {
                    Log.d(TAG, "====================拦截了");
                    return false; // 返回true就拦截回调的handleMessgae,false不拦截,或者不设置Callback对象也不拦截
                }
            });
            weakReference = new WeakReference<Activity>(activity);
        }

        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            Toast.makeText(weakReference.get(), "没拦截到,我出来显示", Toast.LENGTH_SHORT).show();
        }
    }

    private MyHandler myHandler = new MyHandler(this);

    @Override
    protected void onDestroy() {
        super.onDestroy();
        myHandler.removeCallbacksAndMessages(null);
    }

    /*private Handler handler = new Handler(new Handler.Callback() {
            @Override
            public boolean handleMessage(Message msg) {
                Toast.makeText(MainActivity.this, "拦截了吗", Toast.LENGTH_SHORT).show();
                return true;
            }
        }) {
            @Override
            public void handleMessage(Message msg) {
                // textView.setText(msg.arg1 + "---" + msg.arg2);
                Toast.makeText(MainActivity.this, "没拦截到,我出来显示", Toast.LENGTH_SHORT).show();
                //textView.setText(msg.obj.toString());
            }
        };*/
    private ImageView imageView;
    private MyRunnable myRunnable = new MyRunnable();

    public void click(View view) {
        //myHandler.removeCallbacks(myRunnable);
        myHandler.sendEmptyMessage(1);
    }

    class MyRunnable implements Runnable {

        @Override
        public void run() {
            ++index;
            index %= 3;
            imageView.setImageResource(images[index]);
            // 可以看到,post方法调用还是在主线程执行
            Log.d(TAG, "============run: " + Thread.currentThread().getName());
            myHandler.postDelayed(myRunnable, 1000);
        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        imageView = (ImageView) findViewById(R.id.imageView);
        textView = (TextView) findViewById(R.id.textview);
        myHandler.postDelayed(myRunnable, 1000);
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                Message message = myHandler.obtainMessage();
                message.arg1 = 88;
                message.arg2 = 100;
                Person p = new Person();
                p.age = 23;
                p.name = "lcy";
                message.obj = p;
                // handler.sendMessage(message);
                message.sendToTarget(); // 使用sendToTarget这个message必须handler.obtainMessage()获得,见源码
            }
        }).start();
    }
}

扩展知识阅读:关于强引用、软引用、弱引用、虚引用的概念

WeakReference弱引用,与强引用(即我们常说的引用)相对,它的特点是,GC在回收时会忽略掉弱引用,即就算有弱引用指向某对象,但只要该对象没有被强引用指向实际上多数时候还要求没有软引用,但此处软引用的概念可以忽略),该对象就会在被GC检查到时回收掉。对于上面的代码,用户在关闭Activity之后,就算后台线程还没结束,但由于仅有一条来自Handler的弱引用指向Activity,所以GC仍然会在检查的时候把Activity回收掉。这样,内存泄露的问题就不会出现了。

关于强引用、软引用、弱引用、虚引用的概念参考下面文章即可

https://www.cnblogs.com/dolphin0520/p/3784171.html

关于内存泄漏注意事项参见如下:

https://www.jianshu.com/p/ec0f67e867c3

 

========================Talk is cheap, show me the code=======================

posted @ 2018-11-28 16:53  绿叶萌飞  阅读(350)  评论(0编辑  收藏  举报