Android-Programming 2
Thread and Networking
Threads, AsyncTasks & Handler
Java thread
Java Thread: a java thread is represented by an object of type java.lang.thread. Java threads implement the runnable interface, it must implement void run().
Refer: docs.oracle.com/javase/tutorial/essential/concurrency/threads.html
Some thread methods:
void start() // start the thread void sleep (long time) // sleeps for the given period
some object methods:
void wait() // current thread wait until another thread invoke notify() on this object void notify() // wakes up a single thread that is waiting on this object
basic thread use case
-> instantiate a thread object
-> invoke the thread’s start() method --> eventually leads to thread’s run() get called
-> thread terminates when run() returns
UI thread
UI thread: all android applications have a main thread (the UI thread). Application components in the same process use the same UI thread.
User Interaction, system callbacks and lifecycle methods are handled in the UI thread.
UI toolkit is not thread-safe. --> blocking the uI thread hurts application responsiveness, long-running operations should run in background threads.
It is wrong to spawn a separate thread to do time-consuming operation, the application will crash because Android does non UI thread to access the UI toolkit.
Correct solution: do work in a background thread, but update UI in the UI thread.
Background thread is responsible for:
- performs long-running work;
- indicates progress;
UI thread is responsible for:
- does the initial setup;
- publish intermediate progress that the background thread should report;
- complete the operation;
-> Android provide several methods that are guaranteed to run in the UI thread:
boolean View.post(Runnable action) void Activity.runOnUiThread(Runnable action) // the Runnable parameter contains the code that updates the display
e.g.
private void loadIcon() { new Thread(new Runnable() { //create a new thread @Override public void run() { /*time-consuming operations, such as load a bitmap*/ … // call View.post() mImageView.post(new Runnable() { // update the UI … }); } }); }
AsyncTask
AsyncTask: a generic class that provides a structured way to manage work involving background and UI threads.
Refer: developer.android.com/reference/android/os/AsyncTask.html
AsyncTasks should ideally be used for short operations (a few seconds at the most.)
class AsyncTask<Params, Progress, Result> { … }
Params: type used in background work;
Progress: type used when indicating progress;
Result: type of the result commuted by AsyncTasl.
Workflow of AsyncTask:
void onPreExecute(): runs in UI thread before doInBackground(), usually set up the long running operation
doInBackground(Params …params): performs work in background thread, takes a variable list of input parameters, return a result of type Result.
void publishProgress(Progress… values): may be optionally called while doInBackground() is running. Parameters provides indications of the long-running operation’s progress.
void onProgressUpdate(Progress… values): invoked in UI thread if background thread calls publishProgress.
void onPostExecute(Result result): invoked in UI thread after doInBackground() on background thread returns result as its parameter.
e.g.
button.setOnClickListener(new OnClickListener() { public void onClick(View v) { new LoadIconTask().execute(R.drawable.painter); } }); class LoadIconTask extends AsyncTask<Integer, Integer, Bitmap> { /* type of params, progress, result */ @Override protected void onPreExecute() { // runs on UI thread mProgressBar.setVisibility(ProgressBar.VISIBLE); } @Override protected Bitmap doInBackground(Integer… resId) { // simulate a long operation work, such as loading a bitmap Bitmap tmp = BitmapFactory.decodeResource(getResources(), resId[0]); for (int i=1; i<11; i++) { sleep(); publishProgress(i*10); /*passing a integer which represents the percentage of the progress */ } return tmp; } // runs on UI thread, receives integer passed in PublishProgress @Override protected void onProgressUpdate(Integer… values) { mProgressBar.setProgress(values[0]); } @Override protected void onPostExecute(Bitmap result) { //runs on UI thread mProgressBar.setVisibility(ProgressBar.INVISIBLE); mImageView.setImageBitmap(result); } … }
Handler
Runnable: used when the sender knows exactly what work steps it wants performed, but it wants that work performed on the handler thread.
Message: can contain data such as message code, object and integer arguments, used to indicate an operation that should be done in another thread, but the implementation of that operation is left to the handler.
handler: Each handler is associated with a specific thread, one thread can hand off work to another thread by sending Messages or posting Runnables to a handler associated with the other thread.
handler.post(Runnable r): called by a background thread, to add a Runnable to the messageQueue of the thread associated with the handler.
handler.postAtTime(Runnable r, long uptimeMillis): to add a Runnable to the messageQueue, but run at a specific time.
handler.postDelayed (Runnable r, long uptimeMillis): to add a Runnable to the messageQueue, but run after a specified amount of time elapses.
Handler.obtainMessage(): to create a message. After creating a message, call Message.obtain() to set the content for the message.
handler.sendMessage(): called by a background thread, to add a Message to the messageQueue of the thread associate with the handler.
Handler.sendMessageAtFrontQueue(): to insert a Message at the front of the messageQueue to have it execute as soon as possible.
Handler.sendMessageAtTime(): to queue a message according to a specified time.
Handler.sendMessageDelayed(): to queue a message at the current time plus a specified delay.
Handler.handleMessage(): called by the looper, passing in a Message in the messageQueue,, to dispatch the Message. executed in the thread in which the handler was created or associated with.
Each Android thread is associated with a message queue and a looper.
MessageQueue: holds Messages and Runnables .
Looper: takes Messages and Runnables off the messageQueue and dispatch them. To dispatch a Message, Looper calls the handler’s.handleMessage() passing in the Message; To dispatch a Runnable, it calls the runnable’s run() method.
e.g. usage of handler with Runnable
public class HandlerRunnableActivity extends Activity { … private final Handler handler = new Handler(); /* the handler will be created by the main UI thread */ @Override public void onCreate(Bundle saveInstanceState) { … button.setOnClickListener(new OnClickListener() { public void onClick(View v) { /* the button will start a new thread, whose run method is defined by a Runnable */ new Thread(new LoadIconTask(R.drawable.painter)).start(); } }); } private class LoadIconTask implements Runnable { public void run() { /* post a new Runnable that when executed will make the progress bar visible */ handler.post (new Runnable() { @Override public void run() { mProgressBar.setVisibility(ProgressBar.VISIBIE);} }); mBitmap = BitmapFactory.decodeResource(getResources(), resId); for (int i=1; i<11; i++) { sleep(); final int step = i; handler.post (new Runnable() { @Override public void run() { mProgressBar.setProgress(step*10);} }); } handler.post(new Runnable() { @Override public void run() {mImageView.setImageBitmap(mBitmap);} }); handler.post(new Runnable() { @Override public void run() { mProgressBar.setVisibility(ProgressBar.INVISIBLE}; }); } } }
e.g. usage of handler with Message
public class HandlerMessageActivity extends Activity { … Handler handler = new Handler() { /*the work this handler performs will be executed in UI thread */ @Override public void handleMessage(Message msg) { switch (msg.what) { case SET_PROGRESS_BAR_VISIBILITY: mProgressBar.setVisibility((Integer) msg.obj); break; case PROGRESS_UPDATE: mProgressBar.setProgress((Integer) msg.obj); break; case SET_BITMAP: mImageView.setImageBitmap((Bitmap) msg.obj); break; } } } @Override public void onCreate(Bundle savedInstanceState) { … button.setOnClickListener(new OnClickListener() { public void onClick(View v) { new Thread(new LoadIconTask(R.drawable.painter, handler)).start(); } }); } private class LoadIconTask implements Runnable { public void run() { Message msg = null; msg = handler.obtainMessage(SET_PROGRESS_BAR_VISIBILITY, ProgressBar.VISIBLE); handler.sendMessage(msg); final Bitmap tmp = BitmapFactory.decodeResource(getResources(), resId); for (int i=1; i<11; i++) { sleep(); msg = handler.obtainMessage(PROGRESS_UPDATE, i*10); handler.sendMessage(msg); } msg = handler.obtainMessage(SET_BITMAP, tmp); handler.sendMessage(msg); msg = handler.obtainMessage(SET_PROGRESS_BAR_VISIBILITY, ProgressBar.INVISIBLE); handler.sendMessage(msg); } } }
Networking
Android provides multiple networking support classes, e.g.
- in java.net: socket, URL;
- in org.apache: HttpRequest, HttpResponse;
- in android.net: URI, AndroidHttpClient, AudioStream;
AndroidHttpClient has large-size API thus it is difficult to improve without breaking compatibility. The Android team is not actively working on Apache HTTP Client.
HttpUrlConnection is the preferred HTTP client in the future. refer: https://android-developers.googleblog.com/2011/09/androids-http-clients.html
socket
e.g. issue an HTTP GET request to an external server, the server will respond with some complex text containing the requested earthquake data.
//press the button to execute an async task: HttpGetTask public void on Click(View v) { new HttpGetTask().execute(); }
private class HttpGetTask extends AsyncTask<Void, Void, String> { /*variables used in creating an HTTP get request: HOST, USER_NAME, HTTP_GET_COMMAND, TAG.*/ … //When the execute() on the async task is called, doInBackground is invoked @Override protected String doInBackground(Void… params) { Socket socket = null; String data = “”; try { /*create a new socket that will be connected to the host computer on the standard http port (80)*/ socket = new Socket(HOST, 80); /*gets the socket output stream, writes the http get command; The host computer will interpret the http get request, and send back response data. */ PrintWriter pw = new PrintWriter(new OutputStreamWriter(socket.getOutputStream()), true); pw.println(HTTP_GET_COMMAND); data = readStream(socket.getInputStream()); } catch (UnknownHostException exception) { exception.printStackTrace(); } catch (IOException exception) { exception.printStackTrace(); } finally { if (null != socket) try { socket.close(); } catch (IOException e) { Log.e(TAG, “IOException”); } } return data; } /*after readStream(), the respond data string will be passed to onPostExecute(), which executes on main thread*/ @Override protected void onPostExecute(String result) { mTextView.setText(result); } //readStream will read the respond data from socket input stream, return the response as a single string private String readStream(InputStream in) { BufferedReader reader = null; StringBuffer data = new StringBuffer(); try { reader = new BufferedReader(new InputStreamReader(in)); String line = “”; while ((line = reader.readLine()) != null) data.append(line); } catch (IOException e) { Log.e(TAG, “IOException”); } finally { if (reader != null) { try { reader.close(); } catch (IOException e) { Log.e(TAG, “IOException”): } } } return data.toString(); } }
HttpURLConnection – higher level than sockets, but has less flexible API than HttpAndroidClient
e.g. sending HTTP request using HttpURLConnection
/*HttpURLConnection will strip off the HTTP response header and handles the error checking.*/ private class HttpGetTask extends AsyncTask<Void, Void, String> { … @Override protected String doInBackground(Void… params) { String data = “”; HttpURLConnection httpUrlConnection = null; try { /*create a new URL object, passing in a URL string for the desired server as parameter. Then call openConnection() on URL object to get a httpUrlConnection. */ httpUrlConnection = (HttpURLConnection) new URL(URL).openConnection(); // get the HttpURLConnection’s input stream InputStream in = new BufferedInputStream(httpUrlConnection.getInputStream()); Data = readStream(in); } } /* same as the previous example, readStream() will read the response data and return it as string, then the string will be passed to onPostExecute(). */ … }
AndroidHttpClient
AndroidHttpClient: An implementation of Apache’s DefaultHttpClient. This class breaks HTTP transaction into separate request and response objects, so subclasses can be created to customize the handling of requests and responses.
e.g.
private class HttpGetTask extends AsyncTask<Void, Void, String> { … AndroidHttpClient mClient = AndroidHttpClient.newInstance(“”); @Override protected String doInBackground(Void… params) { //pass in URL string HttpGet request = new HttpGet(URL); //ResponseHandler is for handling the response to http get request ResponseHandler<String> responseHandler = new BasicResponseHandler(); try { /* execute() will send the request, get the resonse. The result will be passed to onPostExecute(). */ return mClient.execute(request, responseHandler); } catch (ClientProtocolException exception) { exception.printStackTrace(); } catch (IOException exception) { exception.printStackTrace(); } return null; } @Override protected void onPostExecute(String result) { if (null != mClient) mClient.close(); mTextView.setText(result); } … }
JSON
Javascript Object Notation (JSON): a lightweight data interchange format, data packaged in 2 types of structures: 1) maps of key/value pairs; 2) ordered lists of values.
Refer to: www.json.org
e.g. get requested data in JSON and present it in list view
public class NetworkingAndroidHttpClientJSONActivity extends ListActivity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); new HttpGetTask().execute(); } private class HttpGetTask extends AsyncTask<Void, Void, List<String>> { ... AndroidHttpClient mClient = AndroidHttpClient.newInstance(""); @Override protected List<String> doInBackground(Void... params) { HttpGet request = new HttpGet(URL); // use JSONResponseHandler to process the response JSONResponseHandler responseHandler = new JSONResponseHandler(); } @Override protected void onPostExecute(List<String> result) { //called when doInBackground() finishes if (null != mClient) mClient.close(); //create and set a list adapter for the list view setListAdapter(new ArrayAdapter<String>( NetworkingAndroidHttpClientJSONActivity.this, R.layout.list_item, result)); } } private class JSONResponseHandler implements ResponseHandler<List<String>> { ... @Override public List<String> handleResponse(HttpResponse response) throws ClientProtocolException, IOException { List<String> result = new ArrayList<String>(); /*pass in raw response, BasicResponseHandler will return a response body without header*/ String JSONResponse = new BasicResponseHandler().handleResponse(response); try { /* JSONTokener parses the JSONResponse into a top-level JSON object -- a map.*/ JSONObject responseObject = (JSONObject) new JSONTokener(JSONResponse).nextValue(); // pass in string key to extract value JSONArray earthquakes = responseObject.getJSONArray(EARTHQAKE_TAG); for (int idx=0; idx<earthquakes.length(); idx++) { //get a single data -- a map JSONObject earthquake = (JSONObject) earthquakes.get(idx); // summarize the data as a string and add to result result.add(MAGNITUDE_TAG+":"+earthquake.get(MAGNITUDE_TAG)+"," +LATITUDE_TAG+":"+earthquake.getString(LATITUDE_TAG)+"," +LONGTITUDE_TAG+":"+earthquake.get(LONGTITUDE_TAG)); } } catch (JSONException e) { e.printStackTrace(); } return result; } }
...
}
XML
Extensible Markup Language (XML): can contain markup and content. Markup encodes a description of the document’s storage layout and logical structure.
Refer: www.w3.org/TR/xml
Android provides several types of XML parsers:
- DOM (Document Object Model): convert XML document into a tree of nodes, require more memory but allows multipass processing;
- SAX: streaming with application callbacks, require less memory but are limited to processing in a single pass;
- Pull: read the document as a stream like SAX, but use an iterator base approach. Application rather than the parser decides when to move the parsing process along. Require less memory, but also give the application greater control over the parsing process.
e.g. get requested data in XML and present it in list view
NetworkingAndroidHttpClientJSONActivity.java/HttpGetTask
@Override protected List<String> doInBackground(Void... params) { HttpGet request = new HttpGet(URL); XMLResponseHandler responseHandler = new XMLResponseHandler(); … } //same as the previous example, onPostExecute() creates and sets a list adapter ...
XMLResponseHandler.java
class XMLResponseHandler implements ResponseHandler<List<String>> { ... private final List<String> mResults = new ArrayList<>(); @Override public List<String> handleResponse(HttpResponse response) throws ClientProtocolException, IOException { try { // create pull parser XmlPullParserFactory factory = XmlPullParserFactory.newInstance(); XmlPullParser xpp= factory.newPullParser(); // put the xml document from http response into the parser xpp.setInput(new InputStreamReader(response.getEntity().getContent())); //get the first parser event, iterate over the xml document int eventType = xpp.getEventType(); while (eventType != XmlPullParser.END_DOCUMENT) { if (eventType == XmlPullParser.START_TAG) startTag(xpp.getName()); else if (eventType == XmlPullParser.END_TAG) endTag(xpp.getName()); else if (eventType == XmlPullParser.TEXT) text(xpp.getText()); eventType = xpp.next(); } return mResults; } catch (XmlPullParserException e) {} return null; } //identifies if this data element is one that needs to be saved. public void startTag(String localName) { if (localName.equals(LATITUDE_TAG)) mIsParsingLat = true; else if (localName.equals(LONGTITUDE_TAG)) mIsParsingLng = true; else if (localName.equals(MAGNITUDE_TAG)) mIsParsingMag = true; } public void text(String text) { if (mIsParsingLat) mLat = text.trim(); else if (mIsParsingLng) mLng = text.trim(); else if (mIsParsingMag) mMag = text.trim(); } /*identifies if this data element is one that being saved. if this is the end of the earthquake tag, add the data into the result list.*/ public void endTag(String localName) { if (localName.equals(LATITUDE_TAG)) mIsParsingLat = false; else if (localName.equals(LONGTITUDE_TAG)) mIsParsingLng = false; else if (localName.equals(MAGNITUDE_TAG)) mIsParsingMag = false; else if (localName.equals("earthquake")) { mResults.add(MAGNITUDE_TAG+":"+mMag+","+LATITUDE_TAG+":" +mLat+","+LONGTITUDE_TAG+":"+mLng); mLat = null; mLng = null; mMag = null; } } }
User Notifications, Broadcast Receivers, and Alarms
User notifications
User notification: messages provided to the user outside of the normal UI. These include: a) exist to provide feedback to user (e.g. toast, dialog); or b) notification area notification
Toast
Toast: transitory messages that pop up on the current window, automatically fade in and out of view.
Toast.makeText(context, text, duration) //instantiate a toast object Toast.show() // display the toast
e.g.
Toast.makeText(getApplicationContext(), “You’re toast!”, Toast.LENGTH_LONG).show();
Toast with custom view
-> create a custom layout in XML
-> inflate the layout, attach the inflated view to the toast message with Toast.setView()
e.g.
NotificationToastActivity.java/onCreate()/button/onClick()
Toast toast = new Toast(getApplicationContext()); //set the location and length of time of the toast toast.setGravity(Gravity.CENTER_VERTICAL, 0, 0); toast.setDuration(Toast.LENGTH_SHORT); toast.setView(getLayoutInflater().inflate(R.layout.custom_toast, null)); toast.show();
R/layout/custom_toast.xml
<RelativeLayout …> <ImageView …/> <TextView …/> </RelativeLayout>
notification area notifications
notification drawer: user can pull down to see more detailed information about notifications.
Notification architecture includes:
1) notification itself: title, detail, small icon;
2) notification area: small icon, ticker text (displays when the notification first appears);
3) notification drawer: a view, normally includes title, detail/content text, small icon and timestamp; action that will occur should the user click on the drawer view;
notification manager: Android system service that manages notifications.
e.g. notification area notification with custom view
public class NotificationStatusBarWithCustomViewActivity extends Activity { //for notification manager to update the notification private static final int MY_NOTIFICATION_ID = 1; //notification count private int mNotificationCount; // text elements private final CharSequence tickerText = "This is a Really, Really, Super Long Notification Message!"; private final CharSequence contentTitle = "Notification"; private final CharSequence contentText = "You've Been Notified!"; // Notification action elements private Intent mNotificationIntent; private PendingIntent mContentIntent; // sound and vibration private Uri soundURI = Uri.parse("android.resource://course.examples.Notification.StatusBar/"+R.raw.alarm_rooster); private long[] mVibratePattern = {0, 200, 200, 300}; //custom view that will be displayed on notification drawer RemoteViews mContentView = new RemoteViews("course.examples.Notification.StatusBarWithCustomView", R.layout.custom_notification); @Override public void onCreate(Bundle savedInstanceState) { ... //this intent will explicitly activate another activity mNotificationIntent = new Intent(getApplicationContext(), NotificationSubActivity.class); /*a pending intent is essentially a permission slip that allows one piece of code to stand in another piece of code. * i.e. allows a second piece of code to activate the underlying intent as if it were the first piece of code. * (does it with the permissions and identity of that first piece of code)*/ mContentIntent = PendingIntent.getActivity(getApplicationContext(), 0, mNotificationIntent, Intent.FLAG_ACTIVITY_NEW_TASK); ... //button click listener public void onClick(View v) { mContentView.setTextViewText(R.id.text, contentText+" ("+ ++ mNotificationCount +")"); /*Build the Notification - AutoCancel: cancel the notification if user clicks on drawer view - ContentIntent: pending intent that defines action to take when user clicks on drawer view - sound, vibrate: played when the notificaiton arrives - Content: custom view displayed when user pulls down notification drawer */ Notification.Builder notificationBuilder = new Notification.Builder(getApplicationContext()) .setTicker(tickerText) .setSmallIcon(android.R.drawable.stat_sys_warning) .setAutoCancel(true) .setContentIntent(mContentIntent) .setSound(soundURI) .setVibrate(mVibratePattern) .setContent(mContentView); //pass in the ID of the notificaiton service NotificationManager mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); //build() generates the actual notification object mNotificationManager.notify(MY_NOTIFICATION_ID, notificationBuilder.build()); } ... } ... }
Broadcast Receiver
BroadcastReceiver: base class for components that receive and react to events.
Typical use case:
-> broadcast receivers register to receive specified events;
-> When events occur, some components generate an intent and broadcast it to the system;
-> Android routes the intent to broadcast receiver that registered to receive it;
-> broadcast receiver gets a call to its onReceive();
-> event handled in onReceive().
Registering for intents
Broadcast receivers can register in 2 ways: a) statically; or b) dynamically, by invoking registerReceiver() at runtime;
Statically:
-> in AndroidManifest.xml, put <receiver>, with <intent-filter> inside.
Receivers are registered with the system at boot time or when the application package is added at runtime.
<receiver> attributes e.g.
android:enabled=[“true” | “false”] -- enable or disable a receiver
android:exported=[“true” | “false”] -- if the receiver can receive broadcasts from outside the application
android:name=“string” -- the name of the class that implements this receiver
android:permission=“string” -- permission that the sender of an intent must have in order for this receiver to receive an intent from them
e.g. static registration
public class SingleBroadcast extends Activity { //used to identify the intent private static final String CUSTOM_INTENT="course.examples.BroadcastReceiver.show_toast"; … }
//In onCreate()/ButtonListener: public void onClick(View v) { /*permission string indicates that this intent can only be delivered to broadcast receivers that have this particular permission */ sendBroadcast(new Intent(CUSTOM_INTENT), android.Manifest.permission.VIBRATE); }
Dynamically:
-> create an intent filter;
-> create a broadcast receiver;
-> register broadcast receiver using registerReceiver();
There are different implementations of registerReceiver:
a) in LocalBroadcastManager class, for broadcasts that are meant only for this application so they don’t need to be broadcast systemwide;
b) in Context class, for broadcasts systemwide, so intents can potentially be received by any application.
-> can call unregister Receiver() to unregister;
e.g. dynamic registration
public class SingleBroadcast extends Activity { private static final String CUSTOM_INTENT = "course.examples.BroadcastReceiver.show_toast"; private final IntentFilter intentFilter = new IntentFilter(CUSTOM_INTENT); private final Receiver receiver = new Receiver(); private LocalBroadcastManager mBroadcastMgr; @Override public void onCreate(Bundle savedInstanceState) { ... /*get an instance of the local broadcast manager localBroadcastManager: used to broadcast and receive intents only within this App */ mBroadcastMgr = LocalBroadcastManager.getInstance(getApplicationContext()); // register the receiver for showtoast intent mBroadcastMgr.registerReceiver(receiver, intentFilter); ... } … }
//in buttonListener: public void onClick(View v) { mBroadcastMgr.sendBroadcast(new Intent(CUSTOM_INTENT)); }
Android supports different broadcast methods. Broadcast can be:
Normal v.s. ordered
Normal: intents are delivered to receivers in an undefined order.
Ordered: intents are delivered to multiple receiver one at a time in priority order.
Sticky v.s. non-sticky
Sticky: intent is stored after its initial broadcast, so a receiver that is registered after the initial broadcast of an intent may still be able to receive it. – useful for recording system state change (e.g. changes in battery level, charging status), since to the receiver it doesn’t matter when the state changed but what the current state is.
Non-sticky: intent is discarded after its initial broadcast. – suitable for reporting a certain events has occurred.
With or without receiver permissions
broadcast debugging tips
- Log extra intent resolution information:
Intent.setFlag(FLAG_DEBUG_LOG_RESOLUTION);
- use ADB to list registered broadcast recivers;
adb shell dumpsys activity b #show broadcast receivers that are dynamically registered
adb shell dumpsys package #show broadcast receivers that are statically registered
broadcast receiver’s onReceive()
parameters: 1) context in which the receiver is running; 2) intent that was broadcast;
event handling in onReceive():
- Android may have to start the receiver’s application process first, because it might not actually be running when the intent is broadcast;
- while onReceive() is executing, hosting process has high priority;
- onReceive() runs on the main thread, so it should be short-lived. -> If event handling is lengthy, consider starting a service rather than performing complete operation in onReceive;
- receiver is not considered valid once onReceive() returns (i.e. once return, Android will sometimes terminate the underlying broadcast receiver), so receivers can’t start asynchronous operations which will need to call back to receiver at a later time (e.g. showing a dialog, startActivityForResult());
ordered broadcasts
Broadcasts are sent out normally with sendBroadcast().
If you want receivers to receive broadcasts in a particular order, or want each receiver to have exclusive access to the intent while it’s being process, use sendOrderedBroadcast().
//send Intent to receivers that have a specified permission in priority order void sendOrderedBroadcast(Intent intent, String receiverPermission); //same as the previous one but provides more parameters for greater control void sendOrderedBroadcast(Intent intent, String receiverPermission, BroadcastReceiver resultReceiver, Handler scheduler, int initialCode, String initialData, Bundle initialExtras);
e.g. send ordered broadcast to receivers (1, 2, 3) which have priorities 2>1>3. Receiver 1 has code that aborts the broadcast so only 2,1 receive the broadcast.
In manifest, statistically register receiver 2 (priority 10) and receiver 3 (priority 1).
<application ...> <!-- in manifest, statically register the receivers--> <receiver android:name="Receiver2" android:exported="false"> <intent-filter android:priority="10"> <action android:name="course.examples.BroadcastReceiver.show_toast"> </action> </intent-filter> </receiver> <receiver android:name="Receiver3" android:exported="false"> <intent-filter android:priority="1"> <action android:name="course.examples.BroadcastReceiver.show_toast"> </action> </intent-filter> </receiver> ... </application>
Dynamically register receiver 1 (priority 3).
private final Receiver1 mReceiver = new Receiver1();
// In onCreate(): IntentFilter intentFilter = new IntentFilter(CUSTOM_INTENT); intentFilter.setPriority(3); registerReceiver(mReceiver, intentFilter);
//In button listener: public void onClick(View v) { sendOrderedBroadcast(new Intent(CUSTOM_INTENT), android.Manifest.permission.VIBRATE); }
//In Receiver1 class: public class Receiver1 extends BroadcastReceiver { ... @Override public void onReceive(Context context, Intent intent) { if (isOrderedBroadcast()) { Log.i(TAG, "Calling abortBroadcast()"); abortBroadcast(); // broadcast will be prevented from being sent on to receiver3 } ... } }
e.g. send ordered broadcast with result receiver. Toast will show all receivers that receive the intent and the order.
It is useful if the initial broadcast receivers compute some result, that result will then be available to the result receiver via getResultData().
In Activity/onCreate()/buttonlistener, use the more complicated form of sendOrderedBroadcast():
/* the last parameter is the result receiver, it will receive the intent after all other receivers have gotten the chance to receive it. */ sendOrderedBroadcast(new Intent(CUSTOM_INTENT), null, new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { Toast.makeText(context, "Final Result is "+getResultData(), Toast.LENGTH_LONG).show(); } }, null, 0, null, null);
e.g. how other receivers compute the result data.
public class Receiver1 extends BroadcastReceiver { ... @Override public void onReceive(Context context, Intent intent) { Log.i(TAG, "intent received by Receiver1"); String tmp = getResultData() == null?"":getResultData(); setResultData(tmp+":Receiver 1:"); ... } }
Sticky Broadcasts
Sticky intents usually indicate state changes that may persist over time (e.g. low battery state), so receivers can know about the current device state even if they weren’t registered when that particular state change happened.
Sticky intents are cached by Android, new sticky intents overwrite cached values from older sticky intent they match.
When abroadcast receiver are dynamically registered,
-> cached sticky intents that match the specified intent filter will be broadcast to that receiver;
-> One matching sticky intent will be returned to the caller of the receiver;
same as non-sticky broadcasts, sticky broadcast can be sent normally or in priority order.
//send normal sticky broadcast void sendStickyBroadcast(Intent intent); //send sticky broadcast in priority order void sendStickyOrderedBroadcast(Intent intent, BroadcastReceiver resultReceiver, Handler scheduler, int initialCode, String initialData, Bundle innitialExtras);
Applications that want to broadcast sticky intents must have the BROADCAST_STICKY permission.
Receiver can determine whether an intent they receive was just broadcast or was sticky intent broadcast in the past via isInitialStickyBroadcast(). e.g. display current battery level and distinguish from cached StickyBroadcast / fresh broadcast. (In Android Console, use power capacity to send fresh battery level update broadcast)
@Override protected void onCreate(@Nullable Bundle savedInstanceState) { ... final TextView currentStateView = (TextView) findViewById(R.id.level); registerReceiver(new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { if (intent.getAction().equals(Intent.ACTION_BATTERY_CHANGED)) { String age = "Reading taken recently"; if (isInitialStickyBroadcast()) age = "Reading may be stale"; currentStateView.setText("Current Battery Level:" +String.valueOf(intent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1)) +System.getProperty("line.separator")+age); } } }, new IntentFilter(Intent.ACTION_BATTERY_CHANGED)); }
Alarms
Alarms: mechanism for sending intents at some point in the future. It allows one App to make code execute even when that App is no longer running.
Once registered, alarms remain active even if the device is asleep. Depending on configuration, when an alarm goes off while the device is sleeping, it can wake up the device or retain until the next time the device wake up.
An alarm will continue to be active until the device shuts down.
Alarms are cancelled on device shutdown / restart.
Alarms examples
MMS – retry scheduler: MMS uses alarms to start a service that can find messages haven’t been delivered and try to deliver again;
Settings -- Bluetooth discoverable timeout: Settings sets an alarm when it makes the device discoverable over Bluetooth, when the alarm goes off, it makes the device not discoverable anymore.
Phone – User info cache: Phone keeps a cache of user information, it uses alarms to periodically update the cache.
Alarm Manager – to create & manage alarms in an App, developers interact with AlarmManager service
Refer to: developer.android.com/reference/android/app/AlarmManager.html
-> get a reference to AlarmManager, call:
getSystemService(Context.ALARM_SERVICE)
-> create and set alarms:
a) set a single alarm, call:
/* triggerAtTime: time at which the alarm should go off operation: encapsulates the operation should occur when alarm goes off*/ void set(int type, long triggerAtTime, PendingIntent operation)
or b) set a repeating alarm that repeatedly goes off at specific intervals:
void setRepeating(int type, long triggerAtTime, long interval, PendingIntent operation)
-- repeating alarm will continue to go off until they are cancelled or until the device shuts down.
or c) set a repeating alarm but gives Android more flexibility in modifying the exact alarm timings:
/* interval must be chosen among: {INTERVAL_FIFTEEN_MINUTES; INTERVAL_HALF_HOUR; INTERVAL_HOUR; INTERVAL_HALF_DAY; INTERVAL_DAY}. */
void setInexactRepeating(int type, long triggerAtTime, long interval, PendingIntent operation)
For inexact repeating alarm, Android will try to minimize the number of times it needs to wake up the device if it’s sleeping. E.g. batch up multiple alarms to fire them at the same time.
Alarm types
Android provides 2 degree of configurability: 1) how to interpret time information given in parameter long interval; and 2) how should Android respond if the device is sleeping when alarm fires.
Interpreting time
a) wall clock time: number of milliseconds since midnight Jan 1, 1870;
or b) elapsed time: time since system’s last boot;
deal with sleeping devices
a) wake up device now and deliver intent;
or b) let the device continue sleeping, wait to deliver intent until device wakes up.
Alarm type options include:
- RTC_WAKEUP: wall clock time, wake up the device if it is asleep;
- RTC: wall clock time, don’t wake up the device but wait if it is asleep;
- ELAPSED_REALTIME: …
- ElAPSED_REALTIME_WAKEUP: …
PendingIntent
Obtained from:
//returns a PendingIntent that can be used to start an activity PendingIntent getActivity(Context context, int requestCode, Intent intent, int flags, Bundle options) //returns a PendingIntent that can be used to broadcast intent PendingIntent getBroadcast(Context context, int requestCode, Intent intent, int flags) //returns a PendingIntent that can be used to start a service PendingIntent getService(Context context, int requestCode, Intent intent, int flags)
Example App: set alarms for reminding
AlarmCreateActivity:
private AlarmManager mAlarmManager; private Intent mNotificationReceiverIntent, mLoggerReceiverIntent; private PendingIntent mNotificationReceiverPendingIntent,mLoggerReceiverPendingIntent; private static final long INITIAL_ALARM_DELAY = 2 * 60 * 1000L; …
AlarmCreateActivity/OnCreate():
//get a reference to the AlarmManager service mAlarmManager = (AlarmManager) getSystemService(ALARM_SERVICE); //create a PendingInent that can start AlarmNotificationReceiver mNotificationReceiverIntent = new Intent(AlarmCreateActivity.this, AlarmNotificationReceiver.class); mNotificationReceiverPendingIntent = PendingIntent.getBroadcast(AlarmCreateActivity.this, 0, mNotificationReceiverIntent, 0); …
AlarmCreateActivity/onCreate()/button1's listener: -- start a single alarm
//goes off 2 mins after being pressed mAlarmManager.set(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + INITIAL_ALARM_DELAY, mNotificationReceiverPendingIntent);
AlarmCreateActivity/onCreate()/button2's listener: -- start a repeating alarm
//goes off 2mins after being pressed, repeat every 15 minutes mAlarmManager.setRepeating(AlarmManager.ELAPSED_REALTIME, SystemClock.elapsedRealtime() + INITIAL_ALARM_DELAY, AlarmManager.INTERVAL_FIFTEEN_MINUTES, mNotificationReceiverPendingIntent);
AlarmCreateActivity/onCreate()/button3's listener: -- start an inexact repeating alarm
//goes off 2mins after being pressed, roughly repeat every 15 mins mAlarmManager.setInexactRepeating(AlarmManager.ELAPSED_REALTIME, SystemClock.elapsedRealtime() + INITIAL_ALARM_DELAY, AlarmManager.INTERVAL_FIFTEEN_MINUTES, mNotificationReceiverPendingIntent);
AlarmCreateActivity/onCreate()/button4's listener: -- cancel existing alarm
// Cancel all alarms using mNotificationReceiverPendingIntent mAlarmManager.cancel(mNotificationReceiverPendingIntent);