Android Applications Tutorial 22. Threads and AsyncTask

Android 专栏收录该内容
130 篇文章 0 订阅
Android Applications Tutorial
22. Threads and AsyncTask





22.0 Threads


We want our Android application to be downright snappy. End users are used to responsive apps on the mobile and any small delay is perceived as un-responsiveness or worse they may think the application has hung. Responding to user input quickly typically within in 200 milliseconds should be our goal. At least, we need to make sure we respond within less than 5 seconds. If a main thread is blocked for more than 5 seconds the user is presented with the infamous application not responding (ANR) dialog.

So, what is a thread? 
Whenever we start an Android application, a thread called main is automatically created. The main thread, also called the UI thread, is very important because it is in charge of dispatching the events to the appropriate widgets and this includes the drawing events. It is also the thread we interact with Android widgets on. For instance, if you touch the a button on screen, the UI thread dispatches the touch event to the widget which in turn sets its pressed state and posts an invalidate request to the event queue. The UI thread dequeues the request and notifies the widget to redraw itself.

If you want to see how bad this can look, write a  simple application  with a button that invokes  Thread.sleep(5000)  in its  OnClickListener . The button will remain in its pressed state for about 5 seconds

The main Java code looks like this:

package com.bogotobogo.singlethread;

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;

public class SingleThread extends Activity {
	
	/** Called when the activity is first created. */
	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.main);
		Button start = (Button) findViewById(R.id.Button01);
		start.setOnClickListener(new OnClickListener() {
			@Override
			public void onClick(View view) {
				try {
					Thread.sleep(5000);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		});
	}
}


When this happens, it is very easy for the user to perceive the application as slow.

One of the basic principles to provide a very responsive application is to handle any time consuming code in a separate thread so that we can avoid lengthy operations on the UI thread. We must use extra threads (background or worker threads) to perform these operations.

Android provides a few means to set up background threads, yet allow them to safely interact with the UI on the UI thread.

The safely interact is crucial. We cannot modify any part of the UI from a background thread. It must be done on the UI thread. This generally means that there will need to be some coordination between background threads doing the work and the UI threadshowing the results of that work.

Let's take the example of a click listener downloading an image over the network and displaying it in an ImageView:

	public void onClick(View v) {
  		new Thread(new Runnable() {
    			public void run() {
      				Bitmap b = loadImageFromNetwork();
      				mImageView.setImageBitmap(b);
    			}
  		}).start();
	}

At first, this code seems to be a good solution to your problem, as it does not block the UI thread. Unfortunately, it violates the single thread model: the Android UI toolkit is not thread-safe and must always be manipulated on the UI thread. In this piece of code, the ImageView is manipulated on a worker thread, which can cause really weird problems. Tracking down and fixing such bugs can be difficult and time-consuming.

Android offers several ways to access the UI thread from other threads. You may already be familiar with some of them but here is a comprehensive list:

  • Activity.runOnUiThread(Runnable)
  • View.post(Runnable)
  • View.postDelayed(Runnable, long)
  • Handler

The most flexible ways of making an Android-friendly background thread is to create an instance of a Handler subclass. We need only one object per activity, and we do not need to manually register it. Just creating the instance is sufficient to register it with the Android threading subsystem.

So, it is very essential to understand about how to create new threads (worker or background threads) and how to come back to the parent thread.



22.1 Threads Using Handler - Example 1

How do the two threads, parent/UI and the worker threads, communicate? Via the Handler.

A Handler allows us to send and process Message and Runnable objects associated with a thread's MessageQueue. Each Handler instance is associated with a single thread and that thread's message queue.

So, let us take the handler from the main thread and see how we can use it to communicate with a child thread.

When a handler is created, it is associated by default with the current thread. So, we have this piece of code in the main activity class:

public class HandlerThread extends Activity{
   	
	private ProgressDialog progressDialog;
	/** Called when the activity is first created. */
	@Override
	public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        ((Button) findViewById(R.id.Button01))
        .setOnClickListener(new OnClickListener() {
        	@Override
			public void onClick(View view) {
				DoSomething();
			}      	
        });
	}

	private Handler messageHandler = new Handler() {
		
		public void handleMessage(Message msg) {
			super.handleMessage(msg);
			progressDialog.dismiss();
		}
	};
}

Now, on click of the button, the DoSomething() method is invoked. Assuming that this is a very time consuming task, we're creating a thread and do the task and return from that thread as shown in the code below:

	protected void DoSomething() {
		// TODO Auto-generated method stub
		progressDialog = 
				ProgressDialog.show(this, "", "Doing something...");
		new Thread() {
			public void run() {
				try {
					Thread.sleep(5000);
				} 
				catch (InterruptedException e) {
				}
				messageHandler.sendEmptyMessage(0);
			}
		}.start();
	}

Since it is time consuming, we're starting a ProgressDialog just to inform the end user that some activity is happening. Then, we start the thread, make it sleep for 5000 milliseconds and send back an empty message through the message handler. ThemessageHandler.sendEmptyMessage(0) is the callback on the parent thread�s messageHandler to inform that the child thread has finished its work. In this example, we're sending an empty message. But this can be the means of communication and exchange of data from the child thread to the parent Thread.

Now the control returns to handleMessage() call back method. That method shown in the beginning just dismisses the progressDialog.


 

Files used in this thread via handler example, HandlerThread.zip 


22.2 Threads Using Handler - Example 2

Here is another similar example with a Java code:

package com.bogotobogo.threadb;

import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.widget.ProgressBar;

public class HandlerThreadB extends Activity {
	ProgressBar bar;
	
	@Override
	public void onCreate(Bundle icicle) {
		super.onCreate(icicle);
		setContentView(R.layout.main);
		bar=(ProgressBar)findViewById(R.id.progress);
	}
	
	public void onStart() {
		super.onStart();
		bar.setProgress(0);
		
		new Thread(new Runnable() {
			public void run() {
				try {
					for (int i=0;i<50;i++) {
						Thread.sleep(1000);
						handler.sendEmptyMessage(0);
					}
				}
				catch (Throwable t) {
				}
			}
		}).start();
	}

	Handler handler=new Handler() {
		@Override
		public void handleMessage(Message msg) {
			bar.incrementProgressBy(2);
		}
	};
}

We create an instance of Handler and it has implementation handleMessage(). Actually for any message received, we update the ProgressBar by 2 poins, and then exit the message handler.

In onStart(), we set up a background thread. This thread does something.


 

Files used in this thread via handler example B, HandlerThreadB.zip 



22.3 AsyncTask

AsyncTask enables proper and easy use of the UI thread. This class allows to perform background operations and publish results on the UI thread without having to manipulate threads and/or handlers. In other words, Android will handle all of the chores of doing work on the UI thread not on a background thread. Also, Android itself allocates and removes that background thread.

An asynchronous task is defined by a computation that runs on a background thread and whose result is published on the UI thread.

An asynchronous task is defined by 3 generic types, called ParamsProgress and Result, and 4 steps, called begin,doInBackgroundprocessProgress and end.


AsyncTask's generic types

The detail information about the three types used by an asynchronous task are the following:

  • Params the type of the parameters sent to the task upon execution.
  • Progress the type of the progress units published during the background computation.
  • Result the type of the result of the background computation.

Not all types are always used by an asynchronous task. To mark a type as unused, simply use the type Void:

 private class MyTask extends AsyncTask { ... }


The 4 steps

When an asynchronous task is executed, the task goes through 4 steps:

  • onPreExecute(), invoked on the UI thread immediately after the task is executed. This step is normally used to setup the task, for instance by showing a progress bar in the user interface.
  • doInBackground(Params...), , invoked on the background thread immediately after onPreExecute(), finishes executing. This step is used to perform background computation that can take a long time. The parameters of the asynchronous task are passed to this step. The result of the computation must be returned by this step and will be passed back to the last step. This step can also use publishProgress(Progress...), to publish one or more units of progress. These values are published on the UI thread, in the onProgressUpdate(Progress...), step.
  • onProgressUpdate(Progress...), , invoked on the UI thread after a call to publishProgress(Progress...), . The timing of the execution is undefined. This method is used to display any form of progress in the user interface while the background computation is still executing. For instance, it can be used to animate a progress bar or show logs in a text field.
  • onPostExecute(Result), , invoked on the UI thread after the background computation finishes. The result of the background computation is passed to this step as a parameter.


Threading Rules

There are a few threading rules that must be followed for this class to work properly:

  • The task instance must be created on the UI thread.
  • Do not call onPreExecute()onPostExecute(Result)doInBackground(Params...),onProgressUpdate(Progress...) manually.
  • The task can be executed only once (an exception will be thrown if a second execution is attempted.)

AsyncTask Example

This example is an implementation of a ListActivity that uses an AsyncTask.

In this example, rather than simply pass the list of Java book to an ArrayAdapter, we simulate needing to work to create these list in the background using AddStringTask. Our AsyncTask, the AddStringTask a sublass of AsyncTask, will override at least one method doInBackground(Params...), and most often will override a second one onPostExecute(Result).


Let's look at our code,AsyncThrea.java:

package com.bogotobogo.threadc;

import android.app.ListActivity;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.SystemClock;
import android.widget.ArrayAdapter;
import android.widget.Toast;
import java.util.ArrayList;

public class AsyncThread extends ListActivity {
	private static String[] items={
		"Hello, Android: Introducing Google's Mobile Development Platform ", 
		"Professional Android 2 Application Development ", 
		"Unlocking Android: A Developer's Guide",
		"Android Application Development: Programming with the Google SDK", 
		"Pro Android 2", 
		"Beginning Android 2",
		"Android Programming Tutorials, 2nd Edition", 
		"Android Wireless Application Development", 
		"Pro Android Games",
		"Beginning Smartphone Web Development", 
		"The Busy Coder's Guide to Advanced Android Development", 
		"Head First Java, 2nd Edition",
		"Effective Java (2nd Edition)",
		"Sams Teach Yourself Java in 24 Hours (5th Edition)",
		"Core Java(TM), Volume I--Fundamentals (8th Edition)",
		"Java In A Nutshell, 5th Edition",
		"Thinking in Java (4th Edition)",
		"Java Concurrency in Practice",
		"SCJP Sun Certified Programmer for Java 6 Exam 310-065",
		"Java How to Program, 7th Edition",
		"Learning Java",
		"Head First Design Patterns",
		"Beginning Java� EE 6 Platform with GlassFish� 3",
		"Head First Servlets and JSP",
		"Java Message Service",
		"Core Java(TM), Volume I--Fundamentals (8th Edition)",
		"Beginning Programming with Java For Dummies"
	};
	
	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.main);

		setListAdapter(new ArrayAdapter(this,
				android.R.layout.simple_list_item_1,
					new ArrayList()));
		new AddStringTask().execute();
	}
	
	class AddStringTask extends AsyncTask {
		@Override
		protected Void doInBackground(Void... unused) {
			for (String item : items) {
				publishProgress(item);
				SystemClock.sleep(200);
			}
			return(null);
		}
		
		@Override
		protected void onProgressUpdate(String... item) {
			((ArrayAdapter)getListAdapter()).add(item[0]);
		}
		
		@Override
		protected void onPostExecute(Void unused) {
			Toast.makeText(AsyncThread.this, 
				"Done - Finished updating Java Book List!",
				Toast.LENGTH_SHORT).show();
		}
	}
}

Let's look at the AddStringTask:

	class AddStringTask extends AsyncTask {

We want to pass each string generated by our background task to onProgressUpdate() because we want to add it to our list. So, our second type should be String while others are void types.

The doInBackground() method:

	@Override
	protected Void doInBackground(Void... unused) {
		for (String item : items) {
			publishProgress(item);
			SystemClock.sleep(200);
		}
		return(null);
	}

This method is invoked in a background thread. Here, we iterate over our list of books, the call publishProgress(item) for each item. Then, make it sleep 1/4 second to simulate real work being done.

onProgressUpdate() method:

	@Override
	protected void onProgressUpdate(String... item) {
		((ArrayAdapter)getListAdapter()).add(item[0]);
	}

This method is called on the UI thread, and we want to do something to let the user know we are making progress on loading these strings. In this case, we simply add the string to the ArrayAdapter, so it is appended to the end of the list.

onProgressUpdate(String... ) receives a String... varargs because that is the second data type in our class declaration. Since we are passing only one string per call to onProgressUpdate(), we need to examine just first entry in the varargs array.

The onPostExecute() method is called on the UI thread, and we want to do something to indicate that the background work is complete. Here we simply raise a Toast.

The last thing we look at is :

	new AddStringTask().execute();

To use AddStringTask(), we just create an instance and call execute() on it. That starts the chain of events eventually leading to the background thread doing its work.

Time to run our application.



 

Files used in this thread via handler example B, ThreadAsync.zip 

  • 0
    点赞
  • 0
    评论
  • 0
    收藏
  • 打赏
    打赏
  • 扫一扫,分享海报

参与评论 您还未登录,请先 登录 后发表或查看评论
©️2022 CSDN 皮肤主题:编程工作室 设计师:CSDN官方博客 返回首页

打赏作者

小涵

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值