IPC (InterProcess cummunication)mechanism. This mechanism allows Services to be exposed to other processes and for serialized data to be sent between the processes.
create The Stock class, a Parcelable class that can be sent over IPC:
//The Stock class, a Parcelable class that can be sent over IPC.
//sent back and forth between main application and background service.
public class Stock implements Parcelable {
// user defined
private String symbol;
private double maxPrice;
private double minPrice;
private double pricePaid;
private int quantity;
// dynamic retrieved
private String name ="";
private double currentPrice=0D;
// db assigned
private int id;
//private constructor for Parcel
private Stock(Parcel parcel){
this.readFromParcel(parcel);
}
// constructors
public Stock(String symbol, double pricePaid, int quantity, int id) {
this.symbol = symbol;
this.pricePaid = pricePaid;
this.quantity = quantity;
this.id = id;
}
public Stock(String symbol, double pricePaid, int quantity) {
this(symbol, pricePaid, quantity, 0);
}
public Stock(Stock old, int id){
this.symbol = old.symbol;
this.maxPrice = old.maxPrice;
this.minPrice = old.minPrice;
this.pricePaid = old.pricePaid;
this.quantity = old.quantity;
this.name = old.name;
this.currentPrice = old.currentPrice;
this.id = id;
}
// getters and setters
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getCurrentPrice() {
return currentPrice;
}
public void setCurrentPrice(double currentPrice) {
this.currentPrice = currentPrice;
}
public String getSymbol() {
return symbol;
}
public double getMaxPrice() {
return maxPrice;
}
public double getMinPrice() {
return minPrice;
}
public double getPricePaid() {
return pricePaid;
}
public int getQuantity() {
return quantity;
}
public void setMaxPrice(double maxPrice) {
this.maxPrice = maxPrice;
}
public void setMinPrice(double minPrice) {
this.minPrice = minPrice;
}
public int getId() {
return id;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
if (name != null){
sb.append(name).append(' ');
}
sb.append('(').append(symbol.toUpperCase()).append(')')
.append(" $").append(currentPrice);
return sb.toString();
}
/**
* Any Parcelable needs a static field called CREATOR that
* acts as a factory class for the Parcelable.
* deserialize that Parcel back into a Stock.
*/
public static final Parcelable.Creator<Stock> CREATOR = new Parcelable.Creator<Stock>() {
//a factory method
public Stock createFromParcel(Parcel source) {
return new Stock(source);
}
public Stock[] newArray(int size) {
return new Stock[size];
}
};
public int describeContents() {
return 0;
}
//Serialize stock to Parcel
public void writeToParcel(Parcel parcel, int flags) {
parcel.writeString(symbol);
parcel.writeDouble(maxPrice);
parcel.writeDouble(minPrice);
parcel.writeDouble(pricePaid);
parcel.writeInt(quantity);
parcel.writeDouble(currentPrice);
parcel.writeString(name);
}
//read values from the Parcel in the same order as you wrote them to the Parcel
//Deserialize stock from Parcel
public void readFromParcel(Parcel parcel){
symbol = parcel.readString();
maxPrice = parcel.readDouble();
minPrice = parcel.readDouble();
pricePaid = parcel.readDouble();
quantity = parcel.readInt();
currentPrice = parcel.readDouble();
name = parcel.readString();
}
}
create the StocksDb class:
/**
* A data access object for persisting and retrieving stock data. This uses
* a SQLite database for persistence and retrieval.
*/
public class StocksDb {
private static final String TAG = "StocksDb";
// database metadata
private static final String DB_NAME = "stocks.db";
private static final int DB_VERSION = 1;
private static final String TABLE_NAME = "stock";
// column names
private static final String ID = "id";
private static final String SYMBOL = "symbol";
private static final String MAX_PRICE = "max_price";
private static final String MIN_PRICE = "min_price";
private static final String PRICE_PAID = "price_paid";
private static final String QUANTITY = "quantity";
private static final String CURRENT_PRICE = "current_price";
private static final String NAME = "name";
// SQL statements
private static final String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME +
" ("+ID+" INTEGER PRIMARY KEY, "+SYMBOL+" TEXT, "+
MAX_PRICE+" DECIMAL(8,2), " + MIN_PRICE+" DECIMAL(8,2), " +
PRICE_PAID+ " DECIMAL(8,2), " + QUANTITY + " INTEGER, " +
CURRENT_PRICE + " DECIMAL(8,2), "+NAME+" TEXT)";
private static final String INSERT_SQL = "INSERT INTO " + TABLE_NAME +
" ("+SYMBOL+", "+MAX_PRICE+", "+MIN_PRICE+", "+PRICE_PAID+
", "+QUANTITY+", " + CURRENT_PRICE+", "+NAME+") " +
"VALUES (?,?,?,?,?,?,?)";
private static final String READ_SQL = "SELECT "+ID+", "+SYMBOL+", " +
MAX_PRICE+", " + MIN_PRICE +", "+PRICE_PAID+", "+
QUANTITY+", " +CURRENT_PRICE+ ", "+NAME+" FROM " +
TABLE_NAME;
private static final String UPDATE_SQL = "UPDATE " + TABLE_NAME +
" SET "+CURRENT_PRICE+"=? WHERE "+ID+"=?";
// The Context object that created this StocksDb
private final Context context;
private final SQLiteOpenHelper helper;
private SQLiteStatement stmt;
private SQLiteStatement updateStmt;
private final SQLiteDatabase db;
/**
* Constructor that takes a Contex object, usually the
* Service or Activity that created this
* instance. This will initialize the SQLiteOpenHelper used for the
* database, and pre-compile the insert and update SQL statements.
*
* @param ctx The <code>Context</code> that created this instance
*/
public StocksDb(Context ctx){
context = ctx;
// initialize the database helper
helper = new SQLiteOpenHelper(context, DB_NAME, null, DB_VERSION){
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(CREATE_TABLE);
Log.d(TAG, "Created table: \n" + CREATE_TABLE);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion,
int newVersion) {
throw new UnsupportedOperationException();
}
};
// open the database
db = helper.getWritableDatabase();
// pre-compile statements
stmt = db.compileStatement(INSERT_SQL);
updateStmt = db.compileStatement(UPDATE_SQL);
}
/**
* Saves a <code>Stock</code> to the database.
*/
public Stock addStock(Stock stock){
Log.d(TAG, "Adding stock to db, stock="+stock);
stmt.bindString(1, stock.getSymbol());
stmt.bindDouble(2, stock.getMaxPrice());
stmt.bindDouble(3, stock.getMinPrice());
stmt.bindDouble(4, stock.getPricePaid());
stmt.bindLong(5, stock.getQuantity());
stmt.bindDouble(6, stock.getCurrentPrice());
stmt.bindString(7, stock.getName());
int id = (int) stmt.executeInsert();
return new Stock (stock, id);
}
/**
* Updates the current price of a <code>Stock</code> stored in the
* database.
*/
public void updateStockPrice(Stock stock){
Log.d(TAG, "Updating stock price in DB stock="+stock.toString());
updateStmt.bindDouble(1, stock.getCurrentPrice());
updateStmt.bindLong(2, stock.getId());
updateStmt.execute();
}
/**
* Retrieve all of the <code>Stock</code>s stored in the database.
*
* @return List of all of the Stocks stored in the database.
*/
public ArrayList<Stock> getStocks() {
Log.d(TAG, "Getting stocks form DB");
Cursor results = db.rawQuery(READ_SQL, null);
ArrayList<Stock> stocks = new ArrayList<Stock>(results.getCount());
if (results.moveToFirst()){
int idCol = results.getColumnIndex(ID);
int symbolCol = results.getColumnIndex(SYMBOL);
int maxCol = results.getColumnIndex(MAX_PRICE);
int minCol = results.getColumnIndex(MIN_PRICE);
int priceCol = results.getColumnIndex(PRICE_PAID);
int quanitytCol = results.getColumnIndex(QUANTITY);
int currentPriceCol = results.getColumnIndex(CURRENT_PRICE);
int nameCol = results.getColumnIndex(NAME);
do {
Stock stock = new Stock(results.getString(symbolCol),
results.getDouble(priceCol),
results.getInt(quanitytCol), results.getInt(idCol));
stock.setMaxPrice(results.getDouble(maxCol));
stock.setMinPrice(results.getDouble(minCol));
stock.setCurrentPrice(results.getDouble(currentPriceCol));
stock.setName(results.getString(nameCol));
Log.d(TAG, "Stock from db = " + stock.toString());
stocks.add(stock);
} while (results.moveToNext());
}
if (!results.isClosed()){
results.close();
}
return stocks;
}
/**
* Method to close the underlying database connection.
*/
public void close(){
helper.close();
}
}
Android IDL or AIDL.
create the Stock.aidl :
package example.stockportfolio;
parcelable Stock;
create the IStockService.aidl,The external interface into the stock portfolio service.
package example.stockportfolio.service;
import example.stockportfolio.Stock;
interface IStockService{
Stock addToPortfolio(in Stock stock);
List<Stock> getPortfolio();
}
you can only import other AIDL definitions.It only exposes two methods to the outside world.
A Java interface can be generated from the interface defined in the .aidl file.
Java interface generated from AIDL interface
public interface IStockService extends android.os.IInterface
{
/** Local-side IPC implementation stub class. */
public static abstract class Stub extends android.os.Binder
implements com.flexware.stocks.service.IStockService
{
// generated code
}
public void addToPortfolio(com.flexware.stocks.Stock stock)
throws android.os.RemoteException;
public java.util.List<com.flexware.stocks.Stock> getPortfolio()
throws android.os.RemoteException;
}
......
You’ll want to extend this abstract class,stub implementing the IStockService methods.
change the background service class PortfolioManagerService
public class PortfolioManagerService extends Service {
//Helper class for persisted data
private final StocksDb db=new StocksDb(this);
/*
* a Service usually run in its own process.Interprocess communication (IPC) is necessary.
* The onBind method is where the IPC channel is established.
*/
@Override
public IBinder onBind(Intent intent) {
// TODO Auto-generated method stub
//subclass extends Binder.return.
return new IStockService.Stub() {
@Override
public List<Stock> getPortfolio() throws RemoteException {
// TODO Auto-generated method stub
return db.getStocks();
}
@Override
public Stock addToPortfolio(Stock stock) throws RemoteException {
return db.addStock(stock);
}
};
}
}
create the main activity class ViewStocks
//The main Activity binding to and call the Service
public class ViewStocks extends ListActivity {
private static final String LOGGING_TAG = "ViewStocks";
// The list of stocks shown to the user
private ArrayList<Stock> stocks;
// Service used to persist and retrieve stocks
private IStockService stockService;
// Is the service bound currently?
private boolean bound = false;
// Connection to the stock service, handles lifecycle events
private ServiceConnection connection=new ServiceConnection(){
@Override
public void onServiceConnected(ComponentName className, IBinder service) {
// TODO Auto-generated method stub
stockService=IStockService.Stub.asInterface(service);
Log.d(LOGGING_TAG,"Connected to service");
try {
//refresh();
stocks=(ArrayList<Stock>)stockService.getPortfolio();
if(stocks==null){
stocks=new ArrayList<Stock>(0);
Log.d(LOGGING_TAG, "No stocks returned from service");
}
else{
Log.d(LOGGING_TAG, "Got "+ stocks.size() +" stocks from service");
}
refresh();
} catch (RemoteException e) {
Log.e(LOGGING_TAG, "Exception retrieving portfolio from service",e);
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
// TODO Auto-generated method stub
stockService=null;
}
};
@Override
public void onStart(){
super.onStart();
// create initial list
//bind to remote service
if(!bound){
bound=bindService(new Intent(ViewStocks.this,PortfolioManagerService.class),connection,Context.BIND_AUTO_CREATE);
Log.d(LOGGING_TAG, "Bound to service: " + bound);
}
if (!bound){
Log.e(LOGGING_TAG, "Failed to bind to service");
throw new RuntimeException("Failed to find to service");
}
this.setListAdapter(new BaseAdapter(){
@Override
public int getCount() {
// TODO Auto-generated method stub
if(stocks==null){
return 0;
}
return stocks.size();
}
@Override
public Object getItem(int position) {
// TODO Auto-generated method stub
if(stocks==null){
return null;
}
return stocks.get(position);
}
@Override
public long getItemId(int position) {
// TODO Auto-generated method stub
if(stocks==null){
return 0L;
}
return stocks.get(position).getId();
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
// TODO Auto-generated method stub
if(convertView==null){
LayoutInflater inflater=getLayoutInflater();
convertView=inflater.inflate(R.layout.stock, parent,false);
}
TextView rowTxt=(TextView)convertView.findViewById(R.id.rowTxt);
rowTxt.setText(stocks.get(position).toString());
return convertView;
}
@Override
public boolean hasStableIds() {
return true;
}
});
}
@Override
public void onPause(){
super.onPause();
if(bound){
bound=false;
unbindService(connection);
}
}
@Override
public void onCreate(Bundle savedInstanceState) {
// Create UI elements, data loaded by <code>onStart</code>
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// add widgets
final EditText symbolIn = (EditText) findViewById(R.id.inputSymbol);
final EditText maxIn = (EditText) findViewById(R.id.inputMax);
final EditText minIn = (EditText) findViewById(R.id.inputMin);
final EditText priceIn = (EditText) findViewById(R.id.inputPrice);
final EditText quantIn = (EditText) findViewById(R.id.inputQuant);
// Add event handler to button
Button button = (Button) findViewById(R.id.btn);
button.setOnClickListener(new OnClickListener(){
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
String symbol = symbolIn.getText().toString();
symbolIn.setText("");
double max = Double.parseDouble(maxIn.getText().toString());
maxIn.setText("");
double min = Double.parseDouble(minIn.getText().toString());
minIn.setText("");
double pricePaid = Double.parseDouble(priceIn.getText().toString());
priceIn.setText("");
int quantity = Integer.parseInt(quantIn.getText().toString());
quantIn.setText("");
Stock stock=new Stock(symbol,pricePaid,quantity);
stock.setMaxPrice(max);
stock.setMinPrice(min);
// Add stock to portfolio using service in the background
new AsyncTask<Stock,Void,Stock>(){
@Override
protected Stock doInBackground(Stock... newStocks) {
// TODO Auto-generated method stub
// There can be only one!
try{
return stockService.addToPortfolio(newStocks[0]);
} catch (RemoteException e) {
Log.e(LOGGING_TAG, "Exception adding stock " +"to portfolio", e);
}
return null;
}
@Override
protected void onPostExecute(Stock s){
Log.d(LOGGING_TAG, "Stock returned from service: " + s);
if (s == null){
Log.w(LOGGING_TAG, "Stock returned from Service " +
"was null or invalid");
Toast.makeText(ViewStocks.this, "Stock not found", Toast.LENGTH_SHORT);
} else {
refreshStockData();
}
}
}.execute(stock);
}
});
}
@Override
public void onDestroy(){
super.onDestroy();
// disconnect from the stock service
unbindService(connection);
}
//Refresh UI when data is retrieved
private void refresh(){
Log.d(LOGGING_TAG, "Refreshing UI with new data");
for (Stock s : stocks){
Log.d(LOGGING_TAG, "Got stock: " + s.toString());
}
BaseAdapter adapter=(BaseAdapter)this.getListAdapter();
adapter.notifyDataSetChanged();
}
//Update stock data from the service and refresh the UI
private void refreshStockData(){
if (stocks != null && stocks.size() > 0){
new AsyncTask<Void, Void, ArrayList<Stock>>(){
@Override
protected ArrayList<Stock> doInBackground(Void... nada){
try {
return (ArrayList<Stock>) stockService.getPortfolio();
} catch (Exception e) {
Log.e(LOGGING_TAG, "Exception getting stock data", e);
}
return null;
}
@Override
protected void onPostExecute(ArrayList<Stock> result) {
Log.d(LOGGING_TAG, "Got new stock data from service");
if (result != null){
stocks = result;
refresh();
} else {
Toast.makeText(ViewStocks.this, "Exception getting " +"latest stock data", Toast.LENGTH_SHORT);
}
}
}.execute();
}
}
}
at the same time,create the stock.xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:id="@+id/rowTxt"
android:layout_width="wrap_content" />
</LinearLayout>
change the acitivity_main.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<LinearLayout android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_gravity="top|left">
<EditText
android:id="@+id/inputSymbol"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:inputType="textNoSuggestions"
android:hint="@string/hintSymbol"/>
<EditText
android:id="@+id/inputMin"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:inputType="numberDecimal"
android:hint="@string/hintMin"/>
<EditText
android:id="@+id/inputMax"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:inputType="numberDecimal"
android:hint="@string/hintMax"
/>
</LinearLayout>
<LinearLayout android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom|right">
<EditText
android:id="@+id/inputPrice"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:inputType="numberDecimal"
android:hint="@string/hintPrice"/>
<EditText
android:id="@+id/inputQuant"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:inputType="number"
android:hint="@string/hintQuant"/>
<Button
android:id="@+id/btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/btnLbl"/>
</LinearLayout>
<ListView
android:id="@android:id/list"
android:layout_width="fill_parent"
android:layout_height="wrap_content" />
</LinearLayout>
change the strings.xml, add:
<string name="hintSymbol">Stock Symbol</string>
<string name="hintMin">Min. Price</string>
<string name="hintMax">Max Price</string>
<string name="hintPrice">Price Paid</string>
<string name="hintQuant">Quantity</string>
<string name="btnLbl">Add to Portfolio</string>
<string name="app_name">StockManager</string>
<string name="service_name">Stock Portfolio Service</string>