I have an Activity with a private static field. And that static field is set to null when I turn the screen. Of course, one would guess that Android kills the process, but I'm sure it does not. But let's begin from the beginning.
There is a JNI library whose functions may be called from only one thread. From Java, the library functions are visible as native methods of an object (by the way, only one instance of that object is meaningful, there will be only one instance of the library with its static structures). The library object is used via a wrapper, a Java layer providing read/write access locks. The activity has a private static field referencing that wrapped library object. The library performs a long operation on a separate thread.
The singleton creation was like the following. Since all activities are created on the same UI thread, in onCreate() I just checked if the field is null and if it is null, created a wrapped library object. There was no problem -- at least, on Android 2.x.
Now, on Android 4, the following happens: I start a long operation and turn the screen.
The activity is re-created, the savedInstanceState parameter in onCreate() is not null, but that private static field is null.
Now, a new library object is created, then a new wrapper is created with its new read/write lock, and then a library operation is invoked... of course, the library is still busy with the request initiated before turning the screen, the new read/write lock does not protect the old library, data get corrupt and everything crashes.
I have heard that a remedy to this would be to keep the reference not as a static field but as an instance field of the Application.
Now, the question is:
WHY? What the hell is going on? Do I have to rewrite all singletons?
Related
This question already has answers here:
Non-static variable cannot be referenced from a static context
(15 answers)
Closed 3 years ago.
Here is my method:
public Cursor rawQuery(String sql, String[] selectionArgs) {
try {
return m_db.rawQuery(sql, selectionArgs);
} catch (SQLiteException e) {
reportException(Context.getApplicationContext(), e);
return null;
}
}
Android Studio (3.5.3) complains saying
Non-static method getApplicationContext() cannot be referenced from a static context.
I don't see any static context here. rawQuery is a perfectly good class method of a class which is a wrapper around SQLiteDatabase to save me having to check for exceptions on every call. The exception checks are needed because it's accessing a public database and some other process may have modified it in such a way that my operation fails (for example by dropping one of my tables). It is (currently) only created and its methods called from an Activity. Of course I could pass the calling activity's context, but this is clumsy since it isn't needed most of the time and IMHO it's very poor programming style to include extra arguments in the methods of a wrapper class.
reportException does what its name says and writes a log file or displays a Notification or a Toast, depending on circumstances, and all of these need a context.
I've seen suggestions on the net to create a static instance of a subclass of Application to cache the application context. As commenters have pointed out, this doesn't always work if you need the application context in a class constructor (or anything which is called from one), but I don't expect to do this. My wrapper class is created as needed when I want to access the database. However I'm not sure if tha Application subclassing trick works if I open a database in a background server which may get kicked out of memory when not active and later restarted by the OS. It may be that the only solution is to cache the creator's context in the constructor of the wrapper class: this only requires passing the context once. However I don't much like the idea of keeping a copy of the passed context: it looks inelegant and a potential problem with garbage collection since I have to take care not to use the cached context when creating anything persistent..
However I still don't see Android Studio's justification for complaining in the case shown. I tried removing all the calls to rawQuery and it still complains, so it isn't walking the call tree to look for a non-static context. It looks as if it may be complaining if getApplicationContext is used in any class which isn't a subclass of Activity, which certainly isn't justified.
I don't see any static context here.
The "static context" referred to by the error message is the way you are calling the method: Context.getApplicationContext(). Since you are using the Context class name, this counts as a "static context". You need a Context instance in order to call getApplicationContext().
Of course I could pass the calling activity's context, but this is clumsy since it isn't needed most of the time and IMHO it's very poor programming style to include extra arguments in the methods of a wrapper class.
Yes, I agree that you should keep your argument list as trimmed down as possible. You say that this method is a wrapper around SQLiteOpenHelper which requires a Context as one of its constructor parameters. So presumably your own constructor takes a Context to pass to the wrapped SQLiteOpenHelper instance. One solution is to keep that Context as a field in your class. Then you can just use this.context.
I have several classes in my application that uses the Context object to access SharedPreferences and serialize files. Simply put, I want to know how to "design away" the Context.
The background to why I want to do this is because:
The classes should be created in the onCreate() method of a Fragment (and the Context is not decided at this point)
It's just plain ugly to pass around the Context all the time. Especially since I use Singleton-reminding instantiation of these classes (Don't judge, please)
The specific context isn't really needed here, so it should be possible to design away... (What I mean is that I only need the Application Context)
An example of why this is ugly is my Cache object. It holds cached values downloaded from 1-5 different sources decided at runtime.
public static Cache getInstance(Context context) {
if(instance == null) {
instance = new Cache(context);
}
return instance;
}
When later using this object, it needs to read a SharedPreference which needs the Context, so it has to be passed around every single time I want to get an instance of the Cache.
So how can I get rid of these ridiculous contexts? Using the Application Context should be just fine... I guess that the problem can be boiled down to something like "How do I get a SharedPreferences object" in an object without a specific Context?"
I guess that the problem can be boiled down to something like "How do
I get a SharedPreferences object" in an object without a specific
Context?"
Using the Application Context. For this purpose you can subclass Application, registering it in your AndroidManifest file, and have a method to retrieve it from every where, like a singleton
I have seen the static getContext() method on the Application object before and I think it's slightly ugly and I wasn't sure that it was "Risk free" and correct. I was just about to implement it when I found this: https://androidcookbook.com/Recipe.seam?recipeId=1218 which basically says that the Application object in Android can be treated as a Singleton and that I should place my own Singletons inside that object.
It's essentially the same as #Blackbelt 's solution, but gives a slightly nicer vibe!
I'm getting the following error when using the encrypted SQLCipher database in my Android app, but only off and on:
net.sqlcipher.database.SQLiteException: not an error
at net.sqlcipher.database.SQLiteDatabase.dbopen(Native Method)
at net.sqlcipher.database.SQLiteDatabase.<init>(SQLiteDatabase.java:1950)
at net.sqlcipher.database.SQLiteDatabase.openDatabase(SQLiteDatabase.java:900)
at net.sqlcipher.database.SQLiteDatabase.openDatabase(SQLiteDatabase.java:947)
at net.sqlcipher.database.SQLiteOpenHelper.getReadableDatabase(SQLiteOpenHelper.java:195)
at com.android.storage.DatabaseHelper.getReadable(DatabaseHelper.java:99)
...
I've got the proper files in the assets/ and libs/ folders because the database works fine most of the time. However, every once in awhile I'll see this error. I've seen this twice now on my phone and it's always been after resuming the app after hours of inactivity (I check for user's oauth token in db if it gets cleared from memory).
I call "SQLiteDatabase.loadLibs(this)" only from the Application::onCreate() method so my hunch is that this isn't getting called on a resume and is throwing the error. Does this sound possible? If so, where should I call loadLibs? A user could enter the app in any activity and I access the db if the token isn't in memory. I see my options as either calling loadLibs on each Activity::onCreate or calling it each time I attempt to open the db. Would it cause any harm or performance issues if I called it multiple times like this?
You might consider moving the SQLiteDatabase.loadLibs(this); to your application subclass of net.sqlcipher.database.SQLiteOpenHelper. You can then pass the static instance of your Application subclass as its argument. Something like the following might be an example:
public class SchemaManager extends net.sqlcipher.database.SQLiteOpenHelper {
private static SchemaManager instance;
public static synchronized SchemaManager getInstance() {
if(instance == null) {
SQLiteDatabase.loadLibs(YourApplication.getInstance());
instance = new SchemaManager(…)
}
return instance;
}
}
With regard to the exception that was provided, the Java routine calls into a JNI layer that calls sqlite3_open_v2, setting the soft heap limit and setting the busy timeout. I would suggest adding logging locally to verify you are passing a valid path and a non null passphrase when attempting to acquire the SQLiteDatabase instance when you get a crash. Calling SQLiteDatabase.loadLibs(this); multiple times shouldn't cause a noticeable performance impact, much of what occurs are calls to System.loadLibrary(…) which get mapped into Runtime.getRuntime().loadLibrary(…), once a dynamic library has been loaded, subsequent calls are ignored.
There are many questions and answers on how to implement a global variable in Android/Java.
So it seems one can either implement a singleton or use a data class itself with static variables.
I am about to start a larger project and would like to start on the right foot.
I am just not sure which one to use.
Pro singleton/con Data Class
supposedly "cleaner" way (but I really don't know why)
ensures that there is really always just one representation
creates a new instance should the old one be "cleaned away" (whenever this may happen?)
Con singleton/pro Data Class
not recommendet by some (but did not find convincng reasons)
ensures that there is only one representation by design
very easy to access just by writing MyDataClass.x (vs accessing singleton requires getting access to it first somehow)
no need to pass it as a parameter
So in summary I tend to use DataClass but I am unsure because I read that this is supposedly not good programming style.
I like to add
the data this global object has to hold is quite big, more than 30k strings/keys. And this should not be cleaned at any stage so that when the app return it may crash because of that - as I read in other places eg Singletons vs. Application Context in Android? (the 3rd answer)
it's not a web application, I use only one classloader
it is multithread but only one thread is actually accessing this data
one may certainly also use this approach How to declare global variables in Android?, but isn't an ObjectClass just easier to use and access in this case?
And checking this http://developer.android.com/resources/faq/framework.html, esp under "Persistent Objects", implies that there is no real advantage for on or the other in those cases anyway.
Many thanks
Best way to implement singleton is to use enum.
public enum Singleton
{
INSTANCE;
public void someMethod()
{
// your code here
}
}
For more details you can read Effective Java (2nd Edition)
First of all: There's not much difference between a class with public static member variables and a singleton class. A lot of developers prefer the singleton pattern because the code looks more natural and more Java. E.g. Singleton.Data looks like a constant access and Singleton.getData() looks like you're accessing some kind of static data.
Personally I use the static Application pattern: See Accessing resources without an Activity or Context reference
You can use onCreate to setup any kind of static data or even other singletons. E.g. I prefer to setup a singleton SQLite database like that and access it then via App.getDb(). You can use this pattern to access the application context or resources.
While using static data you should think about memory leeks. I would recommend to take a look at this article then.
I'm developing an app that has a DataManager class, which holds an ArrayList<Object[]>. As this ArrayList needs to be used within other classes, I am wondering what would be the most efficient and fastest way of accessing this list, considering this application will be running on the Android platform.
A) create a public static ArrayList<Object[]> data in the DataManager class and reference it within other classes through DataManager.data
B) create a public ArrayList<Object[]> getData method within the DataManager class and have methods within other classes create local variable ArrayList<Object[]> data = mDataManager.getData() for temporary use.
C) ..?
It seems to me B has more overhead due to object creation. Also I read static is faster than non-static?
Option B does not increase memory use, since you will only have one ArrayList object (all the objects that use it just hold a simple reference, not a copy). The objects that use the ArrayList could also store this reference as an instance variable, instead of requesting it from the manager class each time it is needed.
I read somewhere that access to instance variables is slightly faster than accessing class (static) variables, but I don't have the link to the source.
The difference in performance is not likely to be meaningful. However, Option B gives you better encapsulation.