0

I have a service bound to an activity. The service will access a JSON web service. I would like to create a thread within the activity that calls a member function of the bound service but I am receiving an error.

The following example functions perfectly if I don't use a thread.

Here is the activity:

import java.util.ArrayList;

import android.app.ListActivity;
import android.app.ProgressDialog;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;


public class ProjectsListActivity extends ListActivity {

    ApiService mService;
    boolean mBound = false;

    private static String TAG = "tag";
    private ProgressDialog mProgressDialog = null;
    private ArrayList<Project> mProjects = null;
    private ProjectAdapter mAdapter;
    private Runnable viewProjects;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // set the layout
        setContentView(R.layout.project_list_item);

        // setup the custom list adapter
        mProjects = new ArrayList<Project>();
        this.mAdapter = new ProjectAdapter(this, R.layout.row, mProjects);
        setListAdapter(this.mAdapter);

        ListView lv = getListView();
        lv.setTextFilterEnabled(true);

        lv.setOnItemClickListener(new OnItemClickListener() {
            public void onItemClick(AdapterView<?> parent, View view,
                    int position, long id) {

                if (mBound) {
                    Project p = mProjects.get(position);
                    Toast.makeText(getApplicationContext(), p.getName(),
                            Toast.LENGTH_SHORT).show();
                }
            }
        });

        // use a thread to get the projects
        viewProjects = new Runnable() {
            @Override
            public void run() {
                getProjects();
            }
        };
        Thread thread = new Thread(null, viewProjects, "MagentoBackground");
        thread.start();
        mProgressDialog = ProgressDialog.show(ProjectsListActivity.this,
                "Please wait...", "Retrieving data ...", true);

    }

    @Override
    protected void onStart() {
        super.onStart();
        // Bind to LocalService
        Intent intent = new Intent(this, ApiService.class);
        bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
    }

    @Override
    protected void onStop() {
        super.onStop();
        // Unbind from the service
        if (mBound) {
            unbindService(mConnection);
            mBound = false;
        }
    }

    /** Defines callbacks for service binding, passed to bindService() */
    private ServiceConnection mConnection = new ServiceConnection() {

        @Override
        public void onServiceConnected(ComponentName className, IBinder service) {
            mService = ((ApiService.LocalBinder)service).getService();
            mBound = true;
        }

        @Override
        public void onServiceDisconnected(ComponentName arg0) {
            mBound = false;
        }
    };

    private class ProjectAdapter extends ArrayAdapter<Project> {

        private ArrayList<Project> items;

        public ProjectAdapter(Context context, int textViewResourceId,
                ArrayList<Project> items) {
            super(context, textViewResourceId, items);
            this.items = items;
        }

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            View v = convertView;
            if (v == null) {
                LayoutInflater vi = (LayoutInflater) getContext()
                        .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
                v = vi.inflate(R.layout.row, parent, false);
            }
            Project o = items.get(position);
            if (o != null) {
                TextView tt = (TextView) v.findViewById(R.id.toptext);
                TextView bt = (TextView) v.findViewById(R.id.bottomtext);
                if (tt != null) {
                    tt.setText("Name: " + o.getName());
                }
                if (bt != null) {
                    bt.setText("Status: " + o.getId());
                }
            }
            return v;
        }
    }

    private Runnable returnRes = new Runnable() {
        @Override
        public void run() {
            if (mProjects != null && mProjects.size() > 0) {
                mAdapter.notifyDataSetChanged();
                for (int i = 0; i < mProjects.size(); i++)
                    mAdapter.add(mProjects.get(i));
            }
            mProgressDialog.dismiss();
            mAdapter.notifyDataSetChanged();
        }
    };

    private void getProjects() {
        try {
            mProjects = mService.getUserProjects();
            Log.i(TAG, "" + mProjects.size());
        } catch (Exception e) {
            Log.e("BACKGROUND_PROC", "Exception ["+e.getMessage()+"]", e);
        }
        runOnUiThread(returnRes);
    }

}

Here is the service:

import java.util.ArrayList;

import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;


public class ApiService extends Service {

    // Binder given to clients
    private final IBinder mBinder = new LocalBinder();

    /**
     * Class used for the client Binder. Because we know this service always
     * runs in the same process as its clients, we don't need to deal with IPC.
     */
    public class LocalBinder extends Binder {
        ApiService getService() {
            return ApiService.this;
        }
    }

    @Override
    public IBinder onBind(Intent intent) {
        return mBinder;
    }

    public ArrayList<Project> getUserProjects() {

        ArrayList<Project> mProjects = new ArrayList<Project>();
        Project p1 = new Project();
        p1.setName("Project 1");
        p1.setId("Pending");
        Project p2 = new Project();
        p2.setName("Project 2");
        p2.setId("Completed");
        mProjects.add(p1);
        mProjects.add(p2);

        return mProjects;
    }
}

The error I receive:

06-10 18:45:32.736: ERROR/BACKGROUND_PROC(285): Exception [null]
06-10 18:45:32.736: ERROR/BACKGROUND_PROC(285): java.lang.NullPointerException
06-10 18:45:32.736: ERROR/BACKGROUND_PROC(285):     at ProjectsListActivity.getProjects(ProjectsListActivity.java:160)
06-10 18:45:32.736: ERROR/BACKGROUND_PROC(285):     at ProjectsListActivity.access$3(ProjectsListActivity.java:158)
06-10 18:45:32.736: ERROR/BACKGROUND_PROC(285):     at ProjectsListActivity$4.run(ProjectsListActivity.java:67)
06-10 18:45:32.736: ERROR/BACKGROUND_PROC(285):     at java.lang.Thread.run(Thread.java:1096)

If this still doesn't make sense, essentially what I am trying to do is take the bound service example with the number generator and modify it so that the method is called within a thread in the activity. Within the example it even says, "However, if this call were something that might hang, then this request should occur in a separate thread to avoid slowing down the activity performance."

Thanks in advance for your help.

Kicker
  • 23
  • 4

2 Answers2

1

Odd. It is complaining because of this line:

Log.e("BACKGROUND_PROC", e.getMessage());

I'd suggest checking that you're actually returning valid data: this doesn't appear to be a thread issue.

EDIT: try changing that line in getProjects to this:

Log.e("BACKGROUND_PROC", "Exception ["+e.getMessage()+"]", e);

and see what it says. The issue is whatever Exception is being thrown doesn't have a message, and I guess android.util.Log.e requires a message. That should at least tell you what's going wrong.

EDIT: your problem is that you are running the service request well before the service is bound. Remove the code that says

Thread thread = new Thread(null, viewProjects, "MagentoBackground");
thread.start();

from onCreate, and move it into onServiceConnected like this:

@Override
        public void onServiceConnected(ComponentName className, IBinder service) {
            mService = ((ApiService.LocalBinder)service).getService();
            mBound = true;
Thread thread = new Thread(null, viewProjects, "MagentoBackground");
thread.start();
        }
Femi
  • 64,273
  • 8
  • 118
  • 148
  • Changed the line as per your suggestion. New error is posted above. – Kicker Jun 10 '11 at 18:46
  • Ah. You're having timing issues. – Femi Jun 10 '11 at 19:00
  • So it seemed that I was binding to the service after I created the thread that was to use the service. I moved the thread code into onStart() so it would execute after binding but I am receiving the same error regardless. – Kicker Jun 10 '11 at 19:23
  • Your definitely correct about timing issues though. I slapped a Thread.sleep(2000) in the getProjects() function above everything else and it functions correctly. I don't understand what I am doing wrong though. Even if I bind the service in onCreate() and start the thread in onStart(), the error persists unless I manually create delay. – Kicker Jun 10 '11 at 19:44
  • I modified my answer: move the thread start into the `onServiceConnected` method and you should be fine. – Femi Jun 10 '11 at 19:53
  • I had just come to this conclusion myself from reading [this](http://stackoverflow.com/questions/5771208/nullpointerexception-when-calling-service-in-onresume-after-binding-in-onstart). Is this the "best" way to do this or should I switch over to using another method such as extending IntentService? As said above my service will be accessing a JSON web service and sometimes will take quite a long time as the size of the data returned will be quite large. – Kicker Jun 10 '11 at 20:03
  • This is fine, actually: one optimization I've used in the past (particularly in this case where you are guaranteed to be calling the service's network access functionality) is to pass a Handler to the service in `onServiceConnected` and then send a Message to the Handler from the service when the webservice call returns. – Femi Jun 10 '11 at 20:08
  • Maybe I should start a new topic but do you have any idea why using [Resty](http://beders.github.com/Resty/Resty/Overview.html) within my Android app (private Resty r = new Resty();) is causing a java.lang.ExceptionInInitializerError? It works fine in a standalone application. – Kicker Jun 10 '11 at 20:41
  • Apparently the dalvik virtual machine can't find the class 'java.net.CookieManager' referenced by Resty. Not sure if there is any way around that. – Kicker Jun 10 '11 at 20:48
  • Apparently Resty requires some classes from API level 9. – Kicker Jun 10 '11 at 20:54
1

Threads in Android are fairly useless, as described here. The AsyncTask will help you avoid stalling your main UI thread.

Haphazard
  • 10,900
  • 6
  • 43
  • 55