I followed the tutorial on Google's developer's documentation site for Services and might have missed something. I was able to start and stop the Service correctly, but now that I am trying to bind to the Service, I am getting a NullPointerException because onBind() and onServiceConnected() are not called after bindService(). I have read over 10 questions on StackOverflow and none of them have helped.
Part of my manifest:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.waveplayer">
<application
<service android:name="com.example.waveplayer.ServiceMain"
android:exported="false"
android:description="@string/service_description"
android:enabled="true"/>
</application>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
</manifest>
The code used to start the service on OnCreate():
Intent intent = new Intent(getApplicationContext(), ServiceMain.class);
startService(intent);
getApplicationContext().bindService(intent, connection, BIND_AUTO_CREATE);
private ServiceConnection connection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName className, IBinder service) {
ServiceMain.ServiceMainBinder binder = (ServiceMain.ServiceMainBinder) service;
serviceMain = binder.getService();
serviceMainBound = true;
}
@Override
public void onServiceDisconnected(ComponentName arg0) {
serviceMainBound = false;
}
};
And my service code:
private Looper serviceMainLooper;
private ServiceMainHandler serviceMainHandler;
private final IBinder serviceMainBinder = new ServiceMainBinder();
private final class ServiceMainHandler extends Handler {
public ServiceMainHandler(Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message msg) {
}
}
@Override
public void onCreate() {
super.onCreate();
HandlerThread thread = new HandlerThread("ServiceMainStartArguments",
Process.THREAD_PRIORITY_BACKGROUND);
thread.start();
serviceMainLooper = thread.getLooper();
serviceMainHandler = new ServiceMainHandler(serviceMainLooper);
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Toast.makeText(this, "service starting", Toast.LENGTH_SHORT).show();
// For each start request, send a message to start a job and deliver the
// start ID so we know which request we're stopping when we finish the job
Message msg = serviceMainHandler.obtainMessage();
msg.arg1 = startId;
serviceMainHandler.sendMessage(msg);
Intent notificationIntent = new Intent(this, ServiceMain.class);
PendingIntent pendingIntent =
PendingIntent.getActivity(this, 0, notificationIntent, 0);
String CHANNEL_ID = "ServiceMain";
RemoteViews notificationLayout = new RemoteViews(getPackageName(), R.layout.notification_song_pane);
NotificationCompat.Builder builder = new NotificationCompat.Builder(this, CHANNEL_ID)
.setPriority(NotificationCompat.PRIORITY_MAX)
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
.setStyle(new NotificationCompat.DecoratedCustomViewStyle())
.setCustomContentView(notificationLayout)
.setContentIntent(pendingIntent);
startForeground(CHANNEL_ID.hashCode(), builder.build());
return START_STICKY;
}
public class ServiceMainBinder extends Binder {
ServiceMain getService() {
return ServiceMain.this;
}
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return serviceMainBinder;
}
@Override
public void onDestroy() {
saveFile();
Toast.makeText(this, "service done", Toast.LENGTH_SHORT).show();
}
It turns out that the Service isn't created until the UI thread is clear of code from the Activity, therefore I couldn't access it until after the onStart()
method of my Activity. That is what the onServiceConnected()
callback method is for. At that point the Service has been created and the Activity has access to the Service.