Android 服务导致内存问题

Android service causes memory problems

我的 Android 应用程序遇到了一些问题。在应用程序中,我有一个很长的 运行 任务,用于在用户成功登录后将用户数据从 Azure 服务器同步到本地 SQL。因此我设计了一个服务,它与 activity 启动时,通知用户任务需要多长时间。

应用程序必须从在线数据库复制到本地 SQLlite 的数据非常大,这导致内存峰值非常高,因为 AzureService 和 SqlService 必须请求相当大的数据列表。它从 +- 30mb 到 60mb ram。完成所有这些后,负责同步的 activity 将停止服务并启动 SecondActivity

我的问题是,即使在 FirstActivity(处理同步)已经杀死所有东西(连接、服务、activity 本身……),内存仍然没有不要加入 SecondActivity。我已经尝试了不同的技巧让它下降(在监视器中打出“导致 gc”,打开其他需要内存的应用程序)但它似乎不起作用。我不知道我可能在 FirstActivity 中泄漏了什么,导致内存在 SecondActivity 中保持高位,据我所知调试,所有需要销毁的东西都会被销毁。 我做错了什么?

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!");
    }
}

服务:

[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;
    }

}

首先,打开严格模式以检测 activity 泄漏,首先将其添加到应用程序的 OnCreate activity:

StrictMode.SetVmPolicy (new StrictMode.VmPolicy.Builder ().DetectAll ().PenaltyLog ().Build ());  

每当检测到超过 1 个 activity 类型实例时,这将向 logcat 发出警告。它看起来像这样:

[StrictMode] class md5962cfe7bc03e689322450e055e633b77.FirstActivity; instances=2; limit=1
[StrictMode] android.os.StrictMode$InstanceCountViolation: class md5962cfe7bc03e689322450e055e633b77.FirstActivity; instances=2; limit=1
[StrictMode]    at android.os.StrictMode.setClassInstanceLimit(StrictMode.java:1)

接下来是为您的 activity 切断对等对象连接:

    protected override void OnDestroy ()
    {
        base.OnDestroy ();
        base.Dispose ();
        GC.Collect (GC.MaxGeneration);
    }  

base.Dispose () 中断了 C# 对象和 Java Vm 对象之间的对等对象连接。 http://developer.xamarin.com/guides/android/advanced_topics/garbage_collection/#Disposing_of_Peer_instances

一般来说,这应该足以修复样品的泄漏。您还可以更进一步,将 Activity 中派生自 Java.Lang.Object 的所有对象置为空。例如:

    protected override void OnDestroy ()
    {
        button.Dispose ();
        button = null;

        base.OnDestroy ();
        base.Dispose ();
        GC.Collect (GC.MaxGeneration);
    }

你的问题的核心是你不知道哪些内容仍在被引用,以及哪些内容正在引用它。

使用 Android Studio 中的工具追踪内存分配位置应该很简单。 Memory Profiling 101 介绍如何使用内存监视器、分配跟踪器和堆查看器来查找具体问题。

真的,这些工具可以让您非常清楚地了解内存的去向。我将从那里开始,窥探你的第二个 activity 仍然掌握的内容。