我们经常听到或者看到大家说安卓的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);
}
}
执行效果如下:
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的基本用法。