• 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=======================

    CSDN博客地址:https://blog.csdn.net/qq_34115899
  • 相关阅读:
    P3图片导致iOS9.3以下崩溃问题
    [ios] 如何调用其他app h5界面调用打开app
    Swift学习中遇到的小坑
    代码行数统计(mac)
    路径专题 绝对路径 根路径 相对路径
    java.lang.StackOverflowError 解决办法
    Myeclipse运行提示错误: 找不到或无法加载主类 test.test1 终极解决办法
    myeclipse的最有用的设置
    关闭myeclipse可视化视图
    数据三大范式
  • 原文地址:https://www.cnblogs.com/lcy0515/p/10807837.html
Copyright © 2020-2023  润新知