一般情况下,我们在编写android代码的时候,我们会将一些耗时的操作,比如网络访问、磁盘访问放到一个子线程中来执行。而这类操作往往伴随着UI的更新操作。比如说,访问网络加载一张图片
new Thread() { @Override public void run() { try { URL url = new URL(path); HttpURLConnection connection = (HttpURLConnection) url .openConnection(); // 设置请求方式 connection.setRequestMethod("GET"); // 设置超时时间 connection.setConnectTimeout(10000); int code = connection.getResponseCode(); if (code == 200) { InputStream is = connection.getInputStream(); Bitmap bitmap = BitmapFactory.decodeStream(is); iv_beauty.setImageBitmap(bitmap); } else { Toast.makeText(getApplicationContext(), "图片获取失败!", 0).show(); } } catch (Exception e) { Toast.makeText(getApplicationContext(), "图片获取失败!", 0) .show(); } } }.start();
如果是这样去操作,就会抛出
10-20 02:50:38.219: W/System.err(497): android.view.ViewRoot$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
原因在于,在android里面不是线程安全的,所以android有阻止子线程更新组件的机制。
于是我们应该将UI的更新交给主线程来完成,如何去完成,android为我们提供了一套消息处理机制。
它大概的实现步骤是这样子的
1.子线程利用Handler类发送一条消息,消息会被送完主线程的消息队列里
2.主线程里边有一个叫looper的轮询器,它会循环遍历消息队列
3.如果loopler发现消息队列里有新的消息,android就会调用Handler的handleMessage()方法来处理消息
我们来看一下实例代码吧
1.首先我们先定义好两个常量
private static final int UPDATE_UI = 1; private static final int ERROR = 2;
new Thread() { @Override public void run() { try { URL url = new URL(path); HttpURLConnection connection = (HttpURLConnection) url .openConnection(); // 设置请求方式 connection.setRequestMethod("GET"); // 设置超时时间 connection.setConnectTimeout(10000); // connection.setRequestProperty(field, newValue) int code = connection.getResponseCode(); if (code == 200) { InputStream is = connection.getInputStream(); Bitmap bitmap = BitmapFactory.decodeStream(is); // 告诉主线程一个消息,帮我更新ui,内容:bitmap Message msg = new Message(); // 消息的代号,是一个int类型 msg.what = UPDATE_UI; // 要传递的消息对象 msg.obj = bitmap; // 利用handler发送消息 handler.sendMessage(msg); } else { Message msg = new Message(); msg.what = ERROR; handler.sendMessage(msg); } } catch (Exception e) { e.printStackTrace(); Message msg = new Message(); msg.what = ERROR; handler.sendMessage(msg); } } }.start();
3.在主线程中新建一个Handler类来处理消息
// 主线程创建消息处理器 Handler handler = new Handler() { // 但有新消息时调用 @Override public void handleMessage(Message msg) { if (msg.what == UPDATE_UI) { // 获取消息对象 Bitmap bitmap = (Bitmap) msg.obj; // 更新UI iv_beauty.setImageBitmap(bitmap); } else if (msg.what == ERROR) { // Toast也是属于UI的更新 Toast.makeText(getApplicationContext(), "图片获取失败!", 0).show(); } } };
再次运行代码,就不会抛出android.view.ViewRootImpl$CalledFromWrongThreadException异常了
另外我们还可以利用Activity里边提供的一个runOnUiThread()方法来更新UI
runOnUiThread()的官方文档是这么描述其作用的:
Runs the specified action on the UI thread. If the current thread is the UI thread, then the action is executed immediately. If the current thread is not the UI thread, the action is posted to the event queue of the UI thread.
于是我们只需要修改上边有异常那段代码:
new Thread() { @Override public void run() { try { URL url = new URL(path); HttpURLConnection connection = (HttpURLConnection) url .openConnection(); // 设置请求方式 connection.setRequestMethod("GET"); // 设置超时时间 connection.setConnectTimeout(10000); // connection.setRequestProperty(field, newValue) int code = connection.getResponseCode(); if (code == 200) { InputStream is = connection.getInputStream(); final Bitmap bitmap = BitmapFactory .decodeStream(is); // // 告诉主线程一个消息,帮我更新ui,内容:bitmap // Message msg = new Message(); // // 消息的代号,是一个int类型 // msg.what = UPDATE_UI; // // 要传递的消息对象 // msg.obj = bitmap; // // 利用handler发送消息 // handler.sendMessage(msg); runOnUiThread(new Runnable() { @Override public void run() { iv_beauty.setImageBitmap(bitmap); } }); } else { // Message msg = new Message(); // msg.what = ERROR; // handler.sendMessage(msg); runOnUiThread(new Runnable() { @Override public void run() { Toast.makeText(getApplicationContext(), "图片获取失败!", 0).show(); } }); } } catch (Exception e) { e.printStackTrace(); // Message msg = new Message(); // msg.what = ERROR; // handler.sendMessage(msg); runOnUiThread(new Runnable() { @Override public void run() { Toast.makeText(getApplicationContext(), "图片获取失败!", 0).show(); } }); } } }.start();