Android 使用Thread+Handler实现非UI线程更新UI界面

**摘要:**每个Android应用程序都运行在一个dalvik虚拟机进程中,进程开始的时候会启动一个主线程(MainThread),主线程负责处理和ui相关的事件,因此主线程通常又叫UI线程。而由于Android采用UI单线程模型,所以只能在主线程中对UI元素进行操作。如果在非UI线程直接对UI进行了操作,则会报错:CalledFromWrongThreadException:only the original thread that created a view hierarchy can touch its views。

Android为我们提供了消息循环的机制,我们可以利用这个机制来实现线程间的通信。那么,我们就可以在非UI线程发送消息到UI线程,最终让Ui线程来进行ui的操作。

对于运算量较大的操作和IO操作,我们需要新开线程来处理这些繁重的工作,以免阻塞ui线程。在发送http等网络请求时,会经常使用!


实例讲解

下面承接前几篇文章中对天气情况(Weather)的一些详细说明,继续完善,实现在Android客户端的一个天气预报功能的开发:

WeatherActivity主程序代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
package com.dm.weather;

import android.annotation.TargetApi;
import android.app.Activity;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.support.v4.widget.SwipeRefreshLayout;
import android.util.Log;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;

import com.dm.CommonData.RetData;
import com.dm.CommonData.Weather;
import com.google.gson.Gson;

import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.protocol.HTTP;
import org.apache.http.util.EntityUtils;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.List;

/**
* Thread + Handler 实例
*/
public class WeatherActivity extends Activity {
private SwipeRefreshLayout swipeRefreshLayout;
private TextView refreshTv;

private static final String LOG_TAG = "WeatherActivity";
private static final int MSG_SUCCESS = 1; // 成功标识
private static final int MSG_FAILURE = 0; // 失败标识
private static final String REQUEST_URL = "http://apistore.baidu.com/microservice/weather?cityname=郑州";
private static final String REQUEST_BASE_URL = "http://apistore.baidu.com/microservice/weather";

private ImageView weatherIcon;
private TextView weatherDateTv;
private TextView weatherTimeTv;
private TextView weatherStatusTv;
private TextView windDirectionTv;
private TextView windSpeedTv;
private TextView weatherCurrentTmpTv;
private TextView weatherLowTmpTv;
private TextView weatherHighTmpTv;
private TextView sunRiseTimeTv;
private TextView sunSetTimeTv;

private Thread mThread = null;

private Handler weatherHander = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_SUCCESS:
// 使用Gson对Json数据的解析,请翻看之前的文章:[Java中使用Gson解析json数据](http://blog.csdn.net/mduanfire/article/details/44703059%20+ "Java中使用Gson解析json数据")
Gson weatherGson = new Gson();
Log.i(LOG_TAG, msg.obj.toString());

Weather weather = weatherGson.fromJson(msg.obj.toString(), Weather.class);
RetData retData = weather.getRetData();

weatherDateTv.setText(retData.getDate() + " ");
weatherTimeTv.setText(retData.getTime());
windDirectionTv.setText(retData.getWD());
windSpeedTv.setText(retData.getWS());
weatherStatusTv.setText(retData.getWeather());
weatherCurrentTmpTv.setText(retData.getTemp());
weatherLowTmpTv.setText(retData.getL_tmp());
weatherHighTmpTv.setText(retData.getH_tmp());
sunRiseTimeTv.setText(retData.getSunrise());
sunSetTimeTv.setText(retData.getSunset());

switch (retData.getWeather()) {
case "晴":
weatherIcon.setImageResource(R.drawable.qing);
break;
case "多云":
weatherIcon.setImageResource(R.drawable.duoyun);
break;
case "阴":
weatherIcon.setImageResource(R.drawable.yin);
break;
case "阵雨":
weatherIcon.setImageResource(R.drawable.zhengyu);
break;
case "雷阵雨":
weatherIcon.setImageResource(R.drawable.leizhengyu);
break;
case "雷阵雨伴有冰雹":
weatherIcon.setImageResource(R.drawable.leizhengyubanyoubingbao);
break;
case "雨夹雪":
weatherIcon.setImageResource(R.drawable.yujiaxue);
break;
case "小雨":
weatherIcon.setImageResource(R.drawable.xiaoyu);
break;
case "中雨":
weatherIcon.setImageResource(R.drawable.zhongyu);
break;
case "大雨":
weatherIcon.setImageResource(R.drawable.dayu);
break;
case "暴雨":
weatherIcon.setImageResource(R.drawable.baoyu);
break;
case "大暴雨":
weatherIcon.setImageResource(R.drawable.dabaoyu);
break;
case "特大暴雨":
weatherIcon.setImageResource(R.drawable.tedabaoyu);
break;
case "阵雪":
weatherIcon.setImageResource(R.drawable.zhengxue);
break;
case "小雪":
weatherIcon.setImageResource(R.drawable.xiaoxue);
break;
case "中雪":
weatherIcon.setImageResource(R.drawable.zhongxue);
break;
case "大雪":
weatherIcon.setImageResource(R.drawable.daxue);
break;
case "暴雪":
weatherIcon.setImageResource(R.drawable.baoxue);
break;
case "雾":
weatherIcon.setImageResource(R.drawable.wu);
break;
case "冻雨":
weatherIcon.setImageResource(R.drawable.dongyu);
break;
case "沙尘暴":
weatherIcon.setImageResource(R.drawable.shachenbao);
break;
case "浮尘":
weatherIcon.setImageResource(R.drawable.fuchen);
break;
case "扬沙":
weatherIcon.setImageResource(R.drawable.yangsha);
break;
case "强沙尘暴":
weatherIcon.setImageResource(R.drawable.qiangshachenbao);
break;
case "霾":
weatherIcon.setImageResource(R.drawable.mai);
break;
case "小到中雨":
weatherIcon.setImageResource(R.drawable.zhongyu);
break;
case "中到大雨":
weatherIcon.setImageResource(R.drawable.dayu);
break;
case "大到暴雨":
weatherIcon.setImageResource(R.drawable.baoyu);
break;
case "暴雨到大暴雨":
weatherIcon.setImageResource(R.drawable.dabaoyu);
break;
case "大暴雨到特大暴雨":
weatherIcon.setImageResource(R.drawable.tedabaoyu);
break;
case "小到中雪":
weatherIcon.setImageResource(R.drawable.zhongxue);
break;
case "中到大雪":
weatherIcon.setImageResource(R.drawable.daxue);
break;
case "大到暴雪":
weatherIcon.setImageResource(R.drawable.baoxue);
break;
default:
weatherIcon.setImageResource(R.drawable.qita);
break;
}

refreshTv.setText("刷新成功");
swipeRefreshLayout.setRefreshing(false);
break;
case MSG_FAILURE:
Toast.makeText(getApplicationContext(), "FAILURE, 网络繁忙!", Toast.LENGTH_LONG).show();
break;
}
}
};

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.weather_activity);

initTitleView();
initWeatherView();
initBaseView();
}

@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
private void initBaseView() {
refreshTv = (TextView) findViewById(R.id.refresh_tv);
swipeRefreshLayout = (SwipeRefreshLayout) findViewById(R.id.swipe_container);
swipeRefreshLayout.setColorSchemeResources(android.R.color.holo_blue_light, android.R.color.holo_red_light, android.R.color.holo_orange_light, android.R.color.holo_green_light);
swipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
@Override
public void onRefresh() {
refreshTv.setText("正在刷新");
// 开启新的线程,请求网络数据,返回json数据
mThread = new Thread(runnable);
mThread.start();
}
});
}

Runnable runnable = new Runnable() {
@Override
public void run() {

// Http get
// String requestUrl = REQUEST_URL;
//
// HttpGet httpGet = new HttpGet(requestUrl);
//
// try {
// HttpResponse httpResponse = new DefaultHttpClient().execute(httpGet);
//
// if (httpResponse.getStatusLine().getStatusCode() == 200) {
// String requestResult = EntityUtils.toString(httpResponse.getEntity());
//
// Log.i(LOG_TAG, "requestResult = " + requestResult);
//
// 使用定义的weatherHander的obtainMessage()方法将成功标志和请求成功得到的json字符串传递过去,之后在Hander中进行解析
weatherHander.obtainMessage(MSG_SUCCESS, requestResult).sendToTarget();
// } else {
// Log.i(LOG_TAG, "requestError = " + httpResponse.getStatusLine());
// Toast.makeText(getApplicationContext(), "Response Error!!" + httpResponse.getStatusLine(), Toast.LENGTH_SHORT).show();
// }
// } catch (IOException e) {
// Log.i(LOG_TAG, e.getMessage());
// e.printStackTrace();
// }

// Http post
String url = REQUEST_URL;
HttpPost httpPost = new HttpPost(url);
try {
HttpResponse httpResponse = new DefaultHttpClient().execute(httpPost);

if (httpResponse.getStatusLine().getStatusCode() == 200) {
String requestResult = EntityUtils.toString(httpResponse.getEntity());

Log.i(LOG_TAG, "requestResult = " + requestResult);
weatherHander.obtainMessage(MSG_SUCCESS, requestResult).sendToTarget();
} else {
Log.i(LOG_TAG, "requestError = " + httpResponse.getStatusLine());
Toast.makeText(getApplicationContext(), "Response Error!!" + httpResponse.getStatusLine(), Toast.LENGTH_SHORT).show();
}
} catch (IOException e) {
Log.i(LOG_TAG, "+++++: " + e.getMessage());
e.printStackTrace();
}
}
};

private void initTitleView() {
ImageView titleBackImv = (ImageView) findViewById(R.id.titleBack_iv);
TextView titleTextTv = (TextView) findViewById(R.id.titleText_tv);

titleTextTv.setText("天气状况");
titleBackImv.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
finish();
}
});
}

private void initWeatherView() {
weatherIcon = (ImageView) findViewById(R.id.weather_icon);
weatherDateTv = (TextView) findViewById(R.id.weather_date);
weatherTimeTv = (TextView) findViewById(R.id.weather_time);
windDirectionTv = (TextView) findViewById(R.id.weather_windstatus);
windSpeedTv = (TextView) findViewById(R.id.weather_windspeed);
weatherStatusTv = (TextView) findViewById(R.id.weather_status);
weatherCurrentTmpTv = (TextView) findViewById(R.id.weather_curent_tmp);
weatherLowTmpTv = (TextView) findViewById(R.id.weather_low_tmp);
weatherHighTmpTv = (TextView) findViewById(R.id.weather_high_tmp);
sunRiseTimeTv = (TextView) findViewById(R.id.weather_sunrise_time);
sunSetTimeTv = (TextView) findViewById(R.id.weather_sunset_time);
}
}

weather_activity界面布局代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/seashell">

<include
android:id="@+id/weather_include"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
layout="@layout/title_layout" />

<android.support.v4.widget.SwipeRefreshLayout
android:id="@+id/swipe_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_alignTop="@+id/weather_icon">

<TextView
android:id="@+id/refresh_tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Refresh?"
android:padding="5dp"
android:gravity="center_horizontal" />

</android.support.v4.widget.SwipeRefreshLayout>

<ImageView
android:id="@+id/weather_icon"
android:layout_width="120dp"
android:layout_height="120dp"
android:background="@color/powderblue"
android:padding="10dp"
android:layout_marginTop="15dp"
android:layout_marginLeft="15dp"
android:layout_below="@+id/weather_include"
android:src="@drawable/qita" />

<TextView
android:id="@+id/weather_city"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="郑州"
android:textSize="24sp"
android:textColor="@color/black"
android:layout_alignParentRight="true"
android:layout_marginTop="15dp"
android:layout_marginRight="15dp"
android:layout_below="@+id/weather_include"
android:layout_alignRight="@+id/weather_icon" />

<TextView
android:id="@+id/weather_status"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="~~~~~~"
android:textSize="22sp"
android:textColor="@color/black"
android:layout_alignBottom="@+id/weather_icon"
android:layout_centerHorizontal="true" />

<TextView
android:id="@+id/weather_windspeed"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="~~~ "
android:textSize="18sp"
android:textColor="@color/black"
android:layout_below="@+id/weather_windstatus"
android:layout_alignRight="@+id/weather_city"
android:layout_alignEnd="@+id/weather_city" />

<TextView
android:id="@+id/weather_windstatus"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="~~~~~~"
android:textSize="18sp"
android:textColor="@color/black"
android:layout_above="@+id/weather_status"
android:layout_alignRight="@+id/weather_windspeed"
android:layout_alignEnd="@+id/weather_windspeed" />

<TextView
android:id="@+id/weather_date"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="~~~~~~ "
android:textSize="18sp"
android:textColor="@color/black"
android:layout_toLeftOf="@+id/weather_time"
android:layout_below="@+id/weather_city" />

<TextView
android:id="@+id/weather_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="~~~ "
android:textSize="18sp"
android:textColor="@color/black"
android:layout_below="@+id/weather_city"
android:layout_alignLeft="@+id/weather_city"
android:layout_alignStart="@+id/weather_city" />


<TextView
android:id="@+id/weather_curent_tmp_hint"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="当前气温: "
android:textSize="20sp"
android:textColor="@color/black"
android:layout_marginTop="15dp"
android:layout_below="@+id/weather_icon"
android:layout_alignLeft="@+id/weather_icon"
android:layout_alignStart="@+id/weather_icon" />

<TextView
android:id="@+id/weather_curent_tmp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="~~~"
android:textSize="20sp"
android:textColor="@color/black"
android:layout_alignTop="@+id/weather_curent_tmp_hint"
android:layout_toRightOf="@+id/weather_curent_tmp_hint"
android:layout_toEndOf="@+id/weather_curent_tmp_hint" />

<TextView
android:id="@+id/weather_low_tmp_hint"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="最低气温: "
android:textSize="20sp"
android:textColor="@color/black"
android:layout_below="@+id/weather_curent_tmp_hint"
android:layout_toLeftOf="@+id/weather_curent_tmp"
android:layout_toStartOf="@+id/weather_curent_tmp" />

<TextView
android:id="@+id/weather_low_tmp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="~~~"
android:textSize="20sp"
android:textColor="@color/black"
android:layout_alignTop="@+id/weather_low_tmp_hint"
android:layout_toRightOf="@+id/weather_low_tmp_hint"
android:layout_toEndOf="@+id/weather_low_tmp_hint" />

<TextView
android:id="@+id/weather_high_tmp_hint"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="最高气温: "
android:textSize="20sp"
android:textColor="@color/black"
android:layout_below="@+id/weather_low_tmp_hint"
android:layout_toLeftOf="@+id/weather_high_tmp"
android:layout_toStartOf="@+id/weather_high_tmp" />

<TextView
android:id="@+id/weather_high_tmp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="~~~"
android:textSize="20sp"
android:textColor="@color/black"
android:layout_below="@+id/weather_low_tmp"
android:layout_toRightOf="@+id/weather_low_tmp_hint"
android:layout_toEndOf="@+id/weather_low_tmp_hint" />

<TextView
android:id="@+id/weather_sunrise_time_hint"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="日出时间: "
android:textSize="20sp"
android:textColor="@color/black"
android:layout_alignTop="@+id/weather_low_tmp"
android:layout_alignRight="@+id/weather_date"
android:layout_alignEnd="@+id/weather_date" />

<TextView
android:id="@+id/weather_sunrise_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="~~~"
android:textSize="20sp"
android:textColor="@color/black"
android:layout_alignTop="@+id/weather_sunrise_time_hint"
android:layout_alignLeft="@+id/weather_time"
android:layout_alignStart="@+id/weather_time" />

<TextView
android:id="@+id/weather_sunset_time_hint"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="日落时间: "
android:textSize="20sp"
android:textColor="@color/black"
android:layout_below="@+id/weather_sunrise_time_hint"
android:layout_alignLeft="@+id/weather_sunrise_time_hint"
android:layout_alignStart="@+id/weather_sunrise_time_hint" />

<TextView
android:id="@+id/weather_sunset_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="~~~"
android:textSize="20sp"
android:textColor="@color/black"
android:layout_below="@+id/weather_sunrise_time"
android:layout_alignLeft="@+id/weather_sunrise_time"
android:layout_alignStart="@+id/weather_sunrise_time" />
</RelativeLayout>

{ 布局代码中使用到的SwipeRefreshLayout Google官方下拉刷新控件将在下篇文章中介绍 }


在AndroidManifest.xml中添加获取网络的权限

1
2
3
<uses-permission android:name="android.permission.INTERNET"></uses-permission><!--不要忘记设置网络访问权限-->
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />

最终效果预览:

Handler-no-fresh
Handler-fresh-show

总结:

非UI线程发送消息到UI线程分为两个步骤

1 发送消息到UI线程的消息队列:

1
Message obtainMessage(int what,Object object)

通过使用Handler, 构造一个Message对象,这个对象存储了是否成功标识what和json字符串,然后通过message.sendToTarget()方法把这条message放到消息队列中去。


2 处理发送到UI线程的消息:

ui线程中,我们覆盖了handleMessage

1
public void handleMessage (Message msg)

这个方法是处理分发给ui线程的消息,判断msg.what的值可以知道mThread是否成功获取数据,如果json字符串数据成功获取,那么可以通过msg.obj获取到这个对象,之后开始解析。


最后在使用布局中相应的TextView将数据填充,实现更新。

0%