1. Android's user interface thread
Android modifies the user interface and handles input events from one single user interface thread which is also called the main thread.
If the programmer does not use any concurrency constructs, all code of an Android application runs in this thread.
If you perform a long lasting operation, e.g. loading a file or accessing data from the Internet, the user interface of your Android Application will block until the corresonding code has finished.
To provide a good user experience all potentially slow running operations in an Android application should run asynchronously, e.g. via some way of concurrency constructs of the Java language or the Android framework. This includes all potential slow operations, like network, file and database access and complex calculations.
Android enforced that with an Application not responding (ANR) dialog if an activity does not react within 5 seconds to user input. From this dialog the user can choose to stop the application.
The following assumes that you have already basic knowledge in Android development. Please check the Android development tutorial to learn the basics.
Within Android development you should avoid performing long running operations on the UI thread. This includes file and network access.
But sometimes it is difficult to remember to make all the right decisions in your application during development.StrictMode can be used to test that your application performs long running applications in the background. It allows to setup policies in your application to avoid doing incorrect things. StrictMode should only be used during development and not in your live application.
If you running on an Android version smaller then Android 3.0 add the following two lines to your
onCreate()
method in your RssfeedActivity
class. On Android 3.0 or upwards this check will be done automatically, if you do not turn it off.StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder() .detectAll().penaltyLog().penaltyDeath().build()); StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder().detectAll() .penaltyLog().penaltyDeath().build());
Run your application with this setting. If it crashes you performing a long running operation in the main user interface thread.
Android supports the usage of the standard Java
Thread
class to perform asynchronous processing.
Android also supplies the
java.util.concurrent
package to perform something in the background, e.g. using theThreadPools
and Executor
classes.
If you need to update the user interface from a new
Thread
, you need to synchronize with the user interface thread.
You can use the
android.os.Handler
class or the AsyncTasks
class for this. These classes are specific to Android.
An instance of Handler class created in a scope of the main thread can update the user interface. For instance, if you create new instance of Handler class in onCreate() method of your Activity, this handler will be assigned to the main thread and therefore all runnable tasks posted to this handler can update the user interface. The
Handler
class provides methods for receiving instances of the Message
or Runnable
class.
To use a handler you have to subclass it and override the
handleMessage()
to process messages. To process aRunnable
you can use the post()
method. You only need one instance of a Handler
in your Activity
.
To avoid object creation you can also reuse the existing
Handler
object of your activity.// Reuse existing handler if you don't // have to override the message processing handler = getWindow().getDecorView().getHandler();
Also the
View
class allows to post objects of type Runnable
via the post()
method.
You thread can post messages via the
sendMessage(Message)
method or via the sendEmptyMessage()
method to the Handler
object.
The
AsyncTask
class encapsulates the creation of Threads and Handlers. An AsyncTask
is started via theexecute()
method.
The
execute()
method calls the doInBackground()
and the onPostExecute()
method.
The
doInBackground()
method contains the coding instruction which should be performed in a background thread. This method runs automatically in a separate Thread
.
The
onPostExecute()
method synchronize itself again with the user interface thread and allows to update it. This method is called by the framework once the doInBackground()
method finishes.
To use
AsyncTask
you must subclass it. AsyncTask uses generics and varargs. The parameters are the following AsyncTask <TypeOfVarArgParams , ProgressValue , ResultValue> .
TypeOfVarArgParams is passed into the
doInBackground()
method as input, ProgressValue is used for progress information and ResultValue must be returned from doInBackground()
method and is passed toonPostExecute()
as parameter.
If you are performing a long running operation it is good practice to provide feedback to the user. To block the user interface during the operation you can use the
ProgressBar
dialog, which allow to display progress to the user. The Javadoc of ProgressBar
gives a nice example of its usage.
A better solution is to provide progress feedback in the ActionBar for example via an
ActionView
.
One challenge in using threads is to consider the lifecycle of the application. The Android system may kill your activity or trigger a configuration change which also will restart your activity.
You also need to handle open dialogs, as dialogs are always connected to the activity which created them. In case the activity gets restarted and you access an existing dialog you receive an
View not attached to window manager
exception.
To save an object your can use the method
onRetainNonConfigurationInstance()
method. This method allows to save one object if the activity will be soon restarted.
To retrieve this object you can use the
getLastNonConfigurationInstance()
method. This way can you can save an object, e.g. a running thread, even if the activity is restarted.getLastNonConfigurationInstance()
returns null if the Activity
is started the first time or if it has been finished via the finish() method.onRetainNonConfigurationInstance()
is deprecated as of API 13, it is recommended that you useFragments and the setRetainInstance()
method to retain data over configuration changes.
If more than one object should be stored across activities and configuration changes, you can implement an
Application
class for your Android application.
To use your application class assign the classname to the
android:name
attribute of your application.<application android:icon="@drawable/icon" android:label="@string/app_name" android:name="MyApplicationClass"> <activity android:name=".ThreadsLifecycleActivity" android:label="@string/app_name"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application>
The application class is automatically created by the Android runtime and is available unless the whole application process is terminated.
This class can be used to access object which should be cross activities or available for the whole application lifecycle. In the
onCreate()
method you can create objects and make them available via public fields or getter
methods.
The
onTerminate()
method in the application class is only used for testing. If Android terminates the process of in which your application is running all allocated resources are automatically released.
You can acess the Application via the
getApplication()
method in your activity.
Loaders have been introduced in Android 3.0 and are part of the compatibility layer for older Android versions (from Android 1.6). They are available in the activity and the Fragment class.
Loaders allow to load data asynchronously, can monitor the source of the data and deliver new results when the content changes. They also persists between configuration changes.
You can use the abstract
AsyncTaskLoader
class as basis for own Loader implementations.
For a ContentProvider based on an SQLite database you would typically use the
CursorLoader
class. This Loaderperforms the cursor query in a background thread so that the application is not blocked. Loaders are the replacement for Activity-managed cursors which are deprecated now.
It is good practice that an activity which uses a Loader implements the
LoaderManager.LoaderCallbacks
interface directly.
The creation of a Loader via the
getLoaderManager().initLoader(0, null, this)
method call.
The third parameter of
initLoader()
is the class which is called once the initialization has been started (callback class). Typically the activity is used as callback class. The first parameter is a unique ID which can be used by the callback class to identify that Loader later. The second parameter is a bundle which can be given to the callback class for more information.
The
Loader
is not directly created by the getLoaderManager().initLoader()
method call, but must be created by the callback class in the onCreateLoader()
method.
Once the
Loader
has finished reading data asynchronously, the onLoadFinished()
method of the callback class is called. Here you can update your user interface.
If the
Cursor
becomes invalid, the onLoaderReset()
method is called on the callback class.
You can use Fragments without user interface and retain them between configuration changes via a call to their
setRetainInstance()
method.
This way your
Thread
or AsyncTask
is retained during configuration changes. This allows you to perform background processing without explicitly considering the lifecycle of your Activity
.
You can use the following Fragments Tutorial to learn how to use Fragments.
In this example we use the
Handler
class to update a ProgressBar
view in a background Thread
.
Create a new Android project called de.vogella.android.handler with the activity called ProgressTestActivity.
The layout file should be called
main.xml
. This layout contains the ProgressBar and sets its appearance via a style.<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <ProgressBar android:id="@+id/progressBar1" style="?android:attr/progressBarStyleHorizontal" android:layout_width="match_parent" android:layout_height="wrap_content" android:indeterminate="false" android:max="10" android:padding="4dip" > </ProgressBar> <Button android:id="@+id/button1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:onClick="startProgress" android:text="Start Progress" > </Button> </LinearLayout>
Change your activity to the following code.
import android.app.Activity; import android.os.Bundle; import android.os.Handler; import android.view.View; import android.widget.ProgressBar; import android.widget.TextView; public class ProgressTestActivity extends Activity { private Handler handler; private ProgressBar progress; private TextView text;/** Called when the activity is first created. */@Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); progress = (ProgressBar) findViewById(R.id.progressBar1); text = (TextView) findViewById(R.id.textView1); } public void startProgress(View view) { // Do something long Runnable runnable = new Runnable() { @Override public void run() { for (int i = 0; i <= 10; i++) { final int value = i; try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } progress.post(new Runnable() { @Override public void run() { text.setText("Updating"); progress.setProgress(value); } }); } } }; new Thread(runnable).start(); } }
Run your application. Once you press your button the ProgressBar will get updated from the background thread.
In this example we will use an instance of the
AsyncTask
class to download the content of a webpage. We useAndroid HttpClient for this. Create a new Android project called de.vogella.android.asynctask with an Activity
called ReadWebpageAsyncTask. Add the android.permission.INTERNET
permission to yourAndroidManifest.xml
file.
Create the following layout.
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns: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="Load Webpage" > </Button> <TextView android:id="@+id/TextView01" android:layout_width="match_parent" android:layout_height="match_parent" android:text="Example Text" > </TextView> </LinearLayout>
Change your activity to the following:
import java.io.BufferedReader; import java.io.InputStream; import java.io.InputStreamReader; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.impl.client.DefaultHttpClient; import android.app.Activity; import android.os.AsyncTask; import android.os.Bundle; import android.view.View; import android.widget.TextView; public class ReadWebpageAsyncTask extends Activity { private TextView textView;/** Called when the activity is first created. */@Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); textView = (TextView) findViewById(R.id.TextView01); } private class DownloadWebPageTask extends AsyncTask<String, Void, String> { @Override protected String doInBackground(String... urls) { String response = ""; for (String url : urls) { DefaultHttpClient client = new DefaultHttpClient(); HttpGet httpGet = new HttpGet(url); try { HttpResponse execute = client.execute(httpGet); InputStream content = execute.getEntity().getContent(); BufferedReader buffer = new BufferedReader(new InputStreamReader(content)); String s = ""; while ((s = buffer.readLine()) != null) { response += s; } } catch (Exception e) { e.printStackTrace(); } } return response; } @Override protected void onPostExecute(String result) { textView.setText(result); } } public void readWebpage(View view) { DownloadWebPageTask task = new DownloadWebPageTask(); task.execute(new String[] { "http://www.vogella.com" }); } }
If you run your application and press your button then the content of the defined webpage should be read in the background. Once this process is done your
TextView
will be updated.
The following example will download an image from the Internet in a thread and displays a dialog until the download is done. We will make sure that the thread is preserved even if the activity is restarted and that the dialog is correctly displayed and closed.
For this example create a new Android project called de.vogella.android.threadslifecycle with the Activity calledThreadsLifecycleActivity. Also add the permission to use the Internet to your
AndroidManifest.xml
file.
Your
AndroidManifest.xml
file should look like the following.<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="de.vogella.android.threadslifecycle" android:versionCode="1" android:versionName="1.0" > <uses-sdk android:minSdkVersion="10" /> <uses-permission android:name="android.permission.INTERNET" > </uses-permission> <application android:icon="@drawable/icon" android:label="@string/app_name" > <activity android:name=".ThreadsLifecycleActivity" android:label="@string/app_name" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest>
Change the layout
main.xml
to the following.<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <LinearLayout android:id="@+id/linearLayout1" android:layout_width="match_parent" android:layout_height="wrap_content" > <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:onClick="downloadPicture" android:text="Click to start download" > </Button> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:onClick="resetPicture" android:text="Reset Picture" > </Button> </LinearLayout> <ImageView android:id="@+id/imageView1" android:layout_width="match_parent" android:layout_height="match_parent" android:src="@drawable/icon" > </ImageView> </LinearLayout>
Now adjust your activity. In this activity the thread is saved and the dialog is closed if the activity is destroyed.
import java.io.IOException; import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import org.apache.http.StatusLine; import org.apache.http.client.HttpClient; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpUriRequest; import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.util.EntityUtils; import android.app.Activity; import android.app.ProgressDialog; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.view.View; import android.widget.ImageView; public class ThreadsLifecycleActivity extends Activity { // Static so that the thread access the latest attribute private static ProgressDialog dialog; private static Bitmap downloadBitmap; private static Handler handler; private ImageView imageView; private Thread downloadThread;/** Called when the activity is first created. */@Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); // Create a handler to update the UI handler = new Handler() { @Override public void handleMessage(Message msg) { imageView.setImageBitmap(downloadBitmap); dialog.dismiss(); } }; // get the latest imageView after restart of the application imageView = (ImageView) findViewById(R.id.imageView1); Context context = imageView.getContext(); System.out.println(context); // Did we already download the image? if (downloadBitmap != null) { imageView.setImageBitmap(downloadBitmap); } // Check if the thread is already running downloadThread = (Thread) getLastNonConfigurationInstance(); if (downloadThread != null && downloadThread.isAlive()) { dialog = ProgressDialog.show(this, "Download", "downloading"); } } public void resetPicture(View view) { if (downloadBitmap != null) { downloadBitmap = null; } imageView.setImageResource(R.drawable.icon); } public void downloadPicture(View view) { dialog = ProgressDialog.show(this, "Download", "downloading"); downloadThread = new MyThread(); downloadThread.start(); } // Save the thread @Override public Object onRetainNonConfigurationInstance() { return downloadThread; } // dismiss dialog if activity is destroyed @Override protected void onDestroy() { if (dialog != null && dialog.isShowing()) { dialog.dismiss(); dialog = null; } super.onDestroy(); } // Utiliy method to download image from the internet static private Bitmap downloadBitmap(String url) throws IOException { HttpUriRequest request = new HttpGet(url.toString()); HttpClient httpClient = new DefaultHttpClient(); HttpResponse response = httpClient.execute(request); StatusLine statusLine = response.getStatusLine(); int statusCode = statusLine.getStatusCode(); if (statusCode == 200) { HttpEntity entity = response.getEntity(); byte[] bytes = EntityUtils.toByteArray(entity); Bitmap bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length); return bitmap; } else { throw new IOException("Download failed, HTTP response code " + statusCode + " - " + statusLine.getReasonPhrase()); } } static public class MyThread extends Thread { @Override public void run() { try { // Simulate a slow network try { new Thread().sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } downloadBitmap = downloadBitmap("http://www.devoxx.com/download/attachments/4751369/DV11"); // Updates the user interface handler.sendEmptyMessage(0); } catch (IOException e) { e.printStackTrace(); } finally { } } } }
Run your application and press the button to start a download. You can test the correct lifecycle behavior by changing the orientation in the emulator via the Ctrl+F11 shortcut.
It is important to note that the
Thread
is a static inner class. It is important to use a static inner class for your background process because otherwise the inner class will contain a reference to the class in which is was created. As the thread is passed to the new instance of your activity this would create a memory leak as the old activity would still be referred to by the Thread.
0 comments:
Post a Comment