Android线程学习笔记
Posted 阿蛮家
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android线程学习笔记相关的知识,希望对你有一定的参考价值。
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);
以上是关于Android线程学习笔记的主要内容,如果未能解决你的问题,请参考以下文章
Android-Universal-Image-Loader 学习笔记线程池分析