一、Handle
参考地址:
Android只能在UI线程(主线程)更新UI显示,一般情况在子线程做耗时操作,我们平时通过Handler消息机制让子线程和主线程进行通信。它有三个构造函数:
1.1、子线程创建Handler
如果我们直接在子线程创建Handler的话会导致程序崩溃,提示 Can't create handler inside thread that has not called Looper.prepare(),也就是说
不能在没有调用Looper,prepare()的线程中创建Handler,所以我们在子线程创建Handler就必须先调用Looper.prepare。
new Thread(new Runnable() {
@Override
public void run() {
Looper.prepare();
handler2 = new Handler();
}
}).start();
那么,为什么不调用Looper.prepare()就不行呢,先看看Handler的无参构造函数
可以看出,第10行调用了Looper.prepare()方法来获取Looper对象,如果Looper为空则会抛出Can't create handler inside thread that has not
called Looper.prepare()什么时候Looper会为空呢?我们看下Looper.myLooper()中的代码:
方法很简单,就是从sThreadLocal对象中取出Looper。如果sThreadLocal中有Looper存在就返回该Looper,如果没有Looper存在则返回空了,
那么究竟是在哪里给sThreadLocal设置Looper呢,当然是在Looper.prepare()方法
可以看到,首先判断sThreadLocal中是否已经存在Looper,没有则创建一个,所以就解释为什么要调用Looper.prepare()方法才能创建Handler对象。
同时也可以看出每个线程中最多只会有一个Looper对象。
1.2、主线程创建Handler
主线程中创建Handler没有调用Looper.prepare(),为什么不会崩溃呢?这是因为系统已经帮我们自动调用Looper.prepare()方法,
我们查看ActivityThread中的main()方法
可以看到第7行调用了Looper.prepareMainLooper(),而该方法又调用了Looper.prepare
总结:子线程创建Handler实例必须先调用Looper.prepare()才能正常创建,而主线程中创建Handler则不需要。
1.3、消息传递
当我们创建消息后,可以通过setData、args参数以及obj参数等当时为消息携带数据或对象,再借助Handler将消息发送出去即可
new Thread(new Runnable() {
@Override
public void run() {
Message message = new Message();
message.arg1 = 1;
Bundle bundle = new Bundle();
bundle.putString("data", "data");
message.setData(bundle);
handler.sendMessage(message);
}
}).start();
Handler提供很多发送消息的方法,其中除了sendMessageAtFrontOfQueue()方法外,其他发送消息的方法最终都会调用sedMesageAtTime()方法
它所携带的两个参数都会传递到MessageQueue的enqueueMessage(..)方法中,它是一个先进先出的消息队列,该类是在Looper构造函数中创建,
因此一个Looper对应一个MessageQueue,那么enqueueMessage(..)就是入队的方法了。
如果通过sendMessageAtFrontOfQueue()发送消息,它同样调用enqueueMessage()来让消息入队,只是时间为0而已。
然而出队操作是通过Looper.looper()来进行操作。
每当有一个消息出队,就将它传递到msg.target的dispatchMessage()方法中,而msg.target就是Handler。
Hanlder的dispatchMessage()方法会做一个检查
1.判断Runnable是否为空,不为空则调用handlerCallback()方法。
2.判断是否带callBack是否为空,不为空则调用mCallback的handlerMessage()。
3.最后才调用不带返回值的handlerMessage()
所以Handler在子线程的标准写法是
class LooperThread extends Thread {
public Handler mHandler;
public void run() {
Looper.prepare();
mHandler = new Handler() {
public void handleMessage(Message msg) {
// process incoming messages here
}
};
Looper.loop();
}
}
1.4、其他发送消息
1.Handler的post()方法
2.View的post()方法
3.Activity的runOnUiThread()方法
我们先看下post()方法的源码
调用sendMessageDelayed()方法发送消息,并且使用getPostMessage()方法将Runnable对象转换成一条消息
将消息的callback字段的值指定为传入的Runnable对象。上面曾说,Handler的dispatchMessage()方法会做一个检查,如果Message的callback等于
null才会去调用handleMessage()方法,否则就调用handleCallback()方法。
非常简单,其实就是调用了传入的Runnable的run()方法而已。而View中的post()方法其实就是调用Handler的post()方法,而Activity的runOnUIThread()
方法则是先判断线程如果当前线程不是主线程,就调用Handler的post()方法,否则直接调用Runnable对象的run()方法。
二、AsyncTask
1.1、三个泛型
AsyncTask是一个抽象类,使用时必须创建子类继承它,在继承时我们可以为其指定三个泛型参数:
- Params:在执行AsyncTask时需要传入的参数,可用于后台任务中使用
- Progres:后台任务执行时,如果界面上要显示当前进度,则使用该泛型作为进度单位。
- Result:当任务执行完毕后,如果需要对结果进行返回,则使用该泛型作为返回值类型
因此,一个简单的自定义的AyncTask就可以写成如下方式:
class DownloadTask extends AsyncTask<Void, Integer, Boolean> {
……
}
这里我们把AsyncTask的第一个泛型参数指定为Void,表示在执行AsyncTask的时候不需要传入参数给后台任务。第二个泛型参数指定为Integer,表示使用整型数据来作为进度显示单位。
第三个泛型参数指定为Boolean,则表示使用布尔型数据来反馈执行结果。
当然,目前我们自定义的DownloadTask还是一个空任务,并不能进行任何实际的操作,我们还需要去重写AsyncTask中的几个方法才能完成对任务的定制。经常需要去重写的方法有以下四个:
1、onPreExecute()
这个方法会在后台任务开始执行之间调用,用于进行一些界面上的初始化操作,比如显示一个进度条、对话框等。
2、doInBackground(Params...)
这个方法中所有代码都会在子线程中运行.我们应该在这里去处理所有耗时任务。任务一旦完成就可以通过return语句来将任务的执行结果进行返回,
如果AsyncTask的第三个泛型参数指定的是Void,就可以不返回任务执行结果。注意,这个方法中是不能进行UI操作的,如果需要更新UI元素,比如反馈
当前任务执行进度,可以调用publishProgress(Progress...)方法来完成。
3、onProgressUpdate(Progress...)
当在后台任务中调用了publishProgress(Progress...)方法后,这个方法就很快会被调用,方法中携带的参数就是在后台任务中传递过来的。
在这个方法中可以对UI进行操作,利用参数中的数值就可以对界面元素进行相应的更新。
4、onPostExecute(Result)
当后台任务执行完毕并通过return语句进行返回时,这个方法就很快会被调用。返回的数据会作为参数传递到此方法中,可以利用返回的数据
来进行一些UI操作,比如说提醒任务执行的结果,以及关闭掉进度条对话框等。
因此,一个比较完整的自定义AsyncTask就可以写成如下格式:
class DownloadTask extends AsyncTask<Void, Integer, Boolean> {
@Override
protected void onPreExecute() {
progressDialog.show();
}
@Override
protected Boolean doInBackground(Void... params) {
try {
while (true) {
int downloadPercent = doDownload();
publishProgress(downloadPercent);
if (downloadPercent >= 100) {
break;
}
}
} catch (Exception e) {
return false;
}
return true;
}
@Override
protected void onProgressUpdate(Integer... values) {
progressDialog.setMessage("当前下载进度:" + values[0] + "%");
}
@Override
protected void onPostExecute(Boolean result) {
progressDialog.dismiss();
if (result) {
Toast.makeText(context, "下载成功", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(context, "下载失败", Toast.LENGTH_SHORT).show();
}
}
}
这里我们模拟了一个下载任务,在doInBackground()方法中去执行具体的下载逻辑,在onProgressUpdate()方法中显示当前的下载进度,
在onPostExecute()方法中来提示任务的执行结果。
如果想要启动这个任务,只需要简单地调用以下代码即可:
new DownloadTask().execute();
三、定时器
在固定的每隔一段时间执行某一个任务,我们可以使用计时器的工具类:Timer和TimerTask。
1.1、Timer & TimerTask
Timer是一个普通类,其中有一些比较重要的方法。
TimerTask是一个抽象类,其中有抽象方法run(),类似线程中的run()方法,我们使用Timer创建它的对象,并通过Schedule()方法完成间隔操作。
1.2、Schedule参数
Timer就是个线程,使用schedule方法完成对TimerTask的调度,多个TimerTask可以共用一个Timer,也就是说Timer对象调用一次schedule方法就是创建
一个线程,并且调用一次schedule后TimerTask是无限制的循环下去的,可以使用Timer的cancel()停止操作。
当然同一个Timer执行一次cancel()方法后,所有Timer线程都被终止。
// true 说明这个timer以daemon方式运行(优先级低,程序结束timer也自动结束)
Timer timer = new Timer(true);
TimerTask task = new TimerTask() {
@Override
public void run() {
// 每次需要执行的代码放在这里面
}
};
// 以下是集中调度task的方法:
// time为Date类型:在指定时间执行一次
timer.schedule(task, time);
// 以firstTime为Date类型,period为long,表示从firstTime时刻开始,每隔period毫秒执行一次
timer.schedule(task, firstTime, period);
// delay为long类型:从现在起过delay毫秒后执行一次
timer.schedule(task, delay);
// delay为long,period为long:从现在起过delay毫秒后,每隔period毫秒执行一次
timer.schedule(task, delay, period);
1.3、定时器写法
Timer timer = new Timer();
timer.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() { Message message = new Message(); message.what = 1; handler.sendMessage(message); } }, 1000, 1000);// 表示1000毫秒之后,每隔1000毫秒执行一次