I have a my SQLiteOpenHelper
class, which is written as a singleton. I should note that I am not doing this in Java, I am using Xamarin.Android C# to write this.
Here's a snippet from that class:
public class DatabaseHelper : SQLiteOpenHelper
{
private static readonly string TAG = typeof(DatabaseHelper).FullName;
private static readonly string _databaseName = "istockdb";
private static readonly int _databaseVersion = 32;
private static DatabaseHelper _instance;
private Context _context;
private DatabaseHelper(Context context) : base(context, _databaseName, null, _databaseVersion)
{
_context = context;
}
[MethodImpl(MethodImplOptions.Synchronized)]
public static DatabaseHelper Instance(Context context)
{
// *** Use the application context, which will ensure that ***
// *** the Activity's context is not accidentally leaked ***
return _instance ?? (_instance = new DatabaseHelper(context.ApplicationContext));
}
}
So I have my DatabaseHelper
that is a singleton and is used like this within Activities and Services:
Service:
[Service(Name=Text.MobileBackgroundHbService, Enabled = true, Exported = true), IntentFilter(new []{Intents.SyncHeartbeats})]
public class BGHeartbeatService : BaseIntentService
{
public BGHeartbeatService()
{
this._database = DatabaseHelper.Instance(Application.Context);
}
protected override void OnHandleIntent(Intent intent)
{
if (this._database == null)
this._database = DatabaseHelper.Instance(Application.Context);
if (intent.Action.Equals(Intents.SyncHeartbeats)) SyncHeartbeatRecords();
var broadcastIntent = new Intent(Intents.MobileRefresh);
SendBroadcast(broadcastIntent);
}
}
Activity, actually a BaseActivity
which all Activities inherit from:
[Activity(Label = "BaseActivity")]
public abstract class BaseActivity : AppCompatActivity
{
/// <summary>
/// Reference to the current context.
/// </summary>
protected Context _context { get; set; }
/// <summary>
/// "Tag" used for Log functionallity.
/// </summary>
protected string _tag { get; set; }
/// <summary>
/// Reference To <see cref="RemoteSyncServiceConnection"/>
/// </summary>
protected RemoteSyncServiceConnection _service_connection;
/// <summary>
/// Reference To The Current SessionState.
/// </summary>
protected SessionState _session_state;
/// <summary>
/// Reference To <see cref="SyncReceiver"/>
/// </summary>
protected SyncReceiver _sync_receiver;
/// <summary>
/// Base FloatingActionButton.
/// </summary>
protected FloatingActionButton _base_fab;
/// <summary>
/// Is the Fab Menu Shown / Hid.
/// </summary>
protected static bool _is_fab_open;
protected IConnection _printer_connection;
protected string _printer_address;
protected bool _service_required;
protected bool _receiver_required;
protected MediaPlayer _media_player;
protected DatabaseHelper _database;
/// <summary>
/// <see cref="IntentFilter"/> for the <see cref="SyncReceiver"/>
/// </summary>
protected readonly string[] _intent_filters =
{
Intents.AlarmCompleteOrders,
Intents.AlarmHeartbeats,
Intents.AlarmPkas,
Intents.AlarmTrackingScans,
Intents.MobileRefresh
};
#region Lifecycle Methods
/// <summary>
/// Application Lifecycle Method.
/// </summary>
/// <param name="savedInstanceState"></param>
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
// *** Initialize Xamarin.Essentials ***
Xamarin.Essentials.Platform.Init(this, savedInstanceState);
// *** Initialize the DatabaseHelper ***
if(this._database == null)
this._database = DatabaseHelper.Instance(this.ApplicationContext);
}
}
The DatabaseHelper
instance is being disposed of frequently causing either services, or activities to try and access the disposed _database
object.
How is this being disposed of and why?
I thought making the _instance
static within the DatabaseHelper
as well as making the constructor private and forcing the use of the DatabaseHelper.Instance
method would keep a single instance of the DatabaseHelper
that wouldn't be disposed of between activities and services?
Am I misunderstanding this?
EDIT logcat output from try/catch blocks showing the exception being thrown. The SaveHeartbeat
method exists in the base activity.:
protected void SaveHeartbeat(DateTime time, string sourceActivity, [CallerMemberName] string sourceEvent = "")
{
try
{
var heartbeat = new SmartWarehouse.Shared.Models.Heartbeat(sourceActivity,
sourceEvent,
this._session_state.CurrentSession.ROWID.ToString());
this._database.InsertHeartbeat(heartbeat);
}
catch (Exception e)
{
// TODO: Document Exception
Util.Tools.Bark(e);
}
}
EDIT 2 DatabaseHelper.InsertHeartbeat():
/// <summary>
/// Inserts a Heartbeat record into local DB.
/// </summary>
/// <param name="heartbeat"></param>
/// <returns></returns>
public long InsertHeartbeat(Heartbeat heartbeat)
{
if (heartbeat == null) return -2L;
using (var db = this.WritableDatabase)
{
var id = -3L;
db.BeginTransactionNonExclusive();
try
{
var cv = GetContentValues(heartbeat);
id = db.Insert(DatabaseSchema.Heartbeat.TableName, null, cv);
db.SetTransactionSuccessful();
}
catch (Exception e)
{
// TODO: Document Exception
Util.Tools.Bark(e);
}
finally
{
db.EndTransaction();
}
return id;
}
}
Alright so my theory is that when I access the db
object in the using()
statement it is disposing of the database that the DatabaseHelper
object uses. Also noticed that I'm not using db.InsertOrThrow()
method which I should be.. Gonna do some re-working on my DatabaseHelper
class to see if that resolves the issue.
It turns out that my singleton instance of the DatbaseHelper
was not being disposed of.
Actually what was happening is I was disposing of the SQLiteDatabase
object that was being used by the DatbaseHelper
from within the helper methods.
All I had to do to actually resolve the issue was change:
/// <summary>
/// Inserts a Heartbeat record into local DB.
/// </summary>
/// <param name="heartbeat"></param>
/// <returns></returns>
public long InsertHeartbeat(Heartbeat heartbeat)
{
if (heartbeat == null) return -2L;
// This using() statement is causing the disposal
using (var db = this.WritableDatabase)
{
var id = -3L;
db.BeginTransactionNonExclusive();
try
{
var cv = GetContentValues(heartbeat);
id = db.Insert(DatabaseSchema.Heartbeat.TableName, null, cv);
db.SetTransactionSuccessful();
}
catch (Exception e)
{
// TODO: Document Exception
Util.Tools.Bark(e);
}
finally
{
db.EndTransaction();
}
return id;
}
}
TO:
/// <summary>
/// Inserts a Heartbeat record into local DB.
/// </summary>
/// <param name="heartbeat"></param>
/// <returns></returns>
public long InsertHeartbeat(Heartbeat heartbeat)
{
if (heartbeat == null) return -2L;
// This is no longer initialized in a using() statement
var db = this.WritableDatabase;
var id = -3L;
db.BeginTransactionNonExclusive();
try
{
var cv = GetContentValues(heartbeat);
id = db.Insert(DatabaseSchema.Heartbeat.TableName, null, cv);
db.SetTransactionSuccessful();
}
catch (Exception e)
{
// TODO: Document Exception
Util.Tools.Bark(e);
}
finally
{
db.EndTransaction();
}
return id;
}
SUMMARY:
By initializing my SQLiteDatabase db
object inside of using()
statements inside of my helper methods I was disposing of the SQLiteDatabase
that my DatabaseHelper
needed.