I’m experiencing some trouble with my Android application. In the application I have a long running task for syncing the user data from the Azure server to the local SQL after the user has successfully logged in. Therefore I designed a Service, that binds with an activity when launched, to inform the user how much longer the task will take.
The data the app has to copy from online database to local SQLlite is quite large, this causes the memory to spike very high, because the AzureService & SqlService must request pretty big lists of data. It goes from +- 30mb to 60mb ram. Once all this is done, the activity in charge for the syncing stops the service and a SecondActivity is launched.
My problem is that even after the FirstActivity ( handles the sycing ) has killed everything ( the connection, the service, the activity itself… ) the memory doesn’t drop in the SecondActivity. I’ve tried different tricks to get it to drop ( punch the ‘cause gc’ in the monitor, open other apps that require memory ) but it doesn’t seem to work. I have no idea what I might have leaked in the FirstActivity that causes the memory to stay high in the SecondActivity, as far as I can see trough debugging, everything gets destroyed what needs to be destroyed. What have I done wrong?
FirstActivity:
[Activity (Label = "Dvit.Apps.MemoryManager", MainLauncher = true, Icon = "@drawable/icon")]
public class FirstActivity : Activity
{
BirdyService _service;
IServiceConnection _connection;
TextView _serviceOutput;
Boolean _isBound = false;
public FirstActivity ()
{
_connection = new SyncServiceConnection (this);
}
protected override void OnCreate (Bundle bundle)
{
base.OnCreate (bundle);
// Set our view from the "main" layout resource
SetContentView (Resource.Layout.Main);
Button btnStartService = FindViewById<Button> (Resource.Id.myButton);
Button NextPage = new Button (this);
_serviceOutput = FindViewById<TextView> (Resource.Id.txtServiceOutput);
NextPage.LayoutParameters = new ViewGroup.LayoutParams (WindowManagerLayoutParams.MatchParent, 100);
NextPage.Gravity = GravityFlags.CenterHorizontal;
NextPage.Text = "Next Page";
NextPage.Click += (object sender, EventArgs e) => {
UnboundService();
this.Finish();
StartActivity(typeof(SecondActivity));
};
(btnStartService.Parent as LinearLayout).AddView (NextPage);
btnStartService.Click += delegate {
BindService ();
Intent syncLoginInfoIntent = new Intent (this, typeof(BirdyService));
syncLoginInfoIntent.PutExtra ("commandType", "LoginSync");
StartService (syncLoginInfoIntent);
};
}
void BindService ()
{
Console.WriteLine ("Bind service");
base.BindService (new Intent (this, typeof(BirdyService)), _connection, Bind.AutoCreate);
_isBound = true;
}
void UnboundService ()
{
Console.WriteLine ("Unbinding service");
if (_isBound) {
((SyncServiceConnection)_connection).OnDisconnect ();
base.UnbindService (_connection);
_isBound = false;
}
}
void HandleSyncFeedback (object sender, BirdyService.SyncProgressEventArgs e)
{
this.RunOnUiThread(() =>{_serviceOutput.Text += "\n" + e.Extra;});
}
protected override void OnDestroy ()
{
base.OnDestroy ();
Console.WriteLine ("FirstActivity Destroyed!");
_connection = null;
_serviceOutput = null;
}
class SyncServiceConnection: Java.Lang.Object, IServiceConnection
{
FirstActivity _self;
public SyncServiceConnection (FirstActivity self)
{
_self = self;
}
public void OnServiceConnected (ComponentName className, IBinder service)
{
Console.WriteLine ("~~CONNECTING SERVICE!!!~~");
_self._service = ((BirdyService.SyncBinder)service).Service;
_self._service.SyncProgressEvent += _self.HandleSyncFeedback;
}
public void OnDisconnect ()
{
Console.WriteLine ("~~DISCONECTING SERVICE!!!~~");
if (_self._service != null) {
_self._service.SyncProgressEvent -= _self.HandleSyncFeedback;
_self._service = null;
}
}
public void OnServiceDisconnected (ComponentName className)
{
}
}
}
SecondActivity:
[Activity (Label = "Dvit.Apps.MemoryManager", Icon = "@drawable/icon")]
public class SecondActivity : Activity
{
int count = 1;
protected override void OnCreate (Bundle bundle)
{
base.OnCreate (bundle);
// Set our view from the "main" layout resource
SetContentView (Resource.Layout.Main);
// Get our button from the layout resource,
// and attach an event to it
}
protected override void OnDestroy ()
{
base.OnDestroy ();
Console.WriteLine ("SecondActivity Destroyed!");
}
}
The Service:
[Service (Enabled = true)]
public class BirdyService:Service
{
IAzureService _azureSvc;
ISqlService _sqlSvc;
ISyncService _syncSvc;
IGlobalSettings _settings;
AndroidSqlteConnection _connection;
#region Service 'IsSyncing?' Checkers
private Boolean _syncLoginSync = false;
public Boolean IsServiceSyncing {
get {
if (_syncLoginSync == true)
return true;
else
return false;
}
}
#endregion
public class SyncProgressEventArgs:EventArgs
{
public SyncProgressEventArgs ()
{
this.Progress = 0;
this.IsCompleted = false;
}
public bool IsCompleted {
get;
set;
}
public string Extra{ get; set; }
public Int16 Progress {
get;
set;
}
}
public event EventHandler<SyncProgressEventArgs> SyncProgressEvent;
#region Binder => communication between starting activity & service
private IBinder binder;
public class SyncBinder:Binder
{
BirdyService _service;
public SyncBinder (BirdyService service)
{
_service = service;
}
public BirdyService Service {
get{ return _service; }
}
}
public override IBinder OnBind (global::Android.Content.Intent intent)
{
return binder;
}
#endregion
public BirdyService () : base ()
{
binder = new SyncBinder (this);
}
public override void OnCreate ()
{
base.OnCreate ();
var appContext = this.ApplicationContext as MemoryManagerApplication;
_settings = appContext.GlobalSettings;
_azureSvc = new AzureService (new MobileServiceClient (_settings.AzureMobileServiceUrl, _settings.AzureMobileServicePassword));
_connection = new AndroidSqlteConnection ();
_sqlSvc = new SqlService (_connection);
_syncSvc = new SyncService (_azureSvc, appContext.WebApiClient, _sqlSvc, _settings);
}
public override StartCommandResult OnStartCommand (global::Android.Content.Intent intent, StartCommandFlags flags, int startId)
{
if (intent != null) {
var command = intent.GetStringExtra ("commandType");
var keepAlive = intent.GetBooleanExtra ("keepAlive", false);
Console.WriteLine ("~~>SERVICE TASK RECEIVED COMMMANDNAME: " + command + "<~~");
switch (command) {
case "LoginSync":
{
LoginSync (intent, keepAlive);
}
break;
}
}
return StartCommandResult.Sticky;
}
#region LoginSync
private void LoginSync (global::Android.Content.Intent intent, bool keepAlive = false)
{
new Task (async () => {
try {
var progressEventArg = new SyncProgressEventArgs ();
_syncLoginSync = true;
_settings.IsSyncingOrganisations = true;
progressEventArg.Extra = "Fetching master data...";
progressEventArg.Progress = 10;
if (SyncProgressEvent != null)
this.SyncProgressEvent (null, progressEventArg);
await _syncSvc.SyncMasterData (true);
var requestUserInfoFragment = await Authenticate (progressEventArg);
_settings.IsSyncingOrganisations = false;
progressEventArg.Extra = "Completed!";
progressEventArg.Progress = 100;
progressEventArg.IsCompleted = true;
if (SyncProgressEvent != null)
SyncProgressEvent (requestUserInfoFragment, progressEventArg);
_syncLoginSync = false;
if (!IsServiceSyncing && !keepAlive)
this.StopSelf ();
} catch (Exception ex) {
_syncLoginSync = false;
//_Logger.Trace (ex.Message);
_settings.IsSyncingOrganisations = false;
if (!IsServiceSyncing)
this.StopSelf ();
throw;
}
}).Start ();
}
async Task<Boolean> Authenticate (SyncProgressEventArgs args)
{
try {
args.Extra = "Fetching user...";
args.Progress = 30;
if (this.SyncProgressEvent != null)
this.SyncProgressEvent (null, args);
Account account = await _azureSvc.AccountByGoogleId (_settings.LoginUserId);
if (account != null) {
var party = await _azureSvc.GetItemById<Party> (account.PartyId);
args.Extra = "User found, initializing user data ...";
args.Progress = 50;
if (this.SyncProgressEvent != null)
this.SyncProgressEvent (null, args);
_sqlSvc.Insert<Party> (party);
_sqlSvc.Insert<Account> (account);
args.Extra = "User found, fetching organisation data...";
args.Progress = 50;
if (this.SyncProgressEvent != null)
this.SyncProgressEvent (null, args);
//Fetch organisations
await _syncSvc.SyncPartyInitData (party.Id, true, true);
//Fetch appointments & orders
args.Extra = "Organisation data completed, fetching party data...";
args.Progress = 80;
if (this.SyncProgressEvent != null)
this.SyncProgressEvent (null, args);
await _syncSvc.SyncPartyDetailData (party.Id);
//do not request user info
return false;
} else {
//FILL IN ACCOUNT
account = new Account ();
Party party = new Party ();
party.Id = Guid.NewGuid ().ToString ();
account.Id = Guid.NewGuid ().ToString ();
account.IdentityProvider = _settings.LoginPlatform;
account.IdentityToken = _settings.LoginUserId;
account.PartyId = party.Id;
account.InfoProvider = _settings.UserInfo.RawData;
account.LoginName = _settings.LoginUserId;
//FILL IN PARTY
bool requestUserInfoFragment = false;
if (_settings.UserInfo != null) {
if (!String.IsNullOrWhiteSpace (_settings.UserInfo.FirstName)) {
party.FirstName = _settings.UserInfo.FirstName;
} else
requestUserInfoFragment = true;
if (!String.IsNullOrWhiteSpace (_settings.UserInfo.LastName)) {
party.LastName = _settings.UserInfo.LastName;
} else
requestUserInfoFragment = true;
if (!String.IsNullOrWhiteSpace (_settings.UserInfo.Email)) {
party.Email = _settings.UserInfo.Email;
} else
requestUserInfoFragment = true;
}
party.LocalIsNew = true;
account.LocalIsNew = true;
_sqlSvc.Insert<Party> (party);
_sqlSvc.Insert<Account> (account);
if (!requestUserInfoFragment) {
args.Extra = "Adding new users to the server";
args.Progress = 80;
if (this.SyncProgressEvent != null)
this.SyncProgressEvent (null, args);
await _syncSvc.SyncAllFromType<Party> ();
await _syncSvc.SyncAllFromType<Account> ();
}
//ELSE sync in newuserfragment
return requestUserInfoFragment;
}
} catch (Exception ex) {
throw;
}
}
#endregion
public override void OnDestroy ()
{
base.OnDestroy ();
Console.WriteLine ("Destroying BirdyService");
_connection.Close ();
_connection = null;
_azureSvc = null;
_sqlSvc = null;
_syncSvc = null;
_settings = null;
}
}
Aucun commentaire:
Enregistrer un commentaire