zoukankan      html  css  js  c++  java
  • Android HandlerThread源码解析

    在上一章Handler源码解析文章中,我们知道App的主线程通过Handler机制完成了一个线程的消息循环。那么我们自己也可以新建一个线程,在线程里面创建一个Looper,完成消息循环,可以做一些定时的任务或者写日志的功能。这就是HandlerThread的作用
    Android Handler消息机制源码解析

    1 使用方法如下

    在MainActivity中添加一个HandlerThread的变量,如下:

    public class MainActivity extends AppCompatActivity {
        HandlerThread thread = new HandlerThread("test");
        Handler handler;
    

    在 onCreate()函数中开启线程,获取线程的looper,如下:

     @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            //1 开启线程
            thread.start();
    
            //2 获取线程对应的looper,并用这个looper构造出一个Handler
            //3 并重写Handler的handleMessage()方法
            handler = new Handler(thread.getLooper()){
                @Override
                public void handleMessage(Message msg) {
                    super.handleMessage(msg);
                    if(msg.what == 100){
                        Log.e("TAG","线程名=" + Thread.currentThread().getName());
                        Log.e("TAG","接收到的数据为:" + msg.obj.toString());
                    }
                }
            };
    
            findViewById(R.id.tv_hello).setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    Log.e("TAG","线程名:" + Thread.currentThread().getName());
                    Message message = handler.obtainMessage();
                    message.what = 100;
                    message.obj = "hello world";
                    handler.sendMessage(message);
                }
            });
        }
    

    点击事件,输出如下:

    2018-11-24 12:49:06.575 13589-13589/com.fax E/TAG: 线程名:main
    2018-11-24 12:49:06.576 13589-13612/com.fax E/TAG: 线程名=test
    2018-11-24 12:49:06.576 13589-13612/com.fax E/TAG: 接收到的数据为:hello world
    

    由上面可以看到,我们新建了一个Handler,对应的looper是从HandlerThread实例thead获取的,我们在点击事件中,获取一个消息并用handler分发,主线程发送的消息,在子线程中处理了。

    比如我们有这样一个需求:
    在用户使用APP的时候,需要记录用户的行为,需要把日志记录到本地文件中,等到一定的时机我们再统一一次性把文件上传到我们的服务器。

    那么我们就可以开一个线程,在后台等待写日志的任务的消息到来,收到消息后就把日志顺序的写入到文件中。这时就可以用HandlerThread,省去了我们自己开线程,写任务队列,完成消息循环,这些HandlerThread都帮我们封装好了。下面我们来分析HandlerThread的源码。

    2 HandlerThread源码分析

    首付在使用的时候,我们直接 new 了一个HandlerThread对象 HandlerThread thread = new HandlerThread("test");

    HandlerThread类定义如下:

    public class HandlerThread extends Thread {
        int mPriority;
        int mTid = -1;
        Looper mLooper;
        private @Nullable Handler mHandler;
      
        ......
    }
    

    HandlerThread从字面意思上看,是一个和Handler结合起来用的Thread。
    再看HandlerThread的构造函数:

    public HandlerThread(String name) {
            super(name);
            mPriority = Process.THREAD_PRIORITY_DEFAULT;
        }
    

    构造函数仅仅是对线程的优先级和名字进行赋值。
    接着往下看,我们调用了 thread.start() ,由于HandlerThread是一个继承Thread,所以会调用run()方法,源码如下:

      @Override
        public void run() {
            //1 保存线程的id,没什么好说的
            mTid = Process.myTid();
            
            //2 主要是这句,调用了Looper.prepare()
            // 由上篇Handler源码分析可知,这里创建了一个Looper对象
            Looper.prepare();
            
            //3 获取当前线程对应的Looper对象,保存起来
            // 加锁是为了防止多线程问题
            synchronized (this) {
                mLooper = Looper.myLooper();
                notifyAll();
            }
            Process.setThreadPriority(mPriority);
            
            //4 在循环之前有一个回调,空实现
            onLooperPrepared();
            
            //5 进行消息循环
            Looper.loop();
            mTid = -1;
        }
    

    通过上面可知:
    1 HandlerThread就是一个线程类,在run()方法的开头调用了Looper.prepare()来创建一个线程对应的Looper对象,并保存起来。
    2 在线程的最后面调用了Looper.loop()对消息进行循环。

    所以如果外面想要用的话,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实例,这样外面想用这个的时候,就可以调用getLooper()获取Looper对象,然后再创建一个Handler对象,并把looper传入,这样就可以在其它线程中发送消息,在当前创建的子线程中处理了。

    既然这样,那么有没有这样一个方法,直接返回对应的Handler呢,里面就保存了Looper对象。还真有这样一个方法,如下:

      /**
         * @return a shared {@link Handler} associated with this thread
         * @hide
         */
        @NonNull
        public Handler getThreadHandler() {
            if (mHandler == null) {
                mHandler = new Handler(getLooper());
            }
            return mHandler;
        }
    

    看这个方法,new 了一个Handler对象,并调用getLooper()把当前的Looper对象传入了,并返回了当前这个Handler对象

    但是我们注意到,这个方法是@hide,就是我们在外面并不能调用这个方法,为什么Google已经写了这个方法但是又把这个方法给隐藏起来了不让我们调用呢?

    个人猜测是因为我们调用getThreadHandler()的前提是得先调用start()方法,有了Looper对象后才能调用这个方法,要不获取到的Handler里面是没有Looper实例的,也就没法完成消息循环,所以Google把这个方法给隐藏了。

    所以我们还是像上面的那样用法,先start(),再获取Looper对象,再创建Handler对象。

    那么线程有运行的时候,也应该有退出的时候,当前有,我们看quit()方法:

     public boolean quit() {
            Looper looper = getLooper();
            if (looper != null) {
                looper.quit();
                return true;
            }
            return false;
        }
    

    这就是HandlerThread的源码,下篇我们讲IntentService的源码,和HandlerThread结合起来用的。

    欢迎访问作者的helloworld的个人博客:
    https://www.helloworld.net/jiulu

    同时也可以加作者的微信:daitukeji
    也可以扫下面的二维码添加
    image

  • 相关阅读:
    Erlang学习笔记2
    erlang的Socket参数含义
    Erlang 日期和时间处理、时间戳转换
    图(有向)-拓扑排序
    图(无向连通无权图)-广度优先搜索
    图(无向连通无权图)-深度优先搜索
    图(无向连通无权值图)深度优先生成最小生成树
    客户关系管理系统
    字符串类
    I/O流
  • 原文地址:https://www.cnblogs.com/start1225/p/10011762.html
Copyright © 2011-2022 走看看