I'm trying to find out why the Google Play Services is crashing with a nullpointerexception after the application is coming back from a background state such as device sleep or toggle other program. Sometimes Google Play Services gives the crash popup on application start. So I assume the problem lies somewhere on the path to the service since there is where the threading occurs with rxjava.
Note: I Inject GoogleApiClient in both MainActivity (field injection) and in GoogleApiService (constructor injection).
GoogleApiClient is injected as a @Singleton. I've been trying to trace why this is happening for hours now without progress, any help appreciated.
The Application continues to work without any issues, the "Google Play Services popup" is annoying though, I see one call to getuserLocAndWeather() return lost connection to google play services, but it immediatly returns a valid result in the next call.
The actual object reference in MainActivity and GoogleApiService is never null, the reference is always the same, like com.google.android.gms.internal.zzqd@a768e13 and always connected when the call is made.
Trace:
FATAL EXCEPTION: lowpool[3]
Process: com.google.android.gms.persistent, PID: 12828
java.lang.NullPointerException: GoogleApiClient must not be null
at ilk.a(:com.google.android.gms:73)
at hys.<init>(:com.google.android.gms:115)
at pof.<init>(:com.google.android.gms:86)
at ppz.<init>(:com.google.android.gms:35)
at ppx.<init>(:com.google.android.gms:179)
at ppp.a(:com.google.android.gms:179)
at buc.a(:com.google.android.gms:381)
at jfo.run(:com.google.android.gms:1087)
at itt.run(:com.google.android.gms:453)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1112)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:587)
at iyg.run(:com.google.android.gms:17)
at java.lang.Thread.run(Thread.java:818)
My Service class:, the print out in the try{} for the client is always : true, regardless if google play services crashes or not.
Client: com.google.android.gms.internal.zzqd@3c738f4e Connected? :true
public class GoogleApiService implements IGoogleApi{
private GoogleApiClient client;
private static final String TAG = "GoogleApiClient";
@Inject
public GoogleApiService(GoogleApiClient client){
this.client = client;
}
public Observable<UserCurrentInfo> getLocationWeather(){
Observable<WeatherResult> weatherObservable = Observable.create(subscriber -> {
try {
Log.d(TAG,"Trying to get some Weather");
Log.d(TAG,"Client: " + client.toString() + " Connected? :" + client.isConnected());
Awareness.SnapshotApi.getWeather(client)
.setResultCallback(weather -> {
if (!weather.getStatus().isSuccess()) {
subscriber.onError(new Throwable("could not get weather"));
Log.d(TAG," Error getting weather" + weather.getStatus().toString());
} else {
Log.d(TAG,"Getting dem weathers");
subscriber.onNext(weather);
subscriber.onCompleted();
}
});
}catch (SecurityException e){
throw new SecurityException("No permission: " + e);
}
});
Observable<LocationResult> locationObservable = Observable.create(subscriber -> {
try {
Awareness.SnapshotApi.getLocation(client)
.setResultCallback(retrievedLocation -> {
if (!retrievedLocation.getStatus().isSuccess()) {
subscriber.onError(new Throwable("Could not get location."));
Log.d(TAG," Error getting location");
} else {
subscriber.onNext(retrievedLocation);
subscriber.onCompleted();
}
});
}catch (SecurityException e){
throw new SecurityException("No permission: " + e);
}
});
return Observable.zip(weatherObservable, locationObservable,
(weather, location) -> {
return new UserCurrentInfo(weather.getWeather(),location.getLocation());
});
}
Presenter:
public class FavouritesPresenter implements BasePresenter<IFavouriteView>{
private IFavouriteView favView;
private String TAG = "FavPresenter";
private Subscription subscription;
private GetUserLocationWeatherUseCase useCase;
@Inject
FavouritesPresenter(GetUserLocationWeatherUseCase wlUseCase){
this.useCase = wlUseCase;
}
@Override
public void onCreate() {
}
@Override
public void onStop(){
if(subscription != null){
subscription.unsubscribe();
}
}
public void getUserLocAndWeather(){
subscription = useCase.execute().subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(info -> {
favView.showText(
formatStringDecimals(info.getWeather().getTemperature(Weather.CELSIUS)+"",2),
info.getWeather().getConditions()[0],
formatStringDecimals(""+info.getLocation().getLatitude(),3),
formatStringDecimals("" + info.getLocation().getLongitude(),3)
);},
err ->{favView.showText("??",0,"","");}
);
}
Usecase:
public class GetUserLocationWeatherUseCase implements Usecase<UserCurrentInfo> {
IGoogleApi apihelper;
public GetUserLocationWeatherUseCase(IGoogleApi helper){
this.apihelper = helper;
}
@Override
public Observable<UserCurrentInfo> execute(){
return apihelper.getLocationWeather();
}
Usage in mainactivity:
@Inject
FavouritesPresenter favouritesPresenter;
GoogleApiClient.ConnectionCallbacks connectionCallbacks;
GoogleApiClient.OnConnectionFailedListener connectionFailedListener;
@Inject
GoogleApiClient mGoogleApiClient;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
initInjector();
favouritesPresenter.attachView(this);
favouritesPresenter.onCreate();
registerReceiverGPS();
}
@Override
protected void onStart() {
super.onStart();
if (mGoogleApiClient != null){
registerCallbacks(this.mGoogleApiClient);
registerFailedToConnect(this.mGoogleApiClient);
mGoogleApiClient.connect();
}
}
@Override
protected void onStop() {
favouritesPresenter.onStop();
if (mGoogleApiClient != null) {
mGoogleApiClient.unregisterConnectionCallbacks(this.connectionCallbacks);
mGoogleApiClient.unregisterConnectionFailedListener(this.connectionFailedListener);
mGoogleApiClient.disconnect();
}
}
@Override
public void registerCallbacks(GoogleApiClient client){
this.connectionCallbacks = new GoogleApiClient.ConnectionCallbacks() {
@Override
public void onConnected(@Nullable Bundle bundle)
favouritesPresenter.getUserLocAndWeather(); //Call to presenter that initiates the observable chain, actually this comes later after some GPS checks and such, but for easier cohesion
}
@Override
public void onConnectionSuspended(int i) {}
};
client.registerConnectionCallbacks(this.connectionCallbacks);
}
I assume that
Awareness.SnapshotApi.getWeather(client)
is probably where your code begins the call tocom.google.android.gms:73
, so adding the NPE to your catch statement actually might be worth, particularly as it's intermittent.& now a Note to others: I only suggest this because I see that they're using rxJava with some skill; look at the terseness of the two monad declarations in their
GoogleApiClient
! All they need to do is aretryWhen(Func2<Integer, Throwable, Boolean>)
and evaluate the predicate in said functional parameter to be true giventhrowable instanceof NPE
and count of 1, maybe 2. Before the zip, I think logging and releasing further NPEs--making further anomalous behavior apparent--might satisfy the firm, educated voices telling us never, ever to catch an NPE. ...or, if tweaking the noses of those firm, educated voices sounds like a good time, they could filter out further Exceptions by type while providing appropriate downstream reactions to this predictable event...I was about to say that you can do that because there isn't a bunch of code hanging around in the monad
create()
methods wishing it could be part of a side effect; however @buddhabath, I notice thosecreate()
s can generate exactly the subscription side effect you are describing--right on the subscription thread, actually:You "should be" catching whatever comes out of those
try
s and sending it down the rxChooChoo; the extantonError
shouldn't be a problem b/c only one will be called per evaluation. Just point the parameter of the catch at subscriber.onError() and any Throwable-excepting these Exceptions-will be kept within the tracks, perhaps like the tracks I described above the Kermit, but that leakingcreate()
is your bug.tl;dr: Manually defining monads with
create()
deserves one's full attention; it's the real world, very "imperative"; literally anything could happen in there. Myself, I just now had the pleasure of discovering the new fromEmitter and also am glad to see Observable.Generate on the menu for rxJava 2.0.In your onStart() method only connect the googleApiClient's object and the rest things implement in onCreate() method .
First thing I'd go for is move the part in onStart() to onResume(), to make sure they are there when the user needs them, as that is the last method called before the App is shown. Same thing with onStop() to onPause(). But somehow that would seem like a too simple an answer.