Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What happens if I close my android application without closing an open realm instance?

Tags:

android

realm

I have kept a single realm instance opened on main thread in Application class and I use that single instance to do all kinds of DB operations from MainActivity. Since my application has a single activity, I close the instance in the activity's onDestroy(). The app is working fine for me as of now.

What are the repercussions of not doing a realm.close()? My database hasn't corrupted with or without the same.

Also, I've read that there are scenarios in which the Activity's onDestroy() may not get called at all. What effects the database can have in such a scenario if closing realm is so important?

public class MyApp extends Application {

    private static MyApp instance;
    private Realm realm;

    public void onCreate() {
        super.onCreate();
        Realm.init(this);
        Realm.setDefaultConfiguration(new RealmConfiguration.Builder()
                .schemaVersion(BuildConfig.VERSION_CODE)
                .migration(new RealmMigrationClass())
                .compactOnLaunch()
                .build());
        realm = Realm.getInstance(Realm.getDefaultConfiguration());
    }

    public static MyApp getInstance() {
        return instance;
    }

    public Realm getRealm() {
        return realm;
    }
}

MainActivity

public class MainActivity extends Activity {

    @Override
    protected void onDestroy() {
        MyApp.getInstance().getRealm().close();
        super.onDestroy();
    }

}
like image 575
iamlegend Avatar asked Sep 02 '25 09:09

iamlegend


2 Answers

Closing the realm instance is very important because of realm core has been written in c++ programming language and is compiled in the native code.And we know the c++ garbage collection does not run automatically we require to manually call the garbage collection.So when you call the realm.close() it means that realm deallocation the native memory means free or delete the pointer variable and also do the file descriptor job.From realm.close() means you give the command or tell to native c++ compiler to run the garbage collection.

like image 78
Rishabh Rawat Avatar answered Sep 04 '25 23:09

Rishabh Rawat


If you look the "doc" (REALM_DOC) for Realm for Java you can find:

Realm implements Closeable to take care of native memory deallocation and file descriptors, so always close your Realm instances when you’re done with them.

Realm instances are reference counted—if you call getInstance twice in a thread, you need to call close twice as well. This allows you to implement Runnable classes without having to worry about which thread will execute them: simply start it with getInstance and end it with close.

Personally I suggest you to define a class in which define your Realm functions and an "Realm attribute" (like a "RealmHelper" class) then inside this class define: - a unstatic Realm - a static RealmHelper instance

You will always use this RealmHelper static instance for all operations in your Realm inside your main Thread, inside other threads you will call "new RealmHelper()" and CLOSE the realm just after you did the operation.

Doing this in your MainThread you just need to close ONE realm instance when the application get closed, to do this you can use the "Application.ActivityLifecycleCallbacks" interface inside a Custom defined Application class (so which extends Application of Android).

Example inside you Application custom class:

/* START Override ActivityLifecycleCallbacks Methods */
    @Override
    public void onActivityCreated(Activity activity, Bundle bundle) {
    }

    @Override
    public void onActivityStarted(Activity activity) {
        // Check if your MyRealmClass instance is null or is closed, in this case 
        // re-initialize it.
        if(MyRealmClass.getInstance() == null || MyRealmClass.getInstance().getRealm().isClosed()){
            MyRealmClass.initInstance();
        }
    }

    @Override
    public void onActivityResumed(Activity activity) {

    }

    @Override
    public void onActivityPaused(Activity activity) {

    }

    @Override
    public void onActivityStopped(Activity activity) {
        if(!AppUtils.isAppOnForeground(this)){
            // Close your MyRealmClass instance
            if(MyRealmClass.getInstance() != null) {
                MyRealmClass.getInstance().close();
             MyRealmClass.getInstance().logRealmInstanceCount(LABEL_APP_IN_BACKGROUND);
                MyRealmClass.setMyInstance(null);
            }
        }
    }

    @Override
    public void onActivitySaveInstanceState(Activity activity, Bundle bundle) {

    }

    @Override
    public void onActivityDestroyed(Activity activity) {

    }
    /* END Override ActivityLifecycleCallbacks Methods */

Code of "isAppOnForeground" (check if your app is in foreground, if is not this mean your app is being closed):

public static boolean isAppOnForeground(Context context) {
        boolean ret = false;
        ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
        List<ActivityManager.RunningAppProcessInfo> appProcesses = activityManager.getRunningAppProcesses();
        if(appProcesses != null){
            String packageName = context.getPackageName();
            for (ActivityManager.RunningAppProcessInfo appProcess : appProcesses) {
                if (appProcess.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND && appProcess.processName.equals(packageName)) {
                    ret = true;
                }
            }
        }
        return ret;
    }

Your "MyRealmClass" will look like:

public class MyRealmClass {

 protected Realm mRealm;
    protected static MyRealmClass mInstance;

    public MyRealmClass() {
        mRealm = Realm.getDefaultInstance();
    }

    public static MyRealmClass initInstance(){
        if(mInstance == null){
            mInstance = new MyRealmClass();
        }
        return mInstance;
    }

public static MyRealmClass getInstance(){
        return mInstance;
    }

public static void setMyInstance(MyRealmClass instance) {
        mInstance = instance;
    }

public Realm getRealm() {
        return mRealm;
    }

    public void setRealm(Realm realm){
        this.mRealm = realm;
    }

public void close() {
        if (mRealm != null) {
            try {
                mRealm.close();
            } catch(Exception e){
                onException(e);
            }
        }
    }

[...]

Then you need to check that all your Realm instance is not closed when you use a RealmObject or you do some operation in your Realm. And if it is closed (because the app got in background and then restarted) you need to re-initialize the realm (if you have an activity with a MyRealmClass instance as attribute). Example in a BaseMyActivity:

public abstract class MyBaseActivity extends AppCompatActivity {

    protected MyRealmClass mRealmClass;

    /* START Override Lifecycle Methods */
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        initMyRealmClass();
        Lyra.instance().restoreState(this, savedInstanceState);
    }

    @Override
    protected void onStart() {
        super.onStart();
        initMyRealmClass();
    }

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        Lyra.instance().saveState(this, outState);
    }
    /* END Override Lifecycle Methods */

    /* START Private Methods */
    protected void initMyRealmClass(){
        if(mRealmClass == null || mRealmClass.getRealm().isClosed()){
            mRealmClass = MyRealmClass.initInstance();
        }
    }
    /* END Private Methods */

}

Basically all your activities will extend this BaseActivity if they need to use Realm functions. (Lyra is used to save the state of any of your attributes: LYRA)

REMEMBER THAT:

if you set or get some attributes from a RealmObject or you get an object from a RealmList or RealmResults you NEED THAT YOUR REALM INSTANCE, from which the object was take, IS OPEN. OTHERWISE you need to use this method when you init a variable with objects from the realm: (this methods should be placed in yuour "MyRealmClass")

public <T extends RealmObject> List<T> toList(RealmResults<T> results) {
            return mRealm.copyFromRealm(results);
        }

        public <T extends RealmObject> List<T> toList(RealmList<T> results) {
            return mRealm.copyFromRealm(results);
        }

        public <T extends RealmObject> T copyObjectFromRealm(T obj) {
            return mRealm.copyFromRealm(obj);
        }

    public <T extends RealmObject> RealmResults<T> findAllObject(Class<T> classObject) {
        RealmQuery<T> query = mRealm.where(classObject);
        return query.findAll();
    }

Now if you need to get a List of "MyRealmObjectClass" objects and add them to an adapter you will do this:

List<MyRealmObjectClass> myObjects = mRealmClass.toList(mRealmClass.findAllObject(MyRealmObjectClass.class))
myAdapter.addAll(myObjects);

Doing this if you "get" or "set" an attribute after the Realm instance, from which you got the objects, was closed (for example after the app get to background and then restarted) you won't get an exception. BUT if you "set" an attribute of your RealmObject this WON'T BE SET in the REALM INSTANCE, so to change the value of a RealmObject inside the Realm in this case you need to Save the object! OTHERWISE if you have a RealmResults or a RealmObject which is still connected to the Realm, so you can directly change, inside a transaction, an attribute of it and it will be changed inside the Realm too. To do a Realm Transaction I suggest you to follow the DOC in the first link and, if you don't need to close the Realm in the Finally block, enable lambda and do this:

mRealm.executeTransaction(
    realm -> {
        [do your Realm operations]
    }
)

or you can also do:

public boolean doRealmOperation(Object... params){
AtomicBoolean ret = new AtomicBoolean(false);
mRealm.executeTransaction(
    realm -> {
        try{
            [do your realm operation]
            ret.set(true);
        } catch(Exception e){
           onException(e)
           ret.set(false);
        }
    }
)
}

in this case you need to use the "AtomicBoolean" because you will set the value you want to return inside the transaction, but inside a transaction the value got from outside of the transaction itself (in this case the "ret" variable) MUST BE A FINAL variable. But you can't define "ret" as "final" and then set it again, so you need to use the "AtomicBoolean" to set the variable outside the transaction and set it again inside the transaction itself. (You can also avoid this problem by using a temporary variable to get the "true/false" value inside the transaction and then set the "ret" variable using that "temp variable". But personally I prefer to use "AtomicBoolean" class which is, I think, safer and more clean than a temp variable)

Hope this is helpful, see you by and happy coding! ;)

like image 33
Z3R0 Avatar answered Sep 05 '25 00:09

Z3R0