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主程序代码:
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界面布局代码
<?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中添加获取网络的权限
<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" />
最终效果预览:
总结:
非UI线程发送消息到UI线程分为两个步骤
- 发送消息到UI线程的消息队列:
通过使用Handler的
Message obtainMessage(int what,Object object)
构造一个Message对象,这个对象存储了是否成功标识what和json字符串,然后通过message.sendToTarget()方法把这条message放到消息队列中去。
- 处理发送到UI线程的消息:
ui线程中,我们覆盖了handler的
public void handleMessage (Message msg)
这个方法是处理分发给ui线程的消息,判断msg.what的值可以知道mThread是否成功获取数据,如果json字符串数据成功获取,那么可以通过msg.obj获取到这个对象,之后开始解析。
最后在使用布局中相应的TextView将数据填充,实现更新。
更多相关文章
- Android异步加载图像小结(含线程池,缓存方法)[转]
- Android 应用程序之间数据共享—ContentProvider 保时被访问
- 详解Android中的SQLite数据库存储
- android基于tcpdump的数据包捕获完整解决方案
- android平台下基于MediaRecorder和AudioRecord实现录制AAC、PCM