您的位置:首页 > 移动开发 > Android开发

Android中的handler和AsyncTask

2015-05-02 13:52 253 查看
我们经常听到或者看到大家说安卓的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的基本用法。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: