Android App Components
App Components
● App Components
– Activity
– Content Provider
– Service
– Broadcast Receiver
● Messaging Components
– Intent
– Others (based upon Binder IPC)
Intent
● Intent
– An abstract depiction of the operation
– A message for the OS to perform an action
● Applicability
– Start an activity (internal or external)
– Interact with other components like services and broadcast receivers
● Types
– Explicit
– Implicit
Explicit Intent
Class reference
Intent intent = new Intent(this,ListActivity.class);
startActivity(intent);
Intent sent to the OS
● Contains an exact reference to the class
● Typically used to interact with a component
internal to app
Implicit Intent
A string constant representing an action
Intent intent = new Intent(Intent.ACTION_EDIT);
startActivity(intent);
Intent sent to the OS
● Contains an action that any other app (or its component)
can declare to handle
● Facilitates inter-application communication and integration
Implicit Intent
● Workflow
– Applications declare the intents they handle in the
manifest using intent-filter
– Any interested application component can build and send
an implicit intent object
– Android OS:
● Maintains an index of intent-filters and corresponding
components declared by every app at the installation time
● Searches the intent-filter index, on receipt of an intent
● Launch the matching app component (or asks the user in case of
multiple matches)
An app component can specify multiple filters representing different ways it can handle intents
Implicit Intent
● Primary Information
– Action : a string specifying an action
– Data : a URI referencing data for the action
– Category : a string containing additional information about component
● Secondary Information
– Extras
– Flags
Intent-filter need not declare all of the information constituents
Intent resolution requires matching atleast one of each primary information constituent declared in the filter
Sample intent-filter declaration (in Manifest)
<activity android:name="NotesActivity"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.EDIT" />
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="note" />
</intent-filter>
</activity>
Sample intent sent to OS for matching (in Java)
Intent intent = new Intent(Intent.ACTION_EDIT);
intent.setData(Uri.parse("note://"));
startActivity(intent);
Content Provider
● Manages access to a central repository of data
● Data sharing across multiple applications
– Secure data access
– Inter-process communication
● Data Abstraction
– Tables & Rows (conceptually like a database)
– Independent of underlying data storage medium (e.g. database, files,
shared preferences, etc.)
● API
– ContentProvider
– ContentResolver
– Cursor and ContentValues
– Uri and UriMatcher
Creating a Content Provider
● Design data storage
● Design content URIs
– Authority
– Path
● Implement ContentProvider methods
– onCreate
– getType
– query
– insert
– update
– delete
● Register in manifest and set any permissions required
● Optionally provide a Contract class for specifying provider
details
public class NotesProvider extends ContentProvider{
private static UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH);
static {
matcher.addURI("com.example.smd.notesprovider","notes",1);
}
private NotesDbHelper dbHelper;
private SQLiteDatabase db;
public boolean onCreate(){
dbHelper = new NotesDbHelper(getContext());
db = dbHelper.getReadableDatabase();
return true;
}
public String getType(Uri uri){ … }
public Uri insert(Uri uri,ContentValues values){ … }
public int update(Uri uri,ContentValues values,String selection,String[] args){ … }
public int delete(Uri uri,String selection,String[] args){ … }
public Cursor query(Uri uri,String [] projection, String selection,String[] selectionArgs,String sortOrder){
if(matcher.match(uri) == 1){
return db.query("Notes",projection,selection,selectionArgs,null,null,sortOrder);
}
String[] cols = {"_ID"};
return new MatrixCursor(cols);
}
}
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.smd"
android:versionCode="1"
android:versionName="1.0">
<application android:label="@string/app_name" android:icon="@drawable/ic_launcher"
android:theme="@style/Notes">
<activity android:name="NotesActivity"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name="com.example.smd.ListActivity"
android:label="ListActivity"
android:parentActivityName="com.example.smd.NotesActivity" >
<meta-data android:name="android.support.PARENT_ACTIVITY"
android:value="com.example.smd.NotesActivity" />
</activity>
<provider android:name="NotesProvider"
android:authorities="com.example.smd.notesprovider"
android:exported="true" />
</application>
</manifest>
Using a Content Provider
● Use ContentResolver
– A remote proxy object for the provider
– Provides same methods as provided by the provider
● onCreate
● getType
● query
● insert
● update
● delete
public class AppTest extends Activity
{
...
public void getNotes(View view)
{
String[] colNames = {"title","content"};
Cursor cursor = getContentResolver().query(
Uri.parse("content://com.example.smd.notesprovider/notes"), // uri
colNames, // projection (columns)
null, // search string (where clause)
null, // search arguments (parameters)
null); // sort order
if (cursor.getCount() > 0){
String result = "";
while (cursor.moveToNext()){
result += cursor.getString(cursor.getColumnIndex("title")) + "\n";
}
Toast.makeText(this,result,Toast.LENGTH_SHORT).show();
}
}
}
Standard Content Providers
● Browser
● Calendar
● Contacts
● CallLog
● MediaStore
● UserDictionary
Services
● Application component without a UI
● A long-running operation in the background
● Continues running even if user switches to
another application
● May be invoked remotely from a different
process through IPC
Example Usage Scenarios
● Download / Upload / Synchronize data on network
● Alarms / notifications / reminders (on some specific event)
● Send emails / messages in a fail-safe manner
● Play audio / music in the background
● Archive data
● Authenticate users
● Other application specific scenarios
Example
public class NotesExportService extends Service {
public void onCreate() {
public int onStartCommand(Intent intent,int flags,int startId){
Toast.makeText(this,"Service starting",Toast.LENGTH_SHORT).show();
return START_NOT_STICKY;
}
public IBinder onBind(Intent intent){
return null;
}
}
Service Types
● Started
– Performs a background operation and does not return
any result to the caller
– Once started, may run indefinitely unless
● explicitly stopped
● killed by the system but may be restarted based upon restart
behavior and any pending requests
– Useful for cases like syncing data, playing audio, etc.
– Invocation
● from other App components (Activities or BroadcastRecievers)
using explicit intents
Intent intent = new Intent(this,NotesExportService.class);
startService(intent);
● calls onStartCommand
Service Types
● Bound
– Bound to the application component starting the service
● client-server communication
● service acts as the server
– Runs only as long as another component is bound to it
● Multiple components may bind to a service at once
● Destroys when all bound components unbind
– Useful for cases like authentication, play audio with
status updates, etc.
– Invocation
● Using Binder, ServiceConnection and bindService
● calls onBind
public class NotesExportService extends Service {
private final IBinder binder = new LocalBinder();
public void onCreate() {
public int onStartCommand(Intent intent,int flags,int startId){
Toast.makeText(this,"Service starting",Toast.LENGTH_SHORT).show();
return START_NOT_STICKY;
}
public IBinder onBind(Intent intent){
return binder;
}
public class LocalBinder extends Binder{
public NotesExportService getService(){
return NotesExportService.this;
}
}
public String getStatus(){
return "exported successfully"; // or other recorded status
}
}
public class SettingsActivity extends BaseActivity
{
NotesExportService dataService;
boolean bound = false;
...
private ServiceConnection connection = new ServiceConnection(){
public void onServiceConnected(ComponentName className, IBinder binder){
dataService = ((NotesExportService.LocalBinder) binder).getService();
bound = true;
// other service related requests and operations go here
}
public void onServiceDisconnected(ComponentName className){
bound = false;
}
};
protected void onStart(){
super.onStart();
Intent intent = new Intent(this,NotesExportService.class);
bindService(intent,connection, Context.BIND_AUTO_CREATE);
}
protected void onStop(){
super.onStop();
if(bound){
unbindService(connection);
}
}
Some Considerations
● Service not a separate process
– Service itself doesn't mean a new process
– Runs in the same process of the application by default
– It may be specified to run in a different process
– Remote services require IPC mechanisms
● Service not a thread
– Service itself doesn't launch a new thread
– Thread management needs to be done explicitly
– Necessary to avoid application not responding errors
Broadcast Receivers
● Broadcasts
– system or custom-generated notifications for events
– broadcasted to interested application components that
may need to take corresponding action
– API
● sendBroadcast: asynchronous operation to notify all receivers in
parallel
● sendOrderedBroadcast: asynchronous operation to notify all
receivers in sequence – may be aborted in between
– Examples
● android.intent.action.BOOT_COMPLETED
● android.intent.action.BATTERY_LOW
Broadcast Receivers
● Broadcast receiver
– An event listener
– Registered to receive broadcasted intents
– Runs in background – may send UI notifications
– Invoke a service
● Only start
● Cannot bind
Example broadcast receiver registered statically in the manifest
public class ConnectivityReceiver extends BroadcastReceiver {
public void onReceive(Context context,Intent intent){
String message = "received";
Toast.makeText(context,message,Toast.LENGTH_SHORT).show();
}
<receiver android:name="ConnectivityReceiver">
<intent-filter>
<action
android:name="android.net.conn.CONNECTIVITY_CHANGE" />
</intent-filter>
</receiver>
Example broadcast receiver registered dynamically in an activity
public class NotesActivity extends BaseActivity
{
ConnectivityReceiver receiver;
…
public void onCreate(Bundle savedInstanceState)
{
…
IntentFilter intent = new
IntentFilter("android.net.conn.CONNECTIVITY_CHANGE");
receiver = new ConnectivityReceiver();
registerReceiver(receiver,intent);
}
protected void onDestroy(){
unregisterReceiver(receiver);
super.onDestroy();
}
}
Example broadcast receiver invoking a service
public class ConnectivityReceiver extends BroadcastReceiver {
public void onReceive(Context context,Intent intent){
String message = "received";
Toast.makeText(context,message,Toast.LENGTH_SHORT).show();
SharedPreferences preferences =
context.getSharedPreferences("service",Context.MODE_PRIVATE);
boolean started = preferences.getBoolean("started",false);
SharedPreferences.Editor editor = preferences.edit();
if(!started){
Intent serviceIntent = new Intent(context,NotesDataSyncService.class);
context.startService(serviceIntent);
editor.putBoolean("started",true);
}
else{
Intent serviceIntent = new Intent(context,NotesDataSyncService.class);
context.stopService(serviceIntent);
editor.putBoolean("started",false);
}
editor.commit();
}
}
SMS Example
● SMS received broadcast
– android.provider.Telephony.SMS_RECEIVED
– Ordered broadcast – may be aborted by some
application
– Requires permissions
● android.permission.RECEIVE_SMS
● android.permission.READ_SMS
– Intent structure
● Generally carries an array of PDUs, mapped to string pdus
public class SmsReceiver extends BroadcastReceiver {
public void onReceive(Context context,Intent intent){
Bundle bundle = intent.getExtras();
try {
if (bundle != null) {
Object[] pdusObj = (Object[]) bundle.get("pdus");
for (int i = 0; i < pdusObj.length; i++) {
SmsMessage currentMessage = SmsMessage.createFromPdu((byte[]) pdusObj[i]);
String number = currentMessage.getDisplayOriginatingAddress();
String message = currentMessage.getDisplayMessageBody();
String text = "senderNum: "+ number + ", message: " + message;
Toast.makeText(context, text , Toast.LENGTH_LONG).show();
}
}
} catch (Exception e) {
}
}
}
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.smd.AppTest"
android:versionCode="1"
android:versionName="1.0">
<uses-permission android:name="android.permission.RECEIVE_SMS" />
<uses-permission android:name="android.permission.READ_SMS" />
<application android:label="@string/app_name" android:icon="@drawable/ic_launcher">
<activity android:name="AppTest"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<receiver android:name="SmsReceiver">
<intent-filter>
<action android:name="android.provider.Telephony.SMS_RECEIVED" />
</intent-filter>
</receiver>
</application>
</manifest>