What I Learned for Android Development (Part II)
This is Part II of my notes on what I’ve learned on Android programming. See Part I here for a set of things that are more essential to a simple Android program.
Networking With Java Sockets
AsyncTask
Android has a design rule that some tasks cannot be performed in the main UI
thread. One of such cases is networking, which I happen to have to used a lot in
my project. One solution provided by the Android development framework is to use
AsyncTask.
Steps to use an AsyncTask:
- Define a class
MyAsyncTaskthat extends theAsyncTask<?, ?, ?>class with the three generics types being theParams,Progress, andReturn. - Create an object of the
MyAsyncTaskclass. The constructor can be used to initialize fields of theMyAsyncTaskclass. - In the main UI thread (and only here), call the
execute()method on the object. Provide a (possible empty) list of parameters of typeParamsfor the input. - Inside the
MyAsyncTaskclass, override at least thedoInBackground(Params... params)method. The return type of this function should beReturn. Theparamsvariable is an array of objects of typeParamsthat’s provided in theexecute()method. - In the
doInBackground(Params... params)method, thepublishProgress(Progress...)method can be called so that another overridden methodonProgressUpdate(Progress...)is called automatically to perform some UI feedback on the main UI thread. For example, you can grab a reference to a view of the calling activity and make some updates in this thread. - If code is needed for finishing up the execution of the
doInBackground(Params...)method, then another methodonPostExecute(Result)can be overridden. What I use in my project is often to perform some potentially long running background tasks and then show the user an update of something. Here is the place to do so. - Additionally, before
doInBackground(Params...)executes,onPreExecute()can be used to set up some field variables or preparation code.
Using Java Sockets
Java sockets in Android are supported almost identically as other Java programs.
Steps of using a Java socket in Android:
- Create a
ServerSocketwithnew ServerSocket(port)on the server side to listen to incoming connections. - Create a
Socketwithnew Socket(hostname, port)on the client side, where the hostname is the IP address of the server (as aString), and the port is the one used to create theServerSocket. TheSocket(String host, int port)constructor also connects the sockets automatically (if no exception is thrown). - Sockets talk with each other with streams. To send something out from a
Socket, grab a referencepOuttonew PrintWriter(new BufferedOutputStream(socket.getOutputStream())), and usepOut.println(str); pOut.flush()to put the stringstrout. - To read information from the peer
Socket, grab a referencepIntonew BufferedReader(new InputStreamReader(new BufferedInputStream(socket.getInputStream()))), and read strings frompInline by line usingpIn.readLine(). You can also read bit by bit if your custom communication protocol needs so. - Always a good habit to close the sockets with the
close()methods first on input and output streams and then on the sockets.
Database with SQLite
Database Creation
Android has full support for the SQLite database. Actually using SQLite databases in Android is one of the easiest part with enough caution.
Steps for creating a SQL database:
- Define a contract class that extends
BaseColumnsto hold string constants for table names, column names, etc. - Define some SQL strings that would be executed by the
execSQL(String sql)methods for creating, updating, and downgrading the database. One such string would be like the following:private static final String SQL_CREATE_MEATINFO_TABLE = "CREATE TABLE " + DatabaseContract.MetaInfoColumns.TABLE_NAME + "(" + DatabaseContract.MetaInfoColumns._ID + " INTEGER PRIMARY KEY" + COMMA_SEP + DatabaseContract.MetaInfoColumns.COLUMN_NAME_START_TIME + LONG_TYPE + COMMA_SEP + DatabaseContract.MetaInfoColumns.COLUMN_NAME_END_TIME + LONG_TYPE + ")"; - Define a helper class that extends
SQLiteOpenHelperand override theonCreate(SQLiteDatabase db),onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion), andonDowngrade(SQLiteDatabase db, int oldVersion, int newVersion)methods. - Call the super constructor
super(context, DATABASE_NAME, null, DATABASE_VERSION)to setup the database helper. - To obtain a reference to the actual SQLite database, instantiate a variable
dbHelperof the helper class and usedbHelper.getWritableDatabase()to get an instance, saydb.
Querying A Database
The usual SELECT statement in SQL is performed by the query method which takes eight parameters:
- A
Stringfor the table name. - A
Stringarray of the column names that you want to query. - A
WHEREclause string, for exampleID LIKE 3. The number can be replaced by a question mark?and replaced with the elements in the fourth parameter. - A
Stringarray with equal numbers of elements, each of which fills in a question mark?in the previous string. - The
GROUP BYclause string. - The
HAVINGclause string. - The
ORDER BYclause string. - The
LIMITclause string.
Once the above parameters are defined, a call to the query methods would
return a Cursor object cursor that contains the query result. Then you need
to parse the object to obtain the information. The steps are:
- Check if
cursorisnull. - Move to the first of the entry in
cursorwithcursor.moveToFirst(). - Then iterate over
cursoruntilcursor.moveToNext()givesfalse. - For each iteration, use something like
cursor.getString(cursor.getColumnIndex(DatabaseContract.EntryColumns.COLUMN_NAME_NAME)))to obtain the necessary information. Note that the return types would depend on the corresponding column types when you create the table.
Update Entries
The UPDATE statement in SQL is performed by the update method which takes
four parameters:
- A
Stringfor the table name. - A
ContentValuesobject holding maps from table column names to values you want to update. - A
WHEREclause string. - The arguments to the question marks in the
WHEREclause.
The execution of this method would return an integer representing the number of rows affected.
Deleting Entries
The DELETE statement in SQL is performed by the delete method which takes
three parameters (number 1, 3, 4 of those of the update method).
Database Transactions
There are several reasons transactions should be used for Android SQLite operations. The reason for me is the need for speed. Using transactions is actually very simple. The steps are:
- Use
db.beginTransaction();to start the transaction. - Do the usual database operations, which may be too many and a slow process.
Enclose this part in a
try-catchstatement. Putdb.endTransaction()in the catch statement. - Use
db.setTransactionSuccessful();to mark the successful end of database operations. - After the
try-catchstatement, calldb.setTransactionSuccessful();again. The magic of database transactions are that, ifendTransaction()is called beforesetTransactionSuccessful(), then the transaction is considered erroneous and rolled back; otherwise, every database operation betweenbeginTransaction()andsetTransactionSuccessful()is committed to the database.
Using the sqlite3 Utility
For debugging purposes, it is sometimes handy to see what’s actually stored in
the SQLite database file. The file is usually located at
/data/data/<App-Package>/databases/<db-name> on the Android device. However,
the folder /data is only accessible to root users. It cannot directly be
copied out by the File Explorer of the Eclipse IDE. adb pull does not help
either. I have rooted my device so it is possible to make a copy
of the file to some other places on the device and then use the Eclipse File
Explorer to get the file. Though a little inconvenient, but it works.
Once you’ve got the file, there is a utility called sqlite3 that can be used
to look at the data in the database. Simply use sqlite3 database.db to load
the file, and there are a handy list of commands you can use. Get information on
the commands with a .help command inside sqlite3.