8.4耗时操作的通用方式—多线程与异步处理
Android通过一个主线程对用户界面进行更新,这个线程是UI线程。如果程序不使用任何并发构建、Android的所有代码都会在这个线程中运行。当我们在进行网络连接等比较耗时的操作时,如果此连接动作直接在主线程,也就是UI线程中处理,会发生什么情况呢?整个程序处于等待状态,界面似乎是“假死”掉了。如果5秒钟以上没有响应,系统就会弹出对话框提示是否要强制关闭应用。为了给用户更好的用户体验,必须把这个任务放置到单独线程中运行,避免阻塞UI线程,这样就不会对主线程有任何影响。
8.4.1 多线程和异步处理简介
一般的,网络请求都需要一定的时间,所以在网络开发的过程中,会考虑使用多线程来实现网络请求,配合异步处理完成UI线程的更新。所以我们在本章用一个小节来详细讲下Android中的多线程和异步处理。
Android实现多线程与异步处理一般有下面两种方式:
1)Handler方式。
2)AsyncTask类实现。
下面我们就会对这两种方式做详细的说明。
经验分享: 一般的,如果应用程序中会大量的创建新的线程,就要考虑使用线程池了。使用线程池可以有效的管理线程。由于本节主要介绍如何在Android网络应用中实现异步处理,所以就不对线程池做展开说明了。 |
8.4.2 Handler方式
Handler允许用户发送、处理消息和与线程的消息队列相关联的runnable对象。每一个handler实例都与一个单独的线程相关联。每次创建一个新的Handler对象时,它都与创建该对象的线程或者该线程中的消息队列绑定在一起,这样Handler就可以发送消息和runnable对象到消息队列中,并在从消息队列中取出的时候处理它们。详细的说明可以参考“4.3.1消息的传递—Handler的使用”。
下面是通过Handler来异步加载网络图片的完整例子:
//import略 publicclass HandlerTest extends Activity { publicstatic final int SHOW_PROGRESS = 0; publicstatic final int REFRESH = 1; publicstatic final int REMOVE_PROGRESS = 2; privateProgressBar mProgressBar; privateImageView img; privateButton btn; privateBitmap mBitmap; privateView.OnClickListener mOnClickListener = new View.OnClickListener(){ @Override publicvoid onClick(View v) { intid = v.getId(); switch(id) { caseR.id.download: sendMessage(SHOW_PROGRESS); Thread t = newThread(newDownload("http://www.google.com/images/google_favicon_128.png")); // 开启一个线程下载图片 t.start(); break; } } }; privateHandler mHandler = new Handler() { @Override publicvoid handleMessage(Message msg) { switch(msg.what) { caseSHOW_PROGRESS: //显示等待界面 mProgressBar.setVisibility(View.VISIBLE); break; caseREMOVE_PROGRESS: //隐藏等待界面 mHandler.removeMessages(SHOW_PROGRESS); mProgressBar.setVisibility(View.INVISIBLE); break; caseREFRESH: //更新UI onRefresh(); break; } } }; protectedvoid onRefresh() { if(mBitmap!= null){ //更新UI img.setImageBitmap(mBitmap); } //隐藏等待界面 sendMessage(REMOVE_PROGRESS); } protectedfinal void sendMessage(int what) { mHandler.sendMessage(mHandler.obtainMessage(what)); } @Override publicvoid onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.handlertest_layout); img= (ImageView)findViewById(R.id.imgic); btn= (Button)findViewById(R.id.download); btn.setOnClickListener(mOnClickListener); mProgressBar= new ProgressBar(this); FrameLayout.LayoutParamsparams = new FrameLayout.LayoutParams( LayoutParams.WRAP_CONTENT,LayoutParams.WRAP_CONTENT); params.gravity= Gravity.CENTER; //添加一个等待的view addContentView(mProgressBar,params); //开始时候设置为隐藏 mProgressBar.setVisibility(View.INVISIBLE); } /** * 下载的线程 */ publicclass Download implements Runnable { privateString uri; publicDownload(String uri) { this.uri= uri; } @Override publicvoid run() { try{ URL url = new URL(uri); HttpURLConnectionconn =(HttpURLConnection)url.openConnection(); conn.setDoInput(true); conn.connect(); InputStreaminputStream=conn.getInputStream(); mBitmap= BitmapFactory.decodeStream(inputStream); //下载完成后,发送消息给主线程刷新UI sendMessage(REFRESH); }catch (Exception e) { // } } } } |
具体的Layout文件handlertest_layout.xml代码如下:
<?xmlversion="1.0" encoding="utf-8"?> <LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent" android:background="@android:color/white"> <Button android:id="@+id/download" android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="开始下载图片"/> <ImageView android:id="@+id/imgic" android:layout_width="fill_parent" android:layout_height="wrap_content" /> </LinearLayout> |
程序运行结果如下图8-3所示。
图8-3异步加载网络图片的运行效果图
经验分享: Message是线程之间传递信息的载体,包含了对消息的描述和任意的数据对象。Message中包含了两个额外的intwhat字段(该字段是用来区分每条信息)和一个object字段,这样在大部分情况下,使用者就不需要再做内存分配工作了。虽然Message的构造函数是public的,但是最好是使用Message.obtain()或Handler.obtainMessage()函数来获取Message对象,因为Message的实现中包含了回收再利用的机制,可以提高效率。 |
8.4.3AsyncTask类实现后台任务的处理
Android提供了一个较线程更简单的处理多任务的方法——AsyncTask异步任务类,相对于线程来说,AsyncTask对于简单的任务处理更安全。AsyncTask类是一个抽象类,你要使用它,必须继承它并实现doInBackground()方法。
AsyncTask<Params,Progress, Result>使用三种泛型和可变参数类型Params,Progress和Result。
- Params启动任务执行的输入参数,比如HTTP请求的URL。
- Progress后台任务执行的百分比。
- Result后台执行任务最终返回的结果,比如String。
AsyncTask主要有三个操作方法
1)doInBackground(Params...),将在onPreExecute方法执行后马上执行,该方法运行在后台线程中。这里将主要负责执行那些很耗时的后台计算工作,例如网络获取图片。可以调用publishProgress方法来更新实时的任务进度。该方法是抽象方法,子类必须实现。
2)onProgressUpdate(Progress...),在publishProgress方法被调用后,UIthread将调用这个方法从而在界面上展示任务的进展情况,例如(刷新前台的图片)。
3)onPostExecute(Result),在doInBackground执行完成后,onPostExecute方法将被UIthread调用,后台的计算结果将通过该方法传递到UIthread。
下面我们来看一个下载网页的例子。
首先建一个layout文件asynctask_layout.xml。
<LinearLayoutxmlns:android=”http://schemas.android.com/apk/res/android” android:layout_width=”match_parent” android:layout_height=”match_parent” android:orientation=”vertical”> <Button android:id=”@+id/readWebpage” android:layout_width=”match_parent” android:layout_height=”wrap_content” android:onClick=”readWebpage” android:text=”LoadWebpage” > </Button> <TextView android:id=”@+id/TextView01” android:layout_width=”match_parent” android:layout_height=”match_parent” android:text=”ExampleText” > </TextView> </LinearLayout> |
下面建一个执行的Activity。
//import略 publicclass AsyncTaskTest extends Activity { privateTextView textView; @Override publicvoid onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.asynctask_layout); textView= (TextView) findViewById(R.id.TextView01); } privateclass DownloadWebPageTask extends AsyncTask<String, Void,String> { @Override protectedString doInBackground(String... urls) { Stringresponse = ""; for(String url : urls) { // 循环的获取Params中的参数 DefaultHttpClientclient = new DefaultHttpClient(); HttpGethttpGet = new HttpGet(url); try{ HttpResponseexecute = client.execute(httpGet); InputStreamcontent = execute.getEntity().getContent(); BufferedReaderbuffer = new BufferedReader( newInputStreamReader(content)); Strings = ""; while((s = buffer.readLine()) != null) { response+= s; } }catch (Exception e) { e.printStackTrace(); } } returnresponse; } @Override protectedvoid onPostExecute(String result) { //更新UI //这里的参数是doInBackground返回过来的 textView.setText(result); } } publicvoid readWebpage(View view) { //按钮的动作 DownloadWebPageTasktask = new DownloadWebPageTask(); task.execute(newString[] { "http://www.baidu.com"}); } } |
经验分享: 在使用AsyncTask中需要注意以下几点: 1)AsyncTask的实例必须在UIthread中创建。 2)AsyncTask.execute方法必须在UIthread中调用。 3)不要手动的调用onPreExecute(),onPostExecute(Result), doInBackground(Params...),onProgressUpdate(Progress...)这几个方法。 4)该task只能被执行一次,否则多次调用时将会出现异常。 |