I use signalR lib in my project. but I was not able to compile libs of https://github.com/SignalR/java-client on my own - I found them in internet. But it seems like there are not full (WebsocketTransport is missing)
When I compile https://github.com/SignalR/java-client, i get two libs, and paste them to my app.gradle -
compile files ('libs/signalr-client-sdk.jar')
compile files ('libs/signalr-client-sdk-android.jar')
Progects build suckesfully, butwhen I press run - it fails.
Here is stack trace -
UNEXPECTED TOP-LEVEL EXCEPTION:
com.android.dx.cf.iface.ParseException: bad class file magic (cafebabe) or version (0034.0000)
at com.android.dx.cf.direct.DirectClassFile.parse0(DirectClassFile.java:472)
at com.android.dx.cf.direct.DirectClassFile.parse(DirectClassFile.java:406)
at com.android.dx.cf.direct.DirectClassFile.parseToInterfacesIfNecessary(DirectClassFile.java:388)
at com.android.dx.cf.direct.DirectClassFile.getMagic(DirectClassFile.java:251)
at com.android.dx.command.dexer.Main.processClass(Main.java:704)
at com.android.dx.command.dexer.Main.processFileBytes(Main.java:673)
at com.android.dx.command.dexer.Main.access$300(Main.java:83)
at com.android.dx.command.dexer.Main$1.processFileBytes(Main.java:602)
at com.android.dx.cf.direct.ClassPathOpener.processArchive(ClassPathOpener.java:284)
at com.android.dx.cf.direct.ClassPathOpener.processOne(ClassPathOpener.java:166)
at com.android.dx.cf.direct.ClassPathOpener.process(ClassPathOpener.java:144)
at com.android.dx.command.dexer.Main.processOne(Main.java:632)
at com.android.dx.command.dexer.Main.processAllFiles(Main.java:510)
at com.android.dx.command.dexer.Main.runMonoDex(Main.java:280)
at com.android.dx.command.dexer.Main.run(Main.java:246)
at com.android.dx.command.dexer.Main.main(Main.java:215)
at com.android.dx.command.Main.main(Main.java:106)
...while parsing microsoft/aspnet/signalr/client/Action.class
1 error; aborting
Error:Execution failed for task ':app:preDexDebug'.
com.android.ide.common.process.ProcessException: org.gradle.process.internal.ExecException: Process 'command 'C:\Program Files\Java\jdk1.8.0_45\bin\java.exe'' finished with non-zero exit value 1
I do not know what to do. I even tried to compile forks of this project, but have got the same result, same error. Need any help, Thanks!
UPDATE
in my Application class i have
Platform.loadPlatformComponent(new AndroidPlatformComponent());
in my app.gradle:
compile files ('libs/gson-2.2.2.jar')
compile files ('libs/signalr-client-sdk.jar')
compile files ('libs/signalr-client-sdk-android.jar')
in my code I use
connection.start(new ServerSentEventsTransport(connection.getLogger())).get();
I also create lots of hubs on this connection, nad call methods. All works, but connection always dissapears, if i do not do anything with device more then for 30 seconds. And I do not have any callbacks that the connection was lost\disconected or anything like that - I log all this stuff. But I think it can happen because i have pretty old libs, i googled - as I mention in this question i did not menaged to build signalR project to receive more recent libs
Here is my working basic code, hope this helps!
package com.example.simplesignalrclient;
import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.util.Log;
import android.widget.Toast;
import java.util.concurrent.ExecutionException;
import microsoft.aspnet.signalr.client.Credentials;
import microsoft.aspnet.signalr.client.Platform;
import microsoft.aspnet.signalr.client.SignalRFuture;
import microsoft.aspnet.signalr.client.http.Request;
import microsoft.aspnet.signalr.client.http.android.AndroidPlatformComponent;
import microsoft.aspnet.signalr.client.hubs.HubConnection;
import microsoft.aspnet.signalr.client.hubs.HubProxy;
import microsoft.aspnet.signalr.client.hubs.SubscriptionHandler1;
import microsoft.aspnet.signalr.client.transport.ClientTransport;
import microsoft.aspnet.signalr.client.transport.ServerSentEventsTransport;
public class SignalRService extends Service {
private HubConnection mHubConnection;
private HubProxy mHubProxy;
private Handler mHandler; // to display Toast message
private final IBinder mBinder = new LocalBinder(); // Binder given to clients
public SignalRService() {
}
#Override
public void onCreate() {
super.onCreate();
mHandler = new Handler(Looper.getMainLooper());
}
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
int result = super.onStartCommand(intent, flags, startId);
startSignalR();
return result;
}
#Override
public void onDestroy() {
mHubConnection.stop();
super.onDestroy();
}
#Override
public IBinder onBind(Intent intent) {
// Return the communication channel to the service.
startSignalR();
return mBinder;
}
/**
* 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 {
public SignalRService getService() {
// Return this instance of SignalRService so clients can call public methods
return SignalRService.this;
}
}
/**
* method for clients (activities)
*/
public void sendMessage(String message) {
String SERVER_METHOD_SEND = "Send";
mHubProxy.invoke(SERVER_METHOD_SEND, message);
}
private void startSignalR() {
Platform.loadPlatformComponent(new AndroidPlatformComponent());
Credentials credentials = new Credentials() {
#Override
public void prepareRequest(Request request) {
request.addHeader("User-Name", "BNK");
}
};
String serverUrl = "http://192.168.1.100";
mHubConnection = new HubConnection(serverUrl);
mHubConnection.setCredentials(credentials);
String SERVER_HUB_CHAT = "ChatHub";
mHubProxy = mHubConnection.createHubProxy(SERVER_HUB_CHAT);
ClientTransport clientTransport = new ServerSentEventsTransport(mHubConnection.getLogger());
SignalRFuture<Void> signalRFuture = mHubConnection.start(clientTransport);
try {
signalRFuture.get();
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
return;
}
String HELLO_MSG = "Hello from Android!";
sendMessage(HELLO_MSG);
String CLIENT_METHOD_BROADAST_MESSAGE = "broadcastMessage";
mHubProxy.on(CLIENT_METHOD_BROADAST_MESSAGE,
new SubscriptionHandler1<CustomMessage>() {
#Override
public void run(final CustomMessage msg) {
final String finalMsg = msg.UserName + " says " + msg.Message;
// display Toast message
mHandler.post(new Runnable() {
#Override
public void run() {
Toast.makeText(getApplicationContext(), finalMsg, Toast.LENGTH_SHORT).show();
}
});
}
}
, CustomMessage.class);
}
}
Activity:
public class MainActivity extends AppCompatActivity {
private final Context mContext = this;
private SignalRService mService;
private boolean mBound = false;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Intent intent = new Intent();
intent.setClass(mContext, SignalRService.class);
bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
}
#Override
protected void onStop() {
// Unbind from the service
if (mBound) {
unbindService(mConnection);
mBound = false;
}
super.onStop();
}
public void sendMessage(View view) {
if (mBound) {
// Call a method from the SignalRService.
// 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.
EditText editText = (EditText) findViewById(R.id.edit_message);
if (editText != null && editText.getText().length() > 0) {
String message = editText.getText().toString();
mService.sendMessage(message);
}
}
}
/**
* Defines callbacks for service binding, passed to bindService()
*/
private final ServiceConnection mConnection = new ServiceConnection() {
#Override
public void onServiceConnected(ComponentName className,
IBinder service) {
// We've bound to SignalRService, cast the IBinder and get SignalRService instance
SignalRService.LocalBinder binder = (SignalRService.LocalBinder) service;
mService = binder.getService();
mBound = true;
}
#Override
public void onServiceDisconnected(ComponentName arg0) {
mBound = false;
}
};
}
CustomMessage Class:
public class CustomMessage {
public String UserName;
public String Message;
}
JUST TO HELP SOMEONE WHO GOT STUCK LIKE ME.
I was trying to get my SingalR to work.
I followed the answer given but service was not firing.
This was my first project to implement a service like SignalRService.
So I did not know.
I added <service> entry to my manifest file then everything started working.
<service android:name=".SignalRService"
android:enabled="true"
android:exported="true">
</service>
Thanks a lot BNK
Related
I'm trying to to build an app which has to run in the background. So for this I'm using the ForegroundService, but when I write "this" in the CommunicateViewModel class, it gets underlined and show me:
"Cannot resolve constructor
'Intent(com.harrysoft.androidbluetoothserial.demoapp.CommunicateViewModel,
java.lang.Class<com.harrysoft.androidbluetoothserial.demoapp.TimeService>)'"
and at the next this:
"Wrong 1st argument type. Found:
'com.harrysoft.androidbluetoothserial.demoapp.CommunicateViewModel',
required: 'android.content.Context' less... Inspection info:
startForegroundService (android.content.Context, Intent) in
ContextCompat cannot be applied to
(com.harrysoft.androidbluetoothserial.demoapp.CommunicateViewModel,
Intent) "
How can I solve this problem?
CommunicateViewModel:
package com.harrysoft.androidbluetoothserial.demoapp;
import android.app.Application;
import android.arch.lifecycle.AndroidViewModel;
import android.arch.lifecycle.LiveData;
import android.arch.lifecycle.MutableLiveData;
import android.content.Intent;
import android.os.CountDownTimer;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.StringRes;
import android.support.v4.content.ContextCompat;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
import android.widget.Toast;
import com.harrysoft.androidbluetoothserial.BluetoothManager;
import com.harrysoft.androidbluetoothserial.SimpleBluetoothDeviceInterface;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.schedulers.Schedulers;
public class CommunicateViewModel extends AndroidViewModel {
// A CompositeDisposable that keeps track of all of our asynchronous tasks
private CompositeDisposable compositeDisposable = new CompositeDisposable();
// Our BluetoothManager!
private BluetoothManager bluetoothManager;
// Our Bluetooth Device! When disconnected it is null, so make sure we know that we need to deal with it potentially being null
#Nullable
private SimpleBluetoothDeviceInterface deviceInterface;
// The messages feed that the activity sees
private MutableLiveData<String> messagesData = new MutableLiveData<>();
// The connection status that the activity sees
private MutableLiveData<ConnectionStatus> connectionStatusData = new MutableLiveData<>();
// The device name that the activity sees
private MutableLiveData<String> deviceNameData = new MutableLiveData<>();
// The message in the message box that the activity sees
private MutableLiveData<String> messageData = new MutableLiveData<>();
// Our modifiable record of the conversation
private StringBuilder messages = new StringBuilder();
// Our configuration
private String deviceName;
private String mac;
// A variable to help us not double-connect
private boolean connectionAttemptedOrMade = false;
// A variable to help us not setup twice
private boolean viewModelSetup = false;
// Called by the system, this is just a constructor that matches AndroidViewModel.
public CommunicateViewModel(#NonNull Application application) {
super(application);
}
// Called in the activity's onCreate(). Checks if it has been called before, and if not, sets up the data.
// Returns true if everything went okay, or false if there was an error and therefore the activity should finish.
public boolean setupViewModel(String deviceName, String mac) {
// Check we haven't already been called
if (!viewModelSetup) {
viewModelSetup = true;
// Setup our BluetoothManager
bluetoothManager = BluetoothManager.getInstance();
if (bluetoothManager == null) {
// Bluetooth unavailable on this device :( tell the user
toast(R.string.bluetooth_unavailable);
// Tell the activity there was an error and to close
return false;
}
// Remember the configuration
this.deviceName = deviceName;
this.mac = mac;
// Tell the activity the device name so it can set the title
deviceNameData.postValue(deviceName);
// Tell the activity we are disconnected.
connectionStatusData.postValue(ConnectionStatus.DISCONNECTED);
}
// If we got this far, nothing went wrong, so return true
return true;
}
// Called when the user presses the connect button
public void toconnect(){
Intent serviceIntent = new Intent(this, TimeService.class);
serviceIntent.putExtra("inputExtra", "this can be set invissable");
ContextCompat.startForegroundService(this, serviceIntent);
connect();
}
public void connect() {
// Check we are not already connecting or connected
if (!connectionAttemptedOrMade) {
// Connect asynchronously
compositeDisposable.add(bluetoothManager.openSerialDevice(mac)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(device -> onConnected(device.toSimpleDeviceInterface()), t -> {
toast(R.string.connection_failed);
connectionAttemptedOrMade = false;
connectionStatusData.postValue(ConnectionStatus.DISCONNECTED);
}));
// Remember that we made a connection attempt.
connectionAttemptedOrMade = true;
// Tell the activity that we are connecting.
connectionStatusData.postValue(ConnectionStatus.CONNECTING);
}
}
// Called when the user presses the disconnect button
public void disconnect() {
// Check we were connected
if (connectionAttemptedOrMade && deviceInterface != null) {
connectionAttemptedOrMade = false;
// Use the library to close the connection
bluetoothManager.closeDevice(deviceInterface);
// Set it to null so no one tries to use it
deviceInterface = null;
// Tell the activity we are disconnected
connectionStatusData.postValue(ConnectionStatus.DISCONNECTED);
}
}
// Called once the library connects a bluetooth device
private void onConnected(SimpleBluetoothDeviceInterface deviceInterface) {
this.deviceInterface = deviceInterface;
if (this.deviceInterface != null) {
// We have a device! Tell the activity we are connected.
connectionStatusData.postValue(ConnectionStatus.CONNECTED);
// Setup the listeners for the interface
this.deviceInterface.setListeners(this::onMessageReceived, this::onMessageSent, t -> toast(R.string.message_send_error));
// Tell the user we are connected.
toast(R.string.connected);
// Reset the conversation
messages = new StringBuilder();
messagesData.postValue(messages.toString());
} else {
// deviceInterface was null, so the connection failed
toast(R.string.connection_failed);
connectionStatusData.postValue(ConnectionStatus.DISCONNECTED);
}
getCurrentTime();
}
// Adds a received message to the conversation
private void onMessageReceived(String message) {
messages.append(deviceName).append(": ").append(message).append('\n');
messagesData.postValue(messages.toString());
}
// Adds a sent message to the conversation
private void onMessageSent(String message) {
// Add it to the conversation
messages.append(getApplication().getString(R.string.you_sent)).append(": ").append(message).append('\n');
messagesData.postValue(messages.toString());
// Reset the message box
messageData.postValue("");
}
// Send a message
public void sendMessage(String message) {
new CountDownTimer(1000, 1000) {
public void onTick(long millisUntilFinished) {
if (deviceInterface != null && !TextUtils.isEmpty(message)) {
Log.i("info", "sendMessage: send");
}
deviceInterface.sendMessage(message);
}
public void onFinish() {
disconnect();
timer();
}
}.start();
// Check we have a connected device and the message is not empty, then send the message -----------------
}
//---------------------------------------------------------------------------MY Code
//timer
public void timer(){
new CountDownTimer(28000, 1000) {
public void onTick(long millisUntilFinished) {
Log.i("INFO", "onTick: ");
}
public void onFinish() {
connect();
}
}.start();
}
// Get Time
public void getCurrentTime() {
Calendar calendar = Calendar.getInstance();
SimpleDateFormat mdformat = new SimpleDateFormat("HHmm");
SimpleDateFormat seconds = new SimpleDateFormat("ss");
String strSec = seconds.format(calendar.getTime());
String strDate = "Time" + mdformat.format(calendar.getTime());
//if ((strSec == "29") || (strSec == "59")) {
sendMessage(strDate);
//}
}
// Called when the activity finishes - clear up after ourselves.
#Override
protected void onCleared() {
// Dispose any asynchronous operations that are running
compositeDisposable.dispose();
// Shutdown bluetooth connections
bluetoothManager.close();
}
// Helper method to create toast messages.
private void toast(#StringRes int messageResource) { Toast.makeText(getApplication(), messageResource, Toast.LENGTH_LONG).show(); }
// Getter method for the activity to use.
public LiveData<String> getMessages() { return messagesData; }
// Getter method for the activity to use.
public LiveData<ConnectionStatus> getConnectionStatus() { return connectionStatusData; }
// Getter method for the activity to use.
public LiveData<String> getDeviceName() { return deviceNameData; }
// Getter method for the activity to use.
public LiveData<String> getMessage() { return messageData; }
// An enum that is passed to the activity to indicate the current connection status
enum ConnectionStatus {
DISCONNECTED,
CONNECTING,
CONNECTED
}
}
TimeService:
package com.harrysoft.androidbluetoothserial.demoapp;
import android.app.Notification;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.support.v4.app.*;
import io.reactivex.annotations.Nullable;
import static com.harrysoft.androidbluetoothserial.demoapp.App.CHANNEL_ID;
public class TimeService extends Service {
#Override
public void onCreate() {
super.onCreate();
}
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
String input = intent.getStringExtra("inputExtra");
Intent notificationIntent = new Intent(this, MainActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this,
0, notificationIntent, 0);
Notification notification = new NotificationCompat.Builder(this, CHANNEL_ID)
.setContentTitle("Example Service")
.setContentText(input)
.setSmallIcon(R.drawable.ic_android)
.setContentIntent(pendingIntent)
.build();
startForeground(1, notification);
//do heavy work on a background thread
//stopSelf();
return START_NOT_STICKY;
}
#Override
public void onDestroy() {
super.onDestroy();
}
#Nullable
#Override
public IBinder onBind(Intent intent) {
return null;
}
}
You have to pass a Context as the first argument of the Intent constructor. You can retrieved it thanks to the application object.
Intent serviceIntent = new Intent(getApplication().getApplicationContext(), TimeService.class);
I am trying to create a basic chat app using asmack and Openfire.
I have created a bound service for the XMPPConnection and each Activity binds to it.
Whenever I try to bind to a Service there is a very long delay. I know that the bindService is asynchronous but I want to be certain that my implementation of the Service is correct before I begin looking elsewere for problems.
I bind my Service in the onCreate method and try to access the connection in the onStart.
I am still new to this but I suspect that I have done something wrong thread-wise. The way my app runs now, the mBound variable returns true only if I try to access it from an OnClickListener. What is it that happens in the Listener that makes such a big difference? I tried to find the code for the OnClick method but I couldn't find it.
My XMPPConnectionService is this:
package com.example.smack_text;
import java.io.File;
import org.jivesoftware.smack.ConnectionConfiguration;
import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.XMPPException;
import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.Build;
import android.os.IBinder;
import android.util.Log;
import android.widget.Toast;
public class XMPPService extends Service{
XMPPConnection connection;
// private final IBinder mBinder = new LocalBinder();
#Override
public void onCreate(){
super.onCreate();
Log.d("service","created");
}
/**
* 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.
*/
#Override
public IBinder onBind(Intent intent) {
Log.d("sevice","bound");
LocalBinder mBinder = new LocalBinder (this);
return mBinder;
}
public class LocalBinder extends Binder {
XMPPService service;
public LocalBinder (XMPPService service)
{
this.service = service;
}
public XMPPService getService (){
return service;
}
// XMPPService getService() {
// return XMPPService.this;
// }
}
public void connect(final String user, final String pass) {
Log.d("Xmpp Alex","in service");
ConnectionConfiguration config = new ConnectionConfiguration("10.0.2.2",5222);
// KEYSTORE SETTINGS
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
config.setTruststoreType("AndroidCAStore");
config.setTruststorePassword(null);
config.setTruststorePath(null);
}
else {
config.setTruststoreType("BKS");
String path = System.getProperty("javax.net.ssl.trustStore");
if (path == null)
path = System.getProperty("java.home") + File.separator + "etc"
+ File.separator + "security" + File.separator
+ "cacerts.bks";
config.setTruststorePath(path);
}
// Create XMPP Connection
connection = new XMPPConnection(config);
new Thread(new Runnable() {
#Override
public void run() {
try {
connection.connect();
connection.login(user, pass);
if(connection.isConnected()){
Log.d("Alex", "connected biatch!");
}
else{
Log.d("Alex","not connected");
}
} catch (XMPPException e) {
e.printStackTrace();
}
}
}).start();
}
public void disconnect(){
if(connection.isConnected()){
connection.disconnect();
}
else{
Toast.makeText(getApplicationContext(), "not connected", Toast.LENGTH_LONG).show();
}
}
}
I implement an Android Chat with Asmack.
I have created a Service.
The service has a global variable with the XmppConnection.
At the begining i use the thread for connect and login.
then I set VCard for logged user, set rosterListener
finally set connection.addPacketListener
I update the activities with a BroadcastReceiver activity side and
#Override
public IBinder onBind(Intent arg0) {
return mBinderXmpp;
}
public class BinderServiceXmpp extends Binder {
ServiceXmpp getService() {
return ServiceXmpp.this;
}
}
private Runnable sendUpdatesToUI = new Runnable() {
public void run() {
DisplayInfo();
handler.postDelayed(this, 2000); // 2 segundos
}
};
private void DisplayInfo() {
isRunning = true; // flag to know if service is running
Intent tempIntent;
tempIntent = new Intent(BROADCAST_ACTION);
tempIntent.putExtra("UPDATE_OPTION", UPDATE_ACTION);
sendBroadcast(tempIntent);
}
Your implementation works, you still need to implement the handler for the actions like CONNECT and DISCONNECT from your clients bound (LoginActivity for instance).
Example:
class IncomingHandler extends Handler { // Handler of incoming messages from clients bound.
#Override
public void handleMessage(android.os.Message msg) {
switch (msg.what) {
case MSG_CONNECT_XMPP:
new AsyncTask<Void, Void, Boolean>(){
#Override
protected Boolean doInBackground(Void... params) {
// Do connection
}
#Override
protected void onPostExecute(Boolean aBoolean) {
// Notify the connection status
}
}.execute();
break;
case MSG_DICCONNECT_XMPP:
new AsyncTask<Void, Void, Boolean>(){
#Override
protected Boolean doInBackground(Void... params) {
// Do disconnection
}
#Override
protected void onPostExecute(Boolean aBoolean) {
// Notify the connection status
}
}.execute();
break;
default:
super.handleMessage(msg);
}
}
}
But, this approach of creating an AsyncTask anytime the Service needs to run a network action will reach its limit for the sendBroadcast in a BroadcastReceiver.
If you have BroadcastReceiver that needs to start or stop the connection by sending a message to the XMPPService, you have something like this:
public class NetworkConnectivityReceiver extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
NetworkInfo network = cm.getActiveNetworkInfo();
network = intent.getParcelableExtra(ConnectivityManager.EXTRA_NETWORK_INFO);
if (XmppService.isRunning() && network.isConnected()) {
context.sendBroadcast(new Intent(XmppService.ACTION_CONNECT));
} else if (XmppService.isRunning() && !network.isConnected()) {
context.sendBroadcast(new Intent(XmppService.ACTION_DISCONNECT));
}
}
}
Then, you will need to implement a Broadcast listener in the XmppService class.
But, you CANNOT run an AsyncTask in a Broadcast listener!
The remain options are described in my post here:
Android - Best option to implement Networking Class
I'm trying to bind an Activity to a LocalService to interact with it. But in my Activity I am only able to make calls to methods defined in my LocalBinder and not in my LocalService. What am I doing wrong?
Not starting scratch I read another question and I have read a little how to code some sample code and my code resembles that sample code. Also I have been reading some of the Service Documentation for convenience here is a small quote from that section of the documentation:
"A service is "bound" when an application component binds to it by calling bindService(). A bound service offers a client-server interface that allows components to interact with the service, send requests, get results, and even do so across processes with interprocess communication (IPC). A bound service runs only as long as another application component is bound to it. Multiple components can bind to the service at once, but when all of them unbind, the service is destroyed."
But I can't do that. As mentioned above the best I can do is to have my Activity call methods defined in my LocalBinder. I have achieved nothing like the part highlighted in black above.
If it helps here are the relevant portions of my code.
LocalService to be bound to:
/**************************************************************************************************
* Filename: LocalService.java
* Project name: Local Service Sample
* Application name: Local Service
* Description: This file contains the LocalService (extends Service) for our Local Service app
**************************************************************************************************/
package com.marie.localservicesample;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.UUID;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothSocket;
import android.content.Intent;
import android.os.Bundle;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.os.Messenger;
import android.os.RemoteException;
import android.util.Log;
import android.widget.Toast;
public class LocalService extends Service {
private NotificationManager mNM;
// Unique Identification Number for the Notification.
// We use it on Notification start, and to cancel it.
private int NOTIFICATION = R.string.local_service_started;
// just some arbitrary numbers for test purposes
public static int statusCode = 99;
public static int emptyMsg = 549;
// I get my Extras from onStartCommand and use in ServiceWorker() thread
public static final String EXTRA_MAC = "com.marie.localservicesample.EXTRA_MAC";
private String macString;
public static final String EXTRA_MESSENGER = "com.marie.localservicesample.EXTRA_MESSENGER";
private Messenger messenger;
private static final UUID MY_UUID = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB");
//private static final String macString = "00:06:66:02:D0:EC";
Boolean stop_receive_data = false;
// This is the object that receives interactions from clients. See
// RemoteService for a more complete example - or not because
// this is a local service
private final IBinder mBinder = new LocalBinder();
#Override
public IBinder onBind(Intent intent) {
Log.i("onBind", "called in LocalService" );
Log.i("onBind", "intent: " + intent.toString());
Log.i("onBind", "mBinder: " + mBinder);
return mBinder;
}
#Override
public void onCreate() {
mNM = (NotificationManager)getSystemService(NOTIFICATION_SERVICE);
// Display a notification about us starting. We put an icon in the status bar.
showNotification();
}
// Call this at the end of onStartCommand() after we got the Extras
public void afterStartCommand() {
Thread thr = new Thread(null, new ServiceWorker(), "LocalService");
thr.start();
}
/*
* This is the ServiceWorker thread that passes messages to the handler defined in
* the Controller activity.
*/
class ServiceWorker implements Runnable
{
public void run() {
// do background processing here... something simple
Looper.prepare();
BluetoothAdapter btAdapter = BluetoothAdapter.getDefaultAdapter();
BluetoothDevice btDevice = btAdapter.getRemoteDevice(macString);
BluetoothSocket btSocket = null;
InputStream btIstream = null;
OutputStream btOstream = null;
try {
btSocket = btDevice.createRfcommSocketToServiceRecord(MY_UUID);
} catch (IOException e1) {
e1.printStackTrace();
}
try {
btSocket.connect();
} catch (IOException e1) {
e1.printStackTrace();
}
try {
btIstream = btSocket.getInputStream();
btOstream = btSocket.getOutputStream();
} catch (IOException e1) {
e1.printStackTrace();
}
try {
int data = btIstream.read();
// reset the bluetooth device
while (data != 63) {
Log.d("LocalService", "resetting bluetooth device");
btOstream.write('r');
data = btIstream.read();
}
StringBuffer strBuffer = new StringBuffer("");
Boolean dataBegin = false;
int ndxPlus = 0;
while (data != -1) {
char printableB = (char) data;
if (data < 32 || data > 126) {
//printableB = ' ';
}
//Log.d("LocalService", Character.toString(printableB) + "(" + data + ")");
if (data == 63) {
btOstream.write('$');
btOstream.write(',');
}
if (data == 45) {
btOstream.write('1');
btOstream.write(',');
dataBegin = true;
}
if (dataBegin == true) {
strBuffer = strBuffer.append(Character.toString(printableB));
}
if (data == 13) {
dataBegin = false;
//Log.d("LocalServiceDataString", strBuffer.toString());
// send data to the handler to plot the data
Message msg = Message.obtain();
msg.what = Controller.MESSAGE_MAC;
msg.obj = strBuffer;
try {
messenger.send(msg);
} catch (RemoteException e) {
e.printStackTrace();
}
strBuffer = new StringBuffer("");
if (ndxPlus < 0) {
btOstream.write('+');
ndxPlus++;
}
}
data = btIstream.read();
if (stop_receive_data) data = -1;
}
} catch (IOException e1) {
e1.printStackTrace();
}
try {
btSocket.close();
} catch (IOException e1) {
e1.printStackTrace();
}
LocalService.this.stopSelf();
Looper.loop();
// stop the service when done...
// Or use the unbindBtn in the MainActivity class?
}
}
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.i("LocalService", "Received start id " + startId + ": " + intent);
Bundle extras = intent.getExtras();
messenger = (Messenger)extras.get(EXTRA_MESSENGER);
macString = extras.getString(EXTRA_MAC);
afterStartCommand();
// We want this service to continue running until it is explicitly
// stopped, so return sticky.
return START_STICKY;
}
#Override
public void onDestroy() {
// Cancel the persistent notification.
mNM.cancel(NOTIFICATION);
stop_receive_data = true;
// Tell the user we stopped.
Toast.makeText(this, R.string.local_service_stopped, Toast.LENGTH_SHORT).show();
}
/**
* Show a notification while this service is running.
*/
private void showNotification() {
// In this sample, we'll use the same text for the ticker and the expanded notification
CharSequence text = getText(R.string.local_service_started);
// Set the icon, scrolling text and timestamp
Notification notification = new Notification(R.drawable.stat_sample, text, System.currentTimeMillis());
// The PendingIntent to launch our activity if the user selects this notification
PendingIntent contentIntent = PendingIntent.getActivity(this, 0, new Intent(this, Controller.class), 0);
// Set the info for the views that show in the notification panel.
notification.setLatestEventInfo(this, getText(R.string.local_service_label), text, contentIntent);
// Send the notification.
mNM.notify(NOTIFICATION, notification);
}
}
Activity that binds to LocalService:
/**************************************************************************************************
* Filename: Binding.java
* Project name: Local Service Sample
* Application name: Local Service
* Description: This file contains the Binding class for our Local Service application
**************************************************************************************************/
package com.marie.localservicesample;
import android.app.Activity;
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.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.Toast;
/*
* Example of binding and unbinding to the local service.
* This demonstrates the implementation of a service which the client will
* bind to, receiving an object through which it can communicate with the service.
*/
public class Binding extends Activity {
private ILocalBinder mBoundService;
private boolean mIsBound;
private ServiceConnection mConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder service) {
// This is called when the connection with the service has been
// established, giving us the service object we can use to
// interact with the service. Because we have bound to a explicit
// service that we know is running in our own process, we can
// cast its IBinder to a concrete class and directly access it.
mBoundService = (ILocalBinder)service;
int statusCode = mBoundService.getStatusCode();
Log.d("Binding.java","called onServiceConnected. statusCode: " + statusCode);
Toast.makeText(Binding.this, R.string.local_service_connected,
Toast.LENGTH_SHORT).show();
}
public void onServiceDisconnected(ComponentName className) {
// This is called when the connection with the service has been
// unexpectedly disconnected -- that is, its process crashed.
// Because it is running in our same process, we should never
// see this happen.
mBoundService = null;
Log.d("Binding", "called onServiceDisconnected");
Toast.makeText(Binding.this, R.string.local_service_disconnected,
Toast.LENGTH_SHORT).show();
}
};
void doBindService() {
// Establish a connection with the service. We use an explicit
// class name because we want a specific service implementation that
// we know will be running in our own process (and thus won't be
// supporting component replacement by other applications).
bindService(new Intent(Binding.this, LocalService.class), mConnection, Context.BIND_AUTO_CREATE);
mIsBound = true;
}
void doUnbindService() {
if (mIsBound) {
int statusCode = mBoundService.getStatusCode();
if (statusCode != 0) Log.d("doUnbindService", "Binding.java statusCode: " + statusCode);
// Tell the user we did an unbind
Toast.makeText(this, R.string.local_service_unbound, Toast.LENGTH_SHORT).show();
// Detach our existing connection.
unbindService(mConnection);
mIsBound = false;
}
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.local_service_binding);
// Watch for button clicks.
Button button = (Button)findViewById(R.id.bind);
button.setOnClickListener(mBindListener);
button = (Button)findViewById(R.id.unbind);
button.setOnClickListener(mUnbindListener);
}
private OnClickListener mBindListener = new OnClickListener() {
public void onClick(View v) {
doBindService();
}
};
private OnClickListener mUnbindListener = new OnClickListener() {
public void onClick(View v) {
doUnbindService();
}
};
#Override
protected void onDestroy() {
super.onDestroy();
doUnbindService();
}
}
My ILocalBinder and LocalBinder:
/**************************************************************************************************
* Filename: ILocalBinder.java
* Project name: Local Service Sample
* Application name: Local Service
* Description: This file contains an example interface for my LocalBinder
**************************************************************************************************/
package com.marie.localservicesample;
public interface ILocalBinder {
public int getStatusCode();
}
/**************************************************************************************************
* Filename: LocalBinder.java
* Project name: Local Service Sample
* Application name: Local Service
* Description: This file contains the LocalBinder class for our Local Service application
**************************************************************************************************/
package com.marie.localservicesample;
import android.os.Binder;
import com.marie.localservicesample.LocalService;
/**
* Class for clients to access. 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 implements ILocalBinder {
#Override
public int getStatusCode() {
return LocalService.statusCode;
}
}
Thanks!
See the local service example.
Just copy the binder class code they have into your service instead of making a separate file for it: (inside the LocalService class declaration)
public class LocalService {
// This is the object that receives interactions from clients. See
// RemoteService for a more complete example.
private final IBinder mBinder = new LocalBinder();
/**
* Class for clients to access. 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 {
LocalService getService() {
return LocalService.this;
}
}
...
}
and then:
public void onServiceConnected(ComponentName className, IBinder service) {
// This is called when the connection with the service has been
// established, giving us the service object we can use to
// interact with the service. Because we have bound to a explicit
// service that we know is running in our own process, we can
// cast its IBinder to a concrete class and directly access it.
mBoundService = ((LocalService.LocalBinder)service).getService();
Now you can access your service directly using mBoundService.
I want to test my bound service with ServiceTestCase.
The testing consists of binding to MyBindServer, and sending a Message.
Watching the logs, you can see the service is started when onBind() is called,
and a message is sent from testAHello(), but, the server's handleMessage() is never called.
From the logs:
I/TestRunner( 2099): started: testAHello(com.inthinc.mybindserver.test.MyBindServerTest)
I/MyBindServerTest( 2099): setUp()
I/MyBindServer( 2099): onBind, action=com.inthinc.mybindserver.START
I/MyBindServerTest( 2099): testAHello
I/MyBindServerTest( 2099): sending SAY_HELLO
[here is where I expect to see the output from handleMessage()]
I/MyBindServerTest( 2099): tearDown()
I/TestRunner( 2099): finished:testAHello(com.inthinc.mybindserver.test.MyBindServerTest)
I/TestRunner( 2099): passed: testAHello(com.inthinc.mybindserver.test.MyBindServerTest)
Here is the code for MyBindServer.java:
package com.inthinc.mybindserver;
import android.app.Service;
import android.content.Intent;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.Messenger;
import android.util.Log;
public class MyBindServer extends Service {
static final String TAG = "MyBindServer";
public static final int MSG_SAY_HELLO = 1;
final Messenger mMessenger = new Messenger(new IncomingHandler());
class IncomingHandler extends Handler {
#Override
public void handleMessage(Message msg) {
Log.i(TAG, String.format("handleMessage, what=%d", msg.what));
switch (msg.what) {
case MSG_SAY_HELLO:
Log.i(TAG, "hello");
break;
default:
super.handleMessage(msg);
}
}
}
#Override
public IBinder onBind(Intent intent) {
Log.i(TAG, String.format("onBind, action=%s", intent.getAction()));
return mMessenger.getBinder();
}
}
Here is the code for MyBindServerTest.java:
package com.inthinc.mybindserver.test;
import com.inthinc.mybindserver.MyBindServer;
import android.content.Intent;
import android.os.IBinder;
import android.os.Message;
import android.os.Messenger;
import android.os.RemoteException;
import android.test.ServiceTestCase;
import android.test.suitebuilder.annotation.SmallTest;
import android.util.Log;
public class MyBindServerTest extends ServiceTestCase<MyBindServer> {
private static final String TAG = "MyBindServerTest";
Messenger mServer = null;
public MyBindServerTest() {
super(MyBindServer.class);
}
public MyBindServerTest(Class<MyBindServer> serviceClass) {
super(serviceClass);
}
#Override
public void setUp() {
try {
super.setUp();
Log.i(TAG, "setUp()");
Intent bindIntent = new Intent("com.inthinc.mybindserver.START");
IBinder binder = bindService(bindIntent);
assertNotNull(binder);
mServer = new Messenger(binder);
} catch (Exception e) {
e.printStackTrace();
}
}
#Override
public void tearDown() {
try {
super.tearDown();
Log.i(TAG, "tearDown()");
} catch (Exception e) {
e.printStackTrace();
}
}
#SmallTest
public void testAHello() {
Log.i(TAG, "testAHello");
assertNotNull(mServer);
Message msg = Message.obtain(null, MyBindServer.MSG_SAY_HELLO);
Log.i(TAG, "sending SAY_HELLO");
try {
mServer.send(msg);
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
I was able to get this working using the procedure below..anyone is welcome to chime in if this is incorrect, but the example above works (i.e. MyBindServer's handler receives messages)
It seems as though ServiceTestCase's bindService() method intends to act like a local service. In this case, the goal is to test as a separate process, which means using the following instead of ServiceTestCase's bindService:
Intent bindIntent = new Intent(<registered intent>); //Where registered intent is declared in the manifest file
getContext().bindService(bindIntent,mConn,Context.BIND_AUTO_CREATE);
where mConn is a ServiceConnection object implemented to do whatever your test needs it to do, in the case above, set mServer.
With the above, MyBindServer's handleMessage() is called for the testAHello() test.
UPDATE: I have noticed that depending on how quickly the test processing is done, teardown() can be called before the binding is ready to use. In the case above adding control variables to throttle the program flow based on mConn's onServiceConnected being called provided consistent results.
E.g.
protected boolean bound = false;
protected boolean processed = false;
private ServiceConnection mConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className,
IBinder service) {
Log.i(TAG,"Service conn");
mServer = new Messenger(service);
if(mServer != null
&& mServer != null){
bound = true;
}
processed = true;
}
#Override
public void onServiceDisconnected(ComponentName name) {
// TODO Auto-generated method stub
Log.i(TAG,"Service Disconn");
}
};
Then add:
while(!processed){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
to testAHello()
I am trying to send a message to my main activity from an Async task embedded within a Service. Basically, the Async task has to block on input and it can't run in the main Activity thread (the blocking was removed from the example code below). When the data comes in though, I need to send it to the main activity. I am finding that the messages sent below never make it. If the answer is moving the bind within the Async task, how do you do that? Pointing to example code would be a big help if possible.
public class InputService2 extends Service {
int bufferSize = 1024;
Process process;
DataInputStream os;
TextView inputView;
byte[] buffer = new byte[bufferSize];
private MyAsyncTask inputTask = null;
public void onCreate(){
inputTask = new MyAsyncTask();
inputTask.execute((Void[])null);
}
private class MyAsyncTask extends AsyncTask<Void,Void,Void> {
int mValue = 0;
static final int MSG_SET_VALUE = 3;
protected void onProgressUpdate(Void progress){
}
protected void onPostExecute(Void result) {
}
protected Void doInBackground(Void... params) {
int i = 0;
try {
mValue = 0x23;
Message message = Message.obtain(null,MSG_SET_VALUE,mValue,0);
mMessenger.send(message);
}
catch (Exception e) {
}
}
}
class IncomingHandler extends Handler {
#Override
public void handleMessage(Message msg) {
}
}
final Messenger mMessenger = new Messenger(new IncomingHandler());
public IBinder onBind(Intent intent) {
return mMessenger.getBinder();
}
}
Below is inside the activity:
class IncomingHandler extends Handler {
#Override
public void handleMessage(Message msg) {
Context context = getApplicationContext();
int duration = Toast.LENGTH_LONG;
Toast toast = Toast.makeText(context, msg.arg1, duration);
toast.show();
}
}
boolean mBound;
private ServiceConnection mConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder service) {
// This is called when the connection with the service has been
// established, giving us the object we can use to
// interact with the service. We are communicating with the
// service using a Messenger, so here we get a client-side
// representation of that from the raw IBinder object.
mService = new Messenger(service);
mBound = true;
}
public void onServiceDisconnected(ComponentName className) {
// This is called when the connection with the service has been
// unexpectedly disconnected -- that is, its process crashed.
mService = null;
mBound = false;
}
};
protected void onStart() {
super.onStart();
// Bind to the service
bindService(new Intent(this, InputService2.class), mConnection,
Context.BIND_AUTO_CREATE);
}
It looks like you based your example on the javadoc reference at http://developer.android.com/reference/android/app/Service.html#RemoteMessengerServiceSample, however you left out much of the implementation detail that actually makes it work. You have to go back and implement the full functionality referenced in that example to use that particular pattern: pay careful attention to the REGISTER_CLIENT and UN_REGISTER_CLIENT implementation sections in the IncomingHandler class as these are the bits that actually ensure that the Message can be transferred from the Service to the Activity.