当我们第一次启动一个Android程序的时候,一个被称为“main”的线程就被自动的创建了。它被称为主线程或者UI线程,它是非常重要的,因为它负责分发时间给响应的控件,其中包含屏幕绘图事件。主线程贯穿用户和Android widget(Android控件)的交互的整个过程。例如,我们触摸了屏幕上的按钮(Button),UI线程派发触摸(touch)事件给这个widget,紧接着这个widget设置它自身为按下状态并向事件队列发送一个无效(invalidate)的请求。UI线程会把这个请求移出事件队列并通知按钮在屏幕上重新绘制自身。
单线程模型会在没有考虑到它的影响的情况下引起Android应用程序性能低下,因为所有的任务都在同一个线程中执行,如果执行一些耗时的操作,如访问网络或查询数据库,会阻塞整个用户界面。当在执行一些耗时的操作的时候,不能及时的分发事件,包括用户界面重绘事件。从用户的角度来看,应用程序看上去像卡的很厉害。更糟糕的是,如果阻塞应用程序的时间过长(现在大概是5秒钟)Android会向用户提示一些信息,即弹出一个“application not responding(应用程序没有响应)”的
对话框
。
于是,我验证了一下,写了一个简单的含有一个按钮的程序,并为按钮注册一个点击事件,并在事件处理器汇总调用了Thread.sleep(60000)。在按下这个按钮之后恢复按钮的正常状态之前,它会保持按下一段时间(上面说的是5秒,我检验的的时间远大于5秒),之后弹出提示框。代码如下:
package com.example.study;
import android.os.Bundle;
import android.app.Activity;
import android.graphics.Color;
import android.view.View;
import android.widget.Button;
/**
*
* ClassName:MainActivity
* Function: 测试线程(实现:阻塞应用程序的时间过长,弹出对话框)
* Reason: TODO ADD REASON
*
* @author perArhter
* @version
* @since Ver 1.1
* @Date 2015 2015-10-15 下午9:31:33
*
* @see
*/
public class MainActivity extends Activity {
//定义一个按钮
private Button button1;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//按钮初始化
button1 = (Button) findViewById(R.id.button1);
//按钮初始状态为为按下状态( pressed 加压的;紧迫的)
button1.setPressed(false);
//按钮初始化颜色为粉色
//button1.setBackgroundColor(Color.parseColor("#FFC0CB"));
//按钮点击事件
button1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//按下的一瞬间,按钮的背景颜色变为番茄红
//button1.setBackgroundColor(Color.parseColor("#FF6347"));
//设置按钮为按下状态
button1.setPressed(true);
//然后判断按钮是否是处于被按下的状态,是则过60秒后弹起
if(button1.isPressed()){
try {
//睡眠六秒
Thread.sleep(60000);
//按钮颜色恢复初始色
//button1.setBackgroundColor(Color.parseColor("#FFC0CB"));
//设置按钮为抬起状态
button1.setPressed(false);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
}
现在知道一定要避免在主线程中执行过长时间的操作,那我们可能会使用额外的线程(后台线程或者工作线程)去执行操作。让我们来看看例子:
package com.example.study;
import android.os.Bundle;
import android.app.Activity;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
/**
*
* ClassName:MainActivity
* Function: 测试线程(使用额外的线程,延时6秒后给ImageView设置图片)
* Reason:
TODO ADD REASON
*
* @author perArther
* @version
* @since Ver 1.1
* @Date
2015
2015-10-15
下午10:30:13
*
* @see
*/
public class MainActivity extends Activity {
//定义一个按钮
private Button button1;
//定义一个ImageView
private ImageView imageView1;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//按钮初始化
button1 = (Button) findViewById(R.id.button1);
//ImageView初始化
imageView1 = (ImageView) findViewById(R.id.imageView1);
button1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//另外创建一个线程
new Thread(new Runnable() {
@Override
public void run() {
try {
//延时6秒
Thread.sleep(6000);
//延时完毕后让ImageView放上图片
imageView1.setImageResource(R.drawable.ic_launcher);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
});
}
}
首先,代码看上去是解决的之前的问题,因为它不会阻塞UI线程,可是,它违背了单一线程模式:Android UI操作并不是线程安全的,而且这些直接控制试图控件的操作必须在UI线程中执行。所以它在运行是会报错:
Only the original thread that created a view hierarchy can touch its views.意思是只有UI线程才能创建一个试图层次来触摸它的试图。
为了解决这个问题,Android提供了几种在其他线程中访问UI线程的方法,如下所示:
Activity.runOnUiThread(Runnable)
View.post(Runnable)
View.postDelayed(Runable,long)
Handler
检验四种方法是否可行:
package com.example.study;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.app.Activity;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
/**
*
* ClassName:MainActivity
* Function: 测试线程(利用Android提供的四种方法,使用额外的线程,延时6秒后给ImageView设置图片)
* Reason:
TODO ADD REASON
*
* @author perArther
* @version
* @since Ver 1.1
* @Date
2015
2015-10-15
下午11:05:15
*
* @see
*/
public class MainActivity extends Activity {
//定义一个按钮
private Button button1;
//定义一个ImageView
private ImageView imageView1;
//定义一个Handler
private Handler handler;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//按钮初始化
button1 = (Button) findViewById(R.id.button1);
//ImageView初始化
imageView1 = (ImageView) findViewById(R.id.imageView1);
//初始化一个Handler对象
handler = new Handler(){
@Override
public void handleMessage(Message msg) {
if(msg.what==0x000) imageView1.setImageResource(R.drawable.ic_launcher);
super.handleMessage(msg);
}
};
//按钮的点击事件
button1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//另外创建一个线程
new Thread(new Runnable() {
@Override
public void run() {
try {
//延时60秒
Thread.sleep(6000);
//延时完毕后让ImageView放上图片
//=======================================================================================
/**
* 第一种Activity.runOnUiThread(Runnable)
*
* MainActivity.this.runOnUiThread(new Runnable() {
*
@Override
*
public void run() {
*
imageView1.setImageResource(R.drawable.ic_launcher);
*
}
* });
*
*/
//=======================================================================================
/**
* 第二中View.post(Runnable)
*
* imageView1.post(new Runnable() {
*
@Override
*
public void run() {
*
imageView1.setImageResource(R.drawable.ic_launcher);
*
}
* )};
*/
//=======================================================================================
/**
* 第三种View.postDelayed(Runnable,long)
*
* imageView1.postDelayed(new Runnable() {
*
@Override
*
public void run() {
*
imageView1.setImageResource(R.drawable.ic_launcher);
*
}
* },6000); // 这个就要延时前面的6秒加上这个6秒的时间,即延时12秒
*/
//=======================================================================================
/**
* 第四种Handler,先初始化一个Handler对象
*/
//延时完毕后,利用handler发送一个信息
handler.sendEmptyMessage(0x000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
});
}
}
虽然,这四种方法解决了任务线程直接操作UI线程的View的问题,但是这些类或者方法同样会是我们的代码很复杂很难理解。然而当我们需要实现一些很复杂的操作并需要频繁的更新UI时者会变得很复杂。为了解决这个问题,Android1.5提供了一个工具类:AsyncTask
(async [əˈsɪŋk] abbr. 异步,非同步),它简化了任务线程和主线程之间的通信。
AsyncTask的目的就是帮助你管理线程。
AsyncTask是抽象类。AsyncTask定义了三种泛型类型Params,Progress和Result。 AsyncTask<Params,Progress,Result>
其中Params指的是启动任务执行的输入参数,Progress指的是后台任务执行的百分比,Result指的是后台执行任务最终返回的结果。
AsyncTask的执行分为四个步骤,每一步都对应一个回调方法,这些方法不应该由程序调用,开发者需要做的就是实现这些方法。
1)继承AsyncTask
2)实现AsyncTask中定义的下面一个或几个方法
onPreExecute(),
(execute ['eksɪkjuːt] vt. 实行;执行),该方法将在执行实际的后台操作前被UI线程调用。可以在该方法中做一些准备工作,如在界面上显示一个进度条,或者一些空间的实例化,这个方法可以不用实现。
doInBackground(Params...),将在onPreExecute方法执行后马上执行,该方法运行在后台线程中。这里将主要负责执行那些很耗时的后台处理工作。可以调用publishProgress方法来更新实时的任务进度。该方法是抽象方法,子类必须实现。
onProgressUpdate(Result),在PublishProgress方法被调用后,UI线程将调用这个方法从而在界面上展示任务的进展情况。
onPostExecute(Result),在DoInBackground执行之后,onPostExecute方法将被UI线程调用,后台的计算结果将通过该方法传递到UI线程,并在界面上展示给用户。
onCancelled(),在用户取消线程操作的时候调用。在主线程中调用onCancelled()的时候调用。
为了正确的使用AsyncTask类,以下是几条必须遵守的准则:
1) Task的实例必须在UI 线程中创建
2) execute方法必须在UI 线程中调用
3) 不要手动的调用onPreExecute(), onPostExecute(Result),doInBackground(Params...), onProgressUpdate(Progress...)这几个方法,需要在UI线程中实例化这个task来调用。
4) 该task只能被执行一次,否则多次调用时将会出现异常
doInBackground方法和onPostExecute的参数必须对应,这两个参数在AsyncTask声明的泛型参数列表中指定,第一个为doInBackground接受的参数,第二个为显示进度的参数,第第三个为doInBackground返回和onPostExecute传入的参数。
了解了AsyncTask之后,利用它来实现之前的延时显示图片,代码如下:
package com.example.study;
import android.os.AsyncTask;
import android.os.Bundle;
import android.app.Activity;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
/**
*
* ClassName:MainActivity
* Function: 测试线程(利用AsyncTask实现延时6秒后给ImageView设置图片)
* Reason:
TODO ADD REASON
*
* @author perArther
* @version
* @since Ver 1.1
* @Date
2015
2015-10-15
下午11:57:20
*
* @see
*/
public class MainActivity extends Activity {
//定义一个按钮
private Button button1;
//定义一个ImageView
private ImageView imageView1;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//按钮初始化
button1 = (Button) findViewById(R.id.button1);
//ImageView初始化
imageView1 = (ImageView) findViewById(R.id.imageView1);
//按钮点击事件
button1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//给定执行参数,为Integer类型
new ImageTask().execute(R.drawable.ic_launcher);
}
});
}
/**
* AsyncTask是抽象类.AsyncTask定义了三种泛型类型 Params,Progress和Result。
* Params 启动任务执行的输入参数,比如HTTP请求的URL。
* Progress 后台任务执行的百分比。
* Result 后台执行任务最终返回的结果
*/
private class ImageTask extends AsyncTask<Integer, Integer, Integer>{
//AsyncTask定义了三种泛型类型,输入参数为Integer型,后台任务执行的百分比为Integer型,最终返回的结果为Integer型
@Override
protected Integer doInBackground(Integer... params) {
try {
//延时60秒
Thread.sleep(6000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return params[0];
}
@Override
protected void onPostExecute(Integer result) {
//设置ImageView的前景图片
imageView1.setImageResource(result);
super.onPostExecute(result);
}
}
}