今天接触到了Handler,说到Handler,就不得不说到线程编程。Android为什么要引入Handler机制呢?很多大神都在自己的博客中提到了,一搜一大把,在这里讲一下自己的感受。
每个用户可见的界面(最顶端)的Activity使用的都是同一个线程(main线程,测试过程:从一个界面跳转到另一个界面,打印出各自的threadName,threadId)。如下图:
如果在这一个线程中进行比较耗时的操作,如扫描一个大文件、与数据库进行数据交流等,主界面会卡住,就是所谓的假死,超过5秒手机黑屏甚至引起系统的警报。所以诸如上述行为不能全都在主线程中执行,这就用到了多线程编程。
一、假死的模拟和貌似多线程
1.思路
①Activity1—>跳转到Activity2;
②在Activity2中创建两个Runnable对象,delay1和delay2,delay1单纯sleep5秒,delay2sleep比delay1长(10秒),最后修改TextView的内容。
③在Activity2的onCreate方法中把这两个Runnable对象post()到handler中
伪代码如下:
handler = new Handler();
handler.post(runDelay1);
setContentView(R.layout.activity_thread_test);
handler.post(runDelay2);
tv.setText("Delayed?");
2.预计结果:
①点击Activity1中的按钮,跳转到Activity2,屏幕空白5s!
②显示出原始字符串【Hello World!】屏幕卡主5s;
③字符串更改成【Delayed?】。
3.BUT! 现实结果:
①点击跳转按钮,在Activity1卡住;
②5s之后,屏幕黑了。。
③5s之后,屏幕亮了,显示的是字符串【Delayed?】
原因下面说明,先附上主要代码:
Activity1:
button.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
System.out.println(Thread.currentThread().getName() + ","
+ Thread.currentThread().getId() + ","
+ "MainActivity");
Intent intent = new Intent();
intent.setClass(MainActivity.this, ThreadTestActivity.class);
MainActivity.this.startActivity(intent);
}
});
Activity2:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
handler.post(runDelay1);
setContentView(R.layout.activity_thread_test);
tv = (TextView)findViewById(R.id.hh);
handler.post(runDelay2);
}
Runnable runDelay1 = new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
try {
while (i > 0) {
System.out.println(Thread.currentThread().getName() + "," + Thread.currentThread().getId() + "," + i );
Thread.sleep(1000);
i--;
}
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
};
Runnable runDelay2 = new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
try {
while (j > 0) {
System.out.println(Thread.currentThread().getName() + ","
+ Thread.currentThread().getId() + ","
+ j );
Thread.sleep(1000);
j--;
}
handler.sendEmptyMessage(1);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
};
洋洋洒洒的Activity2白写了,蛤蛤蛤蛤
为什么会这样呢。上面这段代码我试了又试,放在setContentView()前面,后面等等,调试了整整一上午,就是想知道:
为什么代码不按照顺序执行呢?
handler把线程post到哪里去了啊?不是说好的有个队列吗?
随着调试的深入,渐渐有了表面上的认识:
在编译过程中,是把setContentView()编译在最后的,执行的时候是在OnCreate方法中最后执行的。
handler有个队列(Looper),可以用来调度这个队列内的Runnable对象,但只是相当于把代码线性插入到线程中去。
被post进去的run()方法在宏观上就像是嵌入代码一样,并没有开启一个线程去执行。
handler与activity是异步的,不要被这句话骗了,以为是线程间的异步,其实呢,用上面的理论解释,当然是异步的,因为线性执行的一段代码怎么同步?
综上,实际上就是,runDelay1执行了,sleep了5s,然后runDelay2执行,睡5s,直到界面显示Activity2的内容。
二、真多线程
手动创建线程,来实现5s后字符串变成【Delayed?】
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Thread th1 = new Thread(runDelay1);
th1.start();
setContentView(R.layout.activity_thread_test);
Thread th2 = new Thread(runDelay2);
th2.start();
tv = (TextView)findViewById(R.id.hh);
}
Handler handler = new Handler(){
@Override
public void handleMessage(Message msg) {
// TODO Auto-generated method stub
super.handleMessage(msg);
tv.setText("Delayed?");
}
};
Runnable的代码差不多,只是多了在runDelay2中给handler发了个空消息。
如此,完美实现了跳转、变字符串。也跟之前预计的一样了。
Note:
在调试过程中遇到了一个问题,就是我直接在runDelay2中修改了TextView的值,运行时报错说在其他线程中修改了UI,查了一下才知道,修改UI只能在main线程中修改。
所以在这个例子中,handler相当于线程间触发器,同时负责线程间通信。
三、符合基本法的写法
什么是HandlerThread?
HandlerThread本质上是一个Thread(Extends Thread),而又不仅仅是一个Thread,这个Thread拥有Handler的很多特性,使用HandlerThread创建线程能创建Looper对象,我们用这个Looper对象来初始化Handler,就能达到既使用Handler来管理一些操作,又能使之在新的线程中运行。
要想使用HandlerThread需要三个步骤:创建HandlerThread线程
启动线程
获取HandlerThread的Looper,并用它初始化Handler
同步通讯消息
Message msg = myHandler.obtainMessage();
msg.sendToTarget();//target就是创建mseeage的那个对象
使用Handler创建Message对象,Handler就是Message的target,当消息对象调用sendToTarget()方法时,就会把对象发送到Handler对象。
public class HandlerThreadTestActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_handler_thread_test);
System.out.println("Activity: " + Thread.currentThread().getName());
//创建HandlerThread
HandlerThread handlerThread = new HandlerThread("hth");
//开启线程
handlerThread.start();
//创建Handler
MyHandler myHandler = new MyHandler(handlerThread.getLooper());
//用handler创建message
Message msg = myHandler.obtainMessage();
msg.sendToTarget();//target就是创建mseeage的那个对象
}
class MyHandler extends Handler{
//构造函数
public MyHandler(){
}
public MyHandler(Looper looper){
super(looper);
}
@Override
public void handleMessage(Message msg){
System.out.println("handlerThread: " + Thread.currentThread().getName());
}
}
}
运行结果: