本文属于个人随笔,纯原创,转载请注明出处
I. Thread 与 Runnable
首先Thread的基本写法如下:
Thread subThread = new Thread("sub1"){
@Override
public void run() {
printThreadInfo();
}
};
sub1为指定的线程名,重写的run()方法则为线程需要执行的代码块;
其中printThreadInfo()方法的实现为:
private void printThreadInfo() {
String threadName = Thread.currentThread().getName();
long id = Thread.currentThread().getId();
Log.d("ZDP","threadName = " + threadName + ", id = " + id);
}
一个很简单的打印当前线程名与ID的方法,下同;
一个简单的问题:
此时我调用subThread.start()与调用subThread.run()有什么区别?
先看现象:依次执行
subThread.run();
subThread.start();
后的Log如下:
06-20 17:27:10.060 24968-24968/com.example.ryan.threaddemo D/ZDP: threadName = main, id = 2
06-20 17:27:10.061 24968-24968/com.example.ryan.threaddemo D/ZDP: threadName = main, id = 2
06-20 17:27:10.062 24968-24989/com.example.ryan.threaddemo D/ZDP: threadName = sub1, id = 409
结论:调用Thread的run()方法只是单纯的把Thread类型的对象下的run()方法执行一遍,与线程没有任何关系;而调用Thread的start()方法会通过native的nativeCreate方法创建线程,并将线程命名为上方指定的sub1,并在sub1线程内执行run()方法内的代码块;
再来看Runnable的写法:
final Runnable callback = new Runnable() {
@Override
public void run() {
Log.d("ZDP","callback.run--->");
printThreadInfo();
Log.d("ZDP","<---callback.run");
}
};
写法与Thread很像,其实严格来说是Thread的写法与Runnable很像,因为Thread类本身就是一个Runnable接口的实现类;
但是Runnable接口中只定义了一个抽象的run()方法,不存在start()方法,所以我们只能调用:
callback.run();
根据前面Thread的经验,我们可以轻松得出结论,Runnable的实现类的run()方法,一定是执行在调用run()方法所在的线程内;
Log如下:
06-20 17:36:51.540 25272-25272/com.example.ryan.threaddemo D/ZDP: callback.run--->
threadName = main, id = 2
<---callback.run
由于我是在MainActivity的onCreate方法中调用,则理所应当是main线程。
II. Handler与Looper
final Handler mainHandler = new Handler(){
@Override
public void handleMessage(Message msg) {
Log.d("ZDP","mainHandler:handleMessage--->msg = " + msg.what);
printThreadInfo();
}
};
做示范用,暂不考虑内存泄漏之类的问题;
然后我们依次调用:
mainHandler.post(callback);
mainHandler.postDelayed(callback, 1000);
mainHandler.sendEmptyMessage(111);
mainHandler.sendEmptyMessageDelayed(222,500);
mainHandler.sendEmptyMessageDelayed(333,1500);
执行Log如下:
06-20 17:43:23.931 25751-25751/com.example.ryan.threaddemo D/ZDP: callback.run--->
threadName = main, id = 2
<---callback.run
06-20 17:43:23.932 25751-25751/com.example.ryan.threaddemo D/ZDP: mainHandler:handleMessage--->msg = 111
threadName = main, id = 2
06-20 17:43:24.387 25751-25751/com.example.ryan.threaddemo D/ZDP: mainHandler:handleMessage--->msg = 222
threadName = main, id = 2
06-20 17:43:24.887 25751-25751/com.example.ryan.threaddemo D/ZDP: callback.run--->
threadName = main, id = 2
<---callback.run
06-20 17:43:25.387 25751-25751/com.example.ryan.threaddemo D/ZDP: mainHandler:handleMessage--->msg = 333
06-20 17:43:25.388 25751-25751/com.example.ryan.threaddemo D/ZDP: threadName = main, id = 2
可以看到,无论是用Handler的何种方法,代码都是会在Handler所在的线程下执行。究其原因,简单来说是因为Handler与Looper是多对一的关系,即可以存在多个Handler与同一个Looper绑定(其实本例就是如此,每个进程在启动初始就会通过ActivityThread的main方法初始化Looper,而ActivityThread内部就存在一个名为mH的Handler的子类H的成员变量,加上本例中创建的mainHandler,已经是二对一的关系了,有兴趣的可以看Android SDK的sources目录下的代码,或者在Linux下同步AOSP代码进一步查看),而Looper是与线程一一对应的(详见Looper.java中的sThreadLocal静态变量),因此每一个Handler都是与某一个线程对应的,其方法的执行全是在该线程中进行;
具体Handler与Looper的工作原理后续会另开一篇详解;
既然说到Handler的代码执行与所在线程有关,那么通过下面一个实例则能更好说明此问题:
Thread subThread2 = new Thread("sub2"){
@Override
public void run() {
Log.d("ZDP","mainHandler.post(callback)--->");
printThreadInfo();
mainHandler.post(callback);
Log.d("ZDP","<---mainHandler.post(callback)");
}
};
subThread2.start();
创建了一个线程名为sub2的线程,在run()方法中打印当前线程的信息,并通过之前在main线程创建的mainHandler,调用post来执行一个上述名为callback的Runnable实现类;
按照之前得出的结论,由于mainHandler对象的创建是在main线程,因此此次callback的执行会在main线程下,而run()方法的其他代码会执行在sub2线程下。
运行后Log如下:
06-20 18:11:11.502 26392-26417/com.example.ryan.threaddemo D/ZDP: mainHandler.post(callback)--->
threadName = sub2, id = 449
<---mainHandler.post(callback)
06-20 18:11:11.550 26392-26392/com.example.ryan.threaddemo D/ZDP: callback.run--->
threadName = main, id = 2
<---callback.run
结论与推测一致;
继续,我们在subThread2这个对象的run()方法中创建一个Handler,试图通过这个Handler来让callback的代码执行在sub2线程下:
Thread subThread2 = new Thread("sub2"){
@Override
public void run() {
Handler handler = new Handler(){
@Override
public void handleMessage(Message msg) {
Log.d("ZDP","handler:handleMessage--->msg = " + msg.what);
printThreadInfo();
super.handleMessage(msg);
}
};
handler.post(callback);
}
};
subThread2.start();
看似没有问题,运行则会显示:
06-20 18:15:57.496 26534-26556/com.example.ryan.threaddemo E/AndroidRuntime: FATAL EXCEPTION: sub2
Process: com.example.ryan.threaddemo, PID: 26534
java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()
at android.os.Handler.<init>(Handler.java:204)
at android.os.Handler.<init>(Handler.java:118)
at com.example.ryan.threaddemo.MainActivity$3$1.<init>(MainActivity.java:54)
at com.example.ryan.threaddemo.MainActivity$3.run(MainActivity.java:54)
原因很简单,Handler是依赖与Looper工作的,而新建的sub2线程并没有初始化Looper,从而导致报停,解决办法则是在创建Handler之前调用Looper.prepare()即可:
Thread subThread2 = new Thread("sub2"){
@Override
public void run() {
Looper.prepare();
Handler handler = new Handler(){
@Override
public void handleMessage(Message msg) {
Log.d("ZDP","handler:handleMessage--->msg = " + msg.what);
printThreadInfo();
super.handleMessage(msg);
}
};
handler.post(callback);
}
};
subThread2.start();
运行,没有报错,然而也什么都没有输出。为什么?
这里需要了解Looper的工作原理,如有必要会另开一篇详解,这里只说结论,Looper新建后不会自动开始轮询消息队列,而需要手动执行Looper.loop()方法。需要注意的是,Looper.loop()方法是一个死循环,用以保证该线程会持续获取消息队列中的新消息并发送至对应Handler处理,因此Looper.loop()方法的调用需要在run()方法的最后调用:
Thread subThread2 = new Thread("sub2"){
@Override
public void run() {
Looper.prepare();
Handler handler = new Handler(){
@Override
public void handleMessage(Message msg) {
Log.d("ZDP","handler:handleMessage--->msg = " + msg.what);
printThreadInfo();
super.handleMessage(msg);
}
};
handler.post(callback);
Looper.loop();
}
};
subThread2.start();
运行即可看到结果:
06-20 18:22:59.089 26791-26810/? D/ZDP: callback.run--->
threadName = sub2, id = 461
<---callback.run
同样,执行Handler的其他方法,代码依旧运行在sub2线程,因为handler对象本身即所属于sub2线程;
Thread subThread2 = new Thread("sub2"){
@Override
public void run() {
Looper.prepare();
Handler handler = new Handler(){
@Override
public void handleMessage(Message msg) {
Log.d("ZDP","handler:handleMessage--->msg = " + msg.what);
printThreadInfo();
super.handleMessage(msg);
}
};
handler.post(callback);
handler.sendEmptyMessage(111);
handler.sendEmptyMessageDelayed(222,1000);
handler.sendEmptyMessageDelayed(333,2000);
Looper.loop();
}
};
subThread2.start();
运行Log如下:
06-20 18:24:58.915 26903-26925/com.example.ryan.threaddemo D/ZDP: callback.run--->
06-20 18:24:58.916 26903-26925/com.example.ryan.threaddemo D/ZDP: threadName = sub2, id = 465
<---callback.run
handler:handleMessage--->msg = 111
threadName = sub2, id = 465
06-20 18:24:59.917 26903-26925/com.example.ryan.threaddemo D/ZDP: handler:handleMessage--->msg = 222
06-20 18:24:59.918 26903-26925/com.example.ryan.threaddemo D/ZDP: threadName = sub2, id = 465
06-20 18:25:00.917 26903-26925/com.example.ryan.threaddemo D/ZDP: handler:handleMessage--->msg = 333
threadName = sub2, id = 465
以上则是关于Android下多线程的一些基础知识点,可能经常用到,但是其原理可能只有在面试时会被问道,权当做一个笔记,如果能帮到一个人,则是更好。
理论知识不够扎实,如果有描述不准确还请各位大佬斧正,谢谢!