UI线程要处理所有任务时,那些耗时很长的操作——诸如访问网络或查询数据库等——将会阻塞整个UI(线程)。一旦线程被阻塞,所有事件都不能被分发,包括屏幕绘图事件。从用户的角度看来,应用程序看上去像是挂起了。更糟糕的是,如果UI线程被阻塞超过一定时间(目前大约是5秒钟),用户就会被提示那个可恶的“应用程序没有响应”(ANR)对话框。
此外,Andoid的UI组件包并不是线程安全的。因此不允许从工作线程中操作UI——只能从UI线程中操作用户界面。于是,Andoid的单线程模式必须遵守两个规则:
- 不要阻塞UI线程。
- 不要在UI线程之外访问Andoid的UI组件包。
工作线程
根据以上对单线程模式的描述,要想保证程序界面的响应能力,关键是不能阻塞UI线程。如果操作不能很快完成,应该让它们在单独的线程中运行(“后台”或“工作”线程)。
例如:以下响应鼠标点击的代码实现了在单独线程中下载图片并在ImageView显示:
public void onClick(View v) { new Thread(new Runnable() { public void run() { Bitmap b = loadImageFromNetwork("http://example.com/image.png"); mImageView.setImageBitmap(b); } }).start(); }
乍看起来,这段代码似乎能运行得很好,因为创建了一个新的线程来处理访问网络的操作。可是它违反了单线程模式的第二条规则:不要在UI线程之外访问Andoid的UI组件包——以上例子在工作线程里而不是UI线程里修改了ImageView。这可能导致不明确、不可预见的后果,要跟踪这种情况也是很困难很耗时间的。
为了解决以上问题,Android提供了几种途径来从其它线程中访问UI线程。下面列出了有助于解决问题的几种方法:
比如说,可以使用View.post(Runnable)方法来修正上面的代码:
public void onClick(View v) { new Thread(new Runnable() { public void run() { final Bitmap bitmap = loadImageFromNetwork("http://example.com/image.png"); mImageView.post(new Runnable() { public void run() { mImageView.setImageBitmap(bitmap); } }); } }).start(); }
以上代码的执行现在是线程安全的了:网络相关的操作在单独的线程里完成,而ImageView是在UI线程里操纵的。
不过,随着操作变得越来越复杂,这类代码也会变得很复杂很难维护。为了用工作线程完成更加复杂的交互处理,可以考虑在工作线程中用Handler来处理UI线程分发过来的消息。当然,最好的解决方案也许就是继承使用异步任务类AsyncTask,此类简化了一些工作线程和UI交互的操作。
-
使用异步任务
异步任务AsyncTask 允许以异步的方式对用户界面进行操作。它先阻塞工作线程,再在UI线程中呈现结果,在此过程中不需要对线程和handler进行人工干预。
要使用异步任务,必须继承AsyncTask类并实现doInBackground()回调方法,该对象将运行于一个后台线程池中。要更新UI时,须实现onPostExecute()方法来分发doInBackground()返回的结果,由于此方法运行在UI线程中,所以就能安全地更新UI了。然后就可以在UI线程中调用execute()来执行任务了。
例如,可以利用AsyncTask来实现上面的那个例子:
public void onClick(View v) { new DownloadImageTask().execute("http://example.com/image.png"); } private class DownloadImageTask extends AsyncTask<String, Void, Bitmap> { /** The system calls this to perform work in a worker thread and * delivers it the parameters given to AsyncTask.execute() */ protected Bitmap doInBackground(String... urls) { return loadImageFromNetwork(urls[0]); } /** The system calls this to perform work in the UI thread and delivers * the result from doInBackground() */ protected void onPostExecute(Bitmap result) { mImageView.setImageBitmap(result); } }
现在UI是安全的,代码也得到简化,因为任务分解成了工作线程内完成的部分和UI线程内完成的部分。
要全面理解这个类的使用,须阅读AsyncTask的参考文档。
一、Handler+Thread
public class HandlerActivity extends Activity {
private TextView txtCount;
private int mCount;
private Handler mHandler = new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what){
case 111:
int count = (int) msg.obj;
txtCount.setText("网络请求的数据:"+count);
break;
default:
break;
}
}
};
private class MyThread extends Thread{
@Override
public void run() {
while (true){
//模拟网络请求
try {
sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
mCount ++;
Message msg = mHandler.obtainMessage();
msg.what = 111;
msg.obj = mCount;
mHandler.sendMessage(msg);
}
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_handler);
txtCount = (TextView) findViewById(R.id.txtCount);
new MyThread().start();
}
}
二、AsyncTask 从网络下载一张图片显示到UI界面,AsyncTask的几处回调都给了我们机会去中断任务,在任务状态的管理上较之Thread()方式更为灵活。值得注意的是AsyncTask的cancel()方法并不会终止任务的执行,开发者需要自己去检查cancel的状态值来决定是否中止任务。AsyncTask也有隐式的持有外部类对象引用的问题,需要特别注意防止出现意外的内存泄漏。AsyncTask由于在不同的系统版本上串行与并行的执行行为不一致,被不少开发者所诟病,这确实是硬伤,绝大部分的多线程场景都需要明确任务是串行还是并行。
线程优先级为background,对UI线程的执行影响极小。
public class GetInternetImgAsyncTask extends AsyncTask<String,Integer,Bitmap> {
private ProgressBar mProgressBar;
private TextView mTextView;
private ImageView mImageView;
public GetInternetImgAsyncTask(ProgressBar progressBar, TextView textView, ImageView imageView) {
this.mProgressBar = progressBar;
this.mTextView = textView;
this.mImageView = imageView;
}
@Override
protected void onPreExecute() {
super.onPreExecute();
mTextView.setText("下载图片");
}
@Override
protected Bitmap doInBackground(String... params) {
URL myFileUrl = null;
Bitmap bitmap = null;
InputStream is = null;
HttpURLConnection conn = null;
try {
myFileUrl = new URL(params[0]);
} catch (MalformedURLException e) {
e.printStackTrace();
}
try {
conn = (HttpURLConnection)myFileUrl
.openConnection();
conn.setDoInput(true);
conn.connect();
is =conn.getInputStream();
bitmap = BitmapFactory.decodeStream(is);
byte[] data = new byte[1024];
int seg = 0;
long total = conn.getContentLength();
long current = 0;
while (!isCancelled()&&(seg = is.read(data)) != -1){
current += seg;
int progress = (int) ((long)current/total*100);
publishProgress(progress);
}
is.close();
} catch (IOException e) {
e.printStackTrace();
}finally{
try {
if(is != null){
is.close();
}
if( conn != null){
conn.disconnect();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return bitmap;
}
@Override
protected void onProgressUpdate(Integer... values) {
mProgressBar.setProgress(values[0]);
mTextView.setText(values[0] + "%");
super.onProgressUpdate(values);
}
@Override
protected void onPostExecute(Bitmap bitmap) {
if (bitmap != null) {
mImageView.setImageBitmap(bitmap);
}else {
mTextView.setText("下载失败");
}
mProgressBar.setVisibility(View.GONE);
mTextView.setText("下载完成");
super.onPostExecute(bitmap);
}
@Override
protected void onCancelled() {
super.onCancelled();
mTextView.setText("任务取消");
}
}
执行AsyncTask
public class AsyncTaskActivity extends Activity{
private Button btn_get_img;
private ProgressBar progressBar;
private TextView textView;
private ImageView img;
private GetInternetImgAsyncTask getInternetImgAsyncTask;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.asynctask);
progressBar = (ProgressBar)findViewById(R.id.progressBar02);
textView = (TextView)findViewById(R.id.textView01);
img = (ImageView) findViewById(R.id.img_show);
btn_get_img = (Button) findViewById(R.id.btn_get_img);
getInternetImgAsyncTask = new GetInternetImgAsyncTask(progressBar,textView,img);
btn_get_img.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//开启异步线程下载图片
Log.e("AsyncTaskActivity", "任务是否被取消 :"+getInternetImgAsyncTask.isCancelled());
if(!getInternetImgAsyncTask.isCancelled()){
//为了防止task二次执行导致程序崩溃,需要new一个task
getInternetImgAsyncTask = new GetInternetImgAsyncTask(progressBar, textView, img);
getInternetImgAsyncTask.execute("http://c.hiphotos.baidu.com/image/pic/item/d439b6003af33a876bcce3f7c35c10385243b5be.jpg");
}
}
});
}
@Override
protected void onDestroy() {
super.onDestroy();
//取消异步任务
if(getInternetImgAsyncTask != null && getInternetImgAsyncTask.getStatus() != AsyncTask.Status.FINISHED){
getInternetImgAsyncTask.cancel(true);
}
}
}
三、HandlerThread 定时更新UI界面,在需要对多任务做更精细控制,线程切换更频繁的场景之下,Thread()和AsyncTask都会显得力不从心。HandlerThread却能胜任这些需求甚至更多。HandlerThread将Handler,Thread,Looper,MessageQueue几个概念相结合。Handler是线程对外的接口,所有新的message或者runnable都通过handler post到工作线程。Looper在MessageQueue取到新的任务就切换到工作线程去执行。不同的post方法可以让我们对任务做精细的控制,什么时候执行,执行的顺序都可以控制。HandlerThread最大的优势在于引入MessageQueue概念,可以进行多任务队列管理。HandlerThread背后只有一个线程,所以任务是串行执行的。串行相对于并行来说更安全,各任务之间不会存在多线程安全问题。HandlerThread所产生的线程会一直存活,Looper会在该线程中持续的检查MessageQueue。这一点和Thread(),AsyncTask都不同,thread实例的重用可以避免线程相关的对象的频繁重建和销毁。HandlerThread较之Thread(),AsyncTask需要写更多的代码,但在实用性,灵活度,安全性上都有更好的表现。
public class HandlerThreadActivity extends Activity {
private TextView txtCount;
private HandlerThread mHandlerThread;
private Handler mHandler;
//处理UI显示的handler
private Handler mUiHandler = new Handler();
private boolean isUpdateInfo = false;
private static final int MSG_UPADATE_INFO = 0x110;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_handlerthread);
txtCount = (TextView) findViewById(R.id.txtCount);
initHandlerThread();
}
private void initHandlerThread() {
mHandlerThread = new HandlerThread("updatethreadhandler");
mHandlerThread.start();
mHandler = new Handler(mHandlerThread.getLooper()){
@Override
public void handleMessage(Message msg) {
checkForUpdate();
if(isUpdateInfo){
mHandler.sendEmptyMessageDelayed(MSG_UPADATE_INFO, 1000);
}
}
};
}
private void checkForUpdate() {
try {
Thread.sleep(1000);
mUiHandler.post(new Runnable() {
@Override
public void run() {
String result = "实时更新中,当前大盘指数:<font color='red'>%d</font>";
result = String.format(result, (int) (Math.random() * 3000 + 1000));
txtCount.setText(Html.fromHtml(result));
}
});
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Override
protected void onResume() {
super.onResume();
isUpdateInfo = true;
mHandler.sendEmptyMessage(MSG_UPADATE_INFO);
}
@Override
protected void onPause() {
super.onPause();
isUpdateInfo = false;
mHandler.removeMessages(MSG_UPADATE_INFO);
}
@Override
protected void onDestroy() {
super.onDestroy();
mHandlerThread.quit();
}
}
理解handler.post(runnable)可以参考 深入理解Looper、Handler、Message三者之间的关系
四、IntentService,和AsyncTask不同,没有和UI线程的交互,也不像HandlerThread的工作线程会一直存活。IntentService背后其实也有一个HandlerThread来串行的处理Message Queue。只不过在所有的Message处理完毕之后,工作线程会自动结束。所以可以把IntentService看做是Service和HandlerThread的结合体,适合需要在工作线程处理UI无关任务的场景。
public class IntentSer extends IntentService {
public IntentSer(){
super("IntentSer");
}
private static final String TAG = "IntentSer";
private String url_path="http://ww2.sinaimg.cn/bmiddle/9dc6852bjw1e8gk397jt9j20c8085dg6.jpg";
@Override
public void onCreate() {
Log.i(TAG, "Service is Created");
super.onCreate();
}
@Override
public void onStart(Intent intent, int startId) {
super.onStart(intent, startId);
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy() {
Log.i(TAG, "Service is Destroyed");
super.onDestroy();
}
@Override
public IBinder onBind(Intent intent) {
return super.onBind(intent);
}
@Override
protected void onHandleIntent(Intent intent) {
Log.i(TAG, "HandleIntent is execute");
try {
//在设备应用目录创建一个文件
File file = new File(this.getFilesDir(),"weibo.jpg");
Log.i(TAG,"图片存储路径:" + file.getAbsolutePath());
FileOutputStream fos = new FileOutputStream(file);
//获取网络图片的输入流
InputStream inputStream = new URL(url_path).openStream();
//把网络图片输入流写入到文件输出流
byte[] date = new byte[1024];
int len = -1;
while ((len = inputStream.read(date))!=-1){
fos.write(date,0,len);
}
fos.close();
inputStream.close();
Log.i(TAG, "The file download is complete");
} catch (IOException e){
e.printStackTrace();
}
}
}
启动IntentService
Intent service = new Intent(getApplicationContext(),IntentSer.class);
startService(service);