Android进阶之路——线程机制

    今天leo主要总结线程方面的,分为以下几个知识点:

• 认识线程

• 线程的基本用法

• 线程同步

• 子线程更新UI

• 线程间通信机制

    Android是单线程模型,我们创建的Service、Activity以及Broadcast均是在一个主线程处理,这里我们可以理解为UI线程。

    但是在操作一些耗时操作时,比如I/O读写的大文件读写,数据库操作以及网络下载需要很长时间,为了不阻塞用户界面,出现ANR的响应提示窗口,这个时候我们考虑使用Thread线程来解决。

• 进程与线程

    线程是指进程内的一个执行单元,也是进程内的可调度实体,与进程的区别:

(1)地址空间: 进程内的一个执行单元;进程至少有一个线程,它们共享进程的地址空间;而进程有自己独立的地址空间;

(2)资源拥有: 进程是资源分配和拥有的单位,同一个进程内的线程共享进程的资源;

(3)线程是处理器调度的基本单位,但进程不是;

(4)二者均可并发执行。

• 实现 

    » 继承java.lang.Thread类

    » 实现Runnable接口

• 启动

    Thread类代表线程类,它的两个最主要的方法是:

    » run()--包含线程运行时所执行的代码

    » Start()--用于启动线程

• 通信

    Handler机制,它是Runnable和Activity交互的桥梁,在run方法中发送Message,在Handler里,通过不同的Message执行不同的任务。

• 简介

    当使用多个线程来访问同一个数据时,非常容易出现线程安全问题(比如多个线程都在操作同一数据导致数据不一致),所以我们用同步机制来解决这些问题。

• 实现方法

    » 同步代码块:synchronized(同一个数据){} 同       一个数据:就是N条线程同时访问一个数据。

    » 同步方法:public synchronized 数据返回类       型 方法名(){}

• 子线程更新UI的四种方法

    1. handle.post(Runnable r)

    2. handle.handleMessage(Message msg)

    3. runOnUiThread(Runnable r)

    4. View.post(Runnable r)

• 用于线程间通讯的类

    1. Handler :在android里负责发送和处理消息,通过它可以实现其他线程与Main线程之间的消息通讯。

    2. Looper:负责管理线程的消息队列和消息循环 。

    3. Message  :线程间通讯的消息载体。两个码头之间运输货物,Message充当集装箱的功能,里面可以存放任何你想要传递的消息。

    4. MessageQueue:消息队列,先进先出,它的作用是保存有待线程处理的消息。

Android多线程机制

一、使用线程

    1. 任何耗时的处理过程都会降低用户界面的响应速度,甚至导致用户界面失去响应,当用户界面失去响应超过5秒钟,Android系统会允许用户强行关闭应用程序。

    2. 较好的解决方法是将耗时的处理过程转移到子线程上,这样可以避免负责界面更新的主线程无法处理界面事件,从而避免用户界面长时间失去响应。

    3. 线程是独立的程序单元,多个线程可以并行工作。

    4. 在多处理器系统中,每个中央处理器(CPU)单独运行一个线程,因此线程是并行工作的。

    5.  在单处理器系统中,处理器会给每个线程一小段时间,在这个时间内线程是被执行的,然后处理器执行下一个线程,这样就产生了线程并行运行的假象。

    6. 无论线程是否真的并行工作,在宏观上可以认为子线程是独立于主线程,且能与主线程并行工作的程序单元。

    7. Android中的线程是基于Java定义的线程,其内部结构如图所示:

    8. 一个应用程序中可能会包含多个线程(Thread),每个线程中都有一个run()方法,run()方法内部的程序执行完毕后,所在的线程就自动结束。

    9. 每个线程都有一个消息队列,用于不同的线程之间传递消息。在run()方法内部,如果不主动去读取消息队列中的消息,这些消息就是一些无用的消息,因为它们没有被处理。

    10. 在Android系统中,读取消息和处理消息是两个步骤,并由两个不同的部分完成,先读取消息,然后才能处理消息。

    11. 无论是本线程的还是其它线程,都不能直接处理消息队列中的消息,而是需要通过在线程内部定义一个Handler类对象来处理消息队列。一个Thread只能包含一个Handler对象。在实际应用中,读取消息队列一般需要循环执行,即不断地从消息队列中获取消息并进行相应处理,这就又需要一个Looper对象。

    12. Looper对象用于循环读取消息队列的值,并回调Handler对象中定义的消息处理函数,同时,Looper对象还可以将读取的消息从队列中移除,执行完一次消息处理后,再循环从消息队列中读取下一个消息,直到Looper对象调用stop()方法退出循环。如果消息队列中没有消息,Looper对象则会等待,线程不会退出。

    13. 为了更方便地从线程中使用Looper功能,Android又定义了一个HandlerThread类,该类基于Thread,并且内部已经添加了Looper功能,使用者只需重写其onLooperPrepared ()方法,添加具体应用代码即可。Android中一个Activity就是一个线程,多个Activity之间的切换是在同一个线程中。

二、Android中Activity的调用流程

    1. 以上伪代码中,while(1)循环用于指定该Thread一直执行,永不退出,直到被操作系统杀掉。

    2. if(hasHandler())语句判断该Thread中是否定义了Handler对象,该Handler对象是由应用程序定义的(也可不定义)。

    3. 如果有该对象,那么就会读取Thread中消息队列的值,并做一定的处理。执行完一个消息后,接着需要执行Activity中的用户界面响应,例如是否有按键按下、触摸屏是否按下等。 处理完一次用户消息响应后,则继续循环读取Thread中消息队列的值。

三、MessageQueue、Looper、Handler调用关系

    1. 在以上这个大循环中,Handler对象对应的就是Handler.handleMessage()部分完成的功能,Looper对象对应的就是整个while(1)循环控制和MessageQueue.getMessage()完成的功能。

    2. Looper对象负责从线程的消息队列中循环读取消息值,再将这些消息传递给Handler对象,Handler对象中定义的消息处理函数会根据消息类型再调用Thread中定义的其它函数。如果Thread中没有Looper对象,那么Thread的执行体就无法读取消息队列;如果Thread中没有Handler对象,则不会处理任何消息。

    3. 一般情况下, Handler和Looper是同时使用的,要么同时有,要么同时没有。

Android多线程定义

一、线程定义

    1. Android中定义线程的方法与Java相同,可以使用两种方法:一种是Thread类,另一种是Runnable接口。

    2. Thread是一个类,根据Java继承风格,一个类只能有一个父类,继承了Thread的子类不能再继承其它类,这是一个缺陷。于是,出现了Runnable,其作用和Thread相同,都是启动另一个线程,不同的是,Runnable是一个接口(interface),因此可以同时实现多个接口。

    3. Android中使用Thread与Java基本相同,所不同的是,Android抛弃了Java线程中一些不安全的做法。比如:终止一个Thread,在Java中可以调用线程名字.stop()、线程名字.destroy()等;而在Android中,这些方法都没有实现,即不能使用。

    4. 新建一个Thread对象,需要实现两个方法:

      第一个是定义构造方法。在Android程序中,新建的线程多为Activity、Service等程序片断服       务,而在线程的内部执行过程中,很多时候都需要使用应用程序内部的Context对象,因此,       在实际应用中,线程的构造方法往往会传递应用程序的Context对象,从而在线程的内部可以       调用Context相关的系统服务。当然,这不是必须的。

     第二个是run()方法。该方法是Thread对象中必须实现的方法,用于完成具体的任务。启动线       程时,不能直接调用线程名字.run()方法,而是调用线程名字.start()方法启动,start()方法是         Thread内部使用的,该方法包含初始化线程的工作,然后回调run()方法,这些对应用程序都         是不可见的。

    5. 停止线程时,不能调用线程名字.destroy()方法或者线程名字.stop()方法。run()方法执行完毕后,线程默认会自动停止。因此,如果需要线程循环执行run()方法内部的代码,可以在线程内部增加一个状态变量,run()方法内部通过检查该状态变量,决定是否继续执行;同时可在线程外设置该状态变量的值,从而终止该线程。

二、线程定义thread

    1. 线程定义Thread,继承Thread类,并重写run()方法。在run()中放置代码的主体部分。

    2. 以上代码包括3个基本方法:Thread1()为构造方法,用于保存调用者的Context对象,供以后可能使用;run()方法内部是应用代码;setToStop()用于设置全局变量mRunState的值,run()内部循环执行时会判断该值,决定是否退出run()方法,即终止该线程。

    3. 要在Activity启动Thread1,首先需要定义一个Thread1对象,并使用构造方法将Activity的Context对象传递给Thread1,然后调用线程的start()方法启动Thread1线程。要终止Thread的运行,可调用自定义的Thread1的setToStop()方法。

    4. 以上代码中,id值为action_stop的按钮,用于停止Thread1的运行。这是Android系统建议的启动线程和退出线程的方法。

三、线程定义Runnable

    1. Runnable的作用和Thread基本相同,都是用于定义一个线程,但两者本质上有重要区别。

    2. 第一:Runnable只是一个接口(interface),其内部没有定义任何已实现的方法。因此,要使用与线程有关的方法,只能使用Thread的静态方法,比如:不能直接调用sleep(),而要调用Thread.sleep()方法。

    3. 第二:定义一个Thread对象,就意味着创建了一个新线程,而定义一个Runnable对象,只是定义了一个可以当作线程运行的代码对象,并没有创建新线程。因此,如果调用Runnable对象的run()方法,仅相当于把Runnable对象当作普通类对象进行调用,并没有启动一个新线程,Runnable对象和调用者在同一个线程中运行。

    4. 如果要创建一个新线程,则还需要将Runnable对象传入Thread的构造方法,从而创建一个新线程,新线程的执行码就是Runnable所定义的。

    5. 第三:Runnable对象经常被当作参数传递给一些与线程有关的方法,用于启动一个新的线程。

    6. 实现Java的Runnable接口,并重载run()方法。在run()中放置代码的主体部分

    7. 创建Thread对象,并将上面实现的Runnable对象作为参数传递给Thread对象

    ① Thread的构造函数中,第1个参数用来表示线程组。

    ② 第2个参数是需要执行的Runnable对象。

    ③ 第3个参数是线程的名称。

    8. 调用start()方法启动线程。直接用workThread.start();

    9. 线程在run()方法返回后,线程就自动终止了;不推荐使用调用stop()方法在外部终止线程。

    10. 最好的方法是通知线程自行终止,一般调用interrupt()方法通告线程准备终止,线程会释放它正在使用的资源,在完成所有的清理工作后自行关闭。方法是workThread.interrupt();

    ① interrupt()方法并不能直接终止线程,仅是改变了线程内部的一个布尔字段,run()方法能够检测到这个布尔字段,从而知道何时应该释放资源和终止线程

    ② 在run()方法的代码,一般通过Thread.interrupted()方法查询线程是否被中断

    11. 下面的代码是以1秒为间隔循环检测断线程是否被中断

    ① 第4行代码使线程休眠1000毫秒。

    ② 当线程在休眠过程中被中断,则会产生InterruptedException 。

    ③ 在中断的线程上调用sleep()方法,同样会产生InterruptedException。

    12. Thread.interrupted()方法功能

    ① 判断线程是否应被中断。

    ② 通过捕获InterruptedException判断线程是否应被中断,并且在捕获到InterruptedException后,安全终止线程。

Handler

一、使用Handler

    1. Handler用于处理线程中的消息队列。当Looper对象从消息队列中获取消息后,会把消息派发给Handler对象。一个线程中只能有一个Handler对象,可以通过该对象向所在线程发送消息。因此,只要拥有其它线程中Handler对象的引用,就可以向其发送消息;除了给别的线程发送消息外,还可以给本线程发送消息。

    2. Handler一般有两种用途:

    ① 实现一个定时任务。这个有点类似于Windows中的定时器功能,可以通过Handler对象向所在线程发送一个延时消息。当消息指定的时间到达后,通过Handler对象的消息处理方法完成指定任务。

    ② 在线程间传递数据。

    3. Handler完成定时任务:

    ① 在一个Activity内部,经常需要做一些定时器的功能,比如周期性更新某个视图的内容、在指定时间后结束某个操作等。

    ② 完成定时任务,可以通过Handler对象的延迟发送消息方法来实现。

    ③ 在介绍发送消息之前,需要先了解一下消息Message的数据结构。Message是一个描述消息的数据结构类,Message包含很多成员变量和方法,但对于简单的消息处理,一般仅需了解3项,分别是:⑴ int what 这是用户自定义的一个整型值,用于区分消息类型。⑵ int arg1 这是额外消息参数。⑶ int arg2 同arg1。

    对于需要包含更多数据的消息,可以使用message.setData()和getData()方法。setData()方法用于把一个Bundle类数据对象加入到Message中,而getData()则是取出该Bundle数据。Bundle数据类型就是包含“键值对”数据的类型。

    4. Handler发送消息的方式:

    ① 一类是postXXX()方法,该方法用于把一个Runnable对象发送到消息队列。从而当消息被处理时,能够执行Runnable对象;

    ② 另一类是sendXXX()方法,该方法用于发送一个Message类型的消息到消息队列,当消息被处理时,系统会调用Handler对象定义的handleMessage()方法处理该消息。

    5. 实现定时任务则主要使用sendXXX()类,该类具体包含如下方法:

    ① sendEmptyMessage(int what),空消息是指该消息仅包含what值。

    ② sendEmptyMessageAtTime(int what, long uptimeMilis),在指定时间点发送空消息,uptimeMilis是指从本次开机开始运行的时间点,不包含系统休眠的时间,单位为毫秒。参照SystemClock类。

    ③ sendEmptyMessageDelayed(int what, long delayMillis),在指定时间后发送空消息,指定的时间以毫秒为单位。

    ④ sendMessage(Message),发送Message指定的消息。

    ⑤ sendMessageAtTime(Message,long),在指定时间点发送该消息。

    ⑥ sendMessageDelayed(Message, long),在指定的时间后发送该消息。

    实现定时任务时,一般使用sendMessageAtDelayed()或者sendEmptyMessageAtDelayed()方法,即在指定的时间后发送消息。当收到该消息后,系统会调用Handler对象实现的消息处理接口handleMessage(),handleMessage()的参数是Message对象,可以通过Message的相关方法获得Message的具体值,并根据其消息完成不同的任务。

二、Handler定时案例

    1. handleMessage用于处理Activity所在线程接收到的消息,此处是把当前时间显示在文本框中。obtainMessage()方法用于从全局的消息池中获得一个已有的Message对象,系统为了加速线程间的消息传递,创建了一些全局的消息对象供各线程使用,这些全局消息对象称为全局消息池,使用该方法比重新创建一个消息对象的效率高。该方法的第一个参数用于指定初始化返回消息的what值。sendMessageDelayed()方法用于在1000毫秒后发送what值为100的消息,即在显示完当前时间后的1秒,再发送一次消息,从而可以每过1秒更新一次文本框的时间。此处使用100代表该消息类型。

    2. 需要注意的是:在应用程序运行时,当用户按Back键返回后,尽管Activity进入了暂停或者停止的状态,但是消息的发送会依然在后台执行,因此,程序员需要根据情况决定是否要停止消息发送。例如可以在onPause()方法内将消息队列中的消息移除,并在onResume()方法中重新开始消息发送。removeMessage(100)方法用于移除消息队列中what值为100的全部消息。

    3. Handler完成线程间传递数据。

    ① 使用Handler对象不但可以给本线程发送消息,还可以给其它线程发送消息,前提是需要获取其它线程中的Handler对象。

    ② 线程之间传递数据在GUI应用中十分广泛,比如后台线程正在执行具体的数据处理,前台界面需要显示出处理的进度,典型的就是进度对话框。在这种应用中,前台线程(一般是指Activity)创建一个后台线程,并把前台线程的Handler对象传递给后台线程,后台线程就可以通过该Handler对象向前台线程发送消息,报告后台数据处理的进度。

Looper

    1. Thread在默认情况下,只要run()方法执行完毕,线程就结束。简单控制线程不主动退出的方法是:在run()方法内部加一个while()循环,这的确也能解决一些问题,对于那些不需要接收消息而言,基本上够用了。

    2. 但在另一些情况下,新建的线程需要接收消息并处理,因此,在新线程中,除了需要添加一个Handler对象外,还需要从线程的消息队列中取出消息,并负责分发消息,这就需要Looper了。

    3. 事实上,Activity内部就有一个Looper,只是Activity是一个特殊的Thread,操作系统已经将其封装了而已。

  Looper往往和Handler同时使用

    4. Looper.prepare()用于给该线程创建一个Looper对象;Looper.loop()用于开始执行Looper对象,所谓的执行就是让Looper对象开始读取线程的消息队列,并派发消息到Handler对象的handleMessage()方法。

    5. 请注意以上的someOtherFunction()方法,run()方法中,该方法只能被执行一次,执行完该方法后,开始执行Looper.loop()方法,系统便进入了Looper对象的世界里,以后就只会执行handleMessage方法了。

    6. 从LogCat的输出结果来看,someOtherFunction()在线程启动后执行一次。而Thread每收到一次what值为100的消息,method1()就会被执行一次;当thread收到what值为200的消息后,调用getLooper().quit()方法,Looper对象就停止运行,run()方法会执行完毕,该线程结束。

    7. looperTest()方法中,开始时先创建后台线程并启动,然后调用线程的自定义方法getHandler()获得后台线程的Handler对象。注意此处使用do/while语句,线程在启动后其内部的Handler对象不会马上就绪,因此这里需要等待。

    8. 接着让两个按钮按下时分别发送what值为100和200的Message,用于执行method1()方法和looper退出操作。

    HandlerThread类是一种内部包含了Looper对象的Thread子类。该类是为了简化Looper的操作,使用时只需要重写onLooperPrepared()方法,在其中添加一个Handler对象即可。其作用与在Thread中加入Looper对象和Handler对象是完全一样的。onLooperPrepared()被调用的时机类似于在Thread中执行完Looper.prepared()方法。

    线程是独立的程序单元,多个线程可以并行工作,每个线程都有一个消息队列,用于不同的线程之间传递消息。无论是本线程的还是其它线程,都不能直接处理消息队列中的消息,而是需要通过在线程内部定义一个Handler类对象来处理消息队列。

    leo这里给大家强调下一个Thread只能包含一个Handler对象。在实际应用中,读取消息队列一般需要循环执行,即不断地从消息队列中获取消息并进行相应处理,这就又需要一个Looper对象。今天就leo就给大家总结到这了,希望对同是Android开发者的你们有用,如有什么指正建议,欢迎提出并联系!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342

推荐阅读更多精彩内容