应该避免的操作
在主线程上应该避免下列操作:
1 与网络相关的操作
2 需要对文件系统进行读写操作的任务
3 任何种类的繁重事务处理(如图片或视频修改)
4 在等待某件事务完成时会阻碍线程执行的任务
这个列表几乎包含了大多数情况,所以,作为一般规律,如果不涉及对用户界面的设置或修改,就不要在主线程上操作。
什么时候在主线程上
无论何时,当一个方法被系统调用时,(除非显式声明其他调用者) 就可以确保是位于主线程上。再次强调,作为一般规律,如果用户不在自己创建的进程内,就基本可以假定是在主线程上,那就要当心了。
举个例子,下载一个图片,因为会与网络交互,所以要独立到一个service里。不能独立在一个线程里,设置图片的任务也就是更改主进程的UI只能在主进程中更新。
所以下面的方法是错误的。
public void onCreate()
{
new Thread(){
public void run()
{
try{
URL url = new URL("http://baidu.com");
HttpURLConnection httpCon = (HttpURLConnection)url.openConnection();
if(httpCon.getResponseCode() != 200) throw new Exception("Failed to connect");
InputStream is = httpCon.getInputStream();
Bitmap bt = BitmapFactory.decodeStream(is);
ImageView iv = (ImageView)findViewById(R.id.remote_image);
iv.setImageBitmap(bt);
}
catch(Exception e){
}
}
}.start();
}
出现这个问题,主线程是唯一一个具有改变用户界面权限的线程,调用setImageBitmap就完全属于这样的改变,因此只能在主线程上才能完成。
所以,需要回到主线程
android系统通过活动类提供了一种方式,只要能够访问活动就可以回到主线程,下面来修改上面的代码。
public void onCreate()
{
new Thread(){
public void run()
{
try{
URL url = new URL("http://baidu.com");
HttpURLConnection httpCon = (HttpURLConnection)url.openConnection();
if(httpCon.getResponseCode() != 200) throw new Exception("Failed to connect");
InputStream is = httpCon.getInputStream();
Bitmap bt = BitmapFactory.decodeStream(is);
ImageActivity.this.runOnUiThread(new Runnable(){
public void run()
{
ImageView iv = (ImageView)findViewById(R.id.remote_image);
iv.setImageBitmap(bt);
}
});
}
catch(Exception e){
}
}
}.start();
}
记住,现在已经运行在一个线程中,所以需要用this指针会指向线程自身,需要调用runOnUiThread方法。
更好的办法
AsyncTask
AsyncTask介绍
AsyncTask是一个扩展的抽象类,为一个耗时的异步任务提供基本的框架。
Android的AsyncTask比Handler更轻量级一些,适用于简单的异步处理。
首先明确Android之所以有Handler和AsyncTask,都是为了不阻塞主线程(UI线程),且UI的更新只能在主线程中完成,因此异步处理是不可避免的。
Android为了降低这个开发难度,提供了AsyncTask。AsyncTask就是一个封装过的后台任务类,顾名思义就是异步任务。
AsyncTask直接继承于Object类,位置为android.os.AsyncTask。要使用AsyncTask工作我们要提供三个泛型参数,并重载几个方法(至少重载一个)。
三种泛型类型分别代表“启动任务执行的输入参数”、“后台任务执行的进度”、“后台计算结果的类型”。在特定场合下,并不是所有类型都被使用,如果没有被使用,可以用java.lang.Void类型代替。
有两个方法是必须要实现的。doInBackground和onPostExecute(Result)这两个方法。
onPreExecute()
/*
这里是最终用户调用Excute时的接口,当任务执行之前开始调用此方法,可以在这里显示进度对话框。
*/
doInBackground(Params…)
/*
后台执行,比较耗时的操作都可以放在这里。注意这里不能直接操作UI。此方法在后台线程执行,完成任务的主要工作,通常需要较长的时间。在执行过程中可以调用publicProgress(Progress…)来更新任务的进度。
*/
onPostExecute(Result)
/*
相当于Handler 处理UI的方式,在这里面可以使用在doInBackground 得到的结果处理操作UI。 此方法在主线程执行,任务执行的结果作为此方法的参数返回
*/
这项技术的缺点:
http://www.oschina.net/question/54100_27825
IntentService
这是个出色的方法,移动大量的数据而不依赖于任何特定的活动甚至应用,AsyncTask总会占用主线程至少两次, 调用preexecute和postexecute方法,而且它总是必须属于一个能够显示在屏幕上的活动,IntentService没有这样的限制。
流程:
首先声明服务
按照下载图片作为例子,