闲来无事,准备好好梳理一下Handler机制,之前分析过没有写成博客,结果就是慢慢的淡忘了,这次趁着刚分析完,赶紧写下来。
在开始分析之前先打打基础,理解理解什么是线程以及什么是Handler,这里大部分内容引用一篇来自伯乐在线的文章,因为看来看去关于基础的部分这个人已经说得很好了,我就负责把主要的部分抽取出来。
原文地址:Android线程和Handler基础入门
现在大多数的移动设备已经变得越来越快,但是它们其实也不算是非常快。如果你想让你的APP既可以承受一些繁杂的工作而又不影响用户体验的话,那么必须把任务并行执行。在Android上,我们使用线程。
什么是线程?
线程或者线程执行本质上就是一串命令(也是程序代码),然后我们把它发送给操作系统执行。
一般来说,我们的CPU在任何时候一个核只能处理一个线程。多核处理器(目前大多数Android设备已经都是多核)顾名思义,就是可以同时处理多线程(通俗地讲就是可以同时处理多件事)。
多核处理与单核多任务处理的实质
上面我说的是一般情况,并不是所有的描述都是一定正确的。因为单核也可以用多任务模拟出多线程。
每个运行在线程中的任务都可以分解成多条指令,而且这些指令不用同时执行。所以,单核设备可以首先切换到线程1去执行指令1A,然后切换到线程2去执行指令2A,接着返回到线程1再去执行1B、1C、1D,然后继续切换到线程2,执行2B、2C等等,以此类推。
这个线程之间的切换十分迅速,以至于在单核的设备中也会发生。几乎所有的线程都在相同的时间内进行任务处理。其实,这都是因为速度太快造成的假象,就像电影《黑客帝国》里的特工Brown一样,可以变幻出很多的头和手。
Java核心里的线程
在Java中,如果要想做平行任务处理的话,会在Runnable里面执行你的代码。可以继承Thread类,或者实现Runnable接口:
// 1
public class IAmAThread extends Thread {
public IAmAThread() {
super("IAmAThread");
}
@Override
public void run() {
// your code (sequence of instructions)
}
}
// to execute this sequence of instructions in a separate thread.
new IAmAThread().start();
// 2
public class IAmARunnable implements Runnable {
@Override
public void run() {
// your code (sequence of instructions)
}
}
// to execute this sequence of instructions in a separate thread.
IAmARunnable myRunnable = new IAmARunnable();
new Thread(myRunnable).start();
这两个方法基本上是一样的。第一个版本是创建一个Thread类,第二个版本是需要创建一个Runnable对象,然后也需要一个Thread类来调用它。
Android上的线程
无论何时启动APP,所有的组件都会运行在一个单独的线程中(默认的)——叫做主线程。这个线程主要用于处理UI的操作并为视图组件和小部件分发事件等,因此主线程也被称作UI线程(Main Thread)。除了Main Thread之外的线程都可称为Worker Thread。Main Thread主要负责控制UI页面的显示、更新、交互等。 因此所有在UI线程中的操作要求越短越好,只有这样用户才会觉得操作比较流畅。一个比较好的做法是把一些比较耗时的操作,例如网络请求、数据库操作、 复杂计算等逻辑都封装到单独的线程,这样就可以避免阻塞主线程,这样就需要用到了Android的Handler机制。
这里划重点:Handler负责与子线程进行通讯,从而让子线程与主线程之间建立起协作的桥梁,使Android的UI更新的问题得到完美的解决
怎么创建Handler
既然Handler有这样的好处,那么看Handler怎么用,官方给出了两种方式创建一个Handler:
1、使用默认的构造方法:new Handler()。
2、使用带参的构造方法,参数是一个Runnable对象或者回调对象。
//第一种方法
private Handler handler = new Handler();
private Runnable myRunnable= new Runnable() {
public void run() {
//一些耗时操作
}
};
//其他地方调用
handler.post(xxx);
这里就写一个post方法,实际上还有很多,诸如postDelayed、postAtTime
//第二种方法
Handler myHandler = new Handler() {
public void handleMessage(Message msg) {
switch (msg.what) {
//根据参数进行操作
break;
}
super.handleMessage(msg);
}
};
//其他地方调用
myHandler.sendMessage(xxx);
如何使用Handler
这里使用一个简单的Demo来演示Handler的用法,界面偏简单就不贴了,直接贴代码,模拟的是点击Button执行下载,下载完成后更新UI。
public class MainActivity extends Activity implements Button.OnClickListener {
private TextView statusTextView = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
statusTextView = (TextView)findViewById(R.id.statusTextView);
Button btnDownload = (Button)findViewById(R.id.btnDownload);
btnDownload.setOnClickListener(this);
}
@Override
public void onClick(View v) {
DownloadThread downloadThread = new DownloadThread();
downloadThread.start();
}
class DownloadThread extends Thread{
@Override
public void run() {
try{
System.out.println("开始下载文件");
//此处让线程DownloadThread休眠5秒中,模拟文件的耗时过程
Thread.sleep(5000);
System.out.println("文件下载完成");
//文件下载完成后更新UI
MainActivity.this.statusTextView.setText("文件下载完成");
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
}
按照以前写Java的思路的话可能会这么写,但是运行程序时候会发现控制台报错:
android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
错误的意思是只有创建View的原始线程才能更新View。出现这样错误的原因是Android中的View不是线程安全的,下面给出合理的解释:
因为UI访问是没有加锁的,在多个线程中访问UI是不安全的,如果有多个子线程都去更新UI,会导致界面不断改变而混乱不堪。所以最好的解决办法就是只有一个线程有更新UI的权限,所以这个时候就只能有一个线程振臂高呼:放开那女孩,让我来!那么最合适的人选只能是主线程。
来自---Android中线程那些事
那么为了规避Android的这种机制,我们这里分别采用Handler的两种方式来实现上面的代码:
A、使用post方式
public class MainActivity extends Activity implements Button.OnClickListener {
private TextView statusTextView = null;
//uiHandler在主线程中创建,所以自动绑定主线程
private Handler uiHandler = new Handler();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
statusTextView = (TextView)findViewById(R.id.statusTextView);
Button btnDownload = (Button)findViewById(R.id.btnDownload);
btnDownload.setOnClickListener(this);
}
@Override
public void onClick(View v) {
DownloadThread downloadThread = new DownloadThread();
downloadThread.start();
}
class DownloadThread extends Thread{
@Override
public void run() {
try{
System.out.println("开始下载文件");
//此处让线程DownloadThread休眠5秒中,模拟文件的耗时过程
Thread.sleep(5000);
System.out.println("文件下载完成");
//文件下载完成后更新UI
Runnable runnable = new Runnable() {
@Override
public void run() {
MainActivity.this.statusTextView.setText("文件下载完成");
}
};
uiHandler.post(runnable);
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
}
B、使用sendMessage方式实现
public class MainActivity extends Activity implements Button.OnClickListener {
private TextView statusTextView = null;
//uiHandler在主线程中创建,所以自动绑定主线程
private Handler uiHandler = new Handler(){
@Override
public void handleMessage(Message msg) {
switch (msg.what){
case 1:
System.out.println("msg.arg1:" + msg.arg1);
System.out.println("msg.arg2:" + msg.arg2);
MainActivity.this.statusTextView.setText("文件下载完成");
break;
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
statusTextView = (TextView)findViewById(R.id.statusTextView);
Button btnDownload = (Button)findViewById(R.id.btnDownload);
btnDownload.setOnClickListener(this);
System.out.println("Main thread id " + Thread.currentThread().getId());
}
@Override
public void onClick(View v) {
DownloadThread downloadThread = new DownloadThread();
downloadThread.start();
}
class DownloadThread extends Thread{
@Override
public void run() {
try{
System.out.println("开始下载文件");
//此处让线程DownloadThread休眠5秒中,模拟文件的耗时过程
Thread.sleep(5000);
System.out.println("文件下载完成");
//文件下载完成后更新UI
Message msg = new Message();
//虽然Message的构造函数式public的,我们也可以通过以下两种方式通过循环对象获取Message
//msg = Message.obtain(uiHandler);
//msg = uiHandler.obtainMessage();
//what是我们自定义的一个Message的识别码,以便于在Handler的handleMessage方法中根据what识别
//出不同的Message,以便我们做出不同的处理操作
msg.what = 1;
//我们可以通过arg1和arg2给Message传入简单的数据
msg.arg1 = 123;
msg.arg2 = 321;
//我们也可以通过给obj赋值Object类型传递向Message传入任意数据
//msg.obj = null;
//我们还可以通过setData方法和getData方法向Message中写入和读取Bundle类型的数据
//msg.setData(null);
//Bundle data = msg.getData();
//将该Message发送给对应的Handler
uiHandler.sendMessage(msg);
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
}
以上代码来自博客:Android中Handler的使用
上面这两种形式都能达到我们的要求,在此不一一测验,注释写的很详细了,看到这里应该已经大致知道了如何使用Handler,但是我想我们应该远远不满足于此,下一篇博客将带着大家从源码一起看看Handler机制到底是怎么实现的。
Handler机制从入门到放弃(二)