Last active
October 16, 2017 15:09
-
-
Save IngmarBoddington/c7cac2de22c3bef49e576adaa5c36834 to your computer and use it in GitHub Desktop.
Notes from Android / Java Study for Learning Tree exam
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
IDE | |
====== | |
Recommended -> Android Studio | |
Common (Legacy) -> Eclipse with Android Development Tools (ADT) | |
Requires Android SDK + Java JDK | |
Applications are ran in an emulator -> Android Virtual Device (AVD) by the AVD Manager | |
Allows multiple configurations to be created | |
Different API versions and screen sizes | |
Detailed configuration, such as memory size and GPS emulation, available | |
Android applications can be developed on almost any platform | |
Android | |
===== | |
Based on Linux Kernal, open source | |
Android Runtime | |
Set of core libraries supporting most of the Java language | |
The Android Runtime Virtual Machine (ART VM) | |
Java virtual machine highly optimized for minimal memory | |
Runs class files converted to Dalvik Executable (.dex) format | |
Compiled to native code Ahead Of Time (AOT) | |
Prior to Android 5, the VM was called Dalvik | |
Libraries | |
C/C++ libraries providing low-level support to applications | |
System library—core operating system support | |
Media libraries, WebKit, SQLite, etc. | |
Exposed to developers through the application framework | |
Version history -> https://en.wikipedia.org/wiki/Android_version_history | |
Launches a seperate Linux process for each application, seperate Java VM | |
Google recommend that all projects include | |
Support-v4 | |
Appcompat-v7 | |
To test version (for newer feature checks) - v4 and above: | |
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.HONEYCOMB){ | |
return true; | |
} | |
return false; | |
Application Stores | |
===== | |
Google Play -> No approval process, 30% cut | |
Others -> “Unknown sources” must be enabled on the device to use them | |
Screen Sizes | |
===== | |
Android uses a resource system to support multiple resolutions | |
Four screen sizes defined -> small, normal, large, and xlarge | |
Six screen densities defined -> ldpi 120dpi, mdpi 160dpi, hdpi 240dpi, xhdpi 320dpi, xxhdpi 480dpi, xxxhdpi 640dpi | |
Build | |
===== | |
Gradle (written in a Groovy-based Domain-Specific Language (DSL)) used as build tool generally | |
Supports incremental builds by intelligently determining which parts of the | |
build tree are up-to-date | |
build.gradle files define build including dependencies | |
Packaging / Manifest | |
===== | |
All components are packaged in an .apk file | |
Manifest details contents of package - AndroidManifest.xml | |
Components cannot be used unless in the manifest | |
Example Manifest: | |
<manifest> | |
<uses-permission /> | |
<permission /> … | |
<uses-sdk /> | |
<uses-configuration /> | |
<uses-feature /> | |
<supports-screens />… | |
<application> | |
<activity>...</activity> | |
<activity-alias>...</activity-alias> | |
<service>...</service> | |
<receiver>...</receiver> | |
<provider>... </provider> | |
<uses-library /> | |
</application> | |
</manifest> | |
uses-permission A permission needed for the application to run: access to the camera, Internet, external storage, etc. | |
permission Allows restriction of access from other applications | |
uses-sdk Information about SDK versions; attributes specify minimum version needed to run, target SDK version, the maximum SDK version on which the application can run* | |
usesconfiguration Specific hardware configuration required; e.g., a physical keyboard* | |
uses-feature Hardware features required: Bluetooth, camera, GPS, etc.* | |
supports-screens Screen sizes supported—default since API level 4 is that all screen sizes are supported; use this, for instance, to state that small screens are not supported* | |
Google play uses the last four of the items above to determine whether to show an application to a user in the store | |
Manifest must specify permissions it uses | |
Which features of Android system it needs at runtime | |
android.Manifest.permission defines the base set of permission | |
Failure to specify a permission results in an AccessControlException | |
Unchecked: Can result in “silent failures” | |
Google Play warns user about the uses-permissions of the application they are installing | |
Example: | |
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> | |
<uses-permission android:name="android.permission.CAMERA" /> | |
<uses-permission android:name="android.permission.INTERNET" /> | |
activity, activity-alias, service, and receiver May have an intent-filter nested within them to allow receipt of Intents from other applications | |
Android 6.0 added fine-grained runtime permission control | |
User may grant/revoke permissions while an app is running! | |
If targetSdkVersions is 23 or higher | |
App must check permission prior to accessing the protected resource | |
If a permission is dangerous, then Android must check it | |
If permission is not granted, then Android will prompt | |
uses-permissionsmust still be declared in the manifest | |
Example: | |
private void doSomethingIfWeHavePermission(String permissionToCheck){ | |
if (ActivityCompat.checkSelfPermission(this, permissionToCheck) == PackageManager.PERMISSION_GRANTED) { | |
// Do the work that requires a permission here | |
} else { | |
String[] perms = {permissionToCheck}; | |
ActivityCompat.requestPermissions(this, perms, REQUEST_PERMISSIONS); | |
} | |
} | |
Android would then call back: | |
@Override | |
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { | |
if (requestCode == REQUEST_PERMISSIONS) { | |
if (grantResults.length == 1 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { | |
// Do the work originally requested | |
} else { | |
Toast.makeText(this, "Permission not granted“, Toast.LENGTH_LONG).show(); | |
} | |
} | |
} | |
For release, the package must be | |
Zipaligned – Optimizes the structure of the package for memory efficiency | |
Signed – Digitally signed to provide an anti-tamper mechanism | |
Google has introduced a license verification scheme | |
– http://developer.android.com/guide/publishing/licensing.html | |
Performance | |
===== | |
Do not run long processes in th primary (user interface) thread | |
May cause Application Not Responding (ANR) errors | |
Use services, AsyncTasks or intents | |
Activities | |
===== | |
The user interacts with an Android application through activities | |
Each activity is a single screen | |
Displays or captures data through a view (which may be split into fragments) | |
Activities are managed by the Android application framework | |
Main activity launched in response to user request | |
Responsible for | |
Capturing user input | |
Supporting the activity life cycle | |
Interacting with underlying data and business logic | |
Extends android.app.Activity, although generally we extend AppCompatActivity for compatibility | |
onCreate override for launch of activity from intent - called by framework when application is ready | |
Call the overridden method | |
Set the view for the activity | |
Example: | |
@Override | |
public void onCreate(Bundle savedInstanceState) { | |
super.onCreate(savedInstanceState); | |
setContentView(R.layout.main); | |
} | |
Can get the activity which launched the activity: | |
Intent getIntent() | |
Or retrieve the URI for a content provider / any other set data | |
Uri getData() | |
double getDoubleExtra(String name) | |
int getIntExtra(String name) | |
int[] getIntArrayExtra(String name) | |
Serializable and Parcelable extras must be cast to correct type | |
Type type = (Type) getIntent().getParcelableExtra("Name"); | |
Subactivities | |
===== | |
Can be launched using: | |
startActivityForResult (Intent launchIntent, int requestCode) | |
And the callback implementation: | |
protected void onActivityResult(int requestCode, int resultCode, Intent resultIntent) | |
The requestCode can be used to correlate the subactivity result | |
Activity class provides constants to check the resultCode | |
The subactivity must use setResult with option resultIntent and the finish | |
setResult(Activity.RESULT_OK, resultIntent); | |
finish() | |
Views | |
===== | |
Defined in XML | |
Defined in res folder of project | |
ID maps to location R.<folder>.<folder> where R = res | |
Visual portion of an activity, what the user sees | |
Set using setContentView: | |
setContentView(int resourceId) - Using declared resource item | |
setContentView(View view) - Using view object | |
Views can be organised in collections using ViewGroups (which are Views) | |
View contains widgets, such as: | |
TextView - Used to display text; optionally allows text to be edited | |
EditText - A subclass of TextView used to create edit fields on forms | |
AutoCompleteTextView - A text edit control with an associated list of autocomplete entries; autocomplete entries are shown in a drop-down list | |
CheckBox - A button with two states—checked or unchecked | |
RadioButton - A two-state button that can be grouped using RadioGroup so that only one button in the group can be checked | |
Button - A button that has an associated event handler View.OnClickListener() | |
ImageButton - Similar to the Button but supports an image rendered on its surface | |
Widgets can either be defined directly in code or using XML resource files (which is recommended) | |
Widget reference -> https://developer.android.com/reference/android/widget/package-summary.html | |
If declared in XML than can fetch from R: | |
example = (Type) findViewById(R.id.<identifier>); | |
Android provides several different layouts (which are ViewGroups) | |
Frame Layout: Child Views displayed as a stack, fixed to upper left of frame | |
Example: | |
<FrameLayout | |
xmlns:android="http://schemas.android.com/apk/res/android" | |
android:layout_width="match_parent" | |
android:layout_height="match_parent"> | |
<ImageView android:src="@drawable/emo_im_yelling" | |
android:id="@+id/frm_img_yell" android:visibility="invisible" /> | |
<Button android:text="Button" android:id="@+id/button1" … /> | |
</FrameLayout> | |
Linear Layout: Row or Column of children | |
Space is allocated in proportion to child layout_weight value | |
If multiple with highest value: space equally shared | |
Children may be assigned a gravity value | |
top, bottom, left, right, center_vertical, center_horizontal... | |
Example: | |
<LinearLayout … android:orientation="vertical"> | |
<TextView … | |
android:layout_weight="1" | |
android:layout_gravity="center_vertical" /> | |
<TextView … /> | |
</LinearLayout> | |
Table Layout | |
Each row is a <TableRow>, each View within a cell | |
Example: | |
<TableLayout … android:layout_width="match_parent" | |
android:layout_height="match_parent"> | |
<TableRow android:id="@+id/tableRow1" | |
android:layout_width="wrap_content" | |
android:layout_height="wrap_content"> | |
<TextView android:text="R1_C1"></TextView> | |
<TextView android:text="Row1_Column2" ></TextView> | |
<TextView android:text="R1_C3" ></TextView> | |
</TableRow> | |
</TableLayout> | |
Relative Layout | |
Child Views may be positioned relative to each other, or parent layout | |
layout_alignRight, layout_alignBelow, layout_alignParentRight, layout_alignParentBelow... | |
Example: | |
<RelativeLayout...> | |
<EditText android:layout_alignParentRight="true" android:text="EditText1" | |
android:id="@+id/eT1" /> | |
<EditText android:layout_below="@id/eT1" android:text="EditText2" | |
android:id="@+id/eT2" android:layout_toLeftOf="@id/eT1" /> | |
<EditText android:layout_below="@id/eT2" android:text="EditText3" | |
android:id="@+id/eT3" android:layout_toRightOf="@id/eT2" /> | |
<EditText android:layout_alignParentLeft="true" android:text="EditText4" | |
android:id="@+id/eT4" /> | |
</RelativeLayout> | |
++ Sliding Drawer, and Absolute | |
All Views may have height, width, and padding specifiers | |
layout_width, layout_height, paddingTop, paddingLeft, paddingRight, paddingBottom | |
match_parent - View should fill its parent (allowing space for any padding) | |
wrap_content - View should be as small as possible to wrap its content (allowing for padding) | |
Values may be specified as | |
px (pixels) | |
dp (density-independent pixels) | |
sp (scaled pixels based on preferred font size) | |
in (inches) | |
mm (millimeters) | |
Fragments | |
===== | |
Multi-pane user interfaces | |
A single activity may host multiple fragments | |
Has a view and some additional lifecycle methods | |
Multiple fragments can be active and can be optionally put on the backstack | |
Children of a ViewGroup | |
Five additional lifecycle methods: | |
onAttach The Fragment has been associated with an Activity | |
onCreateView The Fragment must create its View | |
onActivityCreated The parent Activity has completed its creation | |
onDestroyView The View associated with the Fragment is being destroyed | |
onDetach The Fragment is about to be disassociated from the Activity | |
A fragment must inflate it's own view (in the onCreateView method using inflater.inflate) | |
Can get / interact with parent activity | |
Activity parentActivity = getActivity() | |
Example declaration in Activity: | |
<LinearLayout android:orientation="horizontal" …> | |
<fragment class="com.ltree.expenses.ExampleListFragment" android:id="@+id/frag_example_list" android:layout_width=… /> | |
... | |
To load a Fragment dynamically: | |
ExampleFragment frag = new ExampleFragment(); | |
FragmentTransaction ft = getFragmentManager().beginTransaction(); | |
ft.replace(R.id.frag_example_details, frag); | |
ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN); | |
ft.commit(); | |
Can add to backstack (for back functionality): | |
ft.addToBackStack("name"); | |
Data can be passed into a Fragment from an Activity using a Bundle: | |
Bundle args = new Bundle(); | |
args.putLong("name", value); | |
frag.setArguments(args); | |
Which can then be fetched in the onCreate method of the activity: | |
example = getArguments().getLong("name"); | |
Good practice to set Framelayout's for dynamic Fragments: | |
<LinearLayout … | |
<FrameLayout android:id="@+id/frag_example_list" …/> | |
<FrameLayout android:id="@+id/frag_example_details" …/> | |
</LinearLayout> | |
Can then set one Framelayout to have a "gone" visibility in Potrait and dynamically add missing fragment to the backstack instead | |
Resources / res / R | |
===== | |
The layout XML files are stored within the resource directory structure | |
Drawables - res/drawable - Graphics and bitmaps | |
Image files (.png, .jpg, .gif) | |
Nine-patch file—a .png with stretchable areas | |
Should have a version in each of: | |
res/drawable-xhdpi: extra-high–density images | |
res/drawable-hdpi: high-density images | |
res/drawable-ldpi: low-density images | |
res/drawable-mdpi: medium-density images | |
– Android autofits it to the available space | |
Layouts - res/layout - Layout files | |
Menus - res/menu - XML declarations of menus | |
Strings - res/values - XML declaration of strings | |
Styles - res/values - XML declaration of styles and themes | |
R is an auto generated class based on the res directory contents, created during build of .apk | |
Example (Strings, same naming for other resource types) - normally res/values/strings.xml): | |
<resources> | |
<string name="hello">Hello Course 577</string> | |
<string name="app_name">Samples</string> | |
</resources> | |
Can then be referenced in Views: | |
<TextView android:layout_width="fill_parent" android:layout_height="wrap_content" | |
android:text="@string/hello"/> | |
In code: R.string.hello | |
Views are slightly different in ID structure (than the other resource types): | |
<EditText android:id="@+id/eT2" android:layout_below="@id/eT1" .../> | |
In code: findViewById(R.id.eT2) | |
Styles can be used instead of directives like android:textColor="#ffff4d6b" / android:typeface="monospace" | |
<style name="ButtonStyle" parent="@android:style/Widget.Button"> | |
<item name="android:textColor">#ffff4d6b</item> | |
<item name="android:typeface">monospace</item> | |
</style> | |
Apply a style in the manifest: | |
<application android:theme="@style/ApplicationTheme" > | |
Can also use localisation in resource directory structure | |
res/<type>-<languageCode>-<regionCode> | |
Language code is defined by a two-letter ISO 639 code | |
Region code is two-letter ISO 3166-1-alpha-2 preceded by an r | |
Specificity matters | |
Other qualifiers see https://developer.android.com/guide/topics/resources/providing-resources.html#AlternativeResources | |
Used generally for differ resolutions, orientation | |
Can use shortcuts to use to much repition, e.g: | |
<resources> | |
<item name="main" type="layout">@layout/narrow_layout</item> | |
</resources> | |
Intents | |
===== | |
Intents are sent to the Framework to request an activity | |
Explicit Intent: Specify a named class, can only be used within application | |
Implicit Intent: Request an action (resolved by the framework) | |
By the user, through interaction, by applications | |
Must be sent through the framework using startService() or startActivity() | |
Example: | |
Intent startSomethingIntent = new Intent(this, Something.class); | |
startService(startSomethingIntent); | |
Data can be attached to an intent: | |
setData(URI data) – Used to pass details about a content provider | |
putExtra(String name, xxx value) | |
Used to pass other data as name/value pairs | |
xxx may be a primitive, CharSequence, or arrays of these types | |
Object data may also be sent using the Parcelable or Serializable types | |
Serializable is part of the JDK | |
Serializable objects may be written to and read from a stream | |
Class be must be marked as implementing Serializable | |
No methods to implement | |
Parcelable classes may be converted to a Parcel | |
Intended to pass data between Android processes | |
Using the Android IPC mechanisms | |
Relatively complex to implement | |
Implicit Intent Example: | |
// Specify an Action in the constructor | |
Intent intent = new Intent("android.intent.action.VIEW"); | |
// Add optional categories | |
intent.addCategory("lt.samples.showAll"); | |
// Add the URI of the content provider | |
intent.setData(Uri.parse("content://com.ltree.weather/stations")); | |
startActivity(intent); | |
Intent Filters (For implicit intents) | |
===== | |
Activities, services, and broadcast receivers register IntentFilters | |
Specify Action, Category, and Data handled | |
Usually declared in the manifest | |
Registered automatically on installation | |
Implicit Intent resolution is handled by the PackageManager | |
Matches Action, Category, and Data | |
Of the Intent against the values in the IntentFilter | |
When the PackageManager resolves an Intent it compares the values in the Intent with available IntentFilters to select a target | |
Intent IntentFilter | |
Action A single action One or more. Intent action must match one. A filter with no actions matches any intent action. | |
Category Zero or more Zero or more. Every category in the Intent object must match a category in the filter. If the filter contains additional values, it will still match. | |
Data Zero or one. Specifies the URI of the data to be acted on Zero or more MIME types. Matches if one of the filter values matches the Intent content type. May refine the filter by specifying values for scheme, authority, and path (zero or more of each). | |
Example implementation: | |
<activity …> | |
<intent-filter> | |
<action android:name="android.intent.action.EDIT" /> | |
<category android:name="android.intent.category.DEFAULT" /> | |
<data android:mimeType="vnd.android.cursor.item/vnd.learningtree.station" /> | |
</intent-filter> | |
</activity> | |
#Specify a main activity (get launcher icon) | |
<intent-filter> | |
<action android:name="android.intent.action.MAIN" /> | |
<category android:name="android.intent.category.LAUNCHER" /> | |
</intent-filter> | |
Broadcast Reciever | |
===== | |
Recieves system intents | |
Example: | |
<receiver android:name="BootCompletedReceiver"> | |
<intent-filter> | |
<action android:name="android.intent.action.BOOT_COMPLETED"/> | |
</intent-filter> | |
</receiver> | |
To catch event and start a service: | |
public class BootCompletedReceiver extends BroadcastReceiver { | |
@Override | |
public void onReceive(Context context, Intent bootIntent) { | |
// Start a service here | |
Intent intent = new Intent(context, SimpleIntentService.class); | |
context.startService(intent); | |
} | |
} | |
Contexts | |
===== | |
Activities, services, and other components interact with the application framework via the Context object | |
Parent of both Activities and Services | |
Services | |
===== | |
Used for background / long running operations | |
Continue to run after associated activities are destroyed | |
Started in main thread so must start a thread for long running actions | |
Service -> IntentService -> MyService | |
IntentService can automatically launch a background thread | |
onCreate() override | |
Code for main thread | |
onHandleIntent() override | |
Code for background thread | |
When startService() is called for an IntentService, the system | |
1. Places the Intent on a queue (FIFO) | |
2. Starts the service if not already running | |
3. Invokes the service's onHandleIntent()for each Intent – In a background thread | |
4. Stops the service when all Intents have been processed | |
Example: | |
public class SyncService extends IntentService { | |
public SyncService() { | |
// Super-class constructor requires a name | |
// (only used for debugging) | |
super("SyncService"); | |
} | |
@Override | |
protected void onHandleIntent(Intent intent) { | |
// Background processing goes in here | |
// It can't access the UI as it is in a thread | |
Content Providers | |
===== | |
Provide access to a data set to one or more applications | |
Exposed through a standard table like model | |
Each ContentProvider is identified by a unique URI | |
Standard providers (bookmarks, contacts, etc.) have standard URIs built into the system | |
ContactsContract.Thing.PROPERTY | |
Custom providers—such as the Expenses app—must define a URI | |
content://com.company.app/thing/property | |
URIs generall follow a REST like standard to identify resources | |
ContentProviders present data in tabular form | |
The first column must be called _ID, is unique numeric, Used to identify record—the end of the provider URI | |
Other columns can be of any type | |
ContentResolver locates and communicate with content providers | |
protected void onResume() { | |
super.onResume(); | |
String[] projection = new String[] { Browser.BookmarkColumns._ID, Browser.BookmarkColumns.TITLE, Browser.BookmarkColumns.URL }; | |
String[] displayFields = new String[] { Browser.BookmarkColumns.TITLE, Browser.BookmarkColumns.URL }; | |
int[] displayViews = new int[] { R.id.text1, R.id.text2 }; | |
Cursor cur = getContentResolver().query( | |
Browser.BOOKMARKS_URI, | |
projection, null, null, null); | |
setListAdapter(new SimpleCursorAdapter(this, | |
R.layout.two_item_list, | |
cur, displayFields, displayViews)); | |
} | |
Example Insert: | |
ContentValues values = new ContentValues(); | |
values.put("<name>", value); | |
getContentResolver().insert(CONTENT_URI, values); | |
Example Delete (could also use where in query): | |
Uri uri = ContentUris.withAppendedId(CONTENT_URI, id); | |
getContentResolver().delete(uri, null, null); | |
Example Update: | |
ContentValues values = new ContentValues(); | |
values.put("<key>","<value>"); | |
String where = "<name> = ?"; | |
String[] selectionArgs = {"<arg>"}; | |
getContentResolver().update(uri, values, where, selectionArgs); | |
A loader can be used to load content in the background: | |
public class BookmarksLoaderActivity extends ListActivity implements LoaderManager.LoaderCallbacks<Cursor> { | |
public void onCreate(Bundle savedInstanceState) { | |
super.onCreate(savedInstanceState); | |
mProjection = new String[] { Browser.BookmarkColumns._ID,…}; | |
String[] displayFields = new String[] {…}; | |
mAdapter = new SimpleCursorAdapter(this, R.layout.two_item_list, | |
null, displayFields, displayViews,0); | |
setListAdapter(mAdapter); | |
getLoaderManager().initLoader(0, null, this); | |
} | |
public Loader<Cursor> onCreateLoader(int id, Bundle args) { | |
return new CursorLoader(this, android.provider.Browser.BOOKMARKS_URI, mProjection, null, null, null ); | |
} | |
public void onLoadFinished(Loader<Cursor> loader, Cursor data) { | |
mAdapter.swapCursor(data); | |
} | |
public void onLoaderReset(Loader<Cursor> loader) { | |
mAdapter.swapCursor(null); | |
} | |
} | |
To create own content provider: | |
public class MyContentProvider extends ContentProvider { | |
// Initialize the provider | |
public boolean onCreate(){…} | |
// Get the MIME type for the content | |
// Return one of | |
// vnd.android.cursor.item/vnd.<company>.<type> for single items | |
// vnd.android.cursor.dir/vnd.<company>.<type> for multiple items | |
// standard MIME type if this actually applies | |
public String getType(Uri uri) {…} | |
// Query the provider – results as a Cursor | |
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {…} | |
// Insert new data – supplied in ContentValues | |
public Uri insert(Uri uri, ContentValues values) {…} | |
// Update values specified by where clause in where, whereArgs | |
public int update(Uri uri, ContentValues values, String where, String[] whereArgs) {…} | |
// Delete – supplied a where clause in where, whereArgs | |
public int delete(Uri uri, String where, String[] whereArgs) {…} | |
} | |
In order to get an ID from the uri (# for int, * for string match): | |
#Add a pattern | |
private static final ID = 2; | |
sUriMatcher.addURI("<com.authority.identifier>", "<path>/#", ID); | |
#Match a pattern | |
sUriMatcher.match("content://<com.authority.identifier>/<path>/6"); //Returns 2 | |
#Can then switch on this value for implementation of methods | |
#If matches ID patter then can extract using: | |
long Id = ContentUris.parseId(uri); | |
#Inserts must return the new uri and dont need _ID | |
Register a content provider in the manifest: | |
<manifest … package="lt.samples" … > | |
<application> | |
... | |
<provider android:authorities="com.<company>.<app>" android:name=".db.SamplesCP" android:exported="false"> | |
</provider> | |
</application> | |
</manifest> | |
Storage | |
===== | |
Database (on or off the device) | |
Internal (on the device) | |
External (removable) | |
Broadcast Receivers | |
===== | |
Generally thin components designed to respond to a systemwide broadcast | |
Lifecycle | |
===== | |
When an activity is started it is put on the back stack, and the previous activity is stopped | |
Previous activity restarted on user pressing the back button generally, current activity is destroyed | |
Three states: | |
Resumed | |
Paused - May be killed by OS, obscured | |
Stopped - In background, may be killed by OS | |
Generally any data should be saved when a activity is removed from the foreground | |
Lifecycle methods (in order) | |
onCreate() | |
onStart() | |
onResume() | |
RUNNING STATE - In foreground | |
onPause() - can also go to onResume() | |
PAUSED - In background (partially obscured), can be cleaned up by OS (rare) | |
onStop() - can also go to onStart() via onRestart() | |
STOPPED STATE - In background (fully obscured), can be killed by OS | |
onDestroy() | |
DEAD - Killed | |
Android sees applications as nonresponsive if | |
They fail to respond to user input within 5 seconds | |
A BroadcastReceiver hasn’t finished executing within 10 seconds | |
Don't do long running work in the main thread (the UI thread) | |
The interface thread is not thread safe, updating from a background thread will fail | |
Creating a thread (main thread get priority, CPU time split between threads): | |
ExecutorService executorService = Executors.newSingleThreadExecutor(); | |
executorService.exec(new Runnable() { | |
@Override | |
public void run() { | |
// Do something in the background | |
} | |
}); | |
Or to run in main thread: | |
view.postDelayed(new Runnable() { | |
public void run() { | |
// Stuff to do after 500mS delay | |
} | |
},500); | |
AsyncTask | |
===== | |
For background processing that needs to interact with the user interface | |
AsyncTask is started by calling execute() on an instance of the class | |
Operation of AsyncTask | |
1. doInBackground(Params...): The main method for the background thread | |
2. onProgressUpdate(Progress...): Runs on UI thread – In response to call to publishProgress()from the background thread | |
3. onPostExecute(Result): Runs on UI thread – Result is the return value from doInBackground() | |
An instance of AsyncTask can only be started (execute() called) once, construct a new one to run again | |
Example AsyncTask: | |
class UpdateServerTask extends AsyncTask<String, Integer, Double> { | |
// Background processing (runs in the background thread) | |
protected Double doInBackground(String... params) { | |
return updateServer(urls[0]); | |
} | |
// Runs on the UI thread (parameter is result of doInBackground) | |
protected void onPostExecute(Double result) { | |
mTextViewResult.setText("Done: Result=" + result | |
} | |
// Runs on UI thread in response to call to publishProgress() | |
protected void onProgressUpdate(Integer... progress) { | |
mProgressBar.setProgress(progress[0]); | |
} | |
private Double updateServer(String url){ | |
while(stuffToDo){ | |
result = doWork(url); | |
publishProgress(percentComplete); | |
} | |
return result; | |
} | |
} | |
Services | |
===== | |
Perform long-running background tasks | |
Even when an application’s activities are paused or destroyed | |
But poor service design can still lead to ANR errors | |
Can be bound to an activity to create a view | |
Run in the main thread | |
Subclass android.app.Service and create code to | |
Start and stop the service | |
Manage background threading | |
Subclass android.app.IntentService | |
Android manages service start/stop and creates a thread for each request | |
Logging | |
===== | |
Android has a logging tech similar to log4j | |
Held in memory | |
LogCat displayes logs during development | |
Log.v (verbose), Log.d (debug), Log.i (information), Log.w (warning), Log.e (error) | |
Define a tag in the class to use with log lines | |
private static final String TAG="IntentService" | |
Toasts | |
===== | |
Invoke a short lived pop-up message: | |
static Toast makeText(Context context, int resId, int duration) | |
static Toast makeText(Context context, CharSequence text, int duration) | |
Status Bar | |
===== | |
Invoked by activities and services, clicking on expanded notification can launch activity | |
Example: | |
int icon = R.drawable.emo_im_yelling; | |
CharSequence notiTickerText = "NOTIFICATION! – Ticker Text"; | |
long notiTime = System.currentTimeMillis(); | |
CharSequence notiTitle = "A NOTIFICATION!"; | |
CharSequence notiContent ="I'm trying to tell you something!"; | |
Intent notificationIntent = new Intent(context, NotificationActivity.class); | |
PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, notificationIntent, 0); | |
Notification.Builder notiBuilder = new Notification.Builder(context); | |
notiBuilder.setSmallIcon(icon).setTicker(notiTickerText).setWhen(notiTime) | |
.setAutoCancel(true).setContentText(notiContent).setTitleText(notiTitle).setContentIntent(pendingIntent); | |
Notification notification = notiBuilder.build(); | |
String ns = Context.NOTIFICATION_SERVICE; | |
NotificationManager mNotificationManager = (NotificationManager) getSystemService(ns); | |
mNotificationManager.notify(SAMPLE_NOTIFICATION, notification); | |
Event Handling | |
===== | |
Common events: | |
Click - User clicks (touches widget or presses an “enter” key) | |
Long click - Same inputs as click, but held for one second | |
Focus change - User navigates onto or away from View item | |
Key - User presses a key while view has focus | |
Touch - Any touch event | |
Views have associated listener methods for the different events: | |
Click - View.OnClickListener | |
Long click - View.OnLongClickListener | |
Focus change - View.OnFocusChangeListener | |
Key - View.OnKeyListener | |
Touch - View.OnTouchListener | |
Example: | |
View but = findViewById(R.id.name); | |
but.setOnClickListener(new View.OnClickListener() { | |
@Override | |
public void onClick(View v) { | |
} | |
}); | |
Dialogs | |
===== | |
Pop-up window to which the user must respond | |
Implemented by DialogFragment | |
Example: | |
public void onClick(View v) { | |
DatePickerFragment newFragment = new DatePickerFragment(); | |
mExpenseDate = Calendar.getInstance(); | |
Bundle args = new Bundle(); | |
args.putSerializable(DatePickerFragment.INPUT_DATE_MS, mExpenseDate); | |
newFragment.setArguments(args); | |
newFragment.show(getActivity().getSupportFragmentManager(), "datePicker"); | |
} | |
public class DatePickerFragment extends DialogFragment implements DatePickerDialog.OnDateSetListener { | |
public static final String INPUT_DATE_MS = "Input Date in Millis"; | |
@Override | |
public Dialog onCreateDialog(Bundle savedInstanceState) { | |
final Calendar c = (Calendar) | |
getArguments().getSerializable(INPUT_DATE_MS); | |
return new DatePickerDialog(getActivity(), this, c.get(Calendar.YEAR), c.get(Calendar.MONTH), c.get(Calendar.DAY_OF_MONTH)); | |
} | |
public void onDateSet(DatePicker view, int y, int m, int d) { | |
final Calendar c = Calendar.getInstance(); | |
c.set(Calendar.YEAR, y); | |
c.set(Calendar.MONTH, m); | |
c.set(Calendar.DAY_OF_MONTH, d); | |
updateDate(c); | |
} | |
} | |
Menus | |
===== | |
Options Menu: When the menu button is pressed | |
Context Menus: Long click (like a right click) | |
Defined in res/menu | |
e.g. res/menu/menu.xml is identified by R.menu.menu | |
Example: | |
<menu xmlns:android="http://schemas.android.com/apk/res/android"> | |
<item android:id="@+id/menu_add" android:title="@string/menu_str_add" /> | |
<item android:id="@+id/menu_sync" android:title="@string/menu_str_sync" android:icon="@drawable/ic_menu_sync" /> | |
</menu> | |
In activity, override: | |
public boolean onCreateOptionsMenu(Menu menu) | |
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) | |
onCreateContextMenu() is only called if registerForContextMenu(View view) has previously been called | |
Example Inflation: | |
@Override | |
public boolean onCreateOptionsMenu(Menu menu) { | |
MenuInflater inflater = getMenuInflater(); | |
inflater.inflate(R.menu.name, menu); | |
return true; | |
} | |
Example Options menu handling: | |
public boolean onOptionsItemSelected(MenuItem item) { | |
switch (item.getItemId()) { | |
case R.id.menu_refresh: | |
//... | |
return true; | |
case R.id.menu_add: | |
//... | |
return true; | |
default: | |
return super.onOptionsItemSelected(item); | |
} | |
} | |
Action Bar | |
===== | |
Displays option menu items at top of screen | |
Options menu items may be displayed as actions | |
<menu …> | |
<item … android:icon="@drawable/ic_menu_add" android:showAsAction="ifRoom|withText" /> | |
Action bar is configured in the manifest | |
Automatically enabled if targetSdkVersion is >= 11 | |
May be disabled by setting the theme to a non-holographic theme | |
Adaptors and AdaptorViews | |
===== | |
Android provides many AdapterView subclasses | |
Each provides a different way of visually organizing items | |
Examples: | |
ListView - Displays items in a vertically scrolling list, uses ListAdapter | |
GridView - A two-dimensional scrolling grid view, uses ListAdapter | |
Spinner - Expanding list view; displays single selected item until activated, uses Adapter | |
Gallery - Horizontally scrolling list, typically of images, uses Adapter | |
The items are Views returned from the Adapter | |
Example (Using a list and ArrayAdapter): | |
#Main Activity | |
protected void onCreate(Bundle savedInstanceState) { | |
super.onCreate(savedInstanceState); | |
setContentView(R.layout.stations_list); | |
#Layout Res File | |
<LinearLayout … | |
<ListView android:layout_width="match_parent" android:layout_height="match_parent" android:id="@+id/listView" android:layout_weight="1" /> | |
</LinearLayout> | |
#Layout For Items Res File (Coult use R.layout defaults instead): | |
<TextView xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/text1" android:layout_width="match_parent" android:layout_height="match_parent" /> | |
#List Activity | |
ArrayList<String> theArrayList = getListData(); | |
ArrayAdapter<String> theListAdapter = new ArrayAdapter<String>(this, R.layout.list_item, theArrayList); | |
#Main Activity | |
ListView mList; | |
mList = (ListView) findViewById(R.id.listView); | |
mList.setAdapter(stationListAdapter); | |
Example (Using TextViews and SimpleCursorAdapter): | |
#R.layout.two_item_list | |
<LinearLayout …> | |
<TextView android:id="@+id/text1" …/> | |
<TextView android:id="@+id/text2" …/> | |
</LinearLayout> | |
#ShowDatabaseInListActivity.java | |
protected void onResume() { | |
String[] projection = new String[] { "<columnName>", "<columnName>", "<columnName>" }; | |
Cursor cursor = qb.query(mDb, projection, null, null, null, null, null); | |
String[] fromFields = new String[] { "<columnName>", "<columnName>" }; | |
int[] toViews = new int[] { R.id.text1, R.id.text2 }; | |
SimpleCursorAdapter cursorAdapter = new SimpleCursorAdapter(this, | |
R.layout.two_item_list, | |
cursor, | |
fromFields, toViews); | |
setListAdapter(cursorAdapter); | |
} | |
JUnit | |
===== | |
Android uses extentions of JUnit to allow for context / lifecycle based unit testing | |
ActivityTestCase, ProviderTestCase, ServiceTestCase, etc | |
Otherwise the same as in Java, test classes extend AndroidTestCase | |
How to create on list item click listener: | |
public void onItemClick(AdapterView<?> parent, View, int position, long id) | |
parent - The AdapterView where the click happened | |
view - The view that was clicked within the ListView | |
position - The position of the view in the list | |
id - The row ID of the item that was clicked | |
Use l.getItemAtPosition(position); to get item clicked | |
Example: | |
public class TheActivity extends Activity implements AdapterView.OnItemClickListener | |
protected void onCreate(Bundle savedInstanceState) { | |
mList = (ListView) findViewById(R.id.listView); | |
mList.setOnItemClickListener(this); | |
} | |
public void onItemClick(AdapterView<?> theList, | |
View view, int position, long id) { | |
String station = (String)theList.getItemAtPosition(position); | |
} | |
... | |
Changes to the Adapter data cascade generally, can be forced with notifyDataSetChanged() | |
Form Widgets (Views) | |
===== | |
Fetch with ID | |
<Type> name = (<Type>) findViewById(R.id.<id>); | |
Set content: | |
name.setText(<CharSequence>); | |
Fetch content: | |
CharSequence name = name.getText(); | |
Content Providers | |
===== | |
All types of storage can be accessed using a content provider | |
Storage - Shared Preferences | |
===== | |
For simple application preferences | |
Restricted to primitives and strings | |
Deleted when application is removed | |
Saved into memory private to the application /data/data/<packageName> | |
Access: | |
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); | |
<Type> name = prefs.get<Type>("<identifer>", false); #Where type is one of boolean, int, float, long, String | |
Edit: | |
SharedPreferences.Editor editor = prefs.edit(); | |
editor.put<Type>("<identifer>", <value>); #Where type is one of boolean, int, float, long, String | |
commit(); | |
Storage - Internal | |
===== | |
Android supports the standard Java API for file I/O with some additions specific to Android’s storage mechanisms | |
Read: | |
FileInputStream openFileInput (String name) | |
Write: | |
FileOutputStream openFileOutput (String name, int mode) | |
Storage - External | |
===== | |
Accessed much like internal, but must check for state: | |
String state = Environment.getExternalStorageState(); #Want Environment.MEDIA_MOUNTED or Environment.MEDIA_MOUNTED_READ_ONLY | |
Can have private or shared storage on external | |
Private locations: | |
File getExternalFilesDir(String type) #From Context | |
Shared locations: | |
File getExternalStoragePublicDirectory(String type) #From Environment | |
Use File.mkdirs() to create directories | |
Storage - SQLite | |
===== | |
Is self-contained, Is serverless, Requires zero configuration, Is transactional, Implements most of SQL92, Is written in C/C++ | |
Private to the application, unless shared with content providers | |
android.database.sqlite.SQLiteDatabase | |
Example class with onCreate implementation (called when DB does not exist): | |
class DatabaseHelper extends SQLiteOpenHelper { | |
private static final String DB_CREATE = "<SQL>"; | |
public void onCreate(SQLiteDatabase db) { | |
db.execSQL(DB_CREATE); //Does not return a result | |
} | |
Methods for insert, update, delete, query - use ContentValues to add key / value pairs | |
nullableColumnName in some methods requires name of a column which can contain NULL value - this protects against a SQLite limitiation (https://stackoverflow.com/questions/2662927/android-sqlite-nullcolumnhack-parameter-in-insert-replace-methods) | |
Example Insert: | |
SQLiteDatabase db = dbHelper.getWritableDatabase(); | |
ContentValues values = new ContentValues(); | |
values.put("<columnName>", "<value>"); | |
long rowId = db.insert("<Tablename>", "<nullableColumnName>", values); | |
Example Update: | |
String where = "<columnName> = ?"; | |
String[] selectionArgs = {"<values>"}; | |
int count = db.update("<tableName>", values, where, selectionArgs); | |
Example Select: | |
SQLiteDatabase db = dbHelper.getReadableDatabase(); | |
SQLiteQueryBuilder qb = new SQLiteQueryBuilder(); | |
qb.setTables("<table>"); | |
String where = "<columnName> = ?"; | |
String[] selectionArgs = {Long.toString(id)}; | |
Cursor c = qb.query( | |
db, | |
new String[] {"<columnToReturnInOrder>", "<columnToReturnInOrder>"}, | |
where, | |
selectionArgs, | |
"<groupBy>", | |
"<having>", | |
"<sort>" | |
); | |
while (c.moveToNext()) { | |
Log.i(TAG, "<columnNameOne>=" + c.get<Type>(0)); | |
Log.i(TAG, "<columnNameOne>=" + c.get<Type>(1)); | |
} | |
Networking | |
===== | |
Android provides good support for networked applications | |
java.net.*: basic Java networking support | |
android.net.*: additional network functionality for the Android platform | |
NetworkInfo: support for querying the network status | |
Wi-Fi or mobile network connection | |
Is the device roaming? | |
Etc. | |
Additional support for SSL in the mobile environment | |
URL sanitizers | |
Etc. | |
Can use sockets, basic http or web service clients | |
For maximum efficiency | |
Use the java.net.HttpURLConnection class for HTTP communication | |
To keep the application responsive | |
Connections must be performed in a background thread | |
Use AsyncTask or implement your own threading | |
Network connection made on the main thread will cause exceptions in API 11+ | |
Simple HTTP example: | |
private String fetchHTML(URL serviceUrl) throws Exception { | |
String result = "Nothing received! "; | |
HttpURLConnection urlConnection = (HttpURLConnection)serviceUrl.openConnection(); | |
try { | |
int responseCode = urlConnection.getResponseCode(); | |
if (responseCode == HttpURLConnection.HTTP_OK) { | |
result = readStringFromStream(urlConnection.getInputStream()); | |
} else { | |
result = readStringFromStream(urlConnection.getErrorStream()); | |
} | |
} catch (Exception e) { | |
Log.e(TAG, "Connection failed " + e.getMessage()); | |
} finally { | |
urlConnection.disconnect(); | |
} | |
return result; | |
} | |
Use getOutputStream to write a post request (as Bytes) | |
To build from stream: | |
private String readStringFromStream(InputStream in) { | |
StringBuilder sb = new StringBuilder(); | |
try (BufferedReader reader = new BufferedReader(new InputStreamReader(in));) { | |
String nextLine = ""; | |
while ((nextLine = reader.readLine()) != null) { | |
sb.append(nextLine); | |
} | |
} catch (IOException e) { | |
e.printStackTrace(); | |
} | |
return sb.toString(); | |
} | |
To create JSON to send to a web service, use JSONArray for arrays: | |
JSONObject jObj = new JSONObject(); | |
jObj.put("name", value); | |
jObj.put("name", value); | |
jObj.put("name", value); | |
String json = jObj.toString(); | |
To get data from JSON response example: | |
JSONTokener tokenizer = new JSONTokener(jsonString); | |
JSONObject wrapper = (JSONObject)tokenizer.nextValue(); | |
JSONArray things = wrapper.getJSONArray("thing"); | |
for (int i = 0; i < things.length(); i++) { | |
Log.i(TAG, "Description = " + things.getJSONObject(i).getString("name")); | |
} | |
Drag and Drop | |
===== | |
Android drag-and-drop has four stages in its life cycle | |
Start: The drag has been started | |
Continuing: Events created as potential targets are dragged over | |
Dropped: Notification that the item has been dropped | |
Ended: The drag has stopped—either dropped or released | |
Example Listener: | |
protected class TrashDragEventListener implements OnDragListener { | |
public boolean onDrag(View v, DragEvent event) { | |
switch (event.getAction()) { | |
case DragEvent.ACTION_DRAG_STARTED: // Drag has started | |
case DragEvent.ACTION_DRAG_ENTERED: // Drag is over this view | |
case DragEvent.ACTION_DRAG_EXITED: // Drag has left this view | |
case DragEvent.ACTION_DRAG_ENDED: // Drag finished | |
case DragEvent.ACTION_DROP: // Dropped in this view | |
return false; // true to accept the event | |
} | |
} | |
Can access passed data: | |
ClipData data = event.getClipData(); | |
ClipData.Item uriClipItem = data.getItemAt(0); | |
Set Listener with: | |
view.setOnDragListener(new View.OnDragListener()… | |
To start the drag: | |
public boolean onItemLongClick(AdapterView<?> av, View v, int pos, long id) { | |
... | |
Uri exampleUri = ContentUris.withAppendedId(intent.getData(), id); | |
ClipData data = ClipData.newUri(getActivity().getContentResolver(), "Drag data", exampleUri); | |
v.startDrag(data, new View.DragShadowBuilder(v), null, 0); | |
return true; | |
} | |
Geolocation | |
===== | |
Android provides several mechanisms for determining location | |
Global Positioning System | |
Cell-based—position based on the cell to which you are connected | |
Wi-Fi—position based on Google’s database of Wi-Fi names | |
Example to get last known location (Using GPS): | |
LocationManager locMan = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE); | |
Location lastKnownLocation = locMan.getLastKnownLocation(LocationManager.GPS_PROVIDER); | |
if (null != lastKnownLocation) { | |
Log.i(TAG,"Location. Lat: " + lastKnownLocation.getLatitude() + "lon" + lastKnownLocation.getLongitude()); | |
} | |
To get update events, register a LocationListener: | |
locMan.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0, new LocationListener() { | |
public void onLocationChanged(Location location) { | |
storePosition(location); | |
} | |
... | |
}); | |
Google Maps can be integrated easily using Android Studio (which automatically insert permissions into the manifest and map fragment) | |
Keys are required for dev and live for google maps | |
There are methods to add markers and navigate to given locations | |
Example: | |
private void setUpMap() { | |
Location location = (Location) getIntent().getParcelableExtra("FIX"); | |
LatLng pos = new LatLng(location.getLatitude(), | |
location.getLongitude()); | |
CameraPosition cameraPosition = new CameraPosition.Builder().target(pos).zoom(9).build(); | |
CameraUpdate camUpdate = CameraUpdateFactory.newCameraPosition(cameraPosition); | |
mMap.moveCamera(camUpdate); | |
mMap.animateCamera(camUpdate); | |
MarkerOptions marker = new MarkerOptions().position(pos).title("Location").draggable(false); | |
mMap.addMarker(marker); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment