Android面试收集录8 HandlerThread详解
1.前言
我们知道在Android系统中,我们执行完耗时操作都要另外开启子线程来执行,执行完线程以后线程会自动销毁。
想象一下如果我们在项目中经常要执行耗时操作,如果经常要开启线程,接着又销毁线程,
这无疑是很消耗性能的?那有什么解决方法呢?
- 使用线程池
- 使用HandlerThread
本篇文章主要讲解一下问题
- HandlerThread的使用场景以及怎样使用HandlerThread?
- HandlerThread源码分析
2.HandlerThread的使用场景以及怎样使用HandlerThread?
使用场景
HandlerThread是Google帮我们封装好的,可以用来执行多个耗时操作,而不需要多次开启线程,
里面是采用handler和Looper实现的
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.
怎样使用HandlerThread?
- 创建HandlerThread的实例对象
HandlerThread handlerThread = new HandlerThread("myHandlerThread");
该参数表示线程的名字,可以随便选择。
- 启动我们创建的HandlerThread线程
handlerThread.start();
3.将我们的handlerThread与Handler绑定在一起。 还记得是怎样将Handler与线程对象绑定在一起的吗?
其实很简单,就是将线程的looper与Handler绑定在一起,代码如下:
mThreadHandler = new Handler(mHandlerThread.getLooper()) {
    @Override
    public void handleMessage(Message msg) {
        checkForUpdate();
        if(isUpdate){
            mThreadHandler.sendEmptyMessage(MSG_UPDATE_INFO);
        }
    }
};
注意必须按照以上三个步骤来,下面在讲解源码的时候会分析其原因
完整测试代码如下:
public class MainActivity extends AppCompatActivity {
    private TextView mTv;
    Handler mMainHandler = new Handler();
    private Handler mThreadHandler;
    private static final int MSG_UPDATE_INFO = 0x100;
    private HandlerThread mHandlerThread;
    private boolean isUpdate=true;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mTv = (TextView) findViewById(R.id.tv);
        initHandlerThread();
    }
    private void initHandlerThread() {
        mHandlerThread = new HandlerThread("xujun");
        mHandlerThread.start();
        mThreadHandler = new Handler(mHandlerThread.getLooper()) {
            @Override
            public void handleMessage(Message msg) {
                checkForUpdate();
                if(isUpdate){
                    mThreadHandler.sendEmptyMessage(MSG_UPDATE_INFO);
                }
            }
        };
    }
    /**
     * 模拟从服务器解析数据
     */
    private void checkForUpdate() {
        try {
            //模拟耗时
            Thread.sleep(1200);
            mMainHandler.post(new Runnable() {
                @Override
                public void run() {
                    String result = "实时更新中,当前股票行情:<font color='red'>%d</font>";
                    result = String.format(result, (int) (Math.random() * 5000 + 1000));
                    mTv.setText(Html.fromHtml(result));
                }
            });
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    @Override
    protected void onResume() {
        isUpdate=true;
        super.onResume();
        mThreadHandler.sendEmptyMessage(MSG_UPDATE_INFO);
    }
    @Override
    protected void onPause() {
        super.onPause();
        isUpdate=false;
        mThreadHandler.removeMessages(MSG_UPDATE_INFO);
    }
    @Override
    protected void onDestroy() {
        super.onDestroy();
        mHandlerThread.quit();
        mMainHandler.removeCallbacksAndMessages(null);
    }
}
运行以上测试代码,将可以看到如下效果图(例子不太恰当,主要使用场景是在handleMessage中执行耗时操作)
3.HandlerThread源码分析
官方源代码如下,是基于sdk23的,可以看到,只有一百多行代码而已
public class HandlerThread extends Thread {
    int mPriority;
    int mTid = -1;
    Looper mLooper;
    public HandlerThread(String name) {
        super(name);
        mPriority = Process.THREAD_PRIORITY_DEFAULT;
    }
    public HandlerThread(String name, int priority) {
        super(name);
        mPriority = priority;
    }
    /**
     * Call back method that can be explicitly overridden if needed to execute some
     * setup before Looper loops.
     */
    protected void onLooperPrepared() {
    }
    @Override
    public void run() {
        mTid = Process.myTid();
        Looper.prepare();
        //持有锁机制来获得当前线程的Looper对象
        synchronized (this) {
            mLooper = Looper.myLooper();
            //发出通知,当前线程已经创建mLooper对象成功,这里主要是通知getLooper方法中的wait
            notifyAll();
        }
        //设置线程的优先级别
        Process.setThreadPriority(mPriority);
        //这里默认是空方法的实现,我们可以重写这个方法来做一些线程开始之前的准备,方便扩展
        onLooperPrepared();
        Looper.loop();
        mTid = -1;
    }
    public Looper getLooper() {
        if (!isAlive()) {
            return null;
        }
        // 直到线程创建完Looper之后才能获得Looper对象,Looper未创建成功,阻塞
        synchronized (this) {
            while (isAlive() && mLooper == null) {
                try {
                    wait();
                } catch (InterruptedException e) {
                }
            }
        }
        return mLooper;
    }
    public boolean quit() {
        Looper looper = getLooper();
        if (looper != null) {
            looper.quit();
            return true;
        }
        return false;
    }
    public boolean quitSafely() {
        Looper looper = getLooper();
        if (looper != null) {
            looper.quitSafely();
            return true;
        }
        return false;
    }
    /**
     * Returns the identifier of this thread. See Process.myTid().
     */
    public int getThreadId() {
        return mTid;
    }
}
1)首先我们先来看一下它的构造方法
 public HandlerThread(String name) {
        super(name);
        mPriority = Process.THREAD_PRIORITY_DEFAULT;
    }
    public HandlerThread(String name, int priority) {
        super(name);
        mPriority = priority;
    }
有两个构造方法,一个参数的和两个参数的,name代表当前线程的名称,priority为线程的优先级别
2)接着我们来看一下run()方法,在run方法里面我们可以看到我们会初始化一个Looper,并设置线程的优先级别
public void run() {
    mTid = Process.myTid();
    Looper.prepare();
    //持有锁机制来获得当前线程的Looper对象
    synchronized (this) {
        mLooper = Looper.myLooper();
        //发出通知,当前线程已经创建mLooper对象成功,这里主要是通知getLooper方法中的wait
        notifyAll();
    }
    //设置线程的优先级别
    Process.setThreadPriority(mPriority);
    //这里默认是空方法的实现,我们可以重写这个方法来做一些线程开始之前的准备,方便扩展
    onLooperPrepared();
    Looper.loop();
    mTid = -1;
}
- 还记得我们前面我们说到使用HandlerThread的时候必须调用start()方法,接着才可以将我们的HandlerThread和我们的handler绑定在一起吗?其实原因就是我们是在run()方法才开始初始化我们的looper,而我们调用HandlerThread的start()方法的时候,线程会交给虚拟机调度,由虚拟机自动调用run方法
mHandlerThread.start();
mThreadHandler = new Handler(mHandlerThread.getLooper()) {
@Override
public void handleMessage(Message msg) {
checkForUpdate();
if(isUpdate){
mThreadHandler.sendEmptyMessage(MSG_UPDATE_INFO);
}
}
};
- 这里我们为什么要使用锁机制和notifyAll();,原因我们可以从getLooper()方法中知道
public Looper getLooper() {
    if (!isAlive()) {
        return null;
    }
    // 直到线程创建完Looper之后才能获得Looper对象,Looper未创建成功,阻塞
    synchronized (this) {
        while (isAlive() && mLooper == null) {
            try {
                wait();
            } catch (InterruptedException e) {
            }
        }
    }
    return mLooper;
}
总结:在获得mLooper对象的时候存在一个同步的问题,
只有当线程创建成功并且Looper对象也创建成功之后才能获得mLooper的值。
这里等待方法wait和run方法中的notifyAll方法共同完成同步问题。
3)接着我们来看一下quit方法和quitSafe方法
//调用这个方法退出Looper消息循环,及退出线程
public boolean quit() {
Looper looper = getLooper();
if (looper != null) {
looper.quit();
return true;
}
return false;
}
//调用这个方法安全地退出线程
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
public boolean quitSafely() {
Looper looper = getLooper();
if (looper != null) {
looper.quitSafely();
return true;
}
return false;
}
跟踪这两个方法容易知道只两个方法最终都会调用MessageQueue的quit(boolean safe)方法
void quit(boolean safe) {
    if (!mQuitAllowed) {
        throw new IllegalStateException("Main thread not allowed to quit.");
    }
    synchronized (this) {
        if (mQuitting) {
            return;
        }
        mQuitting = true;
        //安全退出调用这个方法
        if (safe) {
            removeAllFutureMessagesLocked();
        } else {//不安全退出调用这个方法
            removeAllMessagesLocked();
        }
        // We can assume mPtr != 0 because mQuitting was previously false.
        nativeWake(mPtr);
    }
}
不安全的会调用removeAllMessagesLocked();这个方法,我们来看这个方法是怎样处理的,
其实就是遍历Message链表,移除所有信息的回调,并重置为null
private void removeAllMessagesLocked() {
    Message p = mMessages;
    while (p != null) {
        Message n = p.next;
        p.recycleUnchecked();
        p = n;
    }
    mMessages = null;
}
安全地会调用removeAllFutureMessagesLocked();这个方法,它会根据Message.when这个属性,
判断我们当前消息队列是否正在处理消息,没有正在处理消息的话,直接移除所有回调,正在处理的话,
等待该消息处理处理完毕再退出该循环。因此说quitSafe()是安全的,而quit()方法是不安全的,
因为quit方法不管是否正在处理消息,直接移除所有回调。
private void removeAllFutureMessagesLocked() {
    final long now = SystemClock.uptimeMillis();
    Message p = mMessages;
    if (p != null) {
        //判断当前队列中的消息是否正在处理这个消息,》没有的话,直接移除所有回调
        if (p.when > now) {
            removeAllMessagesLocked();
        } else {//正在处理的话,等待该消息处理处理完毕再退出该循环
            Message n;
            for (;;) {
                n = p.next;
                if (n == null) {
                    return;
                }
                if (n.when > now) {
                    break;
                }
                p = n;
            }
            p.next = null;
            do {
                p = n;
                n = p.next;
                p.recycleUnchecked();
            } while (n != null);
        }
    }
} 
4.参考文章
Android面试收集录8 HandlerThread详解的更多相关文章
- Android面试收集录9  IntentService详解
		一. 定义 IntentService是Android里面的一个封装类,继承自四大组件之一的Service. 二.作用 处理异步请求,实现多线程 三. 工作流程 注意:若启动IntentService ... 
- Android面试收集录4 Fragment详解
		1.什么是Fragment? 你可以简单的理解为,Fragment是显示在Activity中的Activity. 它可以显示在Activity中,然后它也可以显示出一些内容. 因为它拥有自己的生命周期 ... 
- Android面试收集录7 AsyncTask详解
		1.Android中的线程 在操作系统中,线程是操作系统调度的最小单元,同时线程又是一种受限的系统资源,即线程不可能无限制地产生, 并且 **线程的创建和销毁都会有相应的开销.**当系统中存在大量的线 ... 
- Android面试收集录3 ContentProvider详解
		1.ContentProvider简单介绍 1.1.定义 ContentProvider,即内容提供者属于Android的四大组件之一. 1.2.作用 进程间进行数据交互&共享,即跨进程通信. ... 
- Android面试收集录1 Activity+Service
		1.Activity的生命周期 1.1.首先查看一下Activity生命周期经典图片. 在正常情况下,一个Activity从启动到结束会以如下顺序经历整个生命周期: onCreate()->on ... 
- Android面试收集录18 Android Context详解
		Activity mActivity =new Activity() 作为Android开发者,不知道你有没有思考过这个问题,Activity可以new吗?Android的应用程序开发采用JAVA语言 ... 
- Android面试收集录2 Broadcast Receiver详解
		1.Broadcast Receiver广播接收器简单介绍 1.1.定义 Broadcast Receiver(广播接收器),属于Android四大组件之一 在Android开发中,Broadcast ... 
- Android面试收集录6 事件分发机制
		转自:秋招面试宝典. 一. 基础认知 1.1 事件分发的对象是谁? 答:事件 当用户触摸屏幕时(View或ViewGroup派生的控件),将产生点击事件(Touch事件). Touch事件相关细节(发 ... 
- Android面试收集录 Android系统的资源+其他
		1.Android应用程序的资源是如何存储的,如何使用? res文件夹或者assets文件夹 res目录中的资源在R类中生成一个int变量,然后再布局文件中可以直接使用,在代码中,要getResour ... 
随机推荐
- ASP.NET MVC 音乐商店 - 2.控制器
			在典型的 Web 应用中,用户请求的 URL 地址通常映射到保存在网站中的文件上,例如,当用户请求 /Products.aspx 的时候,或者 /Products.php 的时候,很可能是在通过处理 ... 
- java之Socket多线程传递对象
			服务器端利用线程池回复客户端: public class Server implements Runnable { private final ServerSocket server; private ... 
- 关于VisualStudio2010发布项目问题
			VisualStudio2010速度还是很给力的,VS2015打开机器就双100%了:VS2010机器上跑起来还是很好用的. 今天编译一个MVC3.0项目,发布时候出现诡异现象:Content文件夹里 ... 
- Linux安装中文字体包
			进入rhel5.5安装盘/Server路径找到字体安装包: fonts-chinese-3.02-12.el5.noarch.rpm fonts-ISO8859-2-75dpi-1.0-17.1.no ... 
- 深入了解MongoDB
			一.介绍: 数据库分为关系型数据库和非关系型数据库 关系型数据库是建立在关系模型上的数据库,主要的有Oracle MySQL Microsoft SQL Server NoSQL是非关系型数据存储的广 ... 
- Raknet—视频会议系统最佳的数据传输引擎
			RakNet是一个跨平台的C++和C#的游戏引擎,它主要是为高效的数据传输而设计,使用者可以通过它进行游戏和其他的程序的开发.RakNet虽然是一个游戏引擎,但同样也是一个非常好的视频会议系统传输引擎 ... 
- 将Apache2.4手动安装成Windows的服务
			将Apache2.4手动安装成Windows的服务 可以选择在安装Apache时自动将其安装为一个服务.如果选择"for all users",那么Apache将会被安装为服务. ... 
- IOS 数据加密方式(加盐,MD5加密,)
			加密方式封装 @interface NSString (Hash) @property (readonly) NSString *md5String; @property (readonly) NSS ... 
- jquery插入第一个元素? [问题点数:20分,结帖人zsw19909001]
			jquery插入第一个元素? [问题点数:20分,结帖人zsw19909001] JavaScript code ? 1 2 3 4 5 <div id="contain" ... 
- python 3+djanjo 2.0.7简单学习(三)--Django 管理页面
			django里自带了一个管理页面,也就是后台,下面来学习一下 1.创建超级管理员 python manage.py createsuperuser 键入你想要使用的用户名,然后按下回车键: Usern ... 
