Android线程和Handler基础入门

如果需要使用异步处理或者并行任务的话,那么你一定会用到线程。

什么是线程?

线程或者线程执行本质上就是一串命令(也是程序代码),然后我们把它发送给操作系统执行。

一般来说,我们的CPU在任何时候一个核只能处理一个线程。多核处理器(目前大多数Android设备已经都是多核)顾名思义,就是可以同时处理多线程(通俗地讲就是可以同时处理多件事)。

多核处理与单核多任务处理的实质

上面我说的是一般情况,并不是所有的描述都是一定正确的。因为单核也可以用多任务模拟出多线程。

每个运行在线程中的任务都可以分解成多条指令,而且这些指令不用同时执行。所以,单核设备可以首先切换到线程1去执行指令1A,然后切换到线程2去执行指令2A,接着返回到线程1再去执行1B、1C、1D,然后继续切换到线程2,执行2B、2C等等,以此类推。

这个线程之间的切换十分迅速,以至于在单核的设备中也会发生。几乎所有的线程都在相同的时间内进行任务处理。其实,这都是因为速度太快造成的假象,就像电影《黑客帝国》里的特工Brown一样,可以变幻出很多的头和手。

接下来我们来看一些代码。

Java核心里的线程

在Java中,如果要想做平行任务处理的话,会在Runnable里面执行你的代码。可以继承Thread类,或者实现Runnable接口:
// Version 1 public class IAmAThread extends Thread { public IAmAThread() { super("IAmAThread"); } @Override public void run() { // your code (sequence of instructions) } } // to execute this sequence of instructions in a separate thread. new IAmAThread().start(); // Version 2 public class IAmARunnable implements Runnable { @Override public void run() { // your code (sequence of instructions) } } // to execute this sequence of instructions in a separate thread. IAmARunnable myRunnable = new IAmARunnable(); new Thread(myRunnable).start();

这两个方法基本上是一样的。第一个版本是创建一个Thread类,第二个版本是需要创建一个Runnable对象,然后也需要一个Thread类来调用它。

  • 第二个版是通常建议使用的方法。

Android上的线程

无论何时启动APP,所有的组件都会运行在一个单独的线程中(默认的)——叫做主线程。这个线程主要用于处理UI的操作并为视图组件和小部件分发事件等,因此主线程也被称作UI线程。

如果你在UI线程中运行一个耗时操作,那么UI就会被锁住,直到这个耗时操作结束。对于用户体验来说,这是非常糟糕的!这也就是为什么我们要理解Android上的线程机制了。理解这些机制就可以把一些复杂的工作移动到其它的线程中去执行。如果你在UI线程中运行一个耗时的任务,那么很有可能会发生ANR(应用无响应),这样用户就会很快地结束掉你的APP。

Android和Java一样,它支持使用Java里面的Thread类来进行一步任务处理。所以可以轻松地像上面Java的例子一样来使用Android上的线程,不过那好像还是有点困难。

为什么在Android上使用标准Java的线程会困难呢?

其实平行任务处理没有想象中的那么简单,你必须在多线程中保证并发,就像伟大的Tim Bray说的那样:ordinary humans can’t do concurrency at scale (or really at all) …
特别对于Android来说,以下这些功能就略显臃肿:

  • 异步对于UI线程来说是一个主要的PITA(如果你需要在后台线程中向主线程更新界面,那么你就会用到)。
  • 如果屏幕方向或者屏幕配置改变的话,就会出现一些更加奇怪的现象。因为改变屏幕方向,会引起Activity重建(所以后台线程就需要去改变被销毁的Activity的状态了,而如果后台线程不是在UI线程之上的话,那情况会更加复杂,原因如条件
  • 对于线程池来说,没有默认的处理方式。
  • 取消线程操作需要自定义代码实现。
那么在Android上怎么进行任务并发处理呢?

你可能听过一些Android上一些常见的名词:

  • Handler
    这就是我们今天要讨论的详细主题。
  • AsyncTask
    使用AsyncTask是在Android上操作线程最简单的方式,也是最容易出错的方式。
  • IntentService
    这种方式需要写更多的代码,但是这是把耗时任务移动到后台的很好的方式,也是我最喜欢的方式。配上使用一个EventBus机制的框架如Otto,这样的话实现IntentService就非常简单了。
  • Loader
    关于处理异步任务,还有很多事情需要做,比如从数据库或者内容提供者那里处理一些数据。
  • Service
    如果你曾经使用过Service的话,你应该知道这里会有一点误区,其中一个常见的误解就是服务是运行在后台线程的。其实不是!看似运行在后台是因为它们不与UI组件关联,但是它们(默认)是运行在UI线程上的……所以默认运行在UI线程上,甚至在上面没有UI部件。如果想要把服务运行在后台线程中,那么必须自定义一个线程,然后把操作代码都运行在那个线程中(与上面提到的方法很类似)。事实上你应该使用IntentService实现,但是这不是本文讨论的主题。

Android上的Handler

以下是从 Android developer documentation for Handlers:中摘选的一段话:

A Handler allows you to send and process Message and Runnable objects associated with a thread’s MessageQueue. Each Handler instance is associated with a single thread and that thread’s message queue. When you create a new Handler, it is bound to the thread/message queue of the thread that is creating it — from that point on, it will deliver messages and runnables to that message queue and execute them as they come out of the message queue.

为了更好地了解这个概念,也许你需要去看看什么是Message Queues。

消息队列

在线程里基本都有一个叫做“消息队列”的东西,它负责线程间通信。这是一种设计模式,所有控制指令或者内容在线程间传递。

消息队列如同它的名字那样,对于线程来说,它就是一个指令队列。这里我们还可以做一些更酷的事:

定时消息和线程在某个时间点才执行。
需要在另一个线程中去添加入队动作,而不是在本线程中。
注意:这里说的“消息”和Runnable对象、指令队列的概念是一样的。

回到Android上的Handler……如果你仔细阅读的话,可以看到文档是这样说的:

A Handler allows you to send and process Message and Runnable objects associated with a thread’s MessageQueue.

所以Handler可以让你给线程队列发消息:

Each Handler instance is associated with a single thread and that thread’s message queue.

一个Handler对象只能和一个线程关联:

When you create a new Handler, it is bound to the thread / message queue of the thread that is creating it

所以一个Handler到底和哪个线程关联呢?就是创造它的线程。

— from that point on, it will deliver messages and runnables to that message queue and execute them as they come out of the message queue.、

在我们了解这些知识后,请继续看……

小贴士: 这里有几点可能你还不知道。每个线程都和一个Handler类实例绑定,而且可以和别的线程一起运行,相互通信。

还有一个小建议(如果用过AsyncTask的话),AsyncTask内部也是使用Handler进行处理的,只是不是运行在UI线程而已,它会提供一个channel来和UI线程通信,使用postExecute方法即可实现。

这还挺酷的,那怎么创建Handler呢?

有两种方式:

使用默认的构造方法:new Handler()。
使用带参的构造方法,参数是一个Runnable对象或者回调对象。

Handler里面有什么实用的API吗?

请记住:
Handler只是简单往消息队列中发送消息而已(或者使用post方式)
它们有更方便的方法可以帮助与UI线程通信。

如果你现在看看Handler的API,可以清楚看到这几个方法:
post
postDelayed
postAtTime

代码示例

这里的代码都是很基础的,不过你可以好好看看注释。
示例1:使用Handler的“post”方法

public class TestActivity extends Activity { // ... // all standard stuff @Override public void onCreate(Bundle savedInstanceState) { // ... // all standard stuff // we're creating a new handler here // and we're in the UI Thread (default) // so this Handler is associated with the UI thread Handler mHandler = new Handler(); // I want to start doing something really long // which means I should run the fella in another thread. // I do that by sending a message - in the form of another runnable object // But first, I'm going to create a Runnable object or a message for this Runnable mRunnableOnSeparateThread = new Runnable() { @Override public void run () { // do some long operation longOperation(); // After mRunnableOnSeparateThread is done with it's job, // I need to tell the user that i'm done // which means I need to send a message back to the UI thread // who do we know that's associated with the UI thread? mHandler.post(new Runnable(){ @Override public void run(){ // do some UI related thing // like update a progress bar or TextView // .... } }); } }; // Cool but I've not executed the mRunnableOnSeparateThread yet // I've only defined the message to be sent // When I execute it though, I want it to be in a different thread // that was the whole point. new Thread(mRunnableOnSeparateThread).start(); } }
如果根本就没有Handler对象,回调post方法会比较难办。

示例2:使用postDelayed方法
模拟EditText的自动完成功能,每次文字改变后都会触发一个API的调用,从服务器中检索数据。
我想减少APP调用API的次数,所以决定使用Handler的postDelayed方法来实现这个功能。
本例不针对平行处理,只是关于Handler给消息队列发送消息还有安排消息在未来的某一点执行等。

// the below code is inside a TextWatcher // which implements the onTextChanged method // I've simplified it to only highlight the parts we're // interested in private long lastChange = 0; @Override public void onTextChanged(final CharSequence chars, int start, int before, int count) { // The handler is spawned from the UI thread new Handler().postDelayed( // argument 1 for postDelated = message to be sent new Runnable() { @Override public void run() { if (noChangeInText_InTheLastFewSeconds()) { searchAndPopulateListView(chars.toString()); // logic } } }, // argument 2 for postDelated = delay before execution 300); lastChange = System.currentTimeMillis(); } private boolean noChangeInText_InTheLastFewSeconds() { return System.currentTimeMillis() - lastChange >= 300 }

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

推荐阅读更多精彩内容