ashur
ashur

Reputation: 4317

Interface is "blocked" until data is fetched

I have example application from "Android in practice" book that allows user to check share prices. Application creates URL string and does HTTP request. The request works in service that is placed in separate process, when app starts it "connects" to the service by using AIDL.

I don't know why when I launch it, the screen is black until application retreives data from the service. As I read the bindService() is doing its job asynchronously so it shouldn't be the problem.

Update - declaration of service in manifest.

    <service
        android:name=".PortfolioManagerService"
        android:icon="@drawable/icon"
        android:label="@string/service_name"
        android:process=":stocks_background" />

Here is code to main activity:

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(){

        public void onServiceConnected(ComponentName className, 
                IBinder service) {
            stockService = IStockService.Stub.asInterface(service);
            Log.d(LOGGING_TAG,"Connected to service");
            try {
                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);
            }
        }

        public void onServiceDisconnected(ComponentName className) {
            stockService = null;
            Log.d(LOGGING_TAG,"Disconnected from service");
        }

    };

    @Override
    public void onStart(){
        super.onStart();
        // create initial list
        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");
        }
        setListAdapter(new BaseAdapter(){

            public int getCount() {
                if (stocks == null){
                    return 0;
                }
                return stocks.size();
            }

            public Object getItem(int position) {
                if (stocks == null){
                    return null;
                }
                return stocks.get(position);
            }

            public long getItemId(int position) {
                if (stocks == null){
                    return 0L;
                }
                return stocks.get(position).getId();
            }

            public View getView(int position, View convertView, 
                    ViewGroup parent) {
                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 onCreate(Bundle savedInstanceState) {
        // Create UI elements, data loaded by <code>onStart</code>
        super.onCreate(savedInstanceState);
        setContentView(R.layout.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(){
            public void onClick(View v) {               
                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) {
                        // 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).show();
                        } else { 
                            refreshStockData();
                        }
                    }
                }.execute(stock);
            }
        });
    }

    @Override
    public void onPause(){
        super.onPause();
        if (bound){
            bound = false;
            unbindService(connection);
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        // disconnect from the stock service
        if (bound) {
            bound = false;
            unbindService(connection);
        }
    }

    // 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 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).show();
                    }
                }

                @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;
                }
            }.execute();

        }
    }

    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();
    }
}

Here is code to fetching data from web in service class (Note that I only copied this method from that class).

private ArrayList<Stock> fetchStockData(Stock[] stocks) throws IOException {
    Log.d(TAG, "Fetching stock data from Yahoo");
    ArrayList<Stock> newStocks = new ArrayList<Stock>(stocks.length);
    if (stocks.length > 0) {
        StringBuilder sb = new StringBuilder();
        for (Stock stock : stocks) {
            sb.append(stock.getSymbol());
            sb.append('+');
        }
        sb.deleteCharAt(sb.length() - 1);
        String urlStr = "http://finance.yahoo.com/d/quotes.csv?f=sb2n&s="
                + sb.toString();
        HttpClient client = new DefaultHttpClient();
        HttpGet request = new HttpGet(urlStr.toString());
        HttpResponse response = client.execute(request);
        BufferedReader reader = new BufferedReader(new InputStreamReader(
                response.getEntity().getContent()));
        String line = reader.readLine();
        int i = 0;
        Log.d(TAG, "Parsing stock data from Yahoo");
        while (line != null) {
            Log.d(TAG, "Parsing: " + line);
            String[] values = line.split(",");
            Stock stock = new Stock(stocks[i], stocks[i].getId());
            stock.setCurrentPrice(Double.parseDouble(values[1]));
            stock.setName(values[2]);
            Log.d(TAG, "Parsed Stock: " + stock);
            newStocks.add(stock);
            line = reader.readLine();
            i++;
        }
    }
    return newStocks;
}

Upvotes: 0

Views: 95

Answers (1)

Merlevede
Merlevede

Reputation: 8170

I don't know why when I launch it, the screen is black until application retreives data from the service.

A service runs in the same process as the application. The code to bind to the service and the callback from the ServiceConnection... everything runs in the main thread. You're wrong when you say bindService() is working asynchronously.

You still need to create a separate thread to do the job, to prevent the UI from blocking.

The documentation says:

Caution: A service runs in the main thread of its hosting process—the service does not create its own thread and does not run in a separate process (unless you specify otherwise). This means that, if your service is going to do any CPU intensive work or blocking operations (such as MP3 playback or networking), you should create a new thread within the service to do that work. By using a separate thread, you will reduce the risk of Application Not Responding (ANR) errors and the application's main thread can remain dedicated to user interaction with your activities.

Upvotes: 3

Related Questions