浅谈Android中的线程与Handler
注意:
1、在Android中只支持单线程的模式,何为单线程的模式,个人认为是这样的,虽然Android中支持多线程的机制,但是并不是所有的事情都能在子线程中去做得。很重要也是很明确的一点,在Android中的UI是由主线程去更新的,所以说在子线程中是不允许去更新UI的,要到主线程中去更新UI。
2、但是有时候有些事还必须要子线程去做的,比如说在一些网络的操作,循环次数耗时较长的操纵,就不能放在主线程,否则会导致主线程阻塞,主线程阻塞意味着什么,意味着你的主程序就要崩溃了直接报ANR错误(因为一个程序就是一个默认的主线程),这时候你就得去单独开启一个子线程去完成这些耗时的操作。
3、由以上两点特性,大概就可以总结出Android中只支持单线程模式含义,支持单线程模式,就是说一个特定的任务或者操作只能由主线程或者子线程去完成,而不是这个任务既能由主线程又能由子线程去完成。
"Handler的应运而生"
为什么说Handler应运而生呢??,因为正是Android中只支持单线程的模式原因,才导致Handler出现。因为不难分析出,当我有一个子线程去执行一个耗时的任务,但这个任务却有一部分是涉及到更新UI的,那么在子线程中是不能去完成更新UI的操作所以这时候就需要主线程来更新UI,但是问题来了主线程怎么知道它需要更新UI呢??肯定是要有人来告诉它,所以子线程就是通过一个Handler对象来发送消息给主线程,然后Handler对象又到主线程中去处理这些消息,根据消息的标记然后到主线程中去做一些相应的UI更新操作或者其他的操作。所以,Handler就是这样出现了,随即它的作用也明显出来了,就是两点:1、用于发送消息;2、用于接收并处理消息
"幕后默默支持的朋友:Looper的存在"
Handler就是那么简单就把消息从子线程传递到主线程吗??,其实不然,任何完美的事都是需要历经艰险的,人生旅途中需要有很多默默支持的朋友来帮助你,Looper就是扮演着这样的角色。它就是起到了Handler到消息对列的桥梁作用。既然是这种联系,那么他们之间的纽带怎么绑在一起的呢?
很明显,我们现在有三个对象:1 Handler----------->Looper---------->消息队列,
Handler-------->Looper:每一个Handler都会与唯一的线程绑定,而每一个线程又有唯一一个消息队列,作为创建Handler对象时的参数传入,将Looper与Handler绑定
注意:
1、至于Handler是与哪个线程绑定,看这个Handler对象是在哪个线程中创建,与它处于哪个环境下的线程绑定。
2、注意:在主线程中,系统会默认创建一个Looper对象和一个与之绑定的消息队列,而在子线程中则不会,则需要去调用Looper.prepare()获得一个Looper对象,因为Looper是一个静态类不能new出来,而是去获取。
获取到Looper对象还不够还要到HandleMessage后面去启动Looper对象,调用Loop()方法。
获取到主线程的Looper对象方法是:
Looper looper=Looper.getMainLooper();//获取主线程的Looper对象
获取到当前对象:Looper currentLooper=Looper.myLooper();//获取当前的Looper对象
代码:
Looper looper=Looper.getMainLooper();//获取主线程的Looper对象Looper currentLooper=Looper.myLooper();//获取当前的Looper对象
//Looer.myQueue();获取消息队列,只有主线程系统才会默认给它创建消息队列和Looper对象
private Handler handler=new Handler(looper){//将主线程的传入将Handle与looper绑定
public void handleMessage(Message msg) {//Handle去处理信息,
if (msg.what==0x1111) {
img.setImageResource(imageId[(++current)%imageId.length]);//在主线程中更新UI组件
String str= (String) msg.obj;
}
};
};
Loop----------->消息队列:
前面也说过了,当创建一个Looper对象就会创建一个与之绑定的消息队列,这个可以通过Android源码中可以看到Looper的构造器中直接去new了一个消息队列对象。
由Handler------>Looper------>消息队列之间的绑定关系,那么就相当于他们之间建立起一个消息、数据通道,那么他们之间通信当然是唯一的,而是可行的。
"Handler的另一份工作":
处理消息,(一个线程只能对应一个Handler对象,一个Handler对象可以对应很多的线程)
Handler扮演着双重角色,既要发送消息,又要处理消息。
Handler会从消息队列中去取出消息,然后根据传来的消息和数据做相应的操作。
"发送消息的类型":
1、空消息,主要会传一个msg.What过去,主要是传一个标志,便于在处理消息时候,判断是哪个子线程传来的消息请求。
2、携带数据的消息,可以传入一个Object类型的对象(msg.obj),也就是可以传任何类型的数据,可以出一个Bundle对象(msg.setData(Bundle data))
以及传一个整型的数据(msg.arg1或msg.arg2);
"发送消息的几种方式":
1、从主线程发送消息给子线程,让子线程去处理消息,做处理。(不常用)
/*
*测试案例一:大家都知道求某个数范围内的所有质数,如果我的范围较小用主线程去做一点问题都没有,但是数据范围一旦变大,就可能会阻塞主线程
所以,现在需要去做这样的操作,从主线程获取我那个数范围,然后在主线程中通过Handler对象去发送消息并携带这个数据到子线程中去,然后到子线程中去处理这个消息并获取到数据,然后在子线程中处理找到这个数范围内的所有质数,这样即使范围再大,只是会阻塞子线程但是并不会是程序崩溃
*/
package com.zhongqihong.threadapp;
import java.util.HashSet;
import java.util.Set;
import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.view.View;
import android.widget.EditText;
import android.widget.Toast;
public class OtherActivity extends Activity {
private EditText edit;
public class MyThread extends Thread{//创建一个子线程类继承Thread作为内部类
public Handler handler;
@Override
public void run() {
Looper.prepare();//
handler = new Handler(Looper.myLooper()){
@Override
public void handleMessage(Message msg) {
// TODO Auto-generated method stub
Set<Integer>set=new HashSet<Integer>();
if (msg.what==0x1111) {
int num=msg.arg1;
test:
for (int i = 2; i <=num; i++) {
for (int j = 2; j < Math.sqrt(i); j++) {
if (i!=2&&i%j==0) {
continue test;
}
}
set.add(i);
}
Toast.makeText(OtherActivity.this, set.toString(), Toast.LENGTH_SHORT).show();
}
}
};
Looper.loop();//启动Loop
}
}
private MyThread myThread;
public void myCal(View v) {
//先拿到数据,在发送给子线程
int num=Integer.parseInt(edit.getText().toString());
Message msg=new Message();
msg.what=0x1111;
msg.arg1=num;
myThread.handler.sendMessage(msg);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_other);
edit=(EditText) findViewById(R.id.edit);
myThread=new MyThread();
myThread.start();
}
}
2、从子线程发送消息给主线程,让主线程去处理消息,做处理(常用,一般用于子线程向主线程发送消息去更新UI)
/*
测试案例二:我要实现一个简单的自动切换图片方法,这就是通过
子线程去通过Handler发送消息给主线程,告诉主线程需要去更新UI
*/
package com.zhongqihong.threadapp;
import javax.crypto.spec.IvParameterSpec;
import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Handler.Callback;
import android.os.Looper;
import android.os.Message;
import android.widget.ImageView;
public class MainActivity extends Activity {
private ImageView img;
/*
*/
Looper looper=Looper.getMainLooper();//获取主线程的Looper对象
Looper currentLooper=Looper.myLooper();//获取当前的Looper对象
//Looer.myQueue();获取消息队列,只有主线程系统才会默认给它创建消息队列和Looper对象
private Handler handler=new Handler(looper){//将主线程的传入将Handle与looper绑定
public void handleMessage(Message msg) {//Handle去处理信息,
if (msg.what==0x1111) {
img.setImageResource(imageId[(++current)%imageId.length]);//在主线程中更新UI组件
String str= (String) msg.obj;
}
};
};
private int imageId[]={R.drawable.love10,R.drawable.love11,R.drawable.love12,R.drawable.love13,R.drawable.love14};
private int current=0;//保存当前显示的图片
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
img=(ImageView) findViewById(R.id.iv);
//换图片
//启动子线程
new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
while (true) {//会阻塞主线程
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//利用消息机制更新UI
//利用发送空消息的方法,发送一个标识,用于区别是哪个子线程请求主线程去更新UI组件
//handler.sendEmptyMessage(0x1111);
/*携带数据的消息*/
Message msg=new Message();//或者Message msg=handle.obtainMessage();
msg.what=0x1111;
msg.obj="更新消息";
handler.sendMessage(msg);
}
}
}).start();
}
}
个人理解消息机制传递示意图: