Android线程与消息机制

OUTLINE

§UI线程

§Looper

§消息机制

§线程交互

§AsyncTask

§Activity/Service与主线程

UI线程


先从一个经典错误开始:

android.view.ViewRootImpl$CalledFromWrongThreadException:

Only the original thread that created a view hierarchy can touch its views

为什么会出现这个错误?

UI的呈现必须在同一个线程里面完成。

试想,如果多个线程可以绘制UI,那么肯定乱套,呈现结果不可预期。

因此界面程序必然有一个UI线程,android,java,windows等都是如此。

Android UI 丈量、排布、绘制最终都是在ViewRootImpl里面完成的。

ViewRootImpl在执行UI操作之前,会进行线程检查。如果当前线程不是UI线程,就会抛出上述异常。

每个应用都对应一个进程,进程创建是伴随一个主线程创建。这个主线程就是UI线程。

Android的主线线程的入口在ActivityThread。ActivityThread和普通的java入口类一样,有一个静态main函数,作为主线程的入口。

ActivityThread与Android应用生命周期密切相关,后续会讲到。

Looper


Thread是一个线性执行,界面程序需要持续存在,因此需要一个循环,Looper就是Android里面线程循环的封装。

这样说还是比较抽象,那么Looper到底是什么?

消息机制


任务的循环执行,需要一个队列,可进可出,Android使用消息队列MessageQueue来实现。

一个Looper绑定一个Thread,在这个线程中循环;同时绑定一个MessageQueue,在这个消息队列中存取消息;然后,通过Handler向外接口。通过Handler把消息加入。

MessageQueue, Looper调用loop进行循环,循环地从消息队列中获取消息,处理消息。

Message

§消息出队:MessageQueue::next

§消息入队<- 生成消息:绑定Handler

   Handler::obtainMessage

   Message::obtainMessage(Handler)

消息队列中的消息是供Looper来消耗的,Looper通过MessageQueue的next方法取出消息。这个过程是在Looper内部完成,我们不需要太过关心。

不过next方法也是比较讲究的,这个方法最终会调用native的方法,可能会等待睡眠,直到IO事件或者消息入队把它唤醒。

Android的Message必须绑定到一个Handler,由这个Handler来发送和处理消息。消息入队的时候会检查消息是否绑定了Handler,如果没有绑定,会直接抛出异常。

因此我们通常是Handler::obtainMessage,这个方法获得的Message直接绑定到了Handler;另外,也会Message::obtainMessage,当前必须传递Handler。

Looper的创建


§Looper.prepare生成Looper实例

§ThreadLocal映射,将Looper和Thread绑定


Looper.prepare();

 Looper looper = Looper.myLooper();

 ...

 Looper.loop();

在线程中调用Looper.prepare完成Looper的创建。

通过ThreadLocal映射,Looper与线程绑定。

Looper创建时生成了MessageQueue。

Thread和ThreadLocal都是java的东西,Looper是Android的。Thread和Looper之前的绑定使用了ThreadLocal。ThreadLocal就是线程的存储器,它是一个通用设计,它为每个线程存储数据,从每个线程进来看到对应的线程的数据。因此Android的Looper很好地利用了这一点,使用ThreadLocal,从每个线程进来看到的Looper都是绑定的Looper。

MainLooper的创建

§ActivityThread main入口

 Looper. prepareMainLooper();

 ...

 Looper.loop();

主线程入口处(ActivityThread的main入口),调用Looper.prepareMainLooper,完成MainLooper的创建。再调用loop让主线程循环起来。

MainLooper作为静态变量保存在Looper中,可通过getMainLooper获取。

Handler的创建

§Handler——Looper的对外接口

§Handler创建 <- Looper实例

−主线程Handler:直接获取MainLooper来创建。

−其他线程Handler:必须在调用Looper.prepare之后创建。

eg:

HandlerThread::onLooperPrepared

−不指定Looper,使用当前线程Looper。

Handler创建需要指定Looper,因此Handler的创建需要在调用Looper.prepare之后(HandlerThread.onLooperPrepared),否则会报异常。

如果没有Looper,默认使用当前线程绑定的Looper。

因此,通常在主线程可以任意创建Handler,因为MainLooper在主线程启动时已经prepare。

而在其他线程创建Handler时需要先调用Looper.prepare。

非主线线程Handler典型创建方法是通过HandlerThread。HandlerThread是Android联结Handler和线程的封装,它用onLooperPrepared回调提供给外界创建Handler。

 线程的创建

§线性执行:直接或间接new Thread

§循环:

               1. 基于Looper的Thread ->HandlerThread

               2. 自己为线程实现循环机制

线程交互


线程之间的交互通过消息机制完成。具体来说,A线程需要发送消息到B线程,需要通过持有B线程Looper的Handler发送消息。

eg:  A线程需要操作B线程

A发送消息给B

−A发送:A需要Handler实例

−给B:Handler实例必须持有B线程的Looper。

实现:

−B线程生成Handler

−Handler定义消息和消息处理

主线程与非主线程交互

§主线程执行非主线程操作

−获取非主线程Handler发送消息。     针对持久存在的非主线程处理

−通过AsyncTask::doInBackground执行。 针对临时存在的后台线程处理

-View.post(Runnable)。     在View中执行。

-Activity.runOnUiThread。     在Activity中执行。

§非主线程执行主线程操作

−获取MainLooper生成主线程Handler。  针对持久的主线程处理

 eg:ViewRootHander,Activity的Handler

−通过AsyncTask::onProgressUpdate或onPostExecute。   针对临时主线程处理

−通过临时new Handler(传递MainLooper)来post执行。

View的post函数将Runnable加入到ViewRootImpl的执行队列中,在下次ViewRootImpl执行tranversal时,将队列任务加入一个主线程的Handler的消息队列。

Activity的runOnUiThread后文再详细讲解。

AsyncTask是Android对主线程和后台线程处理的一个封装,它是线性执行的,不作循环。

AsyncTask


§主线程执行——持有MainLooper的Handler静态成员sHandler,在主线程入口处初始化。

§后台执行——新建线程,设置为优先级:

THREAD_PRIORITY_BACKGROUND

onProgressUpdate

onPostExecute  主线程

doInBackground     后台线程

onPreExecute      取决于调用线程

AsyncTask持有一个静态Handler,由主线程入口处初始化创建,因此它持有MainLooper。onProgressUpdate和onPostExecute是通过该Handler执行的,因此都是主线程操作。

类的静态成员和静态块,是在类加载的时候执行的。在主线程入口的地方,没有必须创建一个AsyncTask实例,但是需要为它初始Handler,因此调用AsyncTask一个无用的静态方法init,仅仅是为了初始化Handler。

AsyncTask的后台执行是通过创建一个新的线程,非设置线程优先级为Background,因此它执行后台操作。

onProgressUpdate和onPostExecute是通过该Handler执行的,因此都是主线程操作。doInBackground是新建线程(THREAD_PRIORITY_BACKGROUND优先级),因此是后台线程操作。onPreExecute在execute中执行,所在线程取决于调用的线程。

线程优先级

§设置线程优先级

−android.os.Process.setThreadPriority:[-20, 19]。越小优先级越高

−java.lang.Thread.setPriority:[1, 10]。越大优先级越高。

§THREAD_PRIORITY_BACKGROUND(10):标准的后台优先级

§默认线程优先级:THREAD_PRIORITY_DEFAULT(0),中等

设置线程优先级有两种方式:

android.os.Process.setThreadPriority:[-20,19]。越小优先级越高

java.lang.Thread.setPriority:[1, 10]。越大优先级越高。

优先级调度是底层实现的,没有具体深入。优先级的设置和执行结果是没有办法准确预期的,但是可以肯定的是Process.setThreadPriority的效果更符合预期。

Activity/Service与主线程


§Activity和Service运行在主线程(ActivityThread$H)

 onCreate,onResume,…

§Activity Handler

 与AsyncTask类似,Activity内部有一个持有MainLooper的Handler。

Acitivty的启动过程是通过ActivityThread.H这样一个Handler来调用的,这是一个主线程的Handler,因此主线程的启动都在主线程。

另外,与AsyncTask类似,Activity内部有一个持有MainLooper的Handler。因此Activity提供了一个runOnUiThread的方法,方便直接执行UI操作。


【茶工坊】

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

推荐阅读更多精彩内容