Android service causes a singleton's instance variable to be null - android

I have class called ClientManager where i load the dictionary from the server. Once the dictionary is loaded a Service is started (which runs in its own process) that will use the data from this dictionary. However, when i access the dictionary through the class singleton it is null. Dictionary object resides in class DataManager. Please see the code below:
ClientManager.java
DataManager mDataManager = DataManager.getInstance();
public void apiCompleted(ApiResult apiResult){
....
DataDictionary dataDict = (DataDictionary) apiResult.valueObject;
//dictionary loads OK from server since i can see the contents by iterating through it
mDataManager.addDictionary(dataDict);
if (!serviceRunning(MyService.class)){
Intent intent = new Intent(mContext, MyService.class);
mContext.startService(intent);
}
MyService.java
public class MyService extends Service {
...
DataManager mDataManager;
#Override
public void onCreate(){
mDataManager = DataManager.getInstance();
if(!mDataManager.containsDataDictionary()){
//toast dictionary is null
}
}
DataManager.java
public class DataManager {
private static DataManager instance = null;
private DataDictionary mDataDictionary = null;
public static DataManager getInstance(){
if (instance == null){
instance = new DataManager();
}
return instance;
}
public boolean containsDataDictionary() {
if ( m_dataDictionary == null ){
return false;
}
return true;
}
public DataDictionary getDataDictionary() {
return mDataDictionary;
}
public void addDataDictionary(DataDictionary p_dataDictionary) {
mDataDictionary = p_dataDictionary;
}
}

You said it yourself: "which runs in its own process"
Each process has its own VM. You cannot access variables etc from one process to another.
The obvious question to follow up this would be, do you absolutely need to run the service in its own process?

Data sharing between processes are done through Intents or ContentProviders.
If the data is only shared within your application package you may also use SQLite or SharedPreferences but both these uses non-volatile memory and are significantly slower than data sharing through IPC.

The Singleton Pattern is allowed to share data within the application not between applications. Any instances of an app its lifespan is restricted within scope of the app life. It seems that you are trying to extend the scope of singleton object outside the app, which is not possible at all..
Note : Each process runs in own VM, so target processes doesn't have singleton instance of source processes.
There are two ways of communication for an app...
1) Active Communication (IPC) : When both source and target app is running (i.e.. processes) you need source/sender should act as a server and target/receiver should act as a client and both should communicate with Remote Object which both side has same remote object signature.Example : AIDL implementation....
2) Passive Communication (Accessing Source's Resource) : When Source is not alive and target is trying to access the data of source which is stored in any kind of storage can be accessed via Intents/Content Provider.
If you want to share data between process then you go with AIDL implementation only...

Related

best practice to share global variables between activities

I'm a bit confusing on how to share global variables between activities in an android project that considered safe.
What is the best practice to do that? Extends Application class or make a custom singleton class?
An help is apprecciate, thanks in advance.
The problem with storing something in the application class is you cannot count on an activity being resumed from the same instance of the application object. For example an activity can be paused, the application killed (due to memory) along with any changes you made to object in the instance, and then the activity resumed with a new application instance.
Here is a very good BLOG post explaining how data stored in the application class can be lost:
http://www.developerphil.com/dont-store-data-in-the-application-object
I am not sure this is the very "Best" practice, but I think this is a good solution
I have a PersistData class holds application wide "globals". I use Dagger to inject instances of this singleton into any class that requires these variables.
The basic process it this:
When I save a value in this object via something like:
mPersistData.saveEmailId("me#example.com");
I first write it to an entry in SharedPreferences
I then save it to a member variable
In the constructor for the class, I initialize the member variables by reading them from SharedPreferences.
This way reads for the variable are "cached", ie they don't need to be read from SharedPreferences, and if the application is ever killed and restarted the member variables have the correct values. If you just hold the values in the application class, when the application is restarted the member variables will be invalid (not what you expect or even null).
Here is an example:
public class PersistData {
private String email;
public PersistData(MyApp app) {
mApp = app;
email = readEmailId();
}
public void saveEmailId(String emailToSave) {
writeEmailId(emailToSave);
email = emailToSave;
}
private void writeEmailId(String emailId) {
generalSettingsFileEditor.putString("USER_ID", emailId);
generalSettingsFileEditor.commit();
}
public String readEmailId() {
String emaiId = generalSettingsFile.getString("USER_ID","");
return(emaiId);
}
public String getEmail() {
return email;
}
}
In my application Dagger module I have this:
#Provides #Singleton
public PersistData providePersistData () {
System.out.println(String.format("mApp = %s", mApp));
return new PersistData(mApp);
}
Then whenever I need to access any of these variables I inject the singleton as so:
public class HomePresenter {
#Inject
PersistData mPersistData;
...
mPersistData.saveEmailId("me#example.com");
myEmail = mPersistData.getEmailId();
...
}
What is the best practice to do that? Extends Application class or
make a custom singleton class?
Think twice whether those variables are really global and they have to be shared between activities.
If the answer to first question is 'yes', then the best place would be to store them in Application.
You can implement a singleton too, but 1) it's slightly more difficult to test and b) if your global variables require Context, then again Application instance would fit the best.

Pass large string-arrays to another Activity

I am trying to pass a large string-array of maybe 2 or 3 MB to another activity. The chunk is not passed and the only thing I can see in the logcat about what happend is ...
!!! FAILED BINDER TRANSACTION !!!
I tried to create my own class which implements Serializable, put a mutator there in which I put my String, then pass the Object reference to intent.putExtra(key, Serializable obj)
the code:
MyClass mc = new MyClass();
Intent intent = new Intent(MainActivity.this, CalculationsActivity.class);
intent.putExtra("mc", mc);
Is there an easy solution to this issue - that is to pass a large string-array to another activity?
class MyClass implements Serializable {
private String[] str;
public void setString(String[] str) {
this.str = str;
}
public String[] getString() {
return this.str;
}
}
I thought that passing just a reference wouldnt cause this. Reference is not more than a memoryaddress
As others have stated, using Local files (in your sandboxed directory) or database entries will probably be the way to go. However, if you want to do remote (e.g. ftp) hosting of the file and still load them when the application loads you should use a Service. (see the Docs).
I have two previous answers explaining services in more depth that you should look at.
How to use threads and services. Android
and
Android Service with multiple Threads
Essentially though there are two types, a bound thread (which lives with either an activity or the application) and intent services (which can always be active, or only active when the app is open). What you would want is probably the former which would look something like what is in the first link.
here is a snippet
public class BoundService extends Service {
private final BackgroundBinder _binder = new BackgroundBinder();
//Binding to the Application context means that it will be destroyed (unbound) with the app
public IBinder onBind(Intent intent) {
return _binder;
}
//TODO: create your methods that you need here (or link actTwo)
// Making sure to call it on a separate thread with AsyncTask or Thread
public class BackgroundBinder extends Binder {
public BoundService getService() {
return BoundService.this;
}
}
}

how can i persist a model between multiple activities in android?

I'm a beginner to android development, and I'm trying to write my code in an MVC pattern, but I'm having trouble understanding how a model would work. As far as I can tell every time you start a new activity with an intent you are not able to pass a model along with it. As far as i can tell you'd have to reinitialize it each time you start a new activity. Am I missing something? I looked into Parcelable, but it seems that you loose your methods if you make your model Parcelable. right now I'm building a log in system, which checks my local sqllite db on start up if the user has already logged in, and if so it passes to another activity, otherwise it passes to the log in activity, but I wan't to keep that user model alive through all the activities. Is thee a way to do that?
You might want to also consider keeping a static reference around to the model data that you want to share across activities so that you don't have to keep serializing/deserializing the model when switching between activities. You can get away with using Parcelable if your models are small, but at some point, performance may become an issue.
I'm working on a project where we keep the models in a Singleton that we can access throughout the app, and although I generally hate Singleton's for how they can make unit testing more difficult, I have found this approach to perform better with larger models than trying to rely on Android's serialization mechanism.
Here's is a very rough example of what I mean (disclaimer: I have not actually run tested this code, but I hope this illustrates the concept):
You might have a singleton class that I terribly called Models
public class Models {
private static Models instance;
private boolean isInitialized = false;
private User user;
private OtherInterestingModel otherInterestingModel;
private Models() {
}
public static synchronized Models getInstance() {
if (instance == null) {
instance = new Models();
}
return instance;
}
public void loadModels() {
if (!isInitialized) {
/*
* One-time model initialization here.
*/
isInitialized = true;
}
}
public User getUser() {
return user;
}
public OtherInterestingModel getOtherInterestingModel() {
return otherInterestingModel;
}
}
In your LoginActivity, you can initialize the Models class, say, in your onCreate():
public class LoginActivity extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Models.getInstance().loadModels();
User user = Models.getInstance().getUser();
OtherInterestingModelData otherData = Models.getInstance().getOtherInterestingModel();
// Do something with the model data...
}
/*
* This might be called after the user enters data and clicks a login button...
*/
private void login() {
startActivity(new Intent(this, AwesomeLoggedInActivity.class));
}
}
Once the user successfully logs into your app, you could have basically the same code in your main activity:
public class AwesomeLoggedInActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Models.getInstance().loadModels();
User user = Models.getInstance().getUser();
OtherInterestingModelData otherData = Models.getInstance().getOtherInterestingModel();
// Do something with the model data...
}
}
Notice that by having a Singleton, you avoided having to serialize the model data by passing it through the intent that started the main activity.
Yes, you can do that with the Parcelable interface.
You do not lose your class's methods when you implement the Parcelable interface. The interface simply defines a method for writing your member variables to a Parcel object when you need to pass the object around.
Once you retrieve the data from your Intent via getParcelableExtra(), the object is recreated from the Parcel and you can once again treat it as an instance of whatever class it is.
For example, if you have a User class that extends Parcelable, you can bundle it with an Intent by calling putExtra("user", myUser). myUser is then (behind the scenes) packed into a Parcel and attached to the Intent. In your next Activity, you can retrieve that User object with User myUser = (User) getParcelableExtra("user");, and the Parcel will be unpacked and returned to you. You wil once again have a fully functioning User object.

android new activity can't load singleton

I am trying to use a Singleton to share a large data object between Activities. But when I open the new Activity, the singleton comes up as empty. It seems to me that the Singleton should be the same no matter where in the Application I call if from.
It seems like the Scope of the Singleton is being limited to the individual Activity. Working around this is making my App very complicated. I must be doing something wrong. I even tried instantiating them in an extended Application class... Google says I should not have to use that though...
Can someone please point out where I am going wrong? i.e. Why does this singletom not contain the same data in each Activity?
I call it from an Activity with...
DataLog dataLog = DataLog.getInstance(this);
I have...
public class DataLog extends ArrayList<String> implements Serializable {
private static final long serialVersionUID = 0L;
private static DataLog sInstance;
private static Context mContext;
public static DataLog getInstance(Context context) {
mContext = context.getApplicationContext();
prefs = PreferenceManager.getDefaultSharedPreferences(mContext);
if (sInstance == null) {
sInstance = new DataLog();
}
return sInstance;
}
private DataLog() {
}
public boolean add(String entry) {
super.add(entry);
return true;
}
public void add(int index, String entry) {
if (index > 0)
super.add(index, entry);
else
super.add(entry);
}
public void clear() {
super.clear();
}
...
}
Its highly advisable to avoid singleton for sharing large data sets in android.
Singletons are used for short life-cycle objects.
Switch to SharedPrefferences, SQLite DB's or file storing. You are not the only to have experienced this behavior, and the reason lies in the nature of android Activities and the system itself(managing activities and its data).
Here is an example why singleton is bad for your case:
You stored important data in it. The user knows that he can close the app on home button to call someone or whatever)maybe someone called him when he was in your app), and that when he opens your app he will come back at the same place with everything in order. (this is expected behavior from users and android apps). The system can easily kill your process and all static variables in it for memory maintenance, app inactivity etc...result=data lost. Thus its not safe to use it.

How to handle remote objects when an android service restarts?

I have created the following entities in Android :-
Android MyService (apk)
MyServiceClient (jar)
MyApplication (uses MyService via MyServiceClient)
For IPC, I have used AIDL. A sample implementation (of service client) is given below.
AIDL interface - ICLAZZ.aidl(Implemented on the service side, and used internally for IPC with service)
Service Client Side -
CLAZZ.java (API exposed to developers)
ICLAZZ.aidl
interface ICLAZZ {
void doSomething();
}
CLAZZ.java
public class CLAZZ
{
private ICLAZZ mSvcInstance; //remote object
// static method, instead of constructor for object creation due to some reason
public static synchronized CLAZZ getInstance(inputParam)
{
// ICLAZZ remoteObject = get the remote object from service
if(remoteObject!=null) {
INSTANCE = new INSTANCE(inputParam);
INSTANCE.mSvcInstance = remoteObject;
}
return INSTANCE;
}
private CLAZZ() {
}
private CLAZZ(inputParam) {
// initialize based on inputParam
}
public void doSomething() {
if(mSvcInstance!=null)
mSvcInstance.doSomething();
}
};
When the API user calls CLAZZ.getInstance(), I create a remote object instance and save it in the local object of CLAZZ and return the CLAZZ object to the user.
The problem that I am facing is, that in case where the service restarts, all the previous remote objects gets invalidated. However, the API user may have saved the CLAZZ object created earlier and might want to call some functionality on it. This will cause the application to fail. Also, I dont want to keep any global list of API objects created by the application. In the given scenario, is there some mechanism through which I can handle this situation gracefully and provide recovery for existing objects.
Sounds like the same problem as with RMI on standard Java.
When the remote object fails, simply get a new remote object (Bind on Android) from the service.
Ed

Categories

Resources