Android中的handler和AsyncTask

我们经常听到或者看到大家说安卓的UI操作是线程不安全的,那何为线程不安全呢,安卓并不允许多个线程对UI进行更新操作,因为这样做就势必要加入同步操作,因此处于性能考虑,安卓规定只允许主线程(即UI线程,他在程序第一次启动时被安卓系统创建,负责处理UI相关的事件,并进行UI的更新与事件的分发)对UI组件进行修改。但是在实际的开发中我们经常会遇到进行一些负责的运算或处理的情况,尤其是进行网络访问获取较大资源的情况,如果我们尝试着将它们放在主线程里进行操作,很有可能会经常收到ANR(Application Not Response)的警告,极大地影响了用户体验。这主要是因为我们的耗时操作阻塞了主线程的UI更新与事件分发操作。为此安卓从一开始就提供了Handler解决UI异步更新的问题,并且从API 3开始加入了AsyncTask让后台操作更新UI变得更简单,只需要重载几个接口就可以实现。

为了更好地理解Handler的使用,我们必须先要知道线程的消息处理机制。安卓主线程在创建时都有唯一一个Looper对象,Looper类负责对一个MessageQueue的管理,消息队列是在Looper创建时被同时创建,他不断地从他所管理的MessageQueue里取出消息进行消息的分发,直道消息队列没有消息为止,这一操作是由Loop方法的调用开始的,Loop方法是一个死循环,我们可以看一下Looper的源码的对应部分

public static void loop() {
        final Looper me = myLooper();//得到该线程的Looper对象
        if (me == null) {//如果为空,说明该线程Looper对象并没有被创建,一个线程通过调用静态方法Looper.prepare()来创建或者获取绑定到该线程上的Looper对象
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
        final MessageQueue queue = me.mQueue;//获取本对象管理的MessageQueue对象

        // Make sure the identity of this thread is that of the local process,
        // and keep track of what that identity token actually is.
        Binder.clearCallingIdentity();
        final long ident = Binder.clearCallingIdentity();//保存线程的标识号

        for (;;) {//开始消息循环
        	  //取出消息
            Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }

            // This must be in a local variable, in case a UI event sets the logger
            Printer logging = me.mLogging;
            if (logging != null) {
                logging.println(">>>>> Dispatching to " + msg.target + " " +
                        msg.callback + ": " + msg.what);
            }

            msg.target.dispatchMessage(msg);

            if (logging != null) {
                logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
            }

            // Make sure that during the course of dispatching the
            // identity of the thread wasn't corrupted.
            final long newIdent = Binder.clearCallingIdentity();//验证线程标志号
            if (ident != newIdent) {
                Log.wtf(TAG, "Thread identity changed from 0x"
                        + Long.toHexString(ident) + " to 0x"
                        + Long.toHexString(newIdent) + " while dispatching to "
                        + msg.target.getClass().getName() + " "
                        + msg.callback + " what=" + msg.what);
            }

            msg.recycle();//消息回收至Message pool
        }
    }

但是用户自己创建的线程默认是没有Looper对象的,我们需要手动调用Looper.prepare来创建或获取该线程对应的Looper对象,然后调用Looper.loop();进入消息循环,这个时候我们就可以在我们的Handler里面handleMessage了(Handler在创建时自动绑定到该线程的Looper对象上以及对应的MessageQueue上)。了解了以上原理,我们现在就来写一个程序测试一下吧。

</pre><pre name="code" class="java">package com.example.study;

import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;

import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.widget.Button;


public class MainActivity extends Activity {
	private Button myButton ;
	private Handler myHandler;
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		myButton = (Button) findViewById(R.id.button);
		myHandler = new Handler(){//创建一个Handler对象,该对象自动绑定到主线程的Looper对象上

			@Override
			public void handleMessage(Message msg) {//实现这个方法来获取消息
				// TODO Auto-generated method stub
				super.handleMessage(msg);//这句话可以没有,因为超类里的实现为空
				if(msg.what==0x1234){//判断消息来源是否为本程序
					myButton.setText((msg.getData().getLong("time"))+"毫秒");//在这里更新UI
				}
			}
			
		};
		
		new Timer().schedule(new TimerTask() {//用Timer类开启一个定时执行的薪线程
			
			@Override
			public void run() {//重载run方法
				// TODO Auto-generated method stub
				Message msg = new Message();
				Bundle bundle =new Bundle();//先建一个Bundle对象
				bundle.putLong("time", new Date().getTime());//存入当前时间
				msg.what=0x1234;//设置消息标志号
				msg.setData(bundle);//将键值对放入消息对象
				myHandler.sendMessage(msg);//发送该消息
			}
		}, 0, 1000);
	}

}
执行效果如下:



接下来,我们用AsyncTask这个类来实现同样的功能。使用AsyncTask分为三步,首先我们要继承AsyncTask这个抽象类,然后实现其中几个方法来实现异步更新UI的目的,注意到 AsyncTask还提供了三个泛型参数AsyncTask<Params,Progress,Result>分别表示启动任务时传入的参数类型,后台传递的进度信息的类型以及结果的类型,我们在继承时要指定这三个类型,如果不需要可以指定为Void类型。第二,我们需要根据自己的需要实现protected Result doInBackground(Params... params),protected void onPostExecute(String Result),protected void onPreExecute(),protected void onProgressUpdate(Progress... values),protected void onCancelled()这几个方法。
其中protected Result doInBackground(Params... params)用来执行耗时操作,在这里我们可以调用publishProgress来更新进度信息,但是该方法内部不能直接对UI组件进行操作,Params... params是可变参数,当调用AsyncTask的execute方法时根据需要冲入一定数量Params类型的参数,这几个参数就被送到了doInBackground里。
protected void onPostExecute(String Result)这个方法时任务执行完毕时首先调用的方法,一般用来做一些提示或者UI的标识工作。形参是doInBackground函数返回的结果,是后台操作执行的结果。
rotected void onPreExecute()这个方法恰好相反,她是在后台任务执行执行用于对UI做出标记的。
protected void onProgressUpdate(Progress... values)在这个方法里我们可以直接操作UI组件对进度进行更新,其中传入的参数是我们在调用publishProgress时传入的,也是可变参数,数量是一致的。
void onCancelled()方法是调用AsyncTask的cancel方法时执行的。
最后我们只需要在主线程里创建一个Asynctask对象,然后调用它的execute方法并传入必要的参数就可以了。
接下来我们来写一个小程序进行测试一下:
package com.example.study;

import android.app.Activity;
import android.os.Bundle;
import android.view.KeyEvent;
import android.widget.Button;

public class MainActivity extends Activity {
	
	private Button myButton ;
	private myAsyncTask task;
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		myButton = (Button) findViewById(R.id.button);	
		
		task = new myAsyncTask(MainActivity.this, myButton);
		task.execute("param1","param2");//execute方法必须在UI线程中调用
	}

	@Override
	public boolean onKeyDown(int keyCode, KeyEvent event) {
		// TODO Auto-generated method stub
		switch(keyCode){
			case KeyEvent.KEYCODE_BACK://返回键被按下
				task.cancel(true);//取消任务
				this.finish();//程序退出
				return true;
			default:
				return super.onKeyDown(keyCode, event);
		}	
	}
}

package com.example.study;

import java.util.Date;
import android.content.Context;
import android.os.AsyncTask;
import android.widget.Button;
import android.widget.Toast;

public class myAsyncTask extends AsyncTask<String, Integer, String> {
    private Button myButton;
    private Context context;
    
    myAsyncTask(Context context,Button myButton){
        this.myButton = myButton;
        this.context = context;
    }
    /**
     * 在onPreExecute()完成后立即执行,用于执行较为费时的操作,此方法将接收输入参数并返回计算结果。
     * 在执行过程中可以调用publishProgress()来更新进度信息。
     * 该方法中不可更改UI组件信息,也不可手动调用该方法
     */
    @Override
    protected String doInBackground(String... params){
    	long times=new Date().getTime();
    	System.out.println("我是传入的参数1"+params[0]+",我是传入的参数2"+params[1]);
    	
    	while(System.currentTimeMillis()-times<10000){		
    		int progress=(int) ((System.currentTimeMillis()-times)/100);
    		publishProgress(progress);
    		if(isCancelled()){
    			break;
    		}
    	}
    	return "完成";
    }
    /**
     * 当后台操作结束时此方法将会被调用,计算结果将做为参数传递到此方法中,可以在这里对UI进行更新,不可手动调用该方法
     */
    @Override
    protected void onPostExecute(String result) {
    	myButton.setText(result);
    	Toast.makeText(context, "结束时间"+System.currentTimeMillis(), Toast.LENGTH_SHORT).show();
    }
    /**
     * execute()被调用后立即执行,一般用来在执行后台任务前对UI做一些标记,不可手动调用该方法
     */
    @Override
    protected void onPreExecute() {
    	myButton.setText("开始计时");
    	Toast.makeText(context, "开始时间"+System.currentTimeMillis(), Toast.LENGTH_SHORT).show();
    }
    /**
     * onProgressUpdate()方法用于更新进度信息,不可手动调用该方法
     */
    @Override
    protected void onProgressUpdate(Integer... values) {
    	myButton.setText(values[0] + "%");
    }
    /**
     * onCancelled()方法用于在取消执行中的任务时更改UI
     */
    @Override
    protected void onCancelled(){
    	Toast.makeText(context, "任务被取消", Toast.LENGTH_SHORT).show();
    }
}

执行效果如图:



以上就是Handler和AsyncTask的基本用法。


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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值