Responding to user’s input as fast as possible is essential for a good user experience.
Android uses an UI thread, which is also the main thread of an app to render UI.
By default, all the functions exposed to the programmer are called by the UI thread.
Android OS guards on the UI thread, and if the app takes too much of time for input or broadcast event, Android Not Responding dialog will shown up, with which user can choose to wait for the app to respond, or close the app.
Time consuming jobs such as big data processing, database or network accessing that run in UI thread may cause an ANR error (application not responding).
Please note that Android forbids the execution of network accessing code in an UI thread.
As shown below, if we try to access a web page with HttpClient, the execution of DefaultHttpClient().execute() will be terminated with a NetworkOnMainThreadException exception.
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); this.findViewById(R.id.button1).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { // TODO Auto-generated method stub try { new DefaultHttpClient().execute(new HttpGet("http://www.google.com")); } catch (ClientProtocolException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (NetworkOnMainThreadException e) { //Android will reject the network accessing by throwing an exception Log.i("E", "Access network on the main/UI thread!!"); } } }); }
The best solution to avoid ANR (Application not responding) error is using a worker thread running parallel with UI thread.
Besides the standard java thread, android provides the AsyncTask class for easy thread control.
The following are the four AsyncTask functions that can be used:
- onPreExecute – This is called before the execution.
- onPostExecute – This is called after the execution.
- onProgressUpdate – UI update should only be done in this function. When and how this function will be triggered is defined by the programmer. Need to notice that, even onProgressUpdate can access and update the UI component, as Activity would be killed and recreated on screen rotation case, it not safe to continue the update after that. One common solution is save the state and stop the AsyncTask, then restart the AsyncTask to continue its job.
- doInBackground – This is the main working function. If progress update is necessary, the publishProgress can be called, which will finally leads to the calling of onProgressUpdate. This is very important as doInBackground is working on different thread with UI thread, any cross thread accessing is not allowed. isCancelled() can be used to test the state of thread, so as to do the job properly.
The following code is designed to broadcast a dummy package through port 0x4b4b, and put address that responds to the broadcast into a list component.
AsyncTask is a template class which takes three type arguments: the first is the type for argument of doInBackground; the second is the type for the argument of onProgressUpdate; the third is the type for the argument onPostExecute. These three functions each have an input argument of vargars type.
private class GetAddressTask extends AsyncTask<Void, String, Void> { @Override protected void onProgressUpdate(String... values) { // TODO Auto-generated method stub if (!isCancelled()) { // Display current found address in a TextView TextView txt = (TextView) findViewById(R.id.serverTextView); String msg = new String(); msg = String.format("%s%s", getString(R.string.server_found), values[0]); txt.setText(msg); // Append all found address in a listView listItems.add(values[0]); adapter.notifyDataSetChanged(); } super.onProgressUpdate(values); } @Override protected Void doInBackground(Void... params) { // TODO Auto-generated method stub try { DatagramSocket s = new DatagramSocket(); byte data[] = new byte[256]; InetAddress broadAddr = InetAddress.getByName(getBroadcast()); DatagramPacket dp = new DatagramPacket(data, 0, broadAddr, 0x4b4b); s.setBroadcast(true); s.send(dp); s.setSoTimeout(1000); while(!isCancelled()) { try { s.receive(dp); } catch (SocketTimeoutException e) { continue; } publishProgress(dp.getAddress().toString().substring(1)); } s.close(); } catch (Exception e){ Log.w("DEBUG", e.getMessage()); } return null; } @Override protected void onPostExecute(Void result) { // TODO Auto-generated method stub super.onPostExecute(result); } @Override protected void onPreExecute() { // TODO Auto-generated method stub super.onPreExecute(); } private String getBroadcast() throws SocketException { //Get the broadcast address } }