一、Android之线程信使
曾在网上看到一个段子。
面试时,面试官问码农:xxx是线程安全的吗?
码农果断答道:是线程安全的。
面试官:(⊙o⊙)…我正准备接着问为什么不是线程安全的
……
然后就没有然后了。
那么,同样的问题:Android的UI操作是线程安全的吗?
(不要回答的太果断啊)
答案肯定是:不是线程安全的。具体吗?我想这个链接已经说的基本清楚了:http://chenjinhua1595515.blog.163.com/blog/static/21583214720140834012400/
回归正题,Android中有一条规则:只允许UI线程修改Activity里的UI组件,而如果需要在新线程中更新UI线程的组件,则需要通过一个信使:Handler。
Handler类主要有两个作用:
(1)在新线程中发送消息。
(2)在UI线程中获取、处理消息。
好,so easy!完活儿。
二、Handler、Loop和MessageQueue组件
真的这么简单吗?新启动的线程何时发送消息?UI线程何时获取消息?以及消息的传递机制呢?这就有必要了解一下和Handler一起工作的小伙伴啦。
Message:消息(直译,哈哈)。Handler接收和处理的就是它。
Looper:(这个不太好直译)刚开始学的时候,一上来就是东西最难理解,后来发现这个东西就是连接MessageQueue和Handler的中间人,负责把MessageQueue中的Message取出来,送给Handler去处理。需要注意的是:每个线程只有一个Looper。
MessageQueue:消息队列。大家肯定都知道队列的实现机制,不知道也没事,一句话,先进先出。
在线程中使用Handler的步骤有以下三步:
(1)通过Looper的prepare()方法为当前线程创建Looper对象
private Looper() {
mQueue = new MessageQueue();
mRun = true;
mThread = Thread.currentThread();
}
可见,同时创建了与之配套的MessageQueue。
(2)创建Handler子类的实例,重写handleMessage()方法,该方法负责处理其他线程的消息。
Handler mHandler = new Hander() {
@Override
public void handleMessage(Message msg) {
if(msg.what == VALUE)
// WHAT YOU WANT TO DO
}
}
(3)调用Looper的loop方法来启动Looper。loop方法是一个静态方法,网上有它的源代码,是一个死循环不断的取出MessageQueue中的消息,并将其分给该消息对应的Handler处理。代码较长,这里不再贴出。
三、网络编程时的多线程
在Android端的网络编程时(关于网络编程,后续博客中会具体阐述),多线程是必须的编程方法,比如:与服务器进行通信的代码需要在新线程中完成,响应用户操作的代码需要在UI线程中完成……(当然,根据需求,可能这里有非常多的线程,比如多线程下载),这里,仅举例说明网络连接的线程和主线程。
以最简单的登陆服务器为例:
loginButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View arg0) {
final String name = ((EditText) findViewById(R.id.log_in_name_input)).getText().toString();
final String password = ((EditText) findViewById(R.id.log_in_password_input)).getText().toString();
new Thread() {
@Override
public void run() {
try {
HttpPost post = new HttpPost("http://xxx.xxx.xx.xxx:8080/APPLICATION_NAME/login.jsp");
List<NameValuePair> params = new ArrayList<NameValuePair>();
params.add(new BasicNameValuePair("name", name));
params.add(new BasicNameValuePair("pass", password));
post.setEntity(new UrlEncodedFormEntity(params, HTTP.UTF_8));
HttpResponse response = httpClient.execute(post);
if (response.getStatusLine().getStatusCode() == 200) {
String msg = EntityUtils.toString(response.getEntity());
<strong>Looper.prepare();
Toast.makeText(MainActivity.this, msg, Toast.LENGTH_SHORT).show();
Looper.loop();</strong>
}
}
catch (Exception e) {
e.printStackTrace();
}
}
}.start();
}
});
}
在监听登陆按钮时,需要新启动一个线程,进行与服务器的连接,在监听到服务器成功返回响应之后,首先,通过response.getEntity()方法取得返回的内容,然后通过prepare()方法创建Looper对象,最后调用loop()方法启动Looper。