-2

I have a service that store data in SQLite. I want that service to keep running when the application is closed. (Swipe out) But getInstance() return null when the app is destroyed. How can I keep it alive even after my application is closed?

My database singleton class:

public class DbManager {
    private static DbManager instance;
    private CipherDbHelper dbHelper;

    private DbManager() {

    }

    private DbManager(Context context, String password) {
        SQLiteDatabase.loadLibs(context);
        dbHelper = new CipherDbHelper(context, password);
    }

    public static void init(Context context, String password) {
        instance = new DbManager(context, password);
    }

    public static DbManager getInstance() {
        if (instance == null) {
            Log.e("DbManager", "Can't access DbManager : instance is null.")
        }
        return instance;
    }

    //Closing
    public void closeDatabase() {
        dbHelper.close();
        dbHelper = null;
        instance = null;
    }

    public <D extends Dao<T, String>, T> D getDAO(Class<T> clz) throws SQLException {
        return dbHelper.getDao(clz);
    }


}
Jaythaking
  • 2,200
  • 4
  • 25
  • 67
  • Don't take it personally, but you are probably downvoted because the exception that you get is literally hard-coded in your code and not an actual exception message. – OneCricketeer Mar 30 '16 at 18:12
  • What do you mean by "initial issue"? You made it sound like "I get this exception, why?" And the code you posted seems to be that the reason is your variable is null, and so that exception is thrown because it is coded to do so. – OneCricketeer Mar 30 '16 at 18:46

3 Answers3

1

Step #1: Delete closeDatabase() and any code that calls it.

Step #2: Have getDatabase() return null instead of throwing an exception, or have it throw a custom exception instead of IllegalArgumentException.

Step #3: If the service gets null from its getDatabase() call (or it catches your custom exception), have it raise a Notification to tell the user "I need you to log in again" and skip doing whatever work it was supposed to be doing.

Fundamentally, you have an architecture problem: Android processes are short-lived. A ramification of this is that working with an encrypted database in the background usually is not possible. You will lose access to the database (that was unlocked with the user's passphrase) as soon as your process ends, requiring the user to log in again.

You can use a foreground service (via startForeground()) to reduce the chances of your process being terminated, but this has costs to the user (always-visible Notification, possibilities of too much CPU being used by the not-really-background work, etc.).

CommonsWare
  • 986,068
  • 189
  • 2,389
  • 2,491
  • The method closeDatabase() was never called so that wasn't the issue. So, SQLCipher only works only if the app is running? Can I use two database then, one encrypted and one not? Thank you – Jaythaking Mar 30 '16 at 18:27
  • 1
    @Jaythaking: "So, SQLCipher only works only if the app is running?" -- I would describe it more as "SQLCipher requires a password, which you can only get from the user through your UI". "Can I use two database then, one encrypted and one not?" -- sure, though I cannot say whether that addresses the security concerns of the user. – CommonsWare Mar 30 '16 at 18:34
  • "SQLCipher requires a password, which you can only get from the user through your UI" I start the service once the encrypted database has been unlocked though.So it shouldn't require a password anymore unless it's been destroyed when the app is killed no? – Jaythaking Mar 30 '16 at 18:37
  • 1
    @Jaythaking: "So it shouldn't require a password anymore unless it's been destroyed when the app is killed no?" -- only so long as your process is around. Once Android terminates your process, you no longer have access to the database, until you ask the user for a passphrase again. – CommonsWare Mar 30 '16 at 18:37
  • I'll just use another database since those specific data doesn't need to be private. Thank you very much for your help – Jaythaking Mar 30 '16 at 18:41
  • I have one more question if I may ask you, does the Sqlite database lives as long as the context we pass to it exist? – Jaythaking Mar 30 '16 at 19:16
  • @Jaythaking: The database itself is a file. Your ability to decrypt an encrypted database requires a passphrase from the user. – CommonsWare Mar 30 '16 at 19:20
  • "only so long as your process is around" , So it's somehow linked to a process, I was just wondering how? Because if it's through the context we pass, maybe I could pass the service's context so it stays alive when the app is closed. – Jaythaking Mar 30 '16 at 19:29
0

To run a task in the background, you need to use a Service.


Just call DBManager.init(context) before you call DBManager.getInstance(); Because your instance remains null until you call your init method.

OneCricketeer
  • 179,855
  • 19
  • 132
  • 245
Axel
  • 568
  • 7
  • 18
0

To whomever faces the same issue of accessing SQLite Database (encrypted or not) in a service using a singleton class to get the helper instance. Here is how I fixed my issue. Just pass the context of your service to the SQLiteHelper to load the lib again and re-generate the helper instance. Then it won't be destroyed once the app is closed(by swiping it out)

In my example I called this on the onCreate method of my service:

    DbManager.init(TrackingService.this, "examplePassword"));
Jaythaking
  • 2,200
  • 4
  • 25
  • 67