Android中通过HandlerThread实现真正的多线程

上一篇博客中我在解释Handler的过程中提到了HandlerThread,我们先回顾一下上一篇博客中对于Handler的定义:Handler会关联一个单独的线程和消息队列。Handler默认关联主线程,虽然要提供Runnable参数,但默认是直接调用Runnable中的run()方法。也就是默认下会在主线程执行,如果在这里面的操作会有阻塞,界面也会卡住。

也就是说如果我们使用Handler提供的方法handler.post(new Runnable())时候,如果使用不当的话一样会出现界面卡死,因为其默认管线的还是主线程,也就是说其还是会在主线程中简单地调用Runnable中的run()方法,这样耗时任务虽然在run()方法中,但是还是运行在主线程,所以同样会阻塞UI,接下来我们来看两个例子,就会更明白了,第一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package cn.picksomething.testhandler;

import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;

public class MainActivity extends Activity {

private String TAG = "TAG";
private Handler handler = new Handler();
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    handler.post(new myRunnable());
    Log.d(TAG, "onCreate-----The thread id is:" + Thread.currentThread().getId());
    setContentView(R.layout.main);
}

class myRunnable implements Runnable{
    @Override
    public void run() {
        Log.d(TAG, "run method is calling----");
        Log.d(TAG, "The thread id is:" + Thread.currentThread().getId());
        try {
            Thread.sleep(6000);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}
}

运行上述程序,程序启动6秒之后,才看到布局被加载,显示出TextView上的Hello World!字样

分析一下程序执行的过程,首先程序启动,就把myRunnable加入到当前线程的消息队列中,也就是handler默认关联的主线程消息队列,android的handler确实是异步机制,所以在handler.post(new myRunnable());之后,程序会继续执行,所以以后的语句会继续执行,会输出onCreate中的当前线程ID。但同时myRunnable()的run方法也在运行,一样输出run方法中的当前线程ID,然后让当前线程休眠6秒

通过输出结果和分析,我们可以得出handler和主线程是同一个线程,handler也是运行在主线程中的,这个时候若是执行耗时任务,显然会阻塞UI。当然肯定有解决办法的:

Android给我们提供了Looper这样一个类,上一遍博客中已经介绍过了,Android中每一个Thread都跟着一个Looper,Looper可以帮助Thread维护一个消息队列,每个Thread都只有一个消息队列。我们看看另一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
package cn.picksomething.testlooper;

import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
import android.app.Activity;
import android.util.Log;

public class MainActivity extends Activity {

private String TAG = "TAG";
private Handler handler = null;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    HandlerThread mThread = new HandlerThread("handlerThread");
    mThread.start();
    handler = new Handler(mThread.getLooper());
    handler.post(new myRunnable());
    Log.d(TAG, "onCreate-----The Thread id is: " + Thread.currentThread().getId());
    setContentView(R.layout.main);
}

class myRunnable implements Runnable{
@Override
    public void run() {
    Log.d(TAG, "myRunnable is running----");
        Log.d(TAG, "The thread id is: " + Thread.currentThread().getId());
        try {
        Thread.sleep(6000);
        } catch (InterruptedException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
}
}
}

运行上述程序,过滤打印信息,我们看到LogCat输出如下结果:

1
2
3
onCreate-----The Thread id is: xxx
myRunnable is running----
The thread id is: xxx

同时我们看到程序已启动,就立即输出了布局文件中的Hello world!信息。

在这个例子中,用到了HandlerThread,在HandlerThread对象中可以通过getLooper方法获取一个Looper对象控制句柄,我们可以将这个Looper对象映射到一个Handler中来实现一个线程同步机制,所以就会有上面的结果了。这样就达到了多线程的效果

补充:HandlerThread这个类,HandlerThread继承于Thread,所以它本质就是个Thread。HandlerThread类用于方便的创建一个含有looper的线程类,looper用来创建handler类。我们一般不创建looper对象,直接调用HandlerThread即可,HandlerThread本身实现了循环处理消息的功能,不用再直接调用Looper.prepare()方法,与普通Thread的差别就在于它有个Looper成员变量,这个Looper其实就是对消息队列以及队列处理逻辑的封装,简单说就是消息队列+消息循环