I'm using this tutorial to help me use a service that will always run in the background even if the application itself is in the background, and I want to be able to transfer strings between My main process and the service.
The thing is, I see all over the place that marshaling strings is supposed to be very easy, but for more complex stuff one needs AIDL.
Since I only want to send string messages, I figured there must be a very easy way to do this, but I can't find it..
Thanks
I suppose a intent service is the way to go.
Howto create a intent service is explained here
Ok, this is a very hackish solution and very not OOP-like, but it's what I ended up doing, so I'm putting it here if anyone ever finds the needs for a simple, not very elegant (but working) solution.
I'm using sendBroadcast and broadcastReceiver to communicate between the service and the application.
The problem is that sending intents cross process for some reason removes all the extras, so I used SharedPrefences instead.
this is the (singleton) class I use in order to publish and subscribe to such string messaging.
The Visitor interface
public interface Visitor<T> {
public void visit(T element);
}
The publish subscriber interface:
public interface IPulishSubscribe {
/**
* Publish a new string message
*
* #param key The key to publish under
* #param text The message's content
*/
public void publish(String key, String text);
/**
* Subscribe to a message
*
* #param key The key to subscribe to
* #param visitor The visitor to handle the message when received
*/
public void subscribe(final String key, final Visitor<String> visitor);
}
The implementation
public class PublishSubscriber implements IPulishSubscribe {
private static IPulishSubscribe _instance = new PublishSubscriber();
private static Context _context;
// key -> handler, isn't of much use ATM, but you may need it for removals...
private Map<String, BroadcastReceiver> _subscribers = new HashMap<String, BroadcastReceiver>();
private PublishSubscriber() {
}
public static void init(Context context) {
_context = context;
}
public static IPulishSubscribe getInstance() {
return _instance;
}
#Override
public synchronized void publish(String key, String text) {
SharedPreferences mPrefs = PreferenceManager.getDefaultSharedPreferences(_context);
SharedPreferences.Editor editor = mPrefs.edit();
editor.putString(key, text);
editor.commit();
_context.sendBroadcast(new Intent(key));
}
#Override
public synchronized void subscribe(final String key, final Visitor<String> visitor) {
BroadcastReceiver newSubscriber = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
SharedPreferences mPrefs = PreferenceManager.getDefaultSharedPreferences(_context);
String data = mPrefs.getString(key, null);
if (visitor != null) {
visitor.visit(data);
}
}
};
_context.registerReceiver(newSubscriber, new IntentFilter(key));
_subscribers.put(key, newSubscriber);
}
}
#Gal I like your answer. I dont know how fast it is but I hope it gets your job done.
I have been also confronting this problem. My first try was Messenger api, as demonstrated in Service documentation. It works but is rather limiting as to what you can send. You have to use a Bundle to send Strings.
Message msg = Message.obtain();
Bundle data = new Bundle(1); // size of Bundle array
data.putString("anything");
messenger.send(msg);
There are some nuances about reusing Message and Bundle objects in case you dont want to constantly create new objects.
I then tried AIDL in order to do interprocess communications easier. But after building the api, I clicked send send send and saw nothing.
The final way I tried was with files. Simple create a file, write to it and read from it on the other end. Sounds easy, but getting working properly is hard. Then you must understanding that the file will just grow and grow in size.
class Service_ extends Service
{
#Override protected void onCreate
{
new FileObserver("/sdcard/IFILE")
{
#Override public void onEvent(int event,String path)
{
if(event == MODIFY){} // Client sent message
}
}
}
}
class Client extends ServiceConnection
{
java.io.RandomAccessFile serviceWriter = new java.io.RandomAccessFile("/sdcard/IFILE");
}
Problem I have been facing is FileObserver seems to always be notified of file modification, yet my service fails to read the INIT instruction, which the client sents once so that the service can create the backing Console activity.
another thing is that Android is essentially Linux. Linux has raw methods for sending messages and file handle between processes. Atleast in the C api.
Related
I am aware that if I use android:processin the manifest then the class that extends Application will be called twice. Once, for the app and a second time for the new process.
The problem is I have code in MyApplication (extends Application) which should not be called twice. I want to have a separate process but I only want the code in MyApplication#onCreate to ever be ran once per load.
I tried setting a flag in Shared Preferences but it doesn't work, probably because of the different processes issue
Any ideas?
Root cause
From SharedPreferences documentation:
Note: This class does not support use across multiple processes.
That explains why your SharePreferences does not work.
Solution 1: Using Context.MODE_MULTI_PROCESS, but it is deprecated in API level 23
public static final int MODE_MULTI_PROCESS
This constant was deprecated in API level 23.
MODE_MULTI_PROCESS does not work reliably in some versions of Android,
and furthermore does not provide any mechanism for reconciling
concurrent modifications across processes. Applications should not
attempt to use it. Instead, they should use an explicit cross-process
data management approach such as ContentProvider.
Solution 2: Using ContentProvider
2.1. Write your own ContentProvider
We just need a way to store a boolean variable that indicates the onCreate() method of MyApplication is called for the first time or not, using this solution seems inefficient.
2.2. Use pre-defined ContentProvider from Android
The app must declare Read/Write external storage permissions and asking runtime permissions as well. By the way, it confuses users when the first time they open the app.
Solution 3: We can use the below flow to implement
Declare a BroadcastReceiver that runs in the same process as your app. Because they run on the same process, so they have the same process id.
The component (activity, service, receiver, provider) that runs on a separate process (private or global), so they will have a different process id.
Implementation
Step 1. Create a class that extends from BoardcastReceiver, named AppReceiver.
public class AppReceiver extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
Messenger messenger = intent.getParcelableExtra(MyApplication.EXTRA_MESSENGER);
int processId = intent.getIntExtra(MyApplication.EXTRA_PROCESS_ID, -1);
boolean isOnCreateCalledFirstTime = processId == Process.myPid();
Message message = Message.obtain();
message.arg1 = isOnCreateCalledFirstTime ? 1 : 0;
try {
messenger.send(message);
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
Step 2. Add that class to AndroidManifest.xml
<receiver
android:name=".AppReceiver"
android:enabled="true"
android:exported="true" />
Step 3. Modify your MyApplication class
public class MyApplication extends Application {
public static final String EXTRA_MESSENGER = "EXTRA_MESSENGER";
public static final String EXTRA_PROCESS_ID = "EXTRA_PROCESS_ID";
#Override
public void onCreate() {
super.onCreate();
Intent intent = new Intent();
String pkg = getPackageName();
intent.setComponent(new ComponentName(pkg, pkg + ".AppReceiver"));
Handler handler = new Handler(Looper.getMainLooper(), new Handler.Callback() {
#Override
public boolean handleMessage(#NonNull Message msg) {
boolean isOnCreateCalledFirstTime = msg.arg1 != 0;
if (isOnCreateCalledFirstTime) {
// TODO: First time onCreate() is called
} else {
// TODO: Next time onCreate() is called
}
return true;
}
});
Messenger messenger = new Messenger(handler);
intent.putExtra(EXTRA_MESSENGER, messenger);
intent.putExtra(EXTRA_PROCESS_ID, Process.myPid());
sendBroadcast(intent);
}
}
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
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.
I did a lot of research, but I didn't get through it, so that i don't know how to realize my App. The App consists of 2+ Activities, that contain content, that should be updated by a service in the background. So I dont know how to do the connection, some say i should do ipc, but others say thats too much of work, as long as service and activites run within the same process. I concerned to easily create methods like ActivityOne.RefreshData(Data data) and call those within the service, but i did not manage to get it work until now. I hope you have some suggestions to me and sorry for my bad english!
cheers
If you only need to provide data/updates to your own activities then IPC is most certainly not needed.
To achieve this, I would reverse the orientation you seem to be describing and rather than have the service calling methods on the activity, have it pushing messages to a Handler provided to it by the Activity when/if it starts.
See:
http://developer.android.com/reference/android/os/Handler.html
http://mobileorchard.com/android-app-developmentthreading-part-1-handlers/
Note that if what you need to send from the service to activites is always the same type of object, you can simplify your implementation of handleMessage() by using the Message.obj field to hold your type and not bother with Bundles or parcelling. As in:
Handler impl in activity where NotificationModel is the type that the service always sends:
private Handler mNotificationListener = new Handler(){
#Override
public void handleMessage(Message msg) {
handleIncomingNotification((NotificationModel)msg.obj);
}
};
The service side of posting msgs to this handler looks like:
public class NotificationRouter {
private Application mContext;
private SparseArray<Handler> mListeners = new SparseArray<Handler>();
public NotificationRouter (Application app){
this.mContext = app;
}
public void registerListener(Handler handler){
mListeners.put(handler.hashCode(), handler);
}
public void unRegisterListener(Handler handler){
mListeners.remove(handler.hashCode());
}
public void post(NotificationModel notice){
Message m = new Message();
m.obj = notice;
for (int i = 0; i < mListeners.size(); i++){
Handler h = mListeners.valueAt(i);
h.sendMessage(m);
}
}
}
So my problem is as follows: I have 2 services running in different processes and would like to keep it this way. One is busing data from databases to bound applications and the second is polling for incoming data through sockets. I feel keeping these in independent process would be better. The problem is that I would like to have a shared preferences between the two services and would like to implement OnSharedPreferenceChangeListener to update setting needed for polling and busing data. I can't implement OnSharedPreferenceChangeListener in the services since they run on different processes. I could implement this on the PreferenceActivity but how do I communicate to the services for immediate update? I do not want to use AIDL and worry about binding. There is the possibility of creating broadcast receivers and sending out intents but these seems like a big work around if the settings menu grows. Any other great ideas out there?
all right here is your answer...
for the preference of this example lets take 3 classes - 2 services service A and B (href A,B) and Settings(preferenceActivity)
initialize the two services as
public class ServiceA/B extends serice implements OnSharedPreferenceChangeListener{
#Overside
public void onCreate(....){
Settings.getPrefs(this).registerOnSharedPreferenceChangeListener(this);
}
#Override
protected void onResume() {
super.onResume();
Settings.getPrefs(this).registerOnSharedPreferenceChangeListener(this);
}
#Override
protected void onPause() {
super.onPause();
// Unregister the listener whenever a key changes
Settings.getPrefs(this)
.unregisterOnSharedPreferenceChangeListener(this);
}
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences,
String key) {
System.out.println("++"+key+"++");
if(key == "KEYA"||key == "KEYC")
Do_what_ever_you_want();
if (key == "KEYB")
do_anything();
}
do_anything(){}
Do_what_ever_you_want();
}
Shared preference Part.
public class Settings extends PreferenceActivity implements
OnSharedPreferenceChangeListener{
public static final String PREFS_PRIVATE = "PREFS_PRIVATE";
public static final String MASTERKEY = "!##$%^&*";
public static final String KEYA = "KEYA";
public static final String KEYB = "KEYB";
public static final String KEYC = "KEYC";
--- the create and get methods for getting and sharing data in the prefs... .....
// get them from just a google search.
}
I have this system implemented in one of my applicaiton... and deployed... so fiddle around these basics and let me know how it is goes...
Rajesh...
I've created a simple SharedPreferences based on ContentProvider, which can be used accross processes, you can find them from my bitbucket https://bitbucket.org/ABitNo/zi/src/783b6b451ba1/src/me/abitno/zi/provider/preference