Android线程学习笔记
android,线程,线程安全2016-08-06
UI线程要处理所有任务时,那些耗时很长的操作——诸如访问网络或查询数据库等——将会阻塞整个UI(线程)。一旦线程被阻塞,所有事件都不能被分发,包括屏幕绘图事件。从用户的角度看来,应用程序看上去像是挂起了。更糟糕的是,如果UI线程被阻塞超过一定时间(目前大约是5秒钟),用户就会被提示那个可恶的“应用程序没有响应”(ANR)对话框。
此外,Andoid的UI组件包并不是线程安全的。因此不允许从工作线程中操作UI——只能从UI线程中操作用户界面。于是,Andoid的单线程模式必须遵守两个规则:
根据以上对单线程模式的描述,要想保证程序界面的响应能力,关键是不能阻塞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(); } } }
Intent service = new Intent(getApplicationContext(),IntentSer.class); startService(service);