• HandlerThread源码分析


    其实原本HandlerThread的分析不应该单独开一篇博客的,应该在讲消息机制的那一片中一起分析。

    但当时忘记了,而且今天第一次用MarkDown写博客,有点上瘾,就再来一篇,权当滥竽充数过过手瘾。

    1.为什么会有HandlerThread

    在使用Handler的时候,有的时候会报异常“Can’t create handler inside thread that has not called Looper.prepare()”

    为什么会这样呢?回到Handler的源码我们会发现在handler函数中,有这样一段:

    
    public Handler(Callback callback, boolean async) {
        //...
        mLooper = Looper.myLooper();
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread that has not called Looper.prepare()");
        }
        mQueue = mLooper.mQueue;
        mCallback = callback;
        //...
    }
    

    问题的原因找到了,因为我们没有关联到handler所在线程的looper,在主线程中构建Handler时默认关联MainLooper,在其他线程中我们需要先调用Looper.prepare函数,通过ThreadLocal变量将Thread与Looper关联起来,然后在当前线程中构建Handler,就会与其相关联。

    问了应对这种问题,就用了HandlerThread

    官方文档是这样说的:

    Handy class for starting a new thread that has a looper. The looper can then be used to create handler classes. Note that start() must still be called.

    创建一个拥有Looper的线程,这个looper可以被用来创建handler,调用这个线程仍需要使用start方法。

    2.如何实现

    构造函数如下,参数分别是线程的名字和优先级

    
    public HandlerThread(String name, int priority) {
        super(name);
        mPriority = priority;
    }
    

    对于thread,关键的run方法:

    
    public void run() {
        mTid = Process.myTid();
        Looper.prepare();
        synchronized (this) {
            mLooper = Looper.myLooper();
            notifyAll();
        }
        Process.setThreadPriority(mPriority);
        onLooperPrepared();
        Looper.loop();
        mTid = -1;
    }
    

    首先调用Looper.prepare()方法将Looper与Thred关联起来,通过synchroized方法进行looper的同步控制,等到looper能使用的时候再使用,然后通知其他阻塞在此的线程。

    既然HandlerThread的设计初衷就是设置一个带有Looper的线程,那么getLooper自然是这个家伙的重头戏

    
    public Looper getLooper() {
        if (!isAlive()) {
            return null;
        }
    
        // If the thread has been started, wait until the looper has been created.
        synchronized (this) {
            while (isAlive() && mLooper == null) {
                try {
                    wait();
                } catch (InterruptedException e) {
                }
            }
        }
        return mLooper;
    }
    

    这个方法用来返回与这个线程相关联的Looper

    先检测线程是否处于Alive状态,如果没有,则直接返回NULL。

    在try..catch..中的wait()与上run方法中的noytifyall()相呼应、

    即当线程处于ALIVE状态但是mLooper没有初始化完毕的时候,进入等待状态,直至mLooper初始完毕调用notifyAll方法唤醒。

    最后返回mLooper.

    关于此处的wait方法,我理解是Thread也是Object的子类,在主线程中调用getLooper方法时,会调用HandlerThread.wait(),此时如果mLooper没有创建成功,主线程会在这个HandlerThread对象上等待,直至创建成功后,唤醒等待在这个HandlerThread对象上的主线程,如果此时已经成功,就无需进入等待步骤,直接返回mLooper。

    这样避免因为获取一个不存在的对象而引发的异常。

    wait方法和sleep的区别,除了在于wait方法可以被唤醒外,就是wait会释放当前对象的锁,在文中场景就是:主线程释放HandlerThread的锁,让其去完成在run中创建Looper过程

    这里也能帮助理解wait和sleep的区别。

    分析android的源码的一大好处,就是可以边分析边学习,巩固以前在脑海中仅仅是一个概念的知识,不同于一般开源项目的“不靠谱”,因为android是很多大牛智慧的结晶,基本上逻辑很完善,这样可以让自己把精力放在分析和学习上。

    一点点进步,一起努力。

    最后,上个例子:

    
    public class MainActivity extends AppCompatActivity {
        private HandlerThread handlerThread;
        private ImageView imageView,imageView1;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            init();
        }
    
        private void init() {
            imageView= (ImageView) findViewById(R.id.imageView);
            imageView1= (ImageView) findViewById(R.id.imageView1);
    
            handlerThread = new HandlerThread("MainActivity");
            handlerThread.start();
            final Handler handler = new Handler(handlerThread.getLooper());
    
            //点击download开始进行下载
            findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    handler.post(new MyRunable(1));
                    handler.post(new MyRunable(2));
                }
            });
        }
    
        class MyRunable implements Runnable {
            int pos;
    
            public MyRunable(int pos) {
                this.pos = pos;
            }
    
            @Override
            public void run() {
                //模拟耗时
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
    
                if (pos == 1) {
                    imageView.post(new Runnable() {
                        @Override
                        public void run() {
                            imageView.setBackgroundResource(R.mipmap.ic_launcher);
                        }
                    });
                } else {
                    imageView.post(new Runnable() {
                        @Override
                        public void run() {
                            imageView1.setBackgroundResource(R.mipmap.ic_launcher);
                        }
                    });
                }
    
            }
        }
    
        @Override
        protected void onDestroy() {
            super.onDestroy();
            handlerThread.quit();//停止Looper的循环
        }
    }
    
  • 相关阅读:
    蓝牙音箱的连接和断开
    画一个钟表,陪着我走
    利用MediaSession发送信息到蓝牙音箱
    修改Switch 的颜色
    ViewPager PagerAdapter 的使用
    错误:android.view.InflateException: Binary XML file line #167: Binary XML file line #167: Error inflating class <unknown>
    react-project(一)
    create-react-app重建
    nodeJS连接mysql
    nodeJS问题
  • 原文地址:https://www.cnblogs.com/yueyanglou/p/5459601.html
Copyright © 2020-2023  润新知