Android中的主线程由 looper 和 Handlers 组成。所以了解创建无阻碍的响应式 UI 很重要。
MessageQueue 是一个队列,其中包含消息任务。 Handler 在 MessageQueue 中以任务形式排队,Looper 在任务出现时执行它们 MessageQueue. Looper 使线程保持活动状态,循环 MessageQueue 并向相应 Handler 的进程发送消息。 Thread 通过调用 Looper 的 quit() 方法终止。 Handler 及其组件 Handler:框架的重要对象,它具有双重责任。它绑定到给定线程的特定 Looper,并提供了将消息发送到相关 MessageQueue 的方法。该处理器还负责在实际执行消息的内容。 Message:封装有关在特定线程上执行的操作信息。 Runnable:抽象一个线程可以执行的任何操作的接口。 MessageQueue:表示线程消耗的消息队列。 Looper:负责循环的对象,循环检查 MessageQueue 以查看是否有消息要运行并将其发送到特定的 Handler。每个线程只能有一个 Looper。现在,开始编写代码了。
例子1: 使用 Thread:创建主线程处理程序
Handler handler = new Handler(Looper.getMainLooper());
thread = new Thread(new Runnable() {
@Override
public void run() {
handler2 = new Handler(Looper.getMainLooper());
handler2.post(new Runnable() {
@Override
public void run() {
Toast.makeText(MainActivity.this,"hello world",Toast.LENGTH_SHORT).show();
}
});
}
});
使用 HandlerThread:
handlerThread = new HandlerThread("name");
handlerThread.start();
handler = new Handler(handlerThread.getLooper());
handler.post(new Runnable() {
@Override
public void run() {
Toast.makeText(MainActivity.this,"世界你好",Toast.LENGTH_SHORT).show();
}
});
注意:使用完 HandlerThread 后,要调用 quit() 方法退出。
例子2:
进度条
MainActivity.java
public class MainActivity extends AppCompatActivity {
Handler handler;
Thread thread;
Button botton;
int MAX = 100;
TextView textView;
HandlerThread handlerThread;
ProgressBar progressBar;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
initThread();
}
private void initView(){
botton = (Button)findViewById(R.id.start_progress);
progressBar = (ProgressBar)findViewById(R.id.progressBar);
textView = (TextView)findViewById(R.id.textView);
progressBar.setMax(MAX);
}
private void initThread(){
handler = new Handler(){
@Override
public void handleMessage(Message message){
super.handleMessage(message);
textView.setText(message.what+"");
}
};
thread = new Thread(new Runnable() {
@Override
public void run() {
for (int i=0;i<100;i++){
progressBar.setProgress(i);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
Message msg = new Message();
msg.what = i;
msg.obj = "obj";
handler.sendMessage(msg);
}
}
});
}
public void onClick(View view){
switch (view.getId()){
case R.id.start_progress:
thread.start();
break;
default:
break;
}
}
public void onDestroy(){
super.onDestroy();
handlerThread.quit();
}
}
activity_main.xml
例子3:
看到这一点,您可能不太了解,也许会感觉到我在哪里。
接下来,我们使用一个简单的示例来验证和解决我们内心的疑虑。
在这里,我们放置一个按钮,单击后更改TextView的值。
还设置延迟以模拟要处理的大量数据。
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void buttonClick(View view)
{
try {
Thread.sleep(6000);
} catch (InterruptedException e) {
e.printStackTrace();
}
TextView myTextView = (TextView)findViewById(R.id.myTextView);
myTextView.setText("Button Pressed");
}
}
运行后发现,延迟了6秒后才更新视图,这在实际开发中,会严重影响用户体验,造成一种严重卡顿的现象。
要解决这个问题,我们可以使用多线程的方法,修改后如下。
public void buttonClick(View view)
{
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(6000);
Log.d("thread:","hello world");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
thread.start();
}
在主线程以外的线程中更新用户界面元素违反了Android开发的关键规则。因此,为了更新用户界面,有必要实现线程的处理程序(Handler)。
Handler handler = new Handler(){
@Override
public void handleMessage(Message msg){
super.handleMessage(msg);
TextView myTextView = (TextView)findViewById(R.id.myTextView);
myTextView.setText("Button click");
};
};
再次修改 buttonClick() 中的内容
public void buttonClick(View view)
{
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(6000);
Log.d("thread:","hello world");
} catch (InterruptedException e) {
e.printStackTrace();
}
handler.sendEmptyMessage(0);
}
});
thread.start();
}
拓展内容:handler 几种方法
方法
描述
sendMessage
将消息推送到消息队列的末尾。
sendMessageDelayed
将消息推送到消息队列的末尾。
sendMessageAtTime
将消息推送到消息队列的末尾。
sendEmptyMessage
发送仅包含单个int代码的消息。
sendEmptyMessageDelayed
在指定的时间过后发送要传递的消息。
sendEmptyMessageAtTime
发送消息以在指定的绝对时间传递。
使用 messaga 对象保存数据
public void buttonClick(View view)
{
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(6000);
Log.d("thread:","hello world");
} catch (InterruptedException e) {
e.printStackTrace();
}
Message msg = handler.obtainMessage();
Bundle bundle = new Bundle();
bundle.putString("name","anny");
msg.setData(bundle);
handler.sendMessage(msg);
}
});
thread.start();
}
obtainmessage() 是从消息池中拿来一个msg 不需要另开辟空间new
取出数据并赋值给 TextView
Handler handler = new Handler(){
@Override
public void handleMessage(Message msg){
super.handleMessage(msg);
Bundle bundle = msg.getData();
String name = bundle.getString("name");
TextView myTextView = (TextView)findViewById(R.id.myTextView);
myTextView.setText(name);
};
};
例子4:
为线程创建Looper和MessageQueue:
public class Test extends Thread{
public Handler handler;
public void run(){
Looper.prepare();
handler = new Handler(){
@Override
public void handleMessage(Message message){
super.handleMessage(message);
Log.d("obj:","hello world");
}
};
Looper.loop();
}
}
仔细看你会发现,我们在这里使用了 Looper.prepare()和 Looper.loop()
为什么前面的示例不起作用?为什么在这里使用它?
1:在默认线程下,主线程系统将自动创建Looper对象并启动消息循环。
2:在主线程中mainHandler = new Handler()等同于new Handler(Looper.myLooper())
将消息发送到 MessageQueue
第一种方式:Message
Message msg = new Message();
msg.obj = "Ali send message";
handler.sendMessage(msg);
第二种方式:Runnable
new Handler(Looper.getMainLooper()).post(new Runnable() {
@Override
public void run() {
// this will run in the main thread
}
});
拓展:
方法
描述
post
立即让Runnable排队执行。
postAtTime
使Runnable排队以在以毫秒为单位的绝对时间执行。
postDelayed
使Runnable排队以指定的毫秒延迟后执行。
postAtFrontOfQueue
立即将Runnable排队到要执行的前端。
Android 提供了 HandlerThread (线程的子类) 来简化过程。在内部,它以健壮的方式执行与我们相同的操作,因此,请始终使用 HandlerThread。
public class Test extends HandlerThread {
public Handler handler;
public Test(String name) {
super(name);
}
@Override
protected void onLooperPrepared(){
handler = new Handler(getLooper()){
@Override
public void handleMessage(Message message){
super.handleMessage(message);
//接收消息
}
};
}
}
我们在调用 onLooperPrepared 时实例化了 Handler。因此,Handler 可以将其与 Looper 关联。
1:仅在 start() 调用 HandlerThread 之后,即线程运行之后,才准备 Looper。
2:只有在准备好之后,Handler 才能与 HandlerThread Looper 关联。
另一种方式创建 HandlerThread 的方式(之前提过):
HandlerThread handlerThread = new HandlerThread("myHandlerThread");
handlerThread.start();
Looper looper = handlerThread.getLooper();
Handler handler = new Handler(looper);
重要说明:请记住,在完成后台线程或活动onDestroy()方法之后调用handlerThread.quit()。
作者:你敢凝视深渊嘛