熟悉开发的朋友,对多线程编程一定不会陌生。当我们需要执行一些耗时操作时,比如说发起一条网络请求,考虑到网速以及一些其他原因,服务器不一定会立刻响应我们的请求。如果不将这类操作放到子线程里去运行,就可能会导致主线程阻塞,影响用户的正常使用。但是,并不是所有操作都能够在子线程中进行的,接下来我们就通过一个实例来探究一下。
首先我们新建一个AndroidThreadTest项目,编辑activity_main.xml中的代码,如下:
<?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"> <Button android:id="@+id/change_text" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="Change Text" android:textAllCaps="false" /> <TextView android:id="@+id/text" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" android:text="Hello Thread" android:textSize="20sp" android:textAllCaps="false" /> </RelativeLayout>
界面布局如下:
布局文件中就是简单的定义了两个控件,TextView用于在屏幕中央显示一个Hello World字符串。Button用于改变TextView中显示的内容。我们希望在点击Button后可以把屏幕中央的内容变成:Kevin Durant
接下来修改MainActivity中的代码:
public class MainActivity extends AppCompatActivity implements View.OnClickListener{ private TextView text; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Button changeText = (Button) findViewById(R.id.change_text); text = (TextView) findViewById(R.id.text); changeText.setOnClickListener(this); } @Override public void onClick(View v){ switch (v.getId()){ case R.id.change_text: new Thread(new Runnable() { @Override public void run() { text.setText("Kevin Durant"); } }).start(); break; default: break; } }
首先获取到Button和TextView控件的实例,然后在Button按钮的点击事件里面开启了一个子线程,在子线程中调用text的setText()方法将字符串的内容变成 Kevin Durant,也就是更新了子线程中的UI元素,接下来我们运行程序,然后点击Button按钮,会发现程序出现了异常,异常信息如下:
很明显,大概的意思就是,由于在子线程中更新UI导致了程序出现崩溃。也由此证明了,Android确实是不允许在子线程中进行UI操作的。但是有些时候,我们必须在子线程里去执行一些耗时任务,然后根据执行结果来更新响应的UI控件,难道我们就无法解决了吗? 当然NO!
对于这种情况,Android提供了一套异步消息处理机制,很好的解决了在子线程中进行UI操作的问题,接下来我们就来学习一下异步消息时如何解决这个问题的。
首先还是修改MainActivity中的代码:
public class MainActivity extends AppCompatActivity implements View.OnClickListener{ public static final int UPDATE_TEXT = 1; private TextView text; private Handler handler = new Handler(){ public void handleMessage(Message msg){ switch (msg.what){ case UPDATE_TEXT: //在这里进行UI操作 text.setText("Kevin Durant"); break; default: break; } } };
我们首先定义了一个整型常量UPDATE_TEST,用于表示更新TextView这个动作。之后新增了一个Handler对象,并重写了父类的handleMessage()方法。在这里对Message对象进行具体的操作。如果发现Message的what字段值为UPDATE_TEXT。我们就来更新TextView的UI显示内容。
接着再看一下Button按钮点击事件里面子线程的代码逻辑。
@Override public void onClick(View v){ switch (v.getId()){ case R.id.change_text: new Thread(new Runnable() { @Override public void run() { Message message = new Message(); message.what = UPDATE_TEXT; handler.sendMessage(message); //将Message对象发送出去 } }).start(); break; default: break; } }
其实,重点就是在子线程的run()方法里。先是创建了一个Message对象,并将它的字段what指定为UPDATE_TEXT,也就是可以更新UI了。然后调用Handler的sendMessage()方法将这条Message发送出去。很快,Handler就会收到这条Message,并在handleMessage()方法中对它进行处理。
而此时我们应该已经注意到了,handleMessage()方法已经是在主线程中运行了,所以我们可以放心的进行UI操作了。接下来运行程序,点击按钮,我们会发现,程序正常运行,TextView显示的内容变成了: Kevin Durant
这样,我们就通过Android异步消息处理的方法,实现了即在子线程中执行了任务,也解决了子线程中更新UI的问题。后续的章节中,我们继续来深入探讨关于Android异步消息处理机制到底是如何工作的。