12、Handler & AsyncTask & 定时器

一、Handle

参考地址:http://blog.csdn.net/guolin_blog?viewmode=contents

Android只能在UI线程(主线程)更新UI显示,一般情况在子线程做耗时操作,我们平时通过Handler消息机制让子线程和主线程进行通信。它有三个构造函数:

image

image

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的无参构造函数

0bc1a630-cc51-404d-a8a6-3f2d0fe10b50

可以看出,第10行调用了Looper.prepare()方法来获取Looper对象,如果Looper为空则会抛出Can't create handler inside thread that has not

called Looper.prepare()什么时候Looper会为空呢?我们看下Looper.myLooper()中的代码:

image

方法很简单,就是从sThreadLocal对象中取出Looper。如果sThreadLocal中有Looper存在就返回该Looper,如果没有Looper存在则返回空了,

那么究竟是在哪里给sThreadLocal设置Looper呢,当然是在Looper.prepare()方法

image

可以看到,首先判断sThreadLocal中是否已经存在Looper,没有则创建一个,所以就解释为什么要调用Looper.prepare()方法才能创建Handler对象。

同时也可以看出每个线程中最多只会有一个Looper对象。

 

1.2、主线程创建Handler

主线程中创建Handler没有调用Looper.prepare(),为什么不会崩溃呢?这是因为系统已经帮我们自动调用Looper.prepare()方法,

我们查看ActivityThread中的main()方法

image

可以看到第7行调用了Looper.prepareMainLooper(),而该方法又调用了Looper.prepare

372f88f8-81b6-4a4c-93cf-8b4db8939887

总结:子线程创建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()方法

940db60f-278c-49cf-91ad-e751bf2b5f7a

它所携带的两个参数都会传递到MessageQueue的enqueueMessage(..)方法中,它是一个先进先出的消息队列,该类是在Looper构造函数中创建,

因此一个Looper对应一个MessageQueue,那么enqueueMessage(..)就是入队的方法了。

630b523d-b824-4375-93c5-b339dec1ecdc

如果通过sendMessageAtFrontOfQueue()发送消息,它同样调用enqueueMessage()来让消息入队,只是时间为0而已。

然而出队操作是通过Looper.looper()来进行操作。

9a6ed1b6-58b8-414a-b622-d58200fbe6c6

每当有一个消息出队,就将它传递到msg.target的dispatchMessage()方法中,而msg.target就是Handler。

6a8654f9-996a-4e23-82e3-577de792cae4

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()方法的源码

0ab7f945-ccb1-45c2-b837-48e5a4b45b0e

调用sendMessageDelayed()方法发送消息,并且使用getPostMessage()方法将Runnable对象转换成一条消息

3a2c54dc-a888-43a8-a764-450b1179d1ad

将消息的callback字段的值指定为传入的Runnable对象。上面曾说,Handler的dispatchMessage()方法会做一个检查,如果Message的callback等于

null才会去调用handleMessage()方法,否则就调用handleCallback()方法。

7906f5a8-49b8-4edf-a298-ba00a737af4f

非常简单,其实就是调用了传入的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参数

image

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毫秒执行一次

 

转载于:https://www.cnblogs.com/pengjingya/p/5508510.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值