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

Android Application Model II

The document discusses key concepts in the Android application model, including: 1) The activity lifecycle and the standard lifecycle methods that are called as activities transition between states like created, active, paused, and stopped. 2) How to manage transient state across activity transitions using onSaveInstanceState() and onRestoreInstanceState(). 3) How intents and intent filters are used to start activities and allow for loose coupling between caller and responder activities. 4) The use of services for long-running background tasks and how they are similar to but differ from threads.

Uploaded by

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

Android Application Model II

The document discusses key concepts in the Android application model, including: 1) The activity lifecycle and the standard lifecycle methods that are called as activities transition between states like created, active, paused, and stopped. 2) How to manage transient state across activity transitions using onSaveInstanceState() and onRestoreInstanceState(). 3) How intents and intent filters are used to start activities and allow for loose coupling between caller and responder activities. 4) The use of services for long-running background tasks and how they are similar to but differ from threads.

Uploaded by

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

Android Application Model II

CSE 5236: Mobile Application Development


Instructor: Adam C. Champion, Ph.D.
Course Coordinator: Dr. Rajiv Ramnath
Reading: Big Nerd Ranch Guide, Chaps. 3, 5 (Activities);
Chap. 28 (Services); Chap. 14 (DB); Chap. 15 (Contacts)
1
Outline
• Activity Lifecycle
• Services
• Persistence
• Content Providers

2
Recap
• Android Framework
• Activities
• General UI:
– Layouts, Handler methods
– Widgets, Custom UI classes, Handling within activity
• Specialized UIs:
– Menus and the Action Bar
– Special Activities – Preferences
• UI for larger screens: Fragments
3
The Activity Lifecycle
• Android runtime manages Activities
• Activities have a “lifecycle” consisting of
states: from creation until death
• Standard (lifecycle) methods on the
activity are invoked at each state change
(can test by rotating device)

4
Activity States
• Created: Born to run
• Active: Working 9 to 5
• Paused: I’m about to break
• Resumed: Back to work
• Stopped: Obscured by clouds, vulnerable

5
Activity Transitions
• Created ⟹ Active
• Active ⟺ Paused
• Paused ⟹ Stopped ⟹ Active
• Stopped ⟹ Killed

6
Timing of
Activity
Lifecycle
Methods

7
Lifecycle Methods
• onCreate(Bundle savedInstanceState): create
views, (re) initialize state
• onStart(): Restore transient state; one-time processing
• onResume(): Session-specific processing, restore
transient state
• onPause(): Save persistent data, release resources,
quickly! Last method guaranteed to be called.
• onStop(): Called optionally by runtime
• onDestroy(): If finish() is called, or object is being
temporarily destroyed. Distinguish via isFinishing().
8
Transient State Management Methods

• onSaveInstanceState(...): Called
before onPause(); use to save transient
state.
• onRestoreInstanceState(...): Called
after onStart() and onResume(); use to
restore state.

9
Transient State Management: Java
// MapsActivity.java
public class MapsActivity extends AppCompatActivity /* . . . */ {
private MapView mMapView;
private String whereAmIString = null;
. . .
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
mMapView.onSaveInstanceState(outState);
if (whereAmIString != null) {outState.putString(WHERE_AM_I_STRING, whereAmIString);}
}
. . .
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
whereAmIString = savedInstanceState.getString(WHERE_AM_I_STRING);
if (whereAmIString != null) { mEditLocation.setText(whereAmIString); }
}
10
}
Interaction Across Activities
• Activity 1: onPause()
• Activity 2: onCreate(), onStart(),
onResume()
• Activity 1: onStop() – if it is obscured

11
Starting Activities: Intents and
Intent Filters
• Message posted to the Android runtime to launch
an activity; matched against IntentFilter of
activity in AndroidManifest.xml file
• Encourages activity reuse among applications
• Uniform mechanism for launching internal and
external activities
• Enables loose coupling between caller, responder

12
Intent and Intent Filter Components

• Target – fully qualified name (string),


direct reference
• Action – standard or custom (string)
• Data (URI)
• Category of the target object

13
Intent Filters: Examples
<!-- AndroidManifest.xml -->
<intent-filter> <!== for SplashScreen activity ==>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
<intent-filter> <!== for Login activity ==>
<action
android:name="com.wiley.fordummies.androidsdk.tictactoe.Login">
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>

14
Intent Invocations: Examples: Java
• Matching component name: startActivity(new Intent(
"com.wiley.fordummies.androidsdk.tictactoe.Login"));
• Direct invocation: startActivity(new Intent(
getActivity().getApplicationContext(), SettingsActivity.class));
• Passing data: public void sendScoresViaEmail() {
Intent emailIntent = new Intent(Intent.ACTION_SEND);
emailIntent.putExtra(Intent.EXTRA_SUBJECT,
"Look at my AWESOME TicTacToe Score!");
emailIntent.setType("plain/text");
emailIntent.putExtra(Intent.EXTRA_TEXT, firstPlayerName +

" score is " + scorePlayerOne + " and " +


secondPlayerName + " score is " + scorePlayerTwo);
startActivity(emailIntent); 15
Intent Invocations: Examples: Kotlin
• Matching component name: startActivity(Intent(
"com.wiley.fordummies.androidsdk.tictactoe.Login"))

• Direct invocation: startActivity(Intent(activity.applicationContext,


SettingsActivity::class.java))

• Passing data: fun sendScoresViaEmail() {


val emailIntent = Intent(Intent.ACTION_SEND)
emailIntent.putExtra(Intent.EXTRA_SUBJECT,
"Look at my AWESOME TicTacToe Score!")
emailIntent.type = "plain/text"
emailIntent.putExtra(Intent.EXTRA_TEXT, mFirstPlayerName +
" score is " + mScorePlayerOne + " and " + mSecondPlayerName +
" score is " + mScorePlayerTwo)+-
startActivity(emailIntent)
16
}
Tasks and Activity Stacks
• Task: a (conceptual) container for sets of “like-
minded” activities
• Roughly corresponds to a Linux process
• Activities are stacked in tasks
• Activities can move across tasks
• Task is requested by calling intent or selected
based on taskaffinity attribute in
AndroidManifest.xml file
17
Outline
• Activity Lifecycle
• Services
• Persistence
• Content Providers

18
Services
• Activities for background, long-term processing (e.g., email,
music, synchronization)
• Local: Accessible by a single app.
• Remote (aka bound): Accessible by all apps on the device
– See: http://developer.android.com/guide/topics/fundamentals/
services.html#CreatingBoundService
• Comparison with threads:
– Coarser-grained processing
– Lifecycle separated from launching activity
– More resources consumed
– More respected  by OS
19
Service Example: Java
public class MediaPlaybackService extends Service {
MediaPlayer player;

@Override
public IBinder onBind(Intent intent) {/*...*/}
public void onCreate() {
player = MediaPlayer.create(this, R.raw.sampleaudio); player.setLooping(true);
}
public int onStartCommand(Intent intent, int flags, int startId) { /* ... */
Bundle extras = intent.getExtras();
String audioFileURIString = extras.getString("URIString");
Uri audioFileURI = Uri.parse(audioFileURIString);
player.reset(); player.setDataSource(this.getApplicationContext(),
audioFileURI);
player.prepare(); player.start(); /* ... */
}
public void onDestroy() { player.stop(); }
} 20
Service Example: Kotlin
class MediaPlaybackService : Service() {
override fun onBind(intent: Intent): IBinder? {/*. . .*/}
override fun onCreate() {
player = MediaPlayer.create(this, R.raw.sample_audio)
player.apply { isLooping = true } }
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
super.onStartCommand(intent, flags, startId)
val extras = intent.extras
if (extras != null) {
val audioFileURIString = extras.getString("URIString")
val audioFileURI = Uri.parse(audioFileURIString)
player.reset()
player.setDataSource(this.applicationContext, audioFileURI)
player.prepare()
player.start()
} }
override fun onDestroy() { player.stop() }
}
21
Service Invocation
Java Kotlin
. . . . . .
Intent musicIntent = val musicIntent = Intent(
new Intent( activity.applicationContext,
getApplicationContext(), MediaPlaybackService::
MyPlaybackService.class); class.java)
musicIntent.putExtra( musicIntent.putExtra(
"URIString", "URIString",
mAudioFileURI.toString()); mAudioFileUri.toString())
activity.startService(
getActivity().
musicIntent)
startService(musicIntent);
. . .
. . .

22
Outline
• Activity Lifecycle
• Services
• Persistence
• Content Providers

23
Saving Persistent Data – Files
• Same as in any Java or Kotlin app
• Internal storage (Linux file system)
– Local to app
– Destroyed upon uninstall
• External storage (e.g., SD card)
– Stored to SD card or flash memory (device-dependent)
– Shared among applications
– E.g.:
Java Kotlin
File imageFile = val imageFile =
new File(imageFilePath); File(imageFilePath)
24
Saving Persistent Data – SQLite
• http://sqlite.org: “Most widely deployed
SQL database engine in the world”
– Lightweight, transactional
• Helper class for database creation
• Methods for operating on SQL statements:
– Creating a statement
– Binding data
– Executing
25
SQLite Example: Account Model Class

Java Kotlin
// Account.java // Account.kt
public class Account { data class Account(val name: String,
private String mName, mPassword; val password: String) {
// We don't need anything here!
public Account(String name,
}
String password) {mName = name;
mPassword = password; }

public String getName() { . . . } Kotlin data classes automatically


public String getPassword() { ... } generate getters, setters,
equals(), hashCode(),
/* equals(), hashCode(), toString() */
} and toString()

26
SQLite Example: DB Schema: Java
// AccountDbSchema.java
public class AccountDbSchema {
public static final class AccountsTable {
public static final String NAME = "accounts";

public static final class Cols {


public static final String NAME = "name";
public static final String PASSWORD = "password";
}
}
}

// Also used with Kotlin

27
SQLite Example: Account CursorWrapper

Java Kotlin
// AccountCursorWrapper.java // AccountCursorWrapper.kt
public class AccountCursorWrapper class AccountCursorWrapper(cursor: Cursor) :
extends CursorWrapper { CursorWrapper(cursor) {
public AccountCursorWrapper(
Cursor cursor) { super(cursor); } val account: Account
public Account getAccount() { get() {
String name = getString( val name = getString(
getColumnIndex( getColumnIndex(
AccountsTable.Cols.NAME)); AccountsTable.Cols.NAME))
String password = getString(
getColumnIndex( val password = getString(
getColumnIndex(
AccountsTable.Cols.PASSWORD));
AccountsTable.Cols.PASSWORD))
Account account =
new Account(name, password);
return Account(name, password)
}
return account;
}
} 28
}
SQLite Example: Account Singleton: Java
// AccountSingleton.java
public class AccountSingleton { // Continued . . .
private static AccountSingleton sAccount;
public void addAccount(Account account) {
private AccountDbHelper mDbHelper;
ContentValues contentValues =
private SQLiteDatabase mDatabase;
getContentValues(account);
private static final String INSERT_STMT =
. . .
"INSERT INTO " + AccountsTable.NAME +
SQLiteStatement statement =
"(name, password) VALUES (?, ?)" ; /* . . . */
mDatabase.compileStatement(INSERT_STMT);
private AccountSingleton(Context context) {
statement.bindString(1,
mDbHelper = new AccountDbHelper(
contentValues.getAsString(
context.getApplicationContext());
AccountsTable.Cols.NAME));
mDatabase = mDbHelper.getWritableDatabase();
statement.bindString(2,
}
contentValues.getAsString(
private static ContentValues getContentValues(
AccountsTable.Cols.PASSWORD));
Account account) {
statement.executeInsert();
ContentValues values = new ContentValues();
}
values.put(AccountsTable.Cols.NAME,
public void deleteAllAccounts() { /* ... */
account.getName());
values.put(AccountsTable.Cols.PASSWORD, mDatabase.delete(AccountsTable.NAME,
account.getPassword()); null, null);
return values; }
}
}
// Also used with Kotlin
// Continued . . .
29
SQLite Example: Helper Class: Java
// AccountDbHelper.java
public class AccountDbHelper extends SQLiteOpenHelper {
private Context mContext;
private static final String DATABASE_NAME = "TicTacToe.db";
private static final int DATABASE_VERSION = 1;
public AccountDbHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION); }

@Override
public void onCreate(SQLiteDatabase sqLiteDatabase) {
sqLiteDatabase.execSQL("CREATE TABLE " + AccountsTable.NAME + "(" +
"_id INTEGER PRIMARY KEY AUTOINCREMENT, " +
AccountsTable.Cols.NAME + " TEXT, " +
AccountsTable.Cols.PASSWORD + " TEXT" + ")");}

@Override
public void onUpgrade(SQLiteDatabase sqLiteDatabase, /* ... */) {
Log.w("Example", "Example: upgrading DB; dropping/recreating tables.");
sqLiteDatabase.execSQL("DROP TABLE IF EXISTS " + AccountsTable.NAME);
onCreate(sqLiteDatabase); } 30
}
SQLite Example: Helper Class: Kotlin
// AccountDbHelper.kt
class AccountDbHelper(context: Context) : SQLiteOpenHelper(
context, DATABASE_NAME, null, DATABASE_VERSION) {
private lateinit var mContext: Context
override fun onCreate(sqLiteDatabase: SQLiteDatabase) {
sqLiteDatabase.execSQL("CREATE TABLE " + AccountsTable.NAME + "(" +
"_id INTEGER PRIMARY KEY AUTOINCREMENT, " + AccountsTable.Cols.NAME +
" TEXT, " + AccountsTable.Cols.PASSWORD + " TEXT" + ")")}

override fun onUpgrade(database: SQLiteDatabase, /* ... */) {


Log.w("Example", ”Upgrading DB; dropping/recreating tables.")
database.execSQL("DROP TABLE IF EXISTS " + AccountsTable.NAME)
onCreate(database) }

companion object {
private val DATABASE_NAME = "TicTacToe.db"
private val DATABASE_VERSION = 1 }
}
31
SQLite Example: DB Operations: Java
// AccountFragment.java
private void createAccount() {
FragmentActivity activity = getActivity();
String username = mEtUsername.getText().toString(),
String password = mEtPassword.getText().toString();
String confirm = mEtConfirm.getText().toString();
if (activity != null) {
if ((password.equals(confirm)) && (!username.equals("")) && (!password.equals("")) &&
(!confirm.equals(""))) {
AccountSingleton singleton = AccountSingleton.get(activity.getApplicationContext());
Account account = new Account(username, password);
singleton.addAccount(account);
Toast.makeText(activity.getApplicationContext(), "New record inserted",
Toast.LENGTH_SHORT).show();
} else if ((username.equals("")) || (password.equals("")) || (confirm.equals(""))) {
Toast.makeText(activity.getApplicationContext(), "Missing entry",
Toast.LENGTH_SHORT).show();
} else if (!password.equals(confirm)) {
/* Show error message */
} else { /* Error handling */ }
}
} 32
SQLite Example: DB Operations: Kotlin
// AccountFragment.kt
private fun createAccount() {
val username = mEtUsername.text.toString()
val password = mEtPassword.text.toString()
val confirm = mEtConfirm.text.toString()
if (password == confirm && username != "" && password != "" && confirm != "") {
val singleton = AccountSingleton.get(activity?.applicationContext)
val account = Account(username, password)
singleton.addAccount(account)
Toast.makeText(activity?.applicationContext, "New record inserted",
Toast.LENGTH_SHORT).show()
} else if (username == "" || password == "" || confirm == "") {
Toast.makeText(activity?.applicationContext, "Missing entry",
Toast.LENGTH_SHORT).show()
} else if (password != confirm) {
/* Error handling */
} else { /* Error handling */ }
}

33
Cloud Persistence
• Cloud data stores useful when app users need
to read other users’ written data
• Example: Google’s Firebase
– Non-relational data store (high availability)
– Realtime Database: persists (key, value) data in a
shared JSON tree
– Cloud Firestore: supports more flexible persistence
(document-based approach)
– More info: http://firebase.google.com
34
Outline
• Activity Lifecycle
• Services
• Persistence
• Content Providers

35
Sharing Content – Content Providers
• Standard content providers:
– Contacts, dictionary, media
• Referred to by URI prefix: content://
– Standard providers
• Permission must be requested in
manifest:
<uses-permission android:name=
"android.permission.READ_CONTACTS"/>

• Android 6+: permission for


“dangerous” actions must be
requested at runtime (
https://developer.android.com/
ListView of
training/permissions/requesting.html ) user’s contacts
36
Content Provider Example: Java (1)
// ContentsFragment.java
public class ContactsFragment extends Fragment implements
LoaderManager.LoaderCallbacks<Cursor> {
private ListView mContactsListView;
private static final String[] PROJECTION = { ContactsContract.Contacts._ID,
ContactsContract.Contacts.LOOKUP_KEY, ContactsContract.Contacts.DISPLAY_NAME_PRIMARY };
private final static String[] FROM_COLUMNS = {
ContactsContract.Contacts.DISPLAY_NAME_PRIMARY };
@Override
public void onActivityCreated(. . .) { // First, call super.onActivityCreated()
Activity activity = getActivity();
if (activity != null) {
mContactsListView = activity.findViewById(R.id.contact_list_view);
requestContacts(); } }

private void requestContacts() {


if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (!hasReadContactPermission()) {
requestPermissions(new String[]{Manifest.permission.READ_CONTACTS},
PERMISSION_REQUEST_READ_CONTACTS); }
else { showContacts(); } }
37
else { showContacts(); } }
Content Provider Example: Java (2)
// ContactsFragment.java, continued . . .
private boolean hasReadContactPermission() {
Activity activity = getActivity();
return activity != null &&
activity.checkSelfPermission(Manifest.permission.READ_CONTACTS) ==
PackageManager.PERMISSION_GRANTED; }

@Override
public void onRequestPermissionsResult(. . . ) {
if (requestCode == PERMISSION_REQUEST_READ_CONTACTS) {
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { showContacts(); }
else { /* Show error message */} }
}

private void showContacts() {


// Gets a CursorAdapter
mCursorAdapter = new SimpleCursorAdapter(getActivity(), R.layout.list_item_contact,
null, FROM_COLUMNS, TO_IDS, 0);
// Sets the adapter for the ListView
mContactsListView.setAdapter(mCursorAdapter);
// Initializes the loader, which loads the contacts asynchronously
getLoaderManager().initLoader(0, null, this); 38
Content Provider Example: Java (3)
// ContactsFragment.java, continued . . .
@Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
return new CursorLoader(getActivity(), ContactsContract.Contacts.CONTENT_URI,
PROJECTION, null, null, ContactsContract.Contacts.DISPLAY_NAME +
" COLLATE LOCALIZED ASC” );
}

@Override
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
// Put the result Cursor in the adapter for the ListView
mCursorAdapter.swapCursor(data);
}

@Override
public void onLoaderReset(Loader<Cursor> loader) { mCursorAdapter.swapCursor(null); }
} // End of Fragment

39
Content Provider Example: Kotlin (1)
// ContactsFragment.kt
class ContactsFragment : Fragment(), LoaderManager.LoaderCallbacks<Cursor> {
private lateinit var mContactsListView: ListView
companion object { . . .
private val PROJECTION = arrayOf(ContactsContract.Contacts._ID,
ContactsContract.Contacts.LOOKUP_KEY, ContactsContract.Contacts.DISPLAY_NAME_PRIMARY)
private val PERMISSION_REQUEST_READ_CONTACTS = 1
private val FROM_COLUMNS = arrayOf(ContactsContract.Contacts.DISPLAY_NAME_PRIMARY)
private val TO_IDS = intArrayOf(R.id.contact_info) }
private lateinit var mCursorAdapter: SimpleCursorAdapter

override fun onActivityCreated(savedInstanceState: Bundle?) {// First, call super...


mContactsListView = activity!!.findViewById(R.id.contact_list_view)
requestContacts()}

private fun requestContacts() {


if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (!hasReadContactPermission()) { requestPermissions(
arrayOf(Manifest.permission.READ_CONTACTS), PERMISSION_REQUEST_READ_CONTACTS) }
else { showContacts() }
} else { showContacts() } 40
}
Content Provider Example: Kotlin (2)
// ContactsFragment.kt, continued . . .
private fun hasReadContactPermission(): Boolean {
return activity?.checkSelfPermission(Manifest.permission.READ_CONTACTS) ==
PackageManager.PERMISSION_GRANTED }

override fun onRequestPermissionsResult( . . . ) {


if (requestCode == PERMISSION_REQUEST_READ_CONTACTS) {
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {showContacts()}}
else { /* Show error message */ }
}

private fun showContacts() {


// Gets a CursorAdapter
mCursorAdapter = SimpleCursorAdapter(activity, R.layout.list_item_contact,
null, FROM_COLUMNS, TO_IDS, 0)
// Sets the adapter for the ListView
mContactsListView.adapter = mCursorAdapter
// Initializes the loader
loaderManager.initLoader(0, null, this)
41
}
Content Provider Example: Kotlin (3)
// ContactsFragment.kt, continued . . .
override fun onCreateLoader(id: Int, args: Bundle): Loader<Cursor> {
return CursorLoader(activity, ContactsContract.Contacts.CONTENT_URI,
PROJECTION, null, null, ContactsContract.Contacts.DISPLAY_NAME +
" COLLATE LOCALIZED ASC")
}

override fun onLoadFinished(loader: Loader<Cursor>, data: Cursor) {


// Put the result Cursor in the adapter for the ListView
mCursorAdapter.swapCursor(data)
}

override fun onLoaderReset(loader: Loader<Cursor>) {


mCursorAdapter.swapCursor(null)
}
} // End of class definition

42
Even More?!
• Yes!
• More widgets, styles and themes
• Maps
• Email, media, telephony
• Sensors

43
Summary
• Activities: “Single screen” of content that user sees
– Can be paused, resumed by users
– Managing Activity lifecycle is crucial!
• Services: Long-running tasks
• Persistence:
– Files (internal, external storage)
– (Local) SQLite database
– Look at https://developer.android.com/guide/topics/
data/data-storage.html for info about Room ORM (easier to use
than SQLiteOpenHelper)
• Content Providers: mechanism for sharing data 44

You might also like