Android的UI操作并不是线程安全的。Android消息传递机制是另一种形式的“事件处理”,Android只允许UI线程修改Activity里的UI组件,这样就会导致新启动的线程无法改变界面组件的属性值。这就需要Handler来实现
Handler的主要作用:
- 在新线程中发送消息
- 在主线程中获取、处理消息
为让主线程适时处理新线程所发送的消息,只能通过回调的方式来实现——开发者需要重写Handler类中处理消息的方法,当新线程发送消息时,消息会发送到与之关联的MessageQueue,而Handler会不断地从MessageQueue中获取并处理消息
Handler包含如下方法用于发送、处理消息:
void handleMessage(Message msg) | 处理消息的方法 |
final boolean hasMessage(int what) | 检查消息队列中是否包含what属性为指定值的消息 |
final boolean hasMessage(int what ,Object object) | what属性且object属性为制定对象的消息 |
多个重载的Message obtainMessage() | 获取消息 |
sendEmptyMessage(int what) | 发送空消息 |
final boolean sendMessageDelayed(int what, long delayMillis) | 指定多少毫秒之后发送空消息 |
final boolean sendMessage(Message msg) | 立即发送消息 |
final boolean sendMessageDelayed(Message msg, long delayMillis) | 指定多少毫秒之后发送消息 |
示例代码:
public
class
MainActivity
extends
ActionBarActivity {
int
[]
imageIds
=
new
int
[]{
R.drawable.
java
,
R.drawable.
ee
,
R.drawable.
ajax
,
R.drawable.
xml
,
R.drawable.
classic
};
int
currentImageId
= 0;
@Override
protected
void
onCreate(Bundle savedInstanceState) {
super
.onCreate(savedInstanceState);
setContentView(R.layout.
activity_main
);
final
ImageView show = (ImageView) findViewById(R.id.
show
);
final
Handler myHandler =
new
Handler()
{
@Override
public
void
handleMessage(Message msg)
{
if
(msg.
what
== 0x123)
{
show.setImageResource(
imageIds
[
currentImageId
++
%
imageIds
.
length
]);
}
}
};
new
Timer().schedule(
new
TimerTask()
{
@Override
public
void
run()
{
myHandler.sendEmptyMessage(0x123);
}
},0,1200);
}
}
3.5.2 Handler、Loop、MessageQueue的工作原理
Message | Handler接收和处理的消息对象 |
Looper | 每个线程只能拥有一个Looper。负责读取MessageQueue中的消息 |
MessageQueue | 消息队列,它采用先进先出的方式来管理Message。程序创建Looper对象时会在它的构造器中创建Looper对象 |
Looper提供的构造器源代码:
private Looper()
{
mQueue = new MessageQueue();
mRun = true;
mThread = Thread.currentThread();
}
程序在初始化Looper时会创建一个与之失联的MessageQueue。
- Handler: 它的作用有两个——发送和处理消息。如果希望Handler工作,必须在当前线程中有一个Looper对象。为保证当前线程中有Looper对象,可分如下两种情况处理:
- 主UI线程中,系统已经初始化了一个Looper对象
- 程序员自己启动子线程,必须自己创建一个Looper对象,并启动它。创建Looper对象,调用它的prepare()方法即可
prepare()方法代码:
public
static
final
void
prepare()
{
if
(sThreadLocal.get() !=
null
)
{
throw
new
RuntimeException(
"Only one Looper may be created per thread"
);
}
sThreadLocal.set(
new
Looper());
}
然后调用Looper的静态方法loop()方法来启动它。loop()方法使用一个死循环不断取MessageQueue中的消息,并将取出的消息对应的Handler进行处理。
loop()方法源代码:
for
(;;)
{
Message msg = queue.next();
if
(msg ==
null
)
{
return
;
}
Printer logging = me.mLogging;
if
(logging !=
null
)
{
logging.println(
">>>>>Dispatchging to "
+ msg.target +
""
msg.callback +
": "
+ msg.what);
}
msg.target.dispatchMessage(msg);
if
(logging != newIdent)
{
Log.wtf(TAG,
"Thread identity changed from ox"
+Long.toHexString(ident)+
"to 0x"
+Long.toHexString(newIdent)+
"while dispatching to "
+ msg.target.getClass().getName() +
" "
+ msg.callback +
"what="
+ msg.what);
}
msg.recycle();
}
在线程中使用Handler的步骤如下:
- 调用Looper的prepare()方法为当前线程创建Looper对象,此时,构造器会创建与之配套的MessageQueue
- 有了Looper之后,创建Handler子类实例,重写handleMessage()方法,该方法负责处理来自于其他线程的消息
- 调用Looper的loop()方法启动Looper
尽量避免在UI线程中执行耗时操作,因为这样可能导致一个著名的异常:ANR异常,只要在UI线程中执行需要消耗大量时间的操作,都会引发ANR异常,因为这会导致Android应用程序无法响应输入事件和Broadcast
示例代码:
public
class
MainActivity
extends
ActionBarActivity {
static
final
String
UPPER_NUM
=
"upper"
;
EditText
etNum
;
CalThread
calThread
;
class
CalThread
extends
Thread
{
public
Handler
mHandler
;
public
void
run()
{
Looper.prepare();
mHandler
=
new
Handler()
{
@Override
public
void
handleMessage(Message msg)
{
if
(msg.
what
== 0x123)
{
int
upper = msg.getData().getInt(
UPPER_NUM
);
List<Integer> nums =
new
ArrayList<Integer>();
outer:
for
(
int
i = 2; i <= upper; i++)
{
for
(
int
j = 2; j < Math.sqrt(i); j++)
{
if
(i != 2 && i % j == 0)
{
continue
outer;
}
}
nums.add(i);
}
Toast.makeText(MainActivity.
this
, nums.toString(), Toast.
LENGTH_LONG
).show();
}
}
};
Looper.loop();
}
}
@Override
protected
void
onCreate(Bundle savedInstanceState) {
super
.onCreate(savedInstanceState);
setContentView(R.layout.
activity_main
);
etNum
= (EditText) findViewById(R.id.
etNum
);
calThread
=
new
CalThread();
calThread
.start();
}
public
void
cal(View source)
{
Message msg =
new
Message();
msg.
what
= 0x123;
Bundle bundle =
new
Bundle();
bundle.putInt(
UPPER_NUM
, Integer.parseInt(
etNum
.getText().toString()));
msg.setData(bundle);
calThread
.
mHandler
.sendMessage(msg);
}
}