mercredi 12 août 2015

Android service causes memory problems

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