What I have:
I have a library running on a process using aidl.
I have an app that uses this library, and on the messaging activity, I connect with the service to send messaging and I have a broadcast receiver to manage the incoming messages.
The problem?
if this library are going to be used by two apps on the same device the broadcast actions are going to be the same, and I will have problems when I send a broadcast.
What is my doubt?
What is the best way to "listen" the new incoming messages that I receive on my library and send it to the App.
Maybe a callback? or there are any better solution?
More information
The library provides a few methods to start a session, and other methods for send different type of messages (images, text, locations, etc...) and I receive a callback from another library, that uses C and C++, when a new message is incoming.
If you need more information feel free to ask.
My Code:
IRemote.aidl
interface IRemote
{
int sendTextMessage(String to, String message);
}
WrapperLibrary.java
public class MyLibrary extends Service {
// Current context, used to sendbroadcast() from #Callbacks
private Context mContext = this;
private static MyLibrary instance = new MyLibrary();
//Executor to start a new thread from the service.
final ExecutorService service;
#Override
public IBinder onBind(Intent arg0) {
//Return the interface.
return mBinder;
}
/** Return the current instance */
public static WrapperLibrary getInstance() {
return instance;
}
private final IRemote.Stub mBinder = new IRemote.Stub() {
#Override
public int sendTextMessage(String to, String message)
throws RemoteException {
Log.d(TAG, "Send Text Message. ");
int i = -1;
Future<Integer> task;
task = service.submit(new Callable<Integer>() {
public Integer call() {
return tu.tu_message_send_text(to, message);
}
});
try {
i = task.get();
} catch (Exception e) {
Log.e(TAG, "Send Text Message: EXCEPTION *** " + e.getMessage());
}
Log.d(TAG, "Send Text Message: Status Code: " + i);
return 0;
}
}
Callbacks.java
public class Callbacks extends JNICallback {
private Context mContext;
public Callbacks(Context context) {
this.mContext = context;
}
public void on_incoming_text_message(final String from, final String message) {
Log.d(TAG, " Incoming TEXT message from:" + from + " with message: " + message);
Intent i = new Intent(BroadcastActions.INCOMING_TEXT_MESSAGE);
i.putExtra("from", from);
i.putExtra("message", message);
mContext.sendBroadcast(i);
}
}
MainActivity.java
On this activity I have a broadcast receiver and I can update the UI with a new message
public class MessageReceiver extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
Bundle extra = intent.getExtras();
String incomingMessage = "";
if(extra != null) {
incomingMessage = extra.getString("message");
addNewMessage(new Message(incomingMessage, false));
}
Toast.makeText(MessagingActivity.this, "Incoming Message", Toast.LENGTH_LONG).show();
}
};
I was going to suggest to use LocalBroadcastManager or if it becomes messy EventBus, but if the service runs in its own process (which is not something I'm sure about) the messaging will not be passed from one process to the other.
So I would suggest to define the Broadcast Action from the service in strings.xml and make it different for every app. Of course, you'll have to be careful as you'll need also to update the receiver action for every app.
Well, finally I will use a Callbacks implementation.
The architecture is going to be like this:
App
Main Activity (Connect with the LibService)
LibService (is going to have the callbacks and broadcast receivers)
Library
Callbacks and interfaces but not running in a service.
This is the best approach to me to the future integration on other projects, and the library is going to be more simple without aidl and services.
I thought the use of the receivers was a very good options, but thinking about the integration on other projects this is the best way to do it (for me).
Related
I am writing an android app that listens for incoming text messages and then syncs with the backend. The app so far is running ok but after some time the app does not receive any incoming message unless when I open the app again. Have explored various options suggesting implementing JobService or just service but the more research am doing the more am getting confused. For the JobService I can see it only meant to schedule job after some time and the service might be killed once the job completes. I need help on the best way to work it out. Below is my code for the broadcast receiver class;
public class MessageReceiver extends BroadcastReceiver {
private static MessageListener messageListener;
Handler mHandler;
private static final String TAG = "MessageReceiver";
public MessageReceiver() {
}
#TargetApi(Build.VERSION_CODES.M)
#Override
public void onReceive(final Context context, Intent intent) {
if (Telephony.Sms.Intents.SMS_RECEIVED_ACTION.equals(intent.getAction())) {
final Bundle data = intent.getExtras();
HandlerThread bgHandlerThread=new HandlerThread("MyCoolBackgroundThread");
bgHandlerThread.start();
mHandler=new Handler(bgHandlerThread.getLooper());
Runnable backgroundRunnable = new Runnable() {
#Override
public void run() {
passReceivedMsg(data);
}
};
mHandler.post(backgroundRunnable);
}
}
I have implemented an interface where I pass the received TextMessage Object,
private void passReceivedMsg(final Bundle bundleData) {
if (bundleData !=null ){
try {
final Object[] pdusObj = (Object[]) bundleData.get("pdus");
if (pdusObj != null) {
for (int i = 0; i < pdusObj.length; i++) {
SmsMessage currentMessage =
SmsMessage.createFromPdu((byte[]) pdusObj[i]);
Log.d(TAG, "run: currentMessage: "+currentMessage);
messageListener.getReceivedMessage(currentMessage);
Log.d(TAG, "handleMessage: message "+currentMessage);
}
}
}
catch (Exception e){
Log.d(TAG, "onReceive: Error occurred "+e);
}
}
}
public static void bindListener (MessageListener listener){
messageListener = listener;
}
The MessageListener is as below;
public interface MessageListener {
void getReceivedMessage(SmsMessage message);
}
Below is my manifest
<receiver
android:name=".utils.MessageReceiver"
android:enabled="true"
android:exported="true"
android:permission="android.permission.BROADCAST_SMS">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED"/>
<action android:name="android.provider.Telephony.SMS_RECEIVED" />
</intent-filter>
</receiver>
I do not know where you think your MessageListener object is coming from.
Please understand that your application process does not run forever. As soon as your app's UI moves to the background, Android can terminate your process at any time. This gets rid of any MessageListener object that may exist.
And so what is happening, most likely, is that you set up this MessageListener, and it works for a while, until that original process gets terminated. Future SMS messages will start a fresh process for your app, but you no longer have your MessageListener, and so your work does not get done.
Instead, get rid of MessageListener and whatever static field you are using to hold the reference to it. Have your BroadcastReceiver use WorkManager to do its background work, where your Worker can handle everything itself, without relying upon any activities, etc. of your app necessarily having run in the current process.
I have an android activity and a corresponding service, where the activity is just a UI and the service calculates what to display. The service is bound to the activity.
First I establish a connection:
in the Activity.java file:
final Messenger _messenger = new Messenger(new IncomingHandler(new WeakReference<>(this)));
Messenger _serviceMessenger = null;
private ServiceConnection _connection = new ServiceConnection()
{
#Override
public void onServiceConnected(ComponentName name, IBinder service)
{
_serviceMessenger = new Messenger(service);
try
{
// sending the initial welcome message
Message m = Message.obtain(null, ForegroundService.BIND_SERVICE);
m.replyTo = _messenger;
_serviceMessenger.send(m);
}
catch(RemoteException ex)
{
_serviceMessenger = null;
}
}
#Override
public void onServiceDisconnected(ComponentName name)
{
_serviceMessenger = null;
}
};
private static class IncomingHandler extends Handler
{
private WeakReference<MyActivity> _parent;
IncomingHandler(WeakReference<MyActivity> parent)
{
super();
_parent = parent;
}
#Override
public void handleMessage(Message msg)
{
Log.i(LOG_TAG, "Activity: Received message");
MyActivity ta = _parent.get();
switch(msg.what)
{
case ForegroundService.LOCATION_UPDATED:
if(msg.obj == null)
{
Log.e(LOG_TAG, "Activity: msg null");
ta.setTexts("", null, null);
}
else
{
Log.i(LOG_TAG, "Activity: msg ok");
LocWithName loc = (LocWithName)msg.obj;
ta.setTexts(loc.getName(), Double.toString(loc.getHossz()), Double.toString(loc.getSzel()));
Chronometer chronometer = ta.findViewById(R.id.chronom);
chronometer.setBase(SystemClock.elapsedRealtime());
chronometer.start();
}
break;
case ForegroundService.LOST_GPS:
ta.setTexts("", "unknown", "unknown");
break;
default:
super.handleMessage(msg);
break;
}
}
}
in activity onCreate:
Intent startIntent = new Intent(MyActivity.this, ForegroundService.class);
startIntent.setAction(Constants.ACTION.STARTFOREGROUND_ACTION);
bindService(startIntent, _connection, Context.BIND_AUTO_CREATE);
startService(startIntent);
and in the service java file:
final Messenger _messenger = new Messenger(new IncomingHandler(new WeakReference<>(this)));
Messenger _activityMessenger = null;
private static class IncomingHandler extends Handler
{
WeakReference<ForegroundService> _parent;
IncomingHandler(WeakReference<ForegroundService> parent)
{
super();
_parent = parent;
}
#Override
public void handleMessage(Message msg) {
// received welcome message, now we know who to reply to
if(msg.what == ForegroundService.BIND_SERVICE)
{
_parent.get()._activityMessenger = msg.replyTo;
Log.d(LOG_TAG, "received reply address for messenger"); // after 1st edit
}
else
{
super.handleMessage(msg);
}
}
}
private void sendMessageToUI(int message, LocWithName newLoc)
{
Log.i(LOG_TAG, "Service: sending message to UI");
if(_activityMessenger != null)
{
Log.d(LOG_TAG, "messenger not null"); // after 1st edit
try
{
_activityMessenger.send(Message.obtain(null, message, newLoc));
Log.i(LOG_TAG, "Service: message sent");
}
catch(RemoteException ex)
{
// activity is dead
_activityMessenger = null;
}
}
}
then I start sending messages via the sendMessageToUI() function periodically from the service, namely every 5 seconds. The service's onStartCommand runs the first UI update immediately, which reschedules itself for every other iteration.
What I know:
the first immediate "UI update" in the service does run as logcat shows me the "sending message to UI" text at the correct time
all other updates run
all other updates deliver their messages successfully (which means that the first was stopped by _serviceMessenger being null, not the RemoteException, because the catch block would stop all later messages)
the welcome message from the activity to the service arrives as it is a necessity for further replies from the service
What I have tried:
in the activity, first bind then start the service (example code is in this state, initially it was the other way around), so _activityMessenger isn't null by the time it has to send first message
send a "burner message" so that is the one that doesn't get delivered instead of actually important messages
search google for similar problems to no avail - where there are problems, it doesn't work at all, not just the first time around
search this site for similar problems, same result as with google
Since there are five seconds between the first and the second message, I suspect it is an issue with the speed of initializing something, but I couldn't get further than that. So what exactly happens here and why doesn't it work?
EDIT 1: at the suggestion of #pskink, I added Log.d()-s. It turns out the activity only sends the "welcome message" with reply address after the first run of the UI updater despite being called earlier than startService.
Also, the code sending the messages, after #pskink asking:
in service class:
final Handler handler = new Handler();
Runnable updateUI = new Runnable()
{
// do work to get the information to display
// in this code I set "int message" to one of the constants handled by the activity's IncomingHandler and "LocWithName newLoc" to a useful value or null
sendMessageToUI(message, newLoc);
handler.removeCallbacks(updateUI);
handler.postDelayed(updateUI, 5000);
}
in service onStartCommand:
handler.post(updateUI);
Your mistake is assuming that the bindService() and startService() calls block until the service has been "bound" or "started", respectively. The reality is that onServiceConnected() won't get called until sometime after onCreate() returns. Likewise, the order you call them in is basically meaningless; the OS doesn't guarantee that the service will handle the binding or the onStartCommand() first or second, in this case.
To fix, delete the call to startService() (as #pskink suggested); the service is started by virtue of the fact that you are binding to it. onStartCommand() will no longer be called. Instead, have the Service kick off the updateUI Runnable when it gets the ForegroundService.BIND_SERVICE message. This allows you to establish the appropriate "happens before" relationships -- namely, that the ServiceConnection binding "happens before" you start trying to use _activityMessenger to send messages.
My intention is to have download service created when the app first runs and checks for update every 24 hours. I originally had everything running my main activity but it seems to much to run everything on one thread and one class. So this is my attempt to move it to another class and into service. It suppose to run and check for an update ever 24 hours and if there is no internet try again in 4 hours. I specifically want to involve any recursive problems, having two or three same services checking update, just one every 24 hours. But having problem with integrating my code into service, what am I doing wrong?
public class DownloadService extends IntentService {
// TODO 0 - Define your Download Service as Android component in
// AndroidManifest.xml
private int result = Activity.RESULT_CANCELED;
public DownloadService() {
super("DownloadService");
}
// Will be called asynchronously be Android
#Override
protected void onHandleIntent(Intent intent) {
private final Runnable mUpdateUi = new Runnable(){
public void run(){
check();
}
};
private void start(){
new Thread(
new Runnable(){
public void run(){
Log.d(TAG, "inside start");
Looper.prepare();
mHandler = new Handler();
check();
Looper.loop();
}
}
).run();
}
private void check(){
if (isNetworkAvailable()== true){
try {
new checkupdate().execute();
delayTime = 86400000;
Toast.makeText(DownloadService.this, "Daily update check!", Toast.LENGTH_SHORT).show();
}
catch (IOException e) {
e.printStackTrace();
delayTime = 21600000;
}
}else{
delayTime = 21600000;
Toast.makeText(DownloadService.this, "No internet for Daily update check, try again in little!", Toast.LENGTH_SHORT).show();
}
reCheck();
}
private void reCheck(){
mHandler.postDelayed(mUpdateUi, delayTime);
}
}
IntentService already handles setting up a worker thread and queue, and termination when the queue is empty. Which makes it a very good candidate for something like a download service to manage the actual work of downloading data, but not really a great candidate for a time scheduler.
I'd suggest using an AlarmManager to schedule your work instead. What you want is to trigger an Intent to start your DownloadService, by sending it intent with an Action indicating what to do.
Note also that if you want to cancel an IntentService with an Action, you will need to implement onStartCommand in addition to the usual onHandleIntent, so that you can respond to the action immediately -- you cannot do this from onHandleIntent, since the intent won't be sent to that until the current task in the queue is completed. Here's a quick example:
public class DownloadService extends IntentService {
private static final String TAG = "DownloadService";
////////////////////////////////////////////////////////////////////////
// Actions
public static final String ACTION_CANCEL = "package.name.DownloadService.action.CANCEL";
public static final String ACTION_DOWNLOAD = "package.name.DownloadService.action.DOWNLOAD";
////////////////////////////////////////////////////////////////////////
// Broadcasts
public static final String BROADCAST_DOWNLOADED = "package.name.DownloadService.broadcast.DOWNLOADED";
public static final String BROADCAST_ERROR = "package.name.DownloadService.broadcast.ERROR";
////////////////////////////////////////////////////////////////////////
// Extras
public static final String MESSAGE = "package.name.DownloadService.extra.MESSAGE";
// etc.
private boolean isCancelled;
// usual stuff omitted
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
if(intent != null) {
String action = intent.getAction();
Log.v(TAG, "onStartCommand() - action: "+action);
if(ACTION_CANCEL.equals(action)) {
isCancelled = true;
// insert code here to signal any objects to cancel
// their work, etc.
stopSelf();
}
}
return super.onStartCommand(intent, flags, startId);
}
#Override
protected void onHandleIntent(Intent intent) {
if(intent != null) {
final String action = intent.getAction();
Log.v(TAG, "onHandleIntent() - action: "+action);
if(ACTION_DOWNLOAD.equals(action)) {
handleDownloading(intent);
}
else if(ACTION_CANCEL.equals(action)) {
// nothing to do here, handled in onStartCommand
}
}
}
////////////////////////////////////////////////////////////////////
private void handleDownloading(Intent intent) {
// get stuff you need from the intent using intent.getStringExtra(), etc.
if(!isCancelled) {
// do downloading, call broadcastDownloaded() when done
}
else {
// stop work, send broadcast to report cancellation, etc.
}
}
// send a broadcast to a BroadcastReceiver (e.g. in your activity)
// to report that the download completed
private void broadcastDownloaded() {
Log.v(TAG, "broadcastDownloaded()");
Intent broadcastIntent = new Intent();
if (broadcastIntent != null) {
broadcastIntent.setAction(BROADCAST_DOWNLOADED);
broadcastIntent.addCategory(Intent.CATEGORY_DEFAULT);
sendBroadcast(broadcastIntent);
}
}
private void broadcastError(String message) {
Log.v(TAG, "broadcastError(), message: "+message);
Intent broadcastIntent = new Intent();
if (broadcastIntent != null) {
broadcastIntent.setAction(BROADCAST_ERROR);
broadcastIntent.addCategory(Intent.CATEGORY_DEFAULT);
if(message != null) {
broadcastIntent.putExtra(MESSAGE, message);
}
sendBroadcast(broadcastIntent);
}
}
}
This is not how IntentService is meant to be used. As per the documentation, IntentService already creates its own worker threads. You should not be creating your own:
Clients send requests through startService(Intent) calls; the service is started as needed, handles each Intent in turn using a worker thread, and stops itself when it runs out of work.
Apart from the fact that your code as shown here won't compile (your start method is inside the onHandleIntent method), your general approach seems to be to start your own worker thread. What would happen in this approach is that you would start the thread, onHandleIntent would complete and then the service would be stopped. In addition to not actually working, this approach is also a bad idea because (at best if you're lucky) the service would be running continually 24/7.
What you should do instead is actually do your main work in onHandleIntent which IntentService will queue on a worker thread for you. Then instead of using postDelayed use AlarmManager to set an alarm to send an Intent to start the service again in 24 hours or 4 hours.
Rarely I have an issue where a phone will get in a state (known as "funny state") where my Intent Services won't get a startService command from a Broadcast Receiver. (yes, the manifest has the receivers and services defined).
In this example I am listening for push notifications then calling a CheckinService.
Receiver:
public class PushReceiver extends BroadcastReceiver {
private static final String LOG_TAG = "push_receiver";
#Override
public void onReceive(Context context, Intent intent) {
logger.putExtra("debug", "Received Push");
Intent serviceIntent = new Intent(context, CheckinService.class);
context.startService(serviceIntent);
logger.putExtra("debug", "Sent to Service");
}
Service:
public class CheckinService extends IntentService {
private static final String LOG_TAG = "checkin_service";
public static final int SERVICE_ID = 3;
public CheckinService() {
super(LOG_TAG);
Log.i(LOG_TAG, "Service says: Checkin service started no constructor");
}
public CheckinService(String name) {
super(LOG_TAG);
Log.i(LOG_TAG, "Service says: Checkin service started with constructor");
}
#Override
protected void onHandleIntent(Intent intent) {
Log.i(LOG_TAG, "Auto Checkin started");
.....tons of genius logic.....
}
}
So when the phone gets in the funny state the "received push" gets logged and the "sent to service" gets logged but the constructors and onHandleIntent methods of the service never get called.
I also have this happen not only on pushes but on receivers for inexactRepeatingAlarm and perhaps others but these two have been confirmed for sure.
Again this is very, very, rare and seems to happen after the phone has been left unused for a period of time; and perhaps goes into a power saving mode.
Also, terminating the application's process clears this up.
I realized what was happening here.
The IntentService is single threaded. So if something in my " .....tons of genius logic....." was blocking (like a http request with no timeout) the next intent that came into the service would not be processed.
Nice and humbling.
I have this test class to test a remote service:
public class CoreServiceBasicTest extends ServiceTestCase<CoreService> implements ServiceConnection {
/** Tag for logging */
private final static String TAG = CoreServiceBasicTest.class.getName();
/** Receive incoming messages */
private final Messenger inMessenger = new Messenger(new IncomingHandler());
/** Communicate with the service */
private Messenger outMessenger = null;
/** Handler of incoming messages from service */
private static class IncomingHandler extends Handler {
#Override
public void handleMessage(Message msg) {
Log.d(TAG, "Incoming message");
}
}
/** Constructor for service test */
public CoreServiceBasicTest() {
super(CoreService.class);
}
/** Start the service */
#Override
public void setUp() {
// Mandatory
try {
super.setUp();
} catch (Exception e) {
e.printStackTrace();
}
// Start the service
Intent service = new Intent();
service.setClass(this.getContext(), CoreService.class);
startService(service);
Log.d(TAG, "Service started");
}
public void onServiceConnected(ComponentName className, IBinder service) {
outMessenger = new Messenger(service);
Log.d(TAG, "Service attached");
}
public void onServiceDisconnected(ComponentName className) {
// TODO Auto-generated method stub
}
#SmallTest
public void testBindService() {
// Bind to the service
Intent service = new Intent();
service.setClass(getContext(), CoreService.class);
boolean isBound = getContext().bindService(service, this, Context.BIND_AUTO_CREATE);
assertTrue(isBound);
}
}
The problem is that startService(service) in the setUp() method does not launch the service correctly. This is what the AVD shows:
As you can see, the process is launched but the service is not. Then on testBindService(), assertTrue(isBound) fails.
This doesn't happen if I launch the service from an Activity:
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Start the Core service
Intent service = new Intent();
service.setClass(this, CoreService.class);
if (startService(service) == null) {
Toast.makeText(this, "Error starting service!", Toast.LENGTH_LONG).show();
Log.e(TAG, "Error starting service");
} else {
Toast.makeText(this, "Service started sucessfully", Toast.LENGTH_LONG).show();
}
// Die
finish();
}
Here the service is started correctly, as shown below.
How can I start and bind to a remote service that uses Messenger to communicate with activities from an Android Test Project?
The whole point of Android Test Project (test.apk) is to instrument the Application Project (app.apk) and unit-test the Android components (Activity, Service and etc) which are associated with Application Project, in another word, unit-testing Activity and Service that is created and manipulated inside app.apk.
You should not write your MessengerService implementation partially (Messenger, IncomingHandler and etc) second time inside ServiceTestCase implementation under Test project. MessengerService implementation only need to be written once in your Application project's CoreService.java file.
ServiceConnection is used for inter-communication between Activity and Service, as we use ServiceTestCase here (means unit-test service, communication with other components is out-of-scope hence not considered), we don't need a ServiceConnection implementation. The only thing ServiceConnection does is initialize a solid Messenger object so that we could use later, once service is properly created:
public void onServiceConnected(ComponentName className, IBinder service) {
// This is what we want, we will call this manually in our TestCase, after calling
// ServiceTestCase.bindService() and return the IBinder, check out code sample below.
mService = new Messenger(service);
}
Also note that you don't need to call ServiceTestCase.startService() in this case, as ServiceTestCase.bindService() will properly start the service (if it is not started yet) and return a IBinder object we need to use to initialize Messenger object later.
Say if your IncomingHandler.handleMessage() impelementation in CoreService.java look like this:
... ...
switch (msg.what) {
case MSG_SAY_HELLO:
msgReceived = true;
break;
... ...
To test send message functions in ServiceTestCase:
... ...
IBinder messengerBinder = null;
#Override
public void setUp() throws Exception {
super.setUp();
// Bind the service and get a IBinder:
messengerBinder = bindService(new Intent(this.getContext(), CoreService.class));
//log service starting
Log.d(TAG, "Service started and bound");
}
public void testSendMessage() {
// Use IBinder create your Messenger, exactly what we did in ServiceConnection callback method:
Messenger messenger = new Messenger(messengerBinder);
Message msg = Message.obtain(null, MessengerService.MSG_SAY_HELLO, 0, 0);
messenger.send(msg);
// Do some assert stuff here
... ...
}
... ...
If your want to test communication between Activity and Service, then ServiceTestCase is not suitable in this case. Consider using ActivityInstrumentationTestCase2 test the actual Activity (which bound to your CoreService, which gives you ability to indirectly test your Service functions.
Just looking at the documentation for ServiceTestCase it says that the test framework delays starting the service until one of your test methods calls ServiceTestCase.startService() or ServiceTestCase.bindService().
Looking at your code you call ServiceTestCase.startService() in your setUp() method, not in a test method. This doesn't start the service yet. It is waiting for one of your test methods to call ServiceTestCase.startService() or ServiceTestCase.bindService().
Your test method testBindService() isn't calling ServiceTestCase.bindService(), instead it is calling Context.bindService() and failing. The test framework is still waiting, so that's why the service isn't started.
Have another look at the Lifecycle support discussion in the linked developer docs.