Strange NullPointerException with service - android

I have a started service and some activities must bind to it to get some data before set the views. Everything is working fine but, some (rarely) times, I got a NullPointerException. My simplified activitiy is:
public class MyActivity extends Activity {
TextView tvName;
boolean mIsMyServiceBound;
MyService mMyService;
private ServiceConnection mConnection = new ServiceConnection() {
#Override
public void onServiceConnected(ComponentName className, IBinder service) {
MyService.MyServiceBinder myServiceBinder = (MyService.MyServiceBinder) service;
mMyService = myServiceBinder();
mIsMyServiceBound = true;
// Set up views
tvName.setText(mMyService.getName());
}
#Override
public void onServiceDisconnected(ComponentName className) {
mIsMyServiceBound = false;
mMyService = null;
}
};
#Override
public void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.yourlayout);
tvName = (TextView) findViewById(R.id.tv_name);
...
}
#Override
protected void onStart() {
super.onStart();
// Bind to LocalService
Intent intent = new Intent(this, ChatService.class);
bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
mIsChatServiceBound = true;
}
#Override
protected void onStop() {
super.onStop();
// Unbind from the service
if (mIsChatServiceBound) {
unbindService(mConnection);
mIsChatServiceBound = false;
}
}
#Override
public void onDestroy() {
super.onDestroy();
tvName = null;
}
}
Well, it is usually working fine. But I've got a NullPointerException when doing:
tvName.setText(mMyService.getName());
The error tells that tvName is null, but I don't understand how it can be possible, as it will be called after onCreate. This error happens rarely times, but this is quite annoying. May the activity had been destroyed but the service connection listener didn't cancelled? If this is true, how could I cancel that service connection when the activity's destroyed?
Thanks in advance!

Try this. You are missing
setContentView(R.layout.yourlayout);
in onCreate Method.You need to extend to Activity class too
Hope it helps.thanks

You forgot to write setcontentView(R.layout.activity_main) to Activity.
#Override
public void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setcontentView(R.layout.activity_main)
tvName = (TextView) findViewById(R.id.tv_name);
}
Hope this will help you.

Try to see what happens if you open your activity and quickly rotate your phone many times after. If there is a problem with your Service connectivity and activity cycle you should be able to discover it this way. Also as a workaround you can try to get the reference to the TextView inside the onServiceConnected and make the sanity check. (Very weird though, first time I hear about this, it can be something related with your app architecture).
Also here:
MyService.MyServiceBinder myServiceBinder = (MyService.MyServiceBinder)service;
mMyService = myServiceBinder();
You should not use the Service this way, instead create a Messenger from the Binder and use a Handler on Service side to perform the required operations.

Related

Is there a need to have one ServiceConnection per each Service bind?

I have several Android Services that I want to bind to in my Activity, so I can monitor several actions from the user.
To be able to bind every Service, and I will have several, do I need several private ServiceConnections in my activity like the following?
/** Defines callbacks for service binding, passed to bindService() */
private ServiceConnection mConnection = new ServiceConnection() {
#Override
public void onServiceConnected(ComponentName className,
IBinder service) {
// We've bound to LocalService, cast the IBinder and get LocalService instance
GPSLocalBinder gpsBinder = (GPSLocalBinder) service;
PhotoLocalBinder photoBinder = (PhotoLocalBinder) service;
gpsService = gpsBinder.getService();
photoService = photoBinder.getService();
mGpsBound = true;
mPhotoBound = true;
}
#Override
public void onServiceDisconnected(ComponentName arg0) {
mGpsBound = false;
mPhotoBound = false;
}
};
Or do I need a manager class between my Activity and the Services that provides better usage and understanding of the bounded Services?
Is there a need to have one serviceConnection for each android
service?
I assume you're asking if you can reuse the same serviceConnection for multiple services. There's no need to have one for each service connection, but this is probably the best approach. I see in your code this
GPSLocalBinder gpsBinder = (GPSLocalBinder) service;
PhotoLocalBinder photoBinder = (PhotoLocalBinder) service;
gpsService = gpsBinder.getService();
photoService = photoBinder.getService();
This is very confusing... this seems like a service can be cast into two different services!!
You'll realize that the onServiceConnected callback is where most of the magic happens, where you (the Activity) finally can get a pointer to your Service and start calling methods and interact with your service. If you want to reuse the same serviceConnection for different services you'd need to find out which custom subclass the IBinder object belongs to and then cast appropriately. Ufff, too much trouble. I would recommend having one serviceConnection per service.
Or do i need a manager class between my activity and the services that
provides better usage and understanding of the bounded services?
For both this and your first question, you can do whatever you want. There's no approach better than the other (IMHO) and the best one is the one you understand better and makes you feel more comfortable.
A single ServiceConnection instance can be used for binding to multiple Services.
In ServiceConnection.onServiceConnected(), you'd have to check which service was bound (using className.getClassName() or className.getPackageName()) and put it in the appropriate field/variable, etc.
I used this thread as a reference, though I modified it to match my needs.
private static final int SERVICE_1_INDEX = 0;
private static final int SERVICE_2_INDEX = 1;
/** Array of the subclasses of {#link BaseService}s which have been bound */
private BaseService[] mServices;
/** ServiceConnection which handles the binding/unbinding of the services */
private MyServiceConnection mServiceConnection;
#Override
public void onViewCreated(View view, #Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
mServiceConnection = new MyServiceConnection();
}
#Override
public void onResume() {
super.onResume();
bindServices();
}
#Override
public void onPause() {
super.onPause();
unbindServices();
}
private void bindServices() {
Intent service1Intent = new Intent(getActivity().getApplicationContext(), MyService1.class);
Intent service2Intent = new Intent(getActivity().getApplicationContext(), MyService2.class);
getContext().bindService(service1Intent, mServiceConnection, Context.BIND_AUTO_CREATE);
getContext().bindService(service2Intent, mServiceConnection, Context.BIND_AUTO_CREATE);
}
private void unbindServices() {
if (mServiceConnection != null) {
getContext().unbindService(mServiceConnection);
}
}
private class MyServiceConnection implements ServiceConnection {
public void onServiceConnected(ComponentName className, IBinder boundService ) {
Log.d("className(bound)", className.getClassName());
Log.d("className(Service1)", MyService1.class.getName());
Log.d("className(Service2)", MyService2.class.getName());
BaseService.LocalBinder binder = (BaseService.LocalBinder) boundService;
if (className.getClassName().equals(MyService1.class.getName())) {
mServices[SERVICE_1_INDEX] = binder.getService();
// call method on your service like:
// binder.getService().someMethod();
// (you may need to cast to your actual Service)
}
else {
mBaseServices[SERVICE_2_INDEX] = binder.getService();
// call method on the service like in if-block
}
}
public void onServiceDisconnected(ComponentName className) {
if (className.getClassName().equals(MyService1.class.getName())) {
mBaseServices[SERVICE_1_INDEX] = null;
}
else {
mBaseServices[SERVICE_2_INDEX] = null;
}
}
}

Android problems binding started service

I start my service in onCreate() (if onCreate is called the first time) and call bindService in onStart(). The service probaply works, but after calling bindService my local instance of the service is still null. Furthermore, is seems to be that getService() is not called.?
Here is some code:
#Override
protected void onCreate(Bundle savedInstanceState) {
...
if(savedInstanceState == null){
final Intent i = new Intent(this, HostService.class);
startService(i);
}
}
protected void onStart(){
super.onStart();
bindService(new Intent(this, StartGameActivity.class), connection, Context.BIND_AUTO_CREATE);
}
private ServiceConnection connection = new ServiceConnection(){
#Override
public void onServiceConnected(ComponentName arg0, IBinder arg1) {
HostBinder binder = (HostBinder) arg1;
hostService = binder.getService();
isBound = true;
}
#Override
public void onServiceDisconnected(ComponentName arg0) {
isBound = false;
}
};
and in HostService:
...
private HostBinder binder = new HostBinder();
...
public class HostBinder extends Binder{
HostService getService(){
Log.d(TAG, "getService");
return HostService.this;
}
}
#Override
public IBinder onBind(Intent arg0) {
return binder;
}
Why is hostService still null, after onStart() is called and why is getService is not getting called ("getService" is not print in LogCat)?
thx & regards
You don't need to call startService(); it will be started by bindService().
In your onStart() method, you're creating an intent to launch StartGameActivity.class. Was that what you wanted, or did you mean to launch HostService.class?
If neither of those are your problem, we need more to go on. Can you put a logging statement inside your onServiceConnected() and onBind() methods so you can be sure they're called?
Any logcat messages that look interesting?
Are you talking to your service via the binder, or via messaging?

use service after binding without clicking button

public class BindingActivity extends Activity {
LocalService mService;
boolean mBound = false;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
}
#Override
protected void onStart() {
super.onStart();
// Bind to LocalService
Intent intent = new Intent(this, LocalService.class);
bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
}
#Override
protected void onStop() {
super.onStop();
// Unbind from the service
if (mBound) {
unbindService(mConnection);
mBound = false;
}
}
/** Called when a button is clicked (the button in the layout file attaches to
* this method with the android:onClick attribute) */
public void onButtonClick(View v) {
if (mBound) {
// Call a method from the LocalService.
// 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.
int num = mService.getRandomNumber();
Toast.makeText(this, "number: " + num, Toast.LENGTH_SHORT).show();
}
}
/** Defines callbacks for service binding, passed to bindService() */
private ServiceConnection mConnection = new ServiceConnection() {
#Override
public void onServiceConnected(ComponentName className,
IBinder service) {
// We've bound to LocalService, cast the IBinder and get LocalService instance
LocalBinder binder = (LocalBinder) service;
mService = binder.getService();
mBound = true;
}
#Override
public void onServiceDisconnected(ComponentName arg0) {
mBound = false;
}
};
}
this is a example in http://developer.android.com/guide/components/bound-services.html.
I want to use mService Directly and do not need to click button first, how can I do. I have tried many ways, but they are all won't work.
The problem I suspect you're having is that binding to a service is asynchronous. As such, you need to wait until you have gotten the service back before you can make a call into it. If you call bindService(intent, mConnection, Context.BIND_AUTO_CREATE); from say onCreate, you probably will still not have the service at any point during the activity boot up lifecycle... mService will still be null. If you don't want to let the user do this waiting for you (by taking a couple seconds to hit the button) you can simply make a call from your onServiceConnected method to mService and it should work fine. Either way, the key is to wait to make any calls to mService, until onServiceConnected has been run and mService is no longer null. (This will probably happen after even onResume). Does that make sense?

Android instantiation of a service throug bindService()

I got a problem with binding a service:
I use following code:
protected void onCreate(){
...
button.setOnClickListener(new OnClickListener(){
#Override
public void onClick(View v) {
if(mService != null)
Log.e(TAG, "is instantiated (onClick)!");
});
}
protected void onStart(){
super.onStart();
bindService(new Intent(this, HostService.class), connection, Context.BIND_AUTO_CREATE);
if(mService == null)
Log.e(TAG, "is null (onStart)!");
}
...
After starting my activity "is null (onStart)!" is printed in LogCat.
When I click the button "is instantiated (onClick)!" is printed.
Why bindService() doesnt instantiate my service-instance mService directly and how can I solve the problems referring to this?
thx & regards
Why bindService() doesn't instantiate my service-instance mService directly
bindService() is asynchronous, like many operations in Android, so it occurred after your test in onStart.
how can I solve the problems referring to this?
Put your business logic in onServiceConnected() of your ServiceConnection, instead of immediately after the bindService() call.
Here is an example of the ServiceConnection code:
private ServiceConnection sConn;
private Messenger messenger;
...
#Override
protected void onCreate(Bundle savedInstanceState) {
// Service Connection to handle system callbacks
sConn = new ServiceConnection() {
#Override
public void onServiceDisconnected(ComponentName name) {
messenger = null;
}
#Override
public void onServiceConnected(ComponentName name, IBinder service) {
// We are conntected to the service
messenger = new Messenger(service);
}
};
// We bind to the service
bindService(new Intent(this, ConvertService.class), sConn,
Context.BIND_AUTO_CREATE);
}
This is from Francesco Azzola's good example.

Android: How to safely unbind a service

I have a service which is binded to application context like this:
getApplicationContext().bindService(
new Intent(this, ServiceUI.class),
serviceConnection,
Context.BIND_AUTO_CREATE
);
protected void onDestroy() {
super.onDestroy();
getApplicationContext().unbindService(serviceConnection);
}
For some reason, only sometimes the application context does not bind properly (I can't fix that part), however in onDestroy() I do unbindservice which throws an error
java.lang.IllegalArgumentException: Service not registered: tools.cdevice.Devices$mainServiceConnection.
My question is: Is there a way to call unbindservice safely or check if it is already bound to a service before unbinding it?
Thanks in advance.
Try this:
boolean isBound = false;
...
isBound = getApplicationContext().bindService( new Intent(getApplicationContext(), ServiceUI.class), serviceConnection, Context.BIND_AUTO_CREATE );
...
if (isBound)
getApplicationContext().unbindService(serviceConnection);
Note:
You should use same context for binding a service and unbinding a service. If you are binding Service with getApplicationContext() so you should also use getApplicationContext.unbindService(..)
Here you can find a nice explanation and source codes how to work with bound services. In your case you should override methods (onServiceConnected and onServiceDisconnected) of ServiceConnection object. Then you can just check mBound variable in your code.
Doing exactly what Andrey Novikov proposed didn't work for me.
I simply replaced:
getApplicationContext().unbindService(serviceConnection);
With:
unbindService(serviceConnection);
I found there are two issues. Attempting to bind more than once and also attempting to unbind more than once.
Solution:
public class ServiceConnectionManager implements ServiceConnection {
private final Context context;
private final Class<? extends Service> service;
private boolean attemptingToBind = false;
private boolean bound = false;
public ServiceConnectionManager(Context context, Class<? extends Service> service) {
this.context = context;
this.service = service;
}
public void bindToService() {
if (!attemptingToBind) {
attemptingToBind = true;
context.bindService(new Intent(context, service), this, Context.BIND_AUTO_CREATE);
}
}
#Override
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
attemptingToBind = false;
bound = true;
}
#Override
public void onServiceDisconnected(ComponentName componentName) {
bound = false;
}
public void unbindFromService() {
attemptingToBind = false;
if (bound) {
context.unbindService(this);
bound = false;
}
}
}
Why do we get this error?
When you try to unregister a service which is not registered.
What are some common examples?
Binding and Unbinding a service with different Context.
calling unBind(mserviceConnection) more than bind(...)
First point is self explanatory. Lets explore the second source of error more deeply. Debug your bind() and unbind() calls. If you see calls in these order then your application will end up getting the IllegalArgumentException.
How can we avoid this?
There are two ways you should consider to bind and unbind a service in Activity. Android docs recommend that
If you want to interact with a service only when the Activity is visible then
bindService() in onStart() and unbindService() in onStop()
Your Activity {
#Override
public void onStart(){
super.onStart();
bindService(intent, mConnection , Context.BIND_AUTO_CREATE);
}
#Override
public void onStop(){
super.onStop();
unbindService(mConnection);
}
}
If you want to interact with a service even an Activity is in Background then
bindService() in onCreate() and unbindService() in onDestroy()
Your Activity {
#Override
public void onCreate(Bindle sis){
super.onCreate(sis);
....
bindService(intent, mConnection , Context.BIND_AUTO_CREATE);
}
#Override
public void onDestroy() {
super.onDestroy();
unbindService(mConnection);
}
}
I think that guide is not completely correct as mentioned here Surya Wijaya Madjid. Memory leaks can occur when bound service is destryed and not re-connected yet.
I think that this approach is needed:
Service mService;
private final ServiceConnection mServiceConnection = new ServiceConnection()
{
boolean bound = false;
#Override
public void onServiceDisconnected(ComponentName name)
{
mService = null;
}
#Override
public void onServiceConnected(ComponentName name, IBinder service)
{
mService = ((MyService.ServiceBinder) service).getService();
if (!bound)
{
// do some action - service is bound for the first time
bound = true;
}
}
};
#Override
public void onDestroy()
{
if (mService != null)
{
// do some finalization with mService
}
if (mServiceConnection.bound)
{
mServiceConnection.bound = false;
unbindService(mServiceConnection);
}
super.onDestroy();
}
public void someMethod()
{
if (mService != null)
{
// to test whether Service is available, I have to test mService, not mServiceConnection.bound
}
}
Use a variable to record if you have ever bind to a service, and unbind it if the variable is true.
See android official example :
http://androidxref.com/9.0.0_r3/xref/development/samples/ApiDemos/src/com/example/android/apis/app/RemoteService.java#376
Not sure about all the above answers, it seemed far too complicated while none of these would fit the issue I had.
Only binding/unbinding once at a time, and the service was definitely bound at the time of the unbind() call. Don't want to leak anything, so I just made sure I was using the same context for the bind() and unbind() calls and that solved it permanently! Doing something like this:
any_context.getApplicationContext().bind(...);
...
another_context.getApplicationContext().unbind(...);

Categories

Resources