Android下线程知识点总结

本文属于个人随笔,纯原创,转载请注明出处

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下多线程的一些基础知识点,可能经常用到,但是其原理可能只有在面试时会被问道,权当做一个笔记,如果能帮到一个人,则是更好。

理论知识不够扎实,如果有描述不准确还请各位大佬斧正,谢谢!

发布了15 篇原创文章 · 获赞 14 · 访问量 9448

猜你喜欢

转载自blog.csdn.net/u014175785/article/details/80748894