How to use interface to communicate between activities after process death? - android

I am building an SDK and need to implement callbacks between activities, without actually finish an activity. I previously used onActivityResult to provide results back to caller activity. However, this closes activity and I need to deliver callback, without finishing activity from SDK. My current implementation:
fun initializeSDK(){
SDK.getInstance().initialize(resultsCallbackImpl)
}
val resultsCallbackImpl:ResultsCallback = object : ResultsCallback {
override fun response1() {
}
override fun response2() {
}
};
For example, the client calls initializeSDK() from his activity after the button click. Then the client passes interface as parameter, which is set as a property in SDK singleton. Then I use that interface to return results.
The problem occurs after process death. The interface becomes null, because it is not serialized and I can't return callback to client anymore. How should I edit my code to tackle this issue? Is it even possible?
I know that client can initialize SDK in the application class, then it will be re-set after process death. However, such an approach will result in difficulty for the client to communicate results back to activity from application class.

Update:
Do a right click on the project tree and add a new AIDL file called IMyAidlInterface.aidl:
package com.test.aidlsample;
import com.test.aidlsample.MyData;
interface IMyAidlInterface {
List<MyData> getData(long id);
}
If you need to return objects to your client you need to declare and define them as parcelable and import them in aidl file too, here is the MyData.aidl that should be beside the other aidl file:
package com.test.aidlsample;
// Declare MyData so AIDL can find it and knows that it implements
// the parcelable protocol.
parcelable MyData;
and this is MyData.java in the java folder:
public class MyData implements Parcelable {
private long productId;
private String productName;
private long productValue;
public MyData(long productId, String productName, long productValue) {
this.productId = productId;
this.productName = productName;
this.productValue = productValue;
}
#Override
public int describeContents() {
return 0;
}
#Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeLong(this.productId);
dest.writeString(this.productName);
dest.writeLong(this.productValue);
}
protected MyData(Parcel in) {
this.productId = in.readLong();
this.productName = in.readString();
this.productValue = in.readLong();
}
public static final Parcelable.Creator<MyData> CREATOR = new Parcelable.Creator<MyData>() {
#Override
public MyData createFromParcel(Parcel source) {
return new MyData(source);
}
#Override
public MyData[] newArray(int size) {
return new MyData[size];
}
};
}
Now build the project so Stub class gets built. After a successful build continue with the service:
public class SdkService extends Service {
private IMyAidlInterface.Stub binder = new IMyAidlInterface.Stub() {
#Override
public List<MyData> getData(long id) throws RemoteException {
//TODO: get data from db by id;
List<MyData> data = new ArrayList<>();
MyData aData = new MyData(1L, "productName", 100L);
data.add(aData);
return data;
}
};
#Nullable
#Override
public IBinder onBind(Intent intent) {
return binder;
}
}
and add the service to the sdk manifest. If you are adding sdk as a dependency to the client like: implementation project(':sdk') you don't need to add AIDL files to client. If not, you have to add them and build the client application. Now, only remains to implement the client activity:
public class MainActivity extends AppCompatActivity {
IMyAidlInterface mService;
/**
* Class for interacting with the main interface of the service.
*/
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. We are communicating with our
// service through an IDL interface, so get a client-side
// representation of that from the raw service object.
mService = IMyAidlInterface.Stub.asInterface(service);
try {
List<MyData> data = mService.getData(1L);
updateUi(data);
} catch (RemoteException e) {
// In this case the service has crashed before we could even
// do anything with it; we can count on soon being
// disconnected (and then reconnected if it can be restarted)
// so there is no need to do anything here.
}
}
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;
}
};
private void updateUi(List<MyData> data) {
//TODO: Update UI here
}
#Override
protected void onResume() {
if (mService == null) {
Intent serviceIntent = new Intent();
//CAREFUL: serviceIntent.setComponent(new ComponentName("your.client.package", "your.sdk.service.path"));
serviceIntent.setComponent(new ComponentName("com.test.sampleclient", "com.test.aidlsample.SdkService"));
bindService(serviceIntent, mConnection, Context.BIND_AUTO_CREATE);
} else {
try {
updateUi(mService.getData(1L));
} catch (RemoteException e) {
e.printStackTrace();
}
}
super.onResume();
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
}
every time your client activity gets visibility, it gets data from sdk service. Just build your logic over this template. In sdk activity save data to a database and in service query them from database. I've used simple parameters in this sample.
I assumed your sdk is a library in the client app. If not, you need to do some small modifications maybe. And as I mentioned before you can find more details here: Android Interface Definition Language (AIDL). There are lots of samples and even more Q/A here in the SO on the subject. Good luck.
Original: You need to get callbacks from an activity that is currently invisible since your SDK activity is in front, right? To do that you can create a database for your SDK, persist data to your database and get data via an AIDL in the starting activity:
SdkService sdkService;
CallbackData callbackData
private ServiceConnection mConnection = new ServiceConnection() {
// Called when the connection with the service is established
public void onServiceConnected(ComponentName className, IBinder service) {
sdkService = SdkService.Stub.asInterface(service);
}
// Called when the connection with the service disconnects unexpectedly
public void onServiceDisconnected(ComponentName className) {
Log.e(TAG, "Service has unexpectedly disconnected");
sdkService = null;
}
};
in onCreate:
Intent i = new Intent()
i.setClassName("your.sdk.packageName", "your.sdk.service.path.and.name");
bindService(i, mConnection, Context.BIND_AUTO_CREATE);
and in whenever needed:
if(sdkService != null){
callbackData = sdkService.getCallbacks();
updateUI();
}
Just be careful getting a binder is an async job so if you call bindService and right after call sdkService.getCallbackData you get a NullPointerException. So you might want to move getCallbacks and updateUI inside the onServiceConnected and call bindService in onResume so every time activity becomes visible you would check if there is CallbackData so you can update your UI or whatever.

You cannot use interfaces directly to communicate between activities.
As soon as you start a new activity and new activity becomes visible android OS can kill 1st activity anytime (you can try this with a flag inside developer option "Don't keep activities"). So user of your SDK will complain about certain random "null pointer exception".
So, Now if you want to share data between current and previous screen, you might have to rethought your solution using Fragments.
Exposing your UI using a fragment and communicating back your result to activity which then would update proper fragment which needs the data.
I faced similar issue in one existing app which I was asked to fix. I switched entire app to fragments and single activity, first to release a hot fix.

The problem occurs after process death. The interface becomes null, because it is not serialised and I can't return callback to client anymore. How should I edit my code to tackle this issue? Is it even possible?
This is not possible. If the client process dies, all of its executing code - including your SDK - gets wiped away.
I know that client can initialise SDK in the application class, then it will be re-set after process death. However, such approach will result in difficulty for client to communicate results back to activity from application class.
So what? If the client Activity is restarted, it should call the SDK again to set a new callback instance which you can use from that point forward.

You can use a sharedviewmodel that is bound to both activities; have a mutablelivedata variable that you can observe from the two activities.
ideally on the first activity you can just put the value inside the mutablelivedata variable. Then on the second activity get the activity.
Follow the following link to give you a guideline.
ViewModel Overview

Related

Why can't I bind to my foreground service twice from the same object?

I have an object in app A that implements IServiceConnection which binds to a foreground Service in a different app (app B):
public class MyServiceConnection : Java.Lang.Object, IServiceConnection
{
private IBinder _binder;
private TaskCompletionSource<bool> _connectTcs;
public MyServiceConnection()
{
BeginConnect();
}
public void BeginConnect() => ConnectAsync().ContinueWith(EndConnect);
public Task<bool> ConnectAsync(CancellationToken ct = default)
{
if (_connectTcs?.IsCompleted ?? true)
{
_connectTcs = new TaskCompletionSource<bool>();
ct.Register(() => _connectTcs.TrySetCanceled(ct));
var package = "com.sample.appb";
var componentName = new Componentname(package, $"{package}.ServiceInB");
var service = new Intent().SetComponent(componentName);
if (Build.VERSION.SdkInt >= BuildVersionCodes.O)
{
Application.Context.StartForegroundService(service);
}
else
{
Application.Context.StartService(service);
}
Application.Context.BindService(service, this, Bind.AutoCreate);
}
return await _connectTcs;
}
private void EndConnect(Task task)
{
// handle task result
}
public void OnServiceConnected(ComponentName name, IBinder service)
{
_binder = service;
}
public void OnServiceDisconnected(ComponentName name)
{
_binder = null;
}
// methods that are called on the service
}
When the object is created, I bind to the Service and it is successful. When app B crashes or I call am force-stop on app B, OnServiceDisconnected() gets called, which is expected. I handle the lost connection by connecting again, but on the second connection OnServiceConnected() never gets called, so I never get my IBinder object to call methods on. If I restart app A I can perform a single bind again. How do I properly get the Service binder multiple times during one run time?
To solve this issue, I split the original object into two objects: One that inherits from IServiceConnection and holds the IBinder from the service, and another that holds instances of these. When the holder needs to connect, it first disposes any held instance of the IServiceConnection, then calls Context.BindService() with a new instance. When the holder needs to disconnect, it calls Context.UnbindService(), then disposes the IServiceConnection instance.

What process is the best way to send on API and save SQLiteDatabase?

I'm new on Android and working an big app which has sending data to API and saving it on SQlite. All of this process is on one class file . But it leaves me on an error. Sometimes the device hanged. other scenario is the data is incomplete . I have read about Intent Service and Services and I want to learn about the two, but I'm wondering how to get all of my data from UI and put it on services. May I know How?
It depends on the nature of the application. If this should happen in response to a user input...you could well use an AsyncTask. Otherwise, a background service could also do the job.
What you should NEVER do is run a network operation and/or database access on the main UI thread.
Services can receive data via intents. The way to send these intents depend on the type of service (Started, Bound or both). There are plenty of resources out there you can read...here's one from Android documentation...
https://developer.android.com/guide/components/services
An Example of an AsyncTask
The example below shows an implementation of AsyncTask that fetches a user's details from a network resource...
public class FetchUserTask extends AsyncTask<String,Void, UserDTO> {
private FetchUserTaskListener listener;
#Override
protected UserDTO doInBackground(String...params){
if(params == null || params.length == 0)
return null;
String userID = params[0];
UserDataProvider provider = new UserDataProvider(userID);
try {
return provider.get(userID);
}
catch(Exception ex){
//log the error
return null;
}
}
#Override
protected void onPostExecute(UserDTO user){
if(listener != null)
listener.onCompleted(user);
}
public void setListener(FetchUserTaskListener listener){
this.listener = listener;
}
public interface FetchUserTaskListener{
void onCompleted(boolean success);
}
}
How'd you use this AsyncTask?
For example, in an Activity, you would use it as below...
public class UserDetailsActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
//instantiate activity...
super.onCreate(savedInstanceState);
setContentView(R.layout.whatever_layout);
fetchUser(userId);
}
private void fetchUser(String userID){
FetchUserTask task = new FetchUserTask();
task.setListener(new FetchUserTaskListener<UserDTO>() {
#Override
public void onCompleted(UserDTO user) {
//CAUTION: make sure the activity hasn't been stopped before
//accessing any UI elements and/or context
}
}
task.execute(userID);
}
}
Note
You can (and will need to) make the example(s) above a bit more sophisticated. For example you can have the FetchUserTaskListener's onCompleted method return also an error message if an error occurred.
You will also need to check whether the activity has been paused or stopped before you access any context-bound data otherwise you might get an ILlegalStateException.
Make use of SQLiteOpenHelper class and it has methods to be overridden in your own class by extending SQLiteOpenHelper. Create Add, Update, Delete, Get methods as per your requirement in this class and keep this class as Singleton pattern. User Asynctasks to call those methids and you are done.
Hope that helps you visualise things in better way.

Binding and starting a service when application starts

I have a service in Android that encapsulates a framework that has a start method. The service boils down to something like this, many things omitted:
public class MyService extends Service {
private IBinder thisBinder;
public MyService(){
thisBinder = new LocalBinder();
}
#Override
public IBinder onBind(Intent intent) {
return thisBinder;
}
public void start(Map<String, Object> options)
{
getDriverManager().start(options);
}
}
I also have a bridging class that makes calls to the service:
public class MyServiceBridge implements ServiceConnection {
private boolean started = false;
private boolean bound = false;
private MyService myService;
public MyServiceBridge(Context context){
this.context = context;
}
public void bindService(){
Intent intent = new Intent(getContext(), MyService.class);
getContext().bindService(intent, this, getContext().BIND_AUTO_CREATE);
getContext().startService(intent);
}
// Here's a sample call, and the one that is relevant
public void start(Map<String, Object> options){
setOptions(options);
if(bound == true){
getMyService().start(options);
}
else{
started = true;
}
}
}
I call the bridge's start method in order to run the service. This works fine, except in this particular situation (so far). The MyApplication class calls the bridge's start method on onCreate:
public class MyApplication extends Application {
#Override
public void onCreate() {
super.onCreate();
getServiceBridge().start(null);
}
}
This, according to the docs is "Called when the application is starting, before any activity, service, or receiver objects (excluding content providers) have been created.". Indeed it appears to be so, because the service does not start, and instead starts when I close the app (odd, at least). This works if I move the call to an activity's onCreate method, but that's not ideal because I can also stop the service, and I want the service to run for the lifetime of the app. That is, the service should start when the app starts and stop when the app terminates. Does this make sense? Is there another way to do this?
In my opinion, you did a good job when you decided to run service in Application onCreate. but it is strange to hear that service is started when you close the app.
I have done this several times, and service starts in application onCreate which must be called.
Have you checked if your application is alive and run in background? Please make sure that you killed you application before testing. Use Task Killer or something like that, to be sure that application is always freshly started.
Sadly, Android does not have appropriate mechanism to notify you when application is exited, because it is still alive until system decides to kill it and free resources.

sendBroadcast(intent) gives a null pointer exception

This is my second post today as fixing the 1st post lead to another problem which I am really struggling on. I want to use Broadcasts and intent to send data packets back from a service to the UI. I managed to bind and start the service successfully
see my other post if you want history and code
The null pointer exception comes on the sendBroadcast() inside the service. The service class does have its constructor re-called after binding the UI to the service. This happens from another class so Context can not be easily used. So I guess the sendBroadcast has no-where to go :(
I suspect this is my problem...the re-calling of the Service constructor after the initial binding. I have onDestroy, onPause and onResume covered with binding an unbinding.
Any ideas or suggestion would be great or maybe I am just going about this wrong is there another way?
EDIT
The previous problem of binding is now solved and due to me being new to the forums a slight delay in accepting the answer...sorry.
The class diagram is like this (it is ported C#code)
Activity doBind (on Curve.class)---> Activity starts a worker class (not treated as a service) Comm.class with a Handler for some comms--> the Comm starts another worker class --> previous worker class finally calls new Curve.class.
It is this last stage Curve.class where the sendBroadcastReceiver() then throws a nullpointer ref becasue the binder is lost.
I tested the broadcastreceiver just with a simple timer cutting out the worker classes in between and it works fine. Problems start when the Curve.class is recalled later further down the hierarchy and the binder gets nulled or "lost".
I removed all references of the binder from Curve except in onBind(). This might not be a good idea. Again the code below does work if used with a simple timer started directly from the UI (no other worker classes).
Some more code here:
The service
public class Curve extends Service
{
private NewCurvePointEventArgs newpoint = null;
private static final String TAG = "Curve";
Intent intent;
int counter = 0;
#Override
public void onCreate() {
super.onCreate();
}
#Override
public IBinder onBind(Intent arg0) {
// TODO Auto-generated method stub
IBinder mBinder = new LocalBinder<Curve>(this);
return mBinder;
}
#Override
public void onStart(Intent intent, int startId) {
}
#Override
public void onDestroy() {
}
public Curve(){
}
private void refreshintent(NewCurvePointEventArgs tmp)
{
ArrayList<String> thepoint = new ArrayList<String>();
thepoint.add()//various things added here
Bundle bundle = new Bundle();
// add data to bundle
bundle.putStringArrayList("New_Point", thepoint);
intent = new Intent();
intent.setAction(NEWCURVE_POINT);
intent.putExtra("NEW_POINT", bundle
sendBroadcast(intent);
}
The activity has this code now. the doBind() is called after the onCreate of the activity.
private BroadcastReceiver CurveReceiver = new BroadcastReceiver(){
#Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals(Curve.NEWCURVE_POINT)) {
displaynewpoint(intent);
}
}
};
private ServiceConnection CurveServiceConncetion = new ServiceConnection() {
#Override
public void onServiceConnected(ComponentName name, IBinder service) {
// TODO Auto-generated method stub
CurveService = ((LocalBinder<Curve>) service).getService();
}
#Override
public void onServiceDisconnected(ComponentName name) {
// TODO Auto-generated method stub
CurveService = null;
}
};
#Override
public synchronized void onResume() {
super.onResume();
if(D) Log.e(TAG, "+ ON RESUME +");
}
#Override
public synchronized void onPause() {
super.onPause();
if(D) Log.e(TAG, "- ON PAUSE -");
}
#Override
public void onStop() {
super.onStop();
if(D) Log.e(TAG, "-- ON STOP --");
}
#Override
public void onDestroy() {
super.onDestroy();
if(D) Log.e(TAG, "--- ON DESTROY ---");
unregisterReceiver(CurveReceiver);
unbindService(CurveServiceConncetion);
}
public void doBind(){
Boolean tmp;
tmp = bindService(new Intent(this, Curve.class), CurveServiceConncetion, Context.BIND_AUTO_CREATE);//Context.BIND_AUTO_CREATE
IntentFilter filter = new IntentFilter(Curve.NEWCURVE_POINT);
registerReceiver(CurveReceiver, filter);
}
This problem to me is because the Curve.class has its constructor called again after the initial doBind().
Surely there must be a way around this otherwise I have to load my worker classes closer in hierarchy to the UI with the code from Curve.class???
EDIT
Curve is an object that processes data, constants etc sent from an external machine and contains the processed data in arrays.
The logCat did of course exist I just wasn't looking in the right place here it is
ARN/System.err(10505): java.lang.NullPointerException
WARN/System.err(10505): at android.content.ContextWrapper.sendBroadcast(ContextWrapper.java:271)
WARN/System.err(10505): at pi.droid.Core.Curve.refreshintent(Curve.java:206)
WARN/System.err(10505): at pi.droid.Core.Curve.AddPoint(Curve.java:400)
WARN/System.err(10505): at pi.droid.Core.Comm.CommMeasure$CommMeasurement.AddPoint(CommMeasure.java:363)
WARN/System.err(10505): at pi.droid.Core.Comm.CommMeasure$GenericCommMeasurement.TryProcessData(CommMeasure.java:168)
WARN/System.err(10505): at pi.droid.Core.Comm.CommMeasure$CommMeasurement.ProcessData(CommMeasure.java:302)
WARN/System.err(10505):at pi.droid.Core.Comm.ClientConnection$timer_tick.run(ClientConnection.java:164)
WARN/System.err(10505): at java.util.Timer$TimerImpl.run(Timer.java:289)
You can also see the chain of the 2 other worker classes I use. The constructor of Curve is called after the bind from CommMeasure.
So this is my problem.
Do I need to totally change how my program is set up or is there another way around this?
FINAL EDIT
This code is brought from c# and Curve used eventhandlers to pass data around. I got rid of all them(java listeners) and used android Handler and broadcastreceiver.
It has been suggested that I should pass the CurveService around but this will be problematic as Curve has multiple constructors. The no parameter 1 for the service and then 1 like this
public Curve(Unit XUnit, Unit YUnit)
{ this.Title = "Curve";
this.finished = false;
this.XUnit = XUnit;
this.YUnit = YUnit;
this.YDirection = CurveDirection.Unspecified;
}
so surely instantiating that would be a problem with CurveService, which has to be like this: public Curve(){} ??
Anyway many thanks for all your help and advice.
Final Edit +1..lol
The UI creates a new instance of ClientConnection, that in turns creates a new instance of CommMeasure and finally CommMeasure creates a new instance of Curve to access Curve.addpoint.
I think this thread and the other linked 1 goes beyond a simple android problem and highlights the difficulties of code porting. Any .Net developer for example reading this will learn some peculiarities of android a lot quicker than I did. There is also good working code in there.
Thanks to all who helped especially Justin Breitfeller
The best thing for you to do is follow the example from the Android APIDemos.
A service to be used like you want to use it:
http://developer.android.com/resources/samples/ApiDemos/src/com/example/android/apis/app/LocalService.html
Look at the Binding class inside of this file to see a class that does binding like you should:
http://developer.android.com/resources/samples/ApiDemos/src/com/example/android/apis/app/LocalServiceActivities.html
Finally, if your constructor is being called twice on your service, you aren't binding to your service properly, or perhaps you are unbinding from it and binding to it again unexpectedly.
EDIT
From the stack trace, it appears that CommMeasure needs to have a reference to the instance of Curve that you receive in onServiceConnected.
EDIT 2
If you really want to make your life simple, pass getApplicationContext() to your CommMeasure class and just appContext.sendBroadcast() from that class to send out your point. This will prevent you from requiring a reference to the service in your long-running task.

AIDL: Stub class methods not being called

I am using AIDL to pass objects from an Activity to a Service and am getting some strange behavior. To my understanding, the idea behind AIDL is to create an interface in a .aidl file, which android will then implement (partially) in a dynamically generated class. Android will create an abstract class called Stub, which you then need to instantiate and add the implementation of the methods which you defined in your .aidl interface. Once all of that is in place, the remote service can be instantiated, and the methods declared in the .aidl interface file (and defined in your instantiation of the Stub class) can be called.
That is my impression of how this mechanism works, however when I tried implementing it, I notice that the definitions for the methods I declared in the Stub class are not being run; instead what is being run is IBinder.transct()
Here is a snippet of what I'm trying to do:
This is implemented in my Service:
public final INetService.Stub mBinder = new INetService.Stub() {
public void sendInteger(String ID, int data) throws RemoteException {
// TODO Auto-generated method stub
}
public void sendString(String ID, String data) throws RemoteException {
ServiceConnectionHandler connHandler = new ServiceConnectionHandler(ID, data);
}
public void sendObject(String ID, NetMessage data) throws RemoteException {
ServiceConnectionHandler connHandler = new ServiceConnectionHandler(ID, data.getData());
}
};
And this is inside my Activity, which tries to use and talk to the service:
private INetService mService = null;
private NetServiceConnection conn = null;
class NetServiceConnection implements ServiceConnection
{
public void onServiceConnected(ComponentName name, IBinder service) {
mService = INetService.Stub.asInterface(service);
Log.d( "ADDERSERVICECLIENT","onServiceConnected" );
}
#Override
public void onServiceDisconnected(ComponentName name) {
mService = null;
Log.d( "ADDERSERVICECLIENT","onServiceDisconnected" );
}
};
private void initService()
{
conn = new NetServiceConnection();
Intent i = new Intent();
i.setClassName( "framework.network", "framework.network.NetService" );
if (!bindService( i, conn, Context.BIND_AUTO_CREATE))
{
Toast.makeText(this, "bindService fails..", Toast.LENGTH_LONG).show();
}
}
....
mService.sendString((char)0, finalMessage);
The methods defined in INetService.Stub, like sendString, appear never to be called; clearly I'm missing something; any thoughts?
Thanks a lot!
Iva
Without the rest of your code, it is difficult to answer your question. See here and here for a pair of sample projects implementing a remote service and its client, using AIDL. Perhaps those implementations will give you some idea of where things are going wrong for you.
You must specify your class in an .aidl-file to make it public to IPC.
See http://fauxgrammer.com/?p=4 for more information.
Cheers!
I came across the same situation earlier today. In my case, I had two copies of the AIDL file in the client side and server side.
I was updating the AIDL file in on the client side, but totally forgot about updating the server-side AIDL. Since there's no type checking on Stub implementation it just ran but never returned anything.

Categories

Resources