今天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开发者的你们有用,如有什么指正建议,欢迎提出并联系!