Android异步消息处理机制(多线程)
当我们需要执行一些耗时操作,比如说发起一条网络请求时,考虑到网速等其他原因,服务器未必会立刻响应我们的请求,如果不将这类操作放在子线程里去执行,就会导致主线程被阻塞住,从而影响用户对软件的正常使用。
线程的基本用法
Android多线程编程其实并不比Java多线程编程特殊,基本都是使用相同的语法。比如说,定义一个线程只需要新建一个类继承自Thread,然后重写父类的run()方法,并在里面编写耗时逻辑即可。
class MyThread extends Thread { @Override public void run(){ //处理具体的逻辑 } }
启动线程只需要调用它的start()方法,这样run()方法中的代码就会在子线程当中运行了。
new MyThread().start();
使用继承的方式耦合性有点高,更多的时候我们都会选择使用实现Runnable接口的方式来定义一个线程。
class MyThread implements Runnable { @Override public void run(){ //处理具体的逻辑 } }
使用这种写法,启动线程的方法也需要进行相应的改变。
MyThread myThread = new MyThread(); new Thread(myThread).start();
也可以使用匿名类的方式。
new Thread(new Runnable(){ @Override public void run(){ //处理具体的逻辑 } }).start();
在子线程中更新UI
和许多其他的GUI库一样,Android的UI也是线程不安全的。也就是说,如果想要更新应用程序里的UI,则必须在主线程中进行,否则就会出现异常。
新建一个AndroidThreadTest项目,修改activity_main,xml中的代码。
<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"/> <TextView android:id="@+id/text" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Hello World!" android:layout_centerInParent="true" android:textSize="20sp"/> </RelativeLayout>
布局文件中定义了两个控件,TextView用于在屏幕的正中央显示一个Hello world字符串,Button用于改变TextView中显示的内容,希望的结果是点击Button后可以把TextView中显示的字符串改成Nice to meet you。
修改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); text = (TextView) findViewById(R.id.text); Button changeText = (Button) findViewById(R.id.change_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("Nice to meet you"); } }).start(); break; default: break; } } }
在Change Text按钮的点击事件里面开启了一个子线程,然后在子线程中调用TextView的setText()方法将显示的字符串改成Nice to meet you。运行程序,点击Change Text按钮,程序就会崩溃。
由此可见,Android是不允许在子线程中进行UI操作的。但有些时候,我们需要在子线程里去执行一些耗时任务,然后根据任务的执行结果来更新相应的UI控件。对于这种情况,Android提供了一套异步消息处理机制,完美地解决了在子线程中进行UI操作的问题。
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: text.setText("Nice to meet you"); break; default: break; } } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); text = (TextView) findViewById(R.id.text); Button changeText = (Button) findViewById(R.id.change_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() { Message message = new Message(); message.what = UPDATE_TEXT; handler.sendMessage(message); } }).start(); break; default: break; } } }
这里定义了一个整型常量UPDATE_TEXT,用于表示更新TextView这个动作。然后新增一个Handler对象,并重写父类的handleMessage()方法,在这里对具体的Message进行处理。如果发现Message的what字段的值等于UPDATE_TEXT,就将TextView显示的内容改成Nice to meet you。
再看一下点击事件中的代码,这次没有在子线程里直接进行UI操作,而是创建了一个Message(android.os.Message)对象,并将它的what字段的值指定为UPDATE_TEXT,然后调用Handler的sendMessage()方法将这条Message发送出去。Handler会收到这条Message,并在handleMessage()方法中对它进行处理。此时handleMessage()方法中的代码就是在主线程当中运行的,所以可以在这里进行UI操作。接下来对Message携带的what字段的值进行判断,如果等于UPDATE_TEXT,就将TextView显示的内容改成Nice to meet you。重新运行程序,点击按钮,Hello World就会被替换成Nice to meet you。
这就是Android异步处理的基本用法,使用这种机制可以出色地解决掉在子线程中更新UI的问题。下面来分析一下Android异步消息处理机制的工作原理。
解析异步消息处理机制
Android中的异步消息处理主要由4个部分组成:Message、Handler、MessageQueue和Looper。
♦ Message:线程之间传递的消息,它可以在内部携带少量的信息,用于在不同线程之间交换数据。上面我们用到了Message的what字段,除此之外还可以使用arg1和arg2字段来携带一些整型数据,使用obj字段携带一个Object对象。
♦ Handler:处理者,它主要是用于发送和处理消息的。发送消息一般是使用Handler的sendMessage()方法,而发出的消息经过一系列地辗转处理后,最终会传递到Handler的handleMessage()方法中。
♦ MessagQueue:消息队列,主要用于存放所有通过Handler发送的消息。这部分消息会一直存在于消息队列中,等待被处理。每个线程中只会有一个MessageQueue对象。
♦ Looper:每个线程中的MessageQueue的管家,调用Looper的loop()方法后,就会进入到一个无限循环中,然后每当发现MessageQueue中存在一条消息,就会将它取出,并传递到Handler的handleMessage()方法中。每个线程中也只会有一个Looper对象。
了解了Message、Handler、MessageQueue和Looper的基本概念后,再把异步消息处理的整个流程梳理一遍,首先需要在主线程当中创建一个Handler对象,并重写handleMessage()方法。然后当子线程需要进行UI操作时,就创建一个Message对象,并通过Handler将这条消息发送出去。之后这条消息会被添加到MessageQueue的队列中等待被处理,而Looper则会一直尝试从MessageQueue中取出待处理的消息,最后分发回Handler的handleMessage()方法中。由于Handler是在主线程中创建的,所以此时handleMessage()方法中的代码也会在主线程中运行,于是我们在这里就可以安心地进行UI操作了。整个异步消息处理机制的流程示意图如图:
一条Message经过这样一个流程的辗转调用后,也就从子线程进入到主线程,从不能更新UI变成了可以更新UI,整个异步消息处理的核心思想就是如此。