Android的Handler机制

我们都知道在Android中,一般的需求是子线程执行耗时任务,UI线程负责更新界面,所以我们一般都是子线程向主线程发送消息。关于子线程向主线程发送消息时要用到Handler机制,很多时候面试会让描述Android的Handler机制,那么Handler的机制是什么?下面就来解释下。

当子线程要向主线程发送一条消息时,如果发消息需要传递一些数据的话,首先要在子线程构建一个Message对象,然后将要发送的消息的数据放在其中,然后在主线程构建Handler对象,使用Handler对象的sendMessage(Message msg)方法将消息发出,之后消息会进入主线程的MessageQueen队列中等待处理,主线程的Looper会执行Looper.loop()方法会一直轮循MessageQueen队列,从队列中取出待处理的message,交由Handler的handleMessage()方法处理。如果发消息不需要传递数据则可以直接使用sendEmptyMessage(int what)方法。

这么做是没问题的,虽然我们的机制里用到了Looper,但貌似我们一直没见过它,这是因为UI线程会默认执行Looper.pepare()Looper.loop()方法,所以不需要我们手动去调用。

但是如果是主线程向子线程发送消息呢?如果在创建Handler时不指定与其绑定的Looper对象,系统默认会将当前线程的Looper绑定到该Handler上。所以,Handler要在子线程创建对象,然后必须要手动调用Looper.pepare()Looper.loop()这两个方法。

下面还是通过代码来说明一下问题吧。

第一种方式,创建Handler对象时不绑定Looper

子线程向主线程发送消息:

比如下面的这个例子就是从子线程向主线程发送消息告知进度,然后主线程收到消息后更新ProgressBar的进度。

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
public class MainActivity extends AppCompatActivity {
private ProgressBar mProgressBar;
private static final int UPDATE_PROGRESS = 100;
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
if (msg.what == UPDATE_PROGRESS) {
mProgressBar.setProgress(msg.arg1);
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mProgressBar = (ProgressBar) findViewById(R.id.progressBar);
findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i <= 100; i++) {
/*
这里使用Message.obtain()方法获取一个Message对象,而不是new Message(),
通过obtainMessage能避免重复Message创建对象。
*/
Message message = Message.obtain();
message.what = UPDATE_PROGRESS;
message.arg1 = i;
mHandler.sendMessage(message);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
}
});
}
}

效果图:

可以看出,基本上的操作就跟我们描述的一样。

主线程向子线程发消息

这种情况就稍微有点复杂了,需要手动操作Looper,而且Handler对象也得在子线程创建(需要在哪个线程处理消息则Handler就需要在哪个线程创建)。

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
public class SecondActivity extends AppCompatActivity {
private static final int SHOW_TEXT = 100;
private int count = 0;
private Handler mHandler;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_second);
findViewById(R.id.btn_second).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Message message = Message.obtain();
message.what = SHOW_TEXT;
message.arg1 = count += 1;
// 在主线程发送消息
mHandler.sendMessage(message);
}
});
new Thread() {
@Override
public void run() {
// 调用Looper.prepare()启动Looper
Looper.prepare();
// 在子线程创建Handler对象处理消息
mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
if (msg.what == SHOW_TEXT) {
Toast.makeText(SecondActivity.this, "接收到主线程的第" + msg.arg1 + "条消息!", Toast.LENGTH_SHORT).show();
}
}
};
// 调用用Looper.loop()轮循处理消息
Looper.loop();
}
}.start();
}
}

这样实现每点击一次按钮便向子线程发送一条消息,子线程接收到消息后使用Toast显示。

第二种方式,创建Handler对象时绑定Looper

在主线程中,可以创建Handler对象后,其将自动与主线程的Looper对象绑定,所以可以直接使用;在非主线程中直接这样创建Handler则会报错,因为Android系统默认情况下非主线程中没有开启Looper,而Handler对象必须绑定Looper对象。这种情况下,需先在该线程中手动开启Looper(Looper.prepare()–>Looper.loop()),然后将其绑定到Handler对象上;或者通过Looper.getMainLooper(),获得主线程的Looper,将其绑定到此Handler对象上。

下面来看一种子线程给子线程发消息的案例:

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
public class ThirdActivity extends AppCompatActivity {
private Handler mHandler;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_third);
findViewById(R.id.btn_third).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
new Thread() {
@Override
public void run() {
Message message = Message.obtain();
message.obj = Thread.currentThread().getId();
mHandler.sendMessage(message);
}
}.start();
}
});
new Thread(new Runnable() {
@Override
public void run() {
mHandler = new Handler(Looper.getMainLooper(), new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
Toast.makeText(ThirdActivity.this, "线程" + Thread.currentThread().getId() + "收到线程" + msg.obj + "发送的消息", Toast.LENGTH_LONG).show();
return false;
}
});
}
}).start();
}

通过在Handler的构造方法中传入Looper.getMainLooper()参数将Handler在主线程创建了,所以不用调用Looper.prepare()Looper.loop()方法。通过运行效果我们还是能看出handler是运行在主线程的:

QQ20170217-102852@2x

现在来看看不与主线程Looper绑定的方式,这个时候就需要在创建handler的时候手动调用Looper.prepare()Looper.loop()方法了:

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
public class ThirdActivity extends AppCompatActivity {
private Handler mHandler;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_third);
findViewById(R.id.btn_third).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
new Thread() {
@Override
public void run() {
Message message = Message.obtain();
message.obj = Thread.currentThread().getId();
mHandler.sendMessage(message);
}
}.start();
}
});
new Thread(new Runnable() {
@Override
public void run() {
// 初始化Looper
Looper.prepare();
mHandler = new Handler(Looper.myLooper(), new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
Toast.makeText(ThirdActivity.this, "线程" + Thread.currentThread().getId() + "收到线程" + msg.obj + "发送的消息", Toast.LENGTH_LONG).show();
return false;
}
});
// 启动轮循,loop()会调用到handler的handleMessage(Message msg)方法,所以,写在下面
Looper.loop();
}
}).start();
}
}

效果图:

QQ20170217-104236@2x

可以看得出来,这次handler的线程id就不是1了,也就是说不在主线程了。

关于在子线程更新UI的方法

如果需要在子线程更新UI,有两种方法:

  1. 如果是在Activity中,则可以直接在子线程中使用runOnUiThread(Runnable action)方法进行更新UI。
  2. 使用Handler mHandler = new Handler(Looper.getMainLooper())将Handler对象与UI线程绑定,然后在子线程中使用Handler的post(Runnable r)方法,重写Runnable的run()方法,在run()方法中更新UI。

关于这两个方法的区别,从runOnUiThread(Runnable action)方法源码中可以看出来:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* 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.
*
* @param action the action to run on the UI thread
*/
public final void runOnUiThread(Runnable action) {
if (Thread.currentThread() != mUiThread) {
mHandler.post(action);
} else {
action.run();
}
}

可以看出runOnUiThread(Runnable action)方法内部也是进行了判断,如果调用该方法的线程不是UI线程,则调用了Handler的post(Runnable r)方法来更新UI;如果是在UI线程调用的,则直接进行更新UI操作。二者不同的地方是runOnUiThread(Runnable action)这个方法只有Activity有。

如果觉得本文对你有帮助,请支持我!