您的位置:首页 > 其它

线程阻塞与Handler相关超详细原理讲解

2015-12-16 21:42 423 查看
转载请标明出处: /article/9722782.html

Handler的重要性我就不多说了,想学习Android的人必须要懂Handler,下面我们从原理性方面全面讲解Handler

首先,我们来解释一下线程和进程是什么:从一定意义上讲,进程就是一个应用程序在处理机上的一次执行过程,它是一个动态的概念,这就像大家可以在监视程序窗口看到你所打开的程序,你打开它一次它就创建了一个进程,打开多次你就可以看到有多个进程,它里面包含了你打开程序的组件(Android中如果你没有作出特别的要求,那么所有的组件都运行在同一个进程中)等内容,这就是进程,而线程是进程中的一部分,进程包含一个或多个线程在运行,可以理解为线程可以使用并调度进程中的信息。

在Android中,在一个APP启动的时候会检测是否有它的进程和线程,如何没有就会创建一个Linux进程(Android是基于Linux设计的)和一个叫Main的线程,这就是我们所说的主线程,它被用来处理跟UI界面有关的信息,所以也可以叫作UI线程。由于Android采用的是单线程的模型(处理UI界面只有一个线程),意思就是处理UI界面的只能是UI线程,其他线程(也叫Worker工作线程)处理UI界面,程序就会报出异常,所以也就出现了官方API所提出的两个UI线程原则之一:

Do not access the Android UI toolkit from outside the UI thread(不要从UI线程以外访问Android UI工具包)

当然还有一点就是:

Do not block the UI thread(不要阻塞UI线程)

好了,什么是阻塞UI线程,其实线程工作的时候可以认为是处理一个一个的事件,而处理这些事件是需要时间的,Android中规定如何UI线程处理一个事件超过了5秒(这时就像假死一样),就被Android认为是UI线程阻塞了,就会弹出一个ANR来提示用户是否关闭APP,其实这一规定也就是优化了我们在使用APP时的体验,相信任何用户都不希望程序等待5秒还没有任何变化,这会让大多数人心情极差,直接就选择关闭APP了,这也是我们不希望,所以在我们编写APP时应该特别注意UI线程阻塞的问题。

那么什么会使UI线程阻塞呢,也就是处理时间超过了5秒,其实在很多时候处理时间都会超过5秒,就如我们要上网下载一个软件的时间很容易就超过了5秒,我们将这些需要较长线程处理时间的事件叫作耗时操作, 很显然我们不可能完全不进行耗时操作,但是如何在UI线程进行耗时操作又会线程阻塞,那么怎么办呢?这里Android提供了多种方法,我们可以重新开启另一个线程,也可以用service服务来进行耗时操作等方式。这里我们先讲开启线程来处理耗时操作的方式。

我们之前讲到一个进程可以有一个或多个线程,Android只有一个UI线程,那么其他的线程我们一般叫作Worker工作线程,接下来我们来开启一个工作线程,代码如下:

public void onClick(View v) {
new Thread(new Runnable() {
public void run() {
Bitmap b = loadImageFromNetwork("http://example.com/image.png");//这是一个从指定网站下载图片的方式(需要自己编写,来代表耗时任务)
mImageView.setImageBitmap(b);
}
}).start();//别忘了.start启动线程
}


当然了,我们可以看到这段代码开启了一个新的线程,在该线程是从网上下载了一张图片,并且对UI界面的mImageView设置该图片,很明显,这行代码是错误的,因为它违反了UI线程中不要从UI线程以外访问Android UI工具包的原则,所以程序会报出这样的异常:

android.view.ViewRoot$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.

那么我们想要做耗时任务又想更新UI界面,那怎么做呢?Handler就应声而出了,那么Handler是什么?它就是一个处理和接收信息的对象,那么什么又是处理和接收什么对象呢?这就需要我们来讲解几个Android特有的机制来,Looper,Message,MessageQueue。

首先,我们之前提到了UI线程是一件一件事件执行的,做完一件做下一件,那么什么东西给它提供下一件呢,这就是Looper,Looper就是用来提供下一个事件的,那么事件存在哪呢,那就是MessageQueue了,MessageQueue是存储我们想要让UI线程做的事件的,而存储的就是一个一个Message(消息)。那么怎么才能让我们想要的消息存入MessageQueue中呢?Handler就可以办到了。好了,让我们梳理一下它的执行步骤,首先是我们将消息Message(当然Message之前必须实例化)送到MessageQueue,然后Looper就像一个水泵,循环不停地将一个一个消息从MessageQueue中压出并送给Handler接收并处理的,所以一个Handler在创建的就绑定了MessageQueue,而一个MessageQueue也拥有它相对应的Looper,这就是消息循环机制。

说了这个多,你肯定会问这跟我们要更新UI界面有神马关系啦,好了,首先我们明确一下,我们在实例化Handler是在主线程中实例化的,也就是我们的Handler是属于主线程的,是可以更新UI并且不违反那两条原则的,所以呢,方法就是:我们还是在工作线程中做耗时任务,在做完之后呢,就发送一个Message(也就是一个信号)给Handler,主线程中Handler收到消息并判断耗时操作完成后就可以更新UI了,这就是Android中处理耗时任务主要的方法,好了话不多说了,将上面的代码简单修改如下:

private Bitmap b;
private Handler handler=new Handler(){//主线程中实例化Handler
@Override
public void handleMessage(Message msg){//有消息时就会被调用
switch(msg.what){//判断完成标志
case 1:
mImageView.setImageBitmap(b);//更新UI
break;
}
}
};

public void onClick(View v) {
new Thread(new Runnable() {
public void run() {        b=loadImageFromNetwork("http://example.com/image.png");//这是一个从指定网站下载图片的方式(需要自己编写,来代表耗时任务)
Message msg=Message.obtain();//获得一个Message,为什么不用new下面会讲
msg.what=1;//设置一个下载完成的标志
handler.sendMessage(msg);//将Message发送给handler
}
}).start();
}


当然发送消息的方式也可以是这样的:

Message msg=Message.obtain(handler);
msg.what=1;
msg.sendToTarget();


我们来看源码:

public void sendToTarget() {
target.sendMessage(this);//this就是message,target就是handler,然后调用sendMessage其实和上面handler.sendMessage(msg)方法是一样的。
}


使用类似如上代码(当然官方也提供了Message msg=Handler.obtainMessage()这方法,其实代码和Message.obtain()基本上是一样的)就可以解决更新UI的问题了。

现在我们来看看Message这个类。这个类其实就可以看作一个可以携带各种信息的载体,官方API中 显示它可以包含int what, int arg1, int arg2(arg1, arg2是一个低消耗的数据,所以我们一般储存一些轻量的数据), Object obj这4个数据,msg.what也就是对它的赋值方式之一,它的赋值方式太多了,这里就只列出3种,更多方式读者可以参考API中Message类。

1.obtain(Handler h, int what, int arg1, int arg2, Object obj)

2.obtain(Handler h, int what)

那么我们为什么要使用Message.obtain()而不使用new Message()呢,其实这就是Android内部用来优化内存资源的方法,我们先看obtain()源代码:

public static Message obtain() {
synchronized (sPoolSync) {
if (sPool != null) {
Message m = sPool;
sPool = m.next;
m.next = null;
m.flags = 0; // clear in-use flag
sPoolSize--;
return m;
}
}
return new Message();
}


源码中显示先是同步消息池,然后判断是否存在可用的Message,存在就取出并返回,如何不存在Message就返回一个新的Message,因为 new Message()会占用很多的系统资源,所以Android设计了一个消息池概念,也就是说我们只要用过的消息不会销毁,Android将它们存储在消息池中,需要使用的时候就取出来使用,只有消息池中消息不够用的时候才会去 new Message()消息(当然消息池最大值现在是50 个消息),这一设计大大减少了系统资源的浪费,提高了资源利用率,我们之后会介绍的线程池概念也就是这样概念。

相信大家对Handler已经有所了解了吧,快去尝试一个工作线程通知主线程更新UI的感觉吧。。。。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: