0% found this document useful (0 votes)
10 views

Ch_Two

Chapter Four discusses data management in mobile application development, focusing on various data storage options such as Shared Preferences, internal and external storage, and SQLite databases. It explains how to store user preferences, manage files, and utilize SQLite for structured data storage, including methods for inserting, reading, and updating data. The chapter emphasizes the importance of choosing the appropriate storage method based on application needs and data privacy considerations.

Uploaded by

matebearagaw6
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
10 views

Ch_Two

Chapter Four discusses data management in mobile application development, focusing on various data storage options such as Shared Preferences, internal and external storage, and SQLite databases. It explains how to store user preferences, manage files, and utilize SQLite for structured data storage, including methods for inserting, reading, and updating data. The chapter emphasizes the importance of choosing the appropriate storage method based on application needs and data privacy considerations.

Uploaded by

matebearagaw6
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 75

Chapter - Four

Data Management

Mobile Application Development


Contents
Storing Data
Shared Preferences
Files
SQLite
Storing Data
 Applications often store information about a user’s preferences in order to provide a
more sophisticated level of personalization and responsiveness
 A user can log into and out of an application and have it remember them when the
application is relaunched
 Users expect to choose settings according to their personal needs, such as specific
background sounds and images, and have them automatically persist across user sessions
 Advanced applications are frequently data-driven and require the management of a larger
volume of data
 Databases and files can be used by an application to store structured data or information
that will be made available to other applications
 Choosing the manner in which data is stored depends upon the specific needs of the
application,
 amount of data that needs to be stored and
 whether the data will be kept private
 The Android platform allows data files to be saved on the device’s internal memory and
on an external storage media
 Files that are saved within the device’s internal storage memory tend to be small, as
opposed to external storage, which typically holds larger volume files
In general
Your data storage options are the following:
 Shared preferences — Store private primitive data in key-value pairs.
 Internal storage — Store private data on the device memory.
 External storage — Store public data on the shared external storage.
 SQLite databases — Store structured data in a private database.
 Network connection — Store data on the web with your own network
server.
 Cloud Backup — Backing up app and user data in the cloud.
 Content providers — Store data privately and make them available
publicly.
Shared Preferences
 Shared Preferences refers to the storage of a limited set of primitive
data used to make persistent changes in an Android application
 A simple way to read and write key-value pairs of data
 Used to store
user’s personal settings and
small amount of application session data
 Shared Preference information is backed up in XML files in internal
storage
 Proprietary and inaccessible to outside applications
 Allows preference data values to automatically persist throughout
sessions, regardless of how they end
exit of an application, shutdown of the device, or system crash
Cont’d
 A key-value pair is a data representation model that uses a set of key
identifiers along with associated data values
 Frequently used in hash tables and configuration files
 Provides access to a data value or object by specifying its associated key
 SharedPreferences API provides a set of methods for reading and writing
key-value pairs to files
 Shared preferences supports
String data type and
Java primitive data types, such as int, float, long, and boolean
Example
private void usingPreferences(){
// Save data in a SharedPreferences container
// We need an Editor object to make preference changes.

1 SharedPreferences myPrefs = getSharedPreferences("my_preferred_choices",MODE_PRIVATE);

2 SharedPreferences.Editor editor = myPrefs.edit();

editor.putString("chosenColor", "RED");
editor.putInt("chosenNumber", 7 );
editor.commit();

// retrieving data from SharedPreferences container (apply default if needed)


3 String favoriteColor = myPrefs.getString("chosenColor", "BLACK");

int favoriteNumber = myPrefs.getInt("chosenNumber", 11 );

}
123
Cont’d
 If you’re targeting devices that run at least API Level 9 (Android 2.3 and
higher), you would benefit from using the apply() method instead of the
commit() method in the preceding code.
 However, if you need to support legacy versions of Android, you’ll want
to stick with the commit() method, or check at runtime before calling the
most appropriate method.
 Even when you are writing as little as one preference, using apply() could
smooth out the operation because any call to the file system may block for
a noticeable (and therefore unacceptable) length of time.
Cont’d
1) The method getSharedPreferences(…) creates (retrieves) a table called
my_preferred_choices file, using the default MODE_PRIVATE access. Under this
access mode only the calling application can operate on the file.
2) A SharedPreferences editor is needed to make any changes on the file. For
instance editor.putString("chosenColor", "RED") creates(updates) the key
“chosenColor” and assigns to it the value “RED”. All editing actions must be
explicitly committed for the file to be updated.
3) The method getXXX(…) is used to extract a value for a given key. If no key
exists for the supplied name, the method uses the designated default value. For
instance myPrefs.getString("chosenColor", "BLACK") looks into the file
myPrefs for the key “chosenColor” to return its value, however if the key is not
found it returns the default value “BLACK”.
Cont’d
 SharedPreference containers are saved as XML files in the application’s internal memory
space. The path to a preference files is
/data/data/packageName/shared_prefs/filename.

 If you pull the file from the device, you will see the following
File Storage – Internal and External Storage
 In Android, a file-based storage option allows data to be written to an actual file
structure
 This storage method requires more control regarding read and write permissions
 Internal storage allows data to be stored directly onto the device’s memory
 This storage is always available, assuming there is space
 External storage may not always be obtainable on a device.
 There are significant differences in how external and internal storage is utilized in
an application
 Internal storage files can be configured to be readable and writeable by the
application
 Typically, internal storage is utilized when processing an image, video and audio
elements, and large data files
 By default, files saved to internal storage are private to the application
Cont’d
 External storage is publicly shared storage, which means that it can be made
available to external applications
 Unlike internal storage, once an application is uninstalled, external storage files
will continue to exist
Cont’d
 You don't need any permissions to save files on the internal storage. Your
application always has permission to read and write files in its internal storage
directory.
 You can create files in two different directories:
 Permanent storage: getFilesDir()
 Temporary storage: getCacheDir(). Recommended for small, temporary
files totaling less than 1MB.
 Note that the system may delete temporary files if it runs low on memory
 To create a new file in one of these directories, you can use the File()
constructor, passing the File provided by one of the above methods that specifies
your internal storage directory. For example:
 File file = new File(context.getFilesDir(), filename);
Write a File to Internal Storage
 Alternatively, you can call openFileOutput() to get a FileOutputStream that
writes to a file in your internal directory. For example, here's how to write some
text to a file:
String filename = "myfile";
String string = "Hello world!";
FileOutputStream outputStream;
try {
outputStream = openFileOutput(filename, MODE_PRIVATE);
outputStream.write(string.getBytes());
outputStream.close();
} catch (Exception e) {
e.printStackTrace();
}
Cont’d
 MODE_PRIVATE (the default) is used to create a file that can be accessed only
by the “owner” application itself. From a Linux perspective, this means the
specific user identifier.
 The constant value of MODE_PRIVATE is 0, so you may see this used in legacy
code.
 MODE_APPEND is used to append data to the end of an existing file.
 The constant value of MODE_APPEND is 32768.
 Up until API Level 17, MODE_WORLD_READABLE and
MODE_WORLD_WRITEABLE constants were viable options for exposing data
to other applications, but they have now been deprecated.
 Using these constants in your applications may present security vulnerabilities
when exposing data to other applications.
Read a File from Internal Storage
 In order to read from the file you just created , call the openFileInput() method with
the name of the file. It returns an instance of FileInputStream. Its syntax is given
below
FileInputStream fin = openFileInput(file);
int c; String temp="";
while( (c = fin.read()) != -1){
temp = temp + Character.toString((char)c); //string temp
contains all the data of the file.
}
fin.close();
Grant Access to External Storage
 To read or write files on the external storage, our app must acquire the
WRITE_EXTERNAL_STORAGE and READ_EXTERNAL_STORAGE
system permissions.
 We need to add following permissions in android manifest file like as shown
below.
Checking External Storage Availability
 Before we do any work with external storage, first we need to check whether the
media is available or not by calling getExternalStorageState().
 The media might be mounted to a computer, missing, read-only or in some other
state.
 To get the media status, we need to write the code like as shown below.
Cont’d
 By using getExternalStoragePublicDirectory() method we can access the files
from appropriate public directory by passing the type of directory we want, such as
DIRECTORY_MUSIC, DIRECTORY_PICTURES, DIRECTORY_RINGTONS, etc.
based on our requirements.
 If we save our files to corresponding media-type public directory, the system's media
scanner can properly categorize our files in the system.
 Following is the example to create a directory for new photo album in the public
pictures directory.
Write a File to External Storage
 In case, if we are handling the files that are not intended for other apps to use, then
we should use a private storage directory on the external storage by calling
getExternalFilesDir().
 By using android FileOutputStream object and
getExternalStoragePublicDirectory method, we can easily create and write a
data to the file in external storage public folders.
 Following is the code snippet to create and write a public file in device Downloads
folder.
Read a File from External Storage
 By using android FileInputStream object and
getExternalStoragePublicDirectory method, we can easily read the
file from external storage.
 Following is the code snippet to read a data from file which is in
downloads folder.
Android Database with SQLite
 SQLite provides the foundation for managing private and embedded databases in
an Android application
 The Android SDK includes the SQLite software library that implements the SQL
(Structured Query Language) database engine
 This library is used for defining the database structure and adding and managing
the database content as required by the application
 The Android SDK also includes an SQLite database tool for explicit database
debugging purposes
 As a condensed version of SQL, SQLite supports the standard SQL syntax and
database transactions
 Though SQLite is not a full-featured database, it supports a large set of the SQL
standard and is sufficient for Android developers needing a simple database
engine to plug into their applications
Cont’d
 An SQLite database file is a collection of data organized in a table
 The SQLite database table is structured by a set of rows and columns
 A row represents a single record, the implicitly structured data entry in the
table
 A column represents a data field, an attribute of a data record
 A field is a single item that exists at the intersection between one row and
one column
 The possible data types for an individual field of data consist of NULL, INTEGER,
REAL,TEXT, and BLOB
 BLOB is a data type used to store a large array of binary data (bytes)
 SQLite does not provide specific data storage classes for values that need to be
represented as Boolean, date, or time
 Instead, these values can be stored as an INTEGER or TEXT
An outline for a students database table schema
SQLiteOpenHelper
 The Android SDK provides a set of classes for working with SQLite
databases
 SQLiteOpenHelper is essential for the creation and management of
database content, as well as database versioning
 In an Android SQLite application, SQLiteOpenHelper must be subclassed
 It must contain the implementation of onCreate() and onUpgrade()
Create Database and Tables using SQLite Helper
 By using SQLiteOpenHelper class we can easily create required
database and tables for our application.
 To use SQLiteOpenHelper, we need to create a subclass that overrides
the onCreate() and onUpgrade() call-back methods.
Insert Data into SQLite Database
In android, we can insert data into SQLite database by passing
ContentValues to insert() method.
Cont’d
long insert(String table, String nullColumnHack, ContentValues values)
String table:
 name of the table on which to perform the insert operation
 name needs to be the same as the name given to the table when it was
created.
String nullColumnHack:
 Specifies a column that will be set to null if the ContentValues argument
contains no data.
ContentValues values:
 Contains the data that will be inserted into the table
Content Values
 An instance of ContentValues stores data as key-value pairs, where
 key is the name of the column and
 value is the value for the cell
 One instance of ContentValues represents one row of a table.
 The insert() method for the database requires that the values to fill a row are
passed as an instance of ContentValues.
ContentValues values = new ContentValues();
// Insert one row. Use a loop to insert multiple rows.
values.put(KEY_WORD, "Android");
values.put(KEY_DEFINITION, "Mobile operating system.");
db.insert(WORD_LIST_TABLE, null, values);
Read the Data from SQLite Database
 we can read the data from SQLite database using query()
Cursor
 The SQLiteDatabase always presents the results as a Cursor in a table format that
resembles that of a SQL database
 You can think of the data as an array of rows. A cursor is a pointer into one row of that
structured data
 The Cursor class provides methods for moving the cursor through the data structure,
and methods to get the data from the fields in each row
Some common operations on cursor are:
 getCount() returns the number of rows in the cursor
 getColumnNames() returns a string array holding the names of all of the columns in
the result set in the order in which they were listed in the result
 getPosition() returns the current position of the cursor in the row set
 Getters are available for specific data types, such as getString(int column) and
getInt(int column).
 Operations such as moveToFirst(), moveToLast() and moveToNext() move the cursor
 close() releases all resources and makes the cursor completely invalid. Remember to
call close() to free resources!
Cont’d
 The Cursor class has a number of subclasses that implement cursors for specific
types of data.
 SQLiteCursor
 MatrixCursor
// Perform a query and store the result in a Cursor
Cursor cursor = db.rawQuery(...);
try {
while (cursor.moveToNext()) {

// Do something with the data


}}
finally {
cursor.close();
}
SQLiteDatabase.rawQuery() Vs SQLiteDatabase.query()
 The database provides two methods with several options for the arguments for
sending queries
1. SQLiteDatabase.rawQuery()
2. SQLiteDatabase.query()
 The open helper query method can construct an SQL query and send it as a
rawQuery to the database which returns a cursor.
 If your data is supplied by your app, and under your full control, you can use
rawQuery()
rawQuery(String sql, String[] selectionArgs)
 If you are processing user-supplied data, even after validation, it is more secure to
construct a query and use a version of the SQLiteDatabase.query() method for the
database.
Cursor query (boolean distinct, String table, String[] columns, String selection,
String[] selectionArgs, String groupBy, String having, String orderBy, String
limit)
Update Data in SQLite Database
 In android, we can update the data in SQLite database using update()
method in android applications
Cont’d
int update(String table, ContentValues values, String whereClause,
String[] whereArgs)
String table:
 Defines the name of the table on which to perform the update
 needs to match the name of a table in the database schema.
ContentValues values:
 Contains the key/value pairs that map the columns and values to be updated by the update
statement.
String whereClause:
 Defines theWHERE clause of an UPDATE SQL statement.
 This string can contain the “?” character that will be replaced by the values in the
whereArgs parameter.
String[] whereArgs:
 Provides the variable substitutions for the whereClause argument.
Delete Data from SQLite Database
 In android, we can delete data from SQLite database using delete()
method in android applications.
 int delete (String table, String whereClause, String[] whereArgs)
Chapter - Five
Content Providers and Service

Mobile Application Development


Contents
 In this Section, you will learn about:
 Activities(already Discussed)

 Intents

 Services

 Service Lifecycle

 Foreground Service

 Providers

 Introduction to content provider


 Query provider

 Receivers

 Application context
Application Components
 Are the essential building blocks of an Android application.

 Are loosely coupled by the application manifest file AndroidManifest.xml that describes each

component of the application and how they interact.

 4 Main Components
Additional Components
 There are additional components which will be used in the construction of above

mentioned entities, their logic, and wiring between them.


Fragments - Represent a behavior or a portion of user
interface in an Activity
Views - UI elements that are drawn onscreen including
buttons, lists forms etc
Layouts - View hierarchies that control screen format and
appearance of the views
Intents - Messages wiring components together
Resources - External elements, such as strings, constants
and drawable pictures
Manifest - Configuration file for the application
Activities
 An activity is an application component that provides a screen with which users can

interact.

 An application typically has multiple activities, and the user flips back and forth among

them.

 Activity is usually a single screen


 Implemented as a single class extending Activity
 Displays user interface controls (views)
 Reacts on user input / events

 An application typically consists of several activities

 Each screen is typically implemented by one activity

 Each activity can then start another activity (new screen)

 An activity may return a result to the previous activity


Cont’d
 The activity life cycle is managed by the Activity Manager, a service that runs
inside the Android Framework layer of the stack
 The Activity Manager is responsible for creating, destroying, and managing
activities
 Each activity receives callbacks due to a change in its state during its lifecycle (

i.e. whether the system is creating, stopping, resuming, or destroying it )


Intents
 Intents are used as a message-passing mechanism within your application and
between applications.
Explicit start Activity/Service using its class name
Start Activity/Service for an action with specific data
Broadcast that an event has occurred

 Intents are messages that are sent among the major building blocks.

 They trigger an activity to start up, tell a service to start, stop, or bind to, or

are simply broadcasts.

 Intents are asynchronous, meaning the code that sends them doesn’t have to

wait for them to be completed.


Cont’d
 Intents consists of the following:
1. Component Name: name of component/activity to handle the
intent
2. Action: to be performed (MAIN / VIEW / EDIT / PICK /
DELETE / …)
3. Data: to act on (expressed as URI)
4. Category: the kind of component that should handle the Intent
5. Extras: Additional information delivered to the handler
6. Flags: Additional information that instructs Android how to launch
an activity, and how to treat it after executed
Intent Types
 There are two types of intents:

 Explicit intents

 Specify the component to start by name (the fully-qualified class name).

 Typically used to start a component in your own app.

 For example, start a new activity in response to a user action or start a

service to download a file in the background.


 Implicit intents
 Do not name a specific component, but instead declare a general action to
perform, which allows a component from another app to handle it.
 For example, if you want to show the user a location on a map, you can use
an implicit intent to request that another capable app show a specified
location on a map.
Cont’d
 When you create an explicit intent to start an activity or service, the system
immediately starts the app component specified in the Intent object.
 Intent Object
 Helps identify the receiving component()
 May contain action to be taken and data act on
 Serve as notification for a system(e.g. new call)
 When you create an implicit intent, the Android system finds the
appropriate component to start by comparing the contents of the intent to
the intent filters declared in the manifest file of other apps on the device.
Cont’d
 If the intent matches an intent filter, the system starts that component
and delivers it the Intent object. If multiple intent filters are compatible,
the system displays a dialog so the user can pick which app to use.
 An intent filter is an expression in an app's manifest file that specifies the
type of intents that the component would like to receive.
For instance, by declaring an intent filter for an activity, you make it
possible for other apps to directly start your activity with a certain
kind of intent.
Likewise, if you do not declare any intent filters for an activity, then it
can be started only with an explicit intent.
Intent Example
Component Name Field
 Specifies the name of the component (name of the activity if the component is

activity) that should handle the intent.

<activity android:name=".FirstActivity“ android:label="@string/app_name">


<intent-filter>
<action android:name=”edu.odu.examples.IntentActivity" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
Action Field
 A string naming the action to be performed

 The Intent class defines a number of predefined action constants, including

 ACTION_CALL, ACTION_EDIT, ACTION_MAIN, ACTION_SYNC,


ACTION_BATTERY_LOW, etc.

 You can also define your own action strings for activating the components in your

application.

 The action largely determines how the rest of the intent is structured particularly

the data and extras fields - much as a method name determines a set of arguments
and a return value.
Intent Actions
Data Field
 The URI of the data to be acted on and the MIME (Multipurpose Internet Mail
Extensions) type of that data.
 Different actions are paired with different kinds of data specifications.
 If the action field is ACTION_EDIT, the data field would contain the URI of the
document to be displayed for editing.
 If the action is ACTION_CALL, the data field would be a tel: URI with the number to
call.
 If the action is ACTION_VIEW and the data field is an http: URI, the receiving activity
would be called upon to download and display whatever data the URI refers to.
 Examples of Action/Data Pairs
Example of Activity Actions
 Starting Activity:
 Launch new activity (without receiving a result)
 void startActivity (Intent intent)

 Launch new activity and expect result


 void startActivityForResult (Intent intent, int requestCode)
 When activity exits, onActivityResult() method is called with given requestCode
(int > 0)
 void onActivityResult (int requestCode, int resultCode, Intent data)
 Shutting Down Activity:
 void finish()
.MAIN Action & .LAUNCHER Category
 Activities that can initiate applications have filters with
“android.intent.action.MAIN” specified as the action
 This is a way an application gets started fresh, without a reference to any
particular data.
 If they are to be represented in the application launcher, they also specify the
“android.intent.category.LAUNCHER” category:

 The Android system populates the application launcher by finding all the activities
with intent filters that specify the “android.intent.action.MAIN” action and
“android.intent.category.LAUNCHER” category. It then displays the icons and
labels of those activities in the launcher.
Example - Explicit Intent
 For example, if you built a service in your app, named DownloadService,
designed to download a file from the web, you can start it with the following
code:
Example - Implicit Intent
 For example, if you have content you want the user to share with other people,
create an intent with the ACTION_SEND action and add extras that specify the
content to share. When you call startActivity() with that intent, the user can pick
an app through which to share the content.
Explicit Intent example
Implicit Intent example
Cont’d
Resources
 When the application is compiled, Android generates the R class, which contains
resource IDs for all the resources in your res/ directory.
 For each type of resource, there is an R subclass (e.g., R.drawable for all
drawable resources). For each resource of that type, there is a static integer
(e.g., R.drawable.icon).
 A resource ID is always composed of:
 The resource type: Examples: string, drawable, and layout.
 The resource name: Either: the filename, excluding the extension; or the value
in the XML android:name attribute, if the resource is a simple value (such as
a string).
 Accessing a resource:
 In code: Using a static integer from a sub-class of your R class, such as:
R.string.hello
 In XML: Using a special XML syntax that also corresponds to the resource ID
defined in your R class, such as: @string/hello
Services
 Services run in the background and don’t have any user interface
components.
 They can perform the same actions as activities, but without any user
interface.
 Services are useful for actions that you want to perform for a while,
regardless of what is on the screen.
 Faceless components that typically run in the background
 Music player, network download ,etc.
 Services have a much simpler life cycle than activities (see figure below).
 You either start a service or stop it. Also, the service life cycle is more or
less controlled by the developer, and not so much by the system.
 Consequently, developers have to be mindful to run services so that they
don’t consume shared resources unnecessarily, such as the CPU and
battery.
Service lifecycle
Cont’d
 Just because a service runs in the background doesn’t necessarily mean it
runs on a separate thread.
 By default, services and activities run on the same main application
thread, often called the UI thread.
 If a service is doing some processing that takes a while to complete (such
as performing network calls), you would typically invoke a separate
thread to run it. Otherwise, your user interface will run noticeably
slower.
Cont’d
 Steps to creating a service are:
1. Create the Java class representing your service.
2. Register the service in the AndroidManifest.xml file.
3. Start the service.
public class RefreshService extends Service {…}

 onStartCommand()
 Called each time the service is started
 onDestroy()
 Called when the service is terminated
 To do that, you can use the Eclipse or Android Studio tool
 Source→Override/Implement Methods and select those three methods.
Service example
Content Providers
 Content providers are interfaces for sharing data between applications
 Enables sharing of data across applications
 Address book, photo gallery, etc.
 Provides API for CRUD operations
 The methods that content providers use to implement the four critical
operations are
Example
Figure: Contacts application using the Content
Figure: Content provider Provider to get the data
Broadcast Receivers
 Components designed to respond to broadcast messages called Intents
 Can receive broadcast messages from the system. For example when
 A new phone call comes in
 There is change in battery level or cell ID
 Can receive messages broadcast by Applications
 Apps can also define new broadcast Messages
 The receiver is simply dormant code that gets activated by the occurrence of an
event to which the receiver is subscribed.
 The “event” takes the form of an intent.
 The system itself broadcasts events all the time. For example, when an SMS arrives,
a call comes in, the battery runs low, or the system completes booting up, all those
events are broadcast, and any number of receivers could be triggered by them.
 Broadcast receivers themselves do not have any visual representation, nor are they
actively running in memory.
 But when triggered, they get to execute some code, such as starting an activity, a
service, or something else.
Cont’d
 That action is in the form of an intent broadcast.
 When the right intent is fired, the receiver wakes up and executes.
 The “wakeup” happens in the form of an onReceive() callback method.
Application Context
 So far you have seen activities, services, content providers, and broadcast
receivers. Together, they make up an application. They are the basic building
blocks, loosely coupled, of an Android app.
 Think of an application in Android as a container to hold together your blocks.
Your activities, services, providers, and receivers do the actual work.
 The container that they work in is the shared Linux process with common Dalvik
VM, private file system, shared resources, and similar things.
 To use our website analogy, an app would be the website domain.
 Users never really go to Amazon.com (the domain), but rather visit a web page
(which you could compare to an Android activity) within that domain, or
consume a web service (an Android service). So web pages and web services are
building blocks for a website, and the website itself is just a container to hold
them all together under one roof.
 This is very similar to what an Android application does for its components.
Cont’d
Cont’d
 The application context is uniquely identified on a device based on the package name of that

application. For example, com.google.android.apps.maps is package name for Google map

 There cannot be another app with the same package name (unless it comes from us, and we

want to use shared user IDs).

 You can easily obtain a reference to the context by calling Context.getApplicationContext()

or Activity.getApplication().

 Intent intent = new Intent(getApplicationContext(),MainActivity.class);

 Keep in mind that activities and services are already subclasses of the context, and therefore

inherit all its methods.

 Activities and Services are also subclasses of the Context class, which is different from the

application context we’re talking about here.


75

You might also like