Неясная деталь по утечке памяти - PullRequest
0 голосов
/ 24 марта 2020

Я получаю утечки памяти в приложении Xamarin. Android. Они очень маленькие, 2-5kbs, но, очевидно, при сложении это имеет тенденцию к sh с OOM cra * sh. Я могу воссоздать утечку памяти для одного из моих действий, открыв новое действие. Я попытался очистить ресурсы в своей функции OnDestroy (), как показано ниже, и даже попытался форсировать G C, что я не хотел бы делать. Но я все еще получаю утечку памяти. Любая помощь в попытке понять и найти утечку будет очень признательна:

Код

using Android.App;
using Android.Widget;
using Android.OS;
using ProjectName.Activities;
using Android.Support.V7.App;
using Android.Views;
using ProjectName.Classes;
using Android.Preferences;
using Android.Content;
using System;
using System.Linq;
using Newtonsoft.Json;

using Microsoft.AppCenter;
using Microsoft.AppCenter.Analytics;
using Microsoft.AppCenter.Crashes;
using ProjectName.Services;
using Android;
using Android.Content.PM;
using Android.Support.Design.Widget;
using System.Collections.Generic;
using ProjectName.Helpers;
using System.Threading.Tasks;
using Android.Text;

namespace ProjectName
{
    [Activity(Label = "****", TaskAffinity = "", WindowSoftInputMode = SoftInput.AdjustPan, MainLauncher = true, Icon = "@mipmap/animal", ScreenOrientation = Android.Content.PM.ScreenOrientation.Portrait)]
    public class MainActivity : Activity
    {
        private Button loginBtn;
        private TextView version;
        private ImageView logo;
        private EditText techId, techPassword;
        private UserDatabase db;
        private TableLayout layout;
        private AuthRESTFulService authRESTFulService;
        private ISharedPreferences prefs;
        private ISharedPreferencesEditor editor;

        readonly string[] PermissionsLocation =
        {
            Manifest.Permission.ReadExternalStorage,
            Manifest.Permission.WriteExternalStorage
        };

        const int RequestLocationId = 0;

        protected override void OnCreate(Bundle savedInstanceState)
        {
            try
            {
            SwapTheme();

            base.OnCreate(savedInstanceState);

            if (!IsTaskRoot
                && Intent.HasCategory(Intent.CategoryLauncher)
                && Intent.Action != null
                && Intent.Action.Equals(Intent.ActionMain))
            {

                Finish();
                return;
            }

#if DEBUG
            Console.WriteLine("Running Debug");
#else
            AppCenter.Start("****", typeof(Analytics), typeof(Crashes));
#endif

            SetContentView(Resource.Layout.Main);

            loginBtn = FindViewById<Button>(Resource.Id.loginBtn);
            techId = FindViewById<EditText>(Resource.Id.techId);
            version = FindViewById<TextView>(Resource.Id.version);
            logo = FindViewById<ImageView>(Resource.Id.logo);
            techPassword = FindViewById<EditText>(Resource.Id.techPassword);
            layout = FindViewById<TableLayout>(Resource.Id.layout);

            techPassword.SetFilters(new IInputFilter[] { new InputFilterLengthFilter(6) });
            prefs = PreferenceManager.GetDefaultSharedPreferences(this);

            if (!String.IsNullOrEmpty(Util.GetCompanyTheme(this)))
            {
                logo.SetBackgroundResource((Util.GetCompanyTheme(this).Equals("Theme.1") ? Resource.Drawable.logo : Resource.Drawable.2));

                if(Util.GetCompanyTheme(this).Equals("Theme.1"))
                {
                    logo.LayoutParameters.Height = ViewGroup.LayoutParams.WrapContent;
                    logo.LayoutParameters.Width = ViewGroup.LayoutParams.WrapContent;
                }
                else 
                {
                    logo.LayoutParameters.Height = 300;
                    logo.LayoutParameters.Width = 300;
                }
            }

            version.LongClick += (object sender, View.LongClickEventArgs e) =>
            {
                SampleDatabase sampleDatabase = new SampleDatabase();
                List<Sample> samples = sampleDatabase.Get();
                if (samples.Count < 1)
                {
                    DialogHelper.ShowPinConfirm(this);
                }
                else
                {
                    var categoryCounts =
                        from s in samples
                        group s by s.Herd_ID into g
                        select new { Herd = g.Key, SampleCount = g.Count() };

                    List<string> herdsWithSamples = new List<string>(categoryCounts.Select(x => x.Herd));
                    DialogHelper.ShowBasicAlertDialog(this, "Samples Not Sync'd", MessageCreator(herdsWithSamples), true);
                }
                sampleDatabase = null;
                samples = null;
            };

            db = new UserDatabase();

            setAppVersion();

            loginBtn.Click += async (sender, e) =>
            {
                if(!FieldsFilled())
                {
                    CreateToast("Username or Password Incorrect.");
                    return;
                }

                User user = null;

                await Task.Run(async () =>
                {
                    string techID = techId.Text.ToUpper().Trim();
                    string techPass = techPassword.Text.Trim();

                    user = await Login(techID, techPass);
                });

                if (user.Tech_ID != null && Validate(user.Tech_ID, user.Pin))
                {
                    SaveBearerToken(user.Token);
                    SaveUserRef(user);
                    Intent intent = new Intent(this, typeof(DetailsActivity));
                    intent.PutExtra("TechID", techId.Text.ToUpper());

                    Analytics.TrackEvent("User Logged In", new Dictionary<string, string> {
                        { "User", user.Tech_ID },
                        { "App Version", version.Text },
                        { "Device Serial", Build.Serial },
                        { "Time", DateTime.Now.ToString("dd'/'MM'/'yyyy HH:mm:ss") }
                    });

#if DEBUG
                    Console.WriteLine(String.Format("{0} has been chosen to.", user.Tech_ID));
#else
                    AppCenter.SetUserId(String.Format("{0}", user.Tech_ID));
#endif

                    StartActivity(intent);
                }
                else
                {
                    CreateToast("Authentication Failed");
                    Console.WriteLine("Authentication Failed");
                }
            };

            TryGetFileAccessAsync();
            }
            catch (Exception ex)
            {
                LoggerHelper.LogUser("MainActivity OnCreate Error", ex.InnerException.ToString());
                throw;
            }
        }

        public void ChangeTheme(int _item)
        {
            string org = (_item == 1) ? "Theme.2" : "Theme.1";
            SaveCompanyTheme(org);

            Intent intent = new Intent(this, typeof(MainActivity));
            StartActivity(intent);
            Finish();
        }

        private void SwapTheme()
        {
            Console.WriteLine("THEME ST : " + Util.GetCompanyTheme(this));
            if (String.IsNullOrEmpty(Util.GetCompanyTheme(this)))
            {
                DialogHelper.ShowThemeSetup(this);
            }
            else
            {
                string _theme = Util.GetCompanyTheme(this);
                SetTheme((_theme.Equals("Theme.1")) ? Resource.Style.1 : Resource.Style.2);
            }
        }

        protected override void OnResume()
        {
            try
            {
                base.OnResume();

                Console.WriteLine("ONRESUME");
            }
            catch (Exception ex)
            {
                LoggerHelper.LogUser("MainActivity OnResume Error", ex.InnerException.ToString());
                throw;
            }
        }

        private bool FieldsFilled()
        {
            if (String.IsNullOrWhiteSpace(techId.Text) || String.IsNullOrWhiteSpace(techPassword.Text))
            {
                return false;
            }
            else
            {
                return true;
            }
        }

        private bool Validate(string username, string password)
        {
            return username.Equals(techId.Text.ToUpper()) && password.Equals(techPassword.Text) ? true : false;
        }

        private async Task<User> Login(string username, string password)
        {
            AuthRESTFulService authRESTFulService = new AuthRESTFulService(this);
            return await authRESTFulService.Login(username, password, false);
        }

        void TryGetFileAccessAsync()
        {
            GetReadWritePermissionAsync();
        }

        private void GetReadWritePermissionAsync()
        {
            if (CheckSelfPermission(Manifest.Permission.WriteExternalStorage) == (int)Permission.Granted)
            {
                //await GetLocationAsync();
                Console.WriteLine("Already Granted");
                return;
            }
            else
            {
                RequestPermissions(PermissionsLocation, RequestLocationId);
            }

        }

        private void SaveCompanyTheme(string _theme)
        {
            if(prefs != null)
            {
                editor = prefs.Edit();
                editor.PutString("THEME", _theme);
                editor.Apply();
                Console.WriteLine("THEME : " + _theme);
            }

        }

        private void SaveBearerToken(string bearerToken)
        {
            if(prefs != null)
            {
                editor = prefs.Edit();
                editor.PutString("Bearer Token", bearerToken);
                editor.Apply();
                Console.WriteLine("Saved Token : " + bearerToken);
            }

        }

        private void SaveUserRef(User user)
        {
            if(prefs != null)
            {
                editor = prefs.Edit();
                editor.PutString("User", JsonConvert.SerializeObject(user));
                editor.Apply();
            }
        }

        private void setAppVersion()
        {
#if DEBUG
            version.Text = "Testing Feature";
#else
            version.Text = "V " + Application.Context.ApplicationContext.PackageManager.GetPackageInfo(Application.Context.ApplicationContext.PackageName, 0).VersionName;
#endif
        }

        private void CreateToast(string title)
        {
            try
            {
                Toast.MakeText(this, title, ToastLength.Long).Show();
            }
            catch (Exception e)
            {
                Toast.MakeText(this, title, ToastLength.Long).Show();
                Console.Write(e);
            }
        }

        private string MessageCreator(List<string> _herdsWithSamples)
        {
            string _message = "";

            if (_herdsWithSamples.Count() >= 3)
            {
                _message =
                    String.Format("You need to finish recording on all herds before clearing data. Herds {0}, {1}, {2} have samples not sync'd.",
                                  _herdsWithSamples.ElementAt(0), _herdsWithSamples.ElementAt(1), _herdsWithSamples.ElementAt(2));
            }
            else if (_herdsWithSamples.Count() == 2)
            {
                _message =
                    String.Format("You need to finish recording on all herds before clearing data. Herds {0}, {1} have samples not sync'd.",
                                  _herdsWithSamples.ElementAt(0), _herdsWithSamples.ElementAt(1));
            }
            else if (_herdsWithSamples.Count() == 1)
            {
                _message =
                    String.Format("You need to finish recording on all herds before clearing data. The Herd {0} still has samples not sync'd.",
                                  _herdsWithSamples.ElementAt(0));
            }
            else
            {
                _message = "You need to Sync Samples before clearing Data.";
            }

            return _message;
        }

        protected override void OnDestroy()
        {
            base.OnDestroy();
            Console.WriteLine("COLLECTING MEMORY");
            GC.Collect();
            prefs = null;
            editor = null;
            logo = null;
            version = null;
            db = null;
            loginBtn = null;
            techId = null;
            version = null;
            logo = null;
            techPassword = null;
            layout = null;
            Console.WriteLine("MEMORY COLLECTED");
        }

    }
}

Я получаю очень расплывчатый след стека, который не говорит мне много, если я не читаю это неправильно:

G C ROOT md5818b2c16ed113ad7c01849f502d834ed.MainActivity экземпляр

Утечка памяти из LeakCanary

* md5818b2c16ed113ad7c01849f502d834ed.MainActivity has leaked:
* GC ROOT md5818b2c16ed113ad7c01849f502d834ed.MainActivity instance

* Retaining: 28 KB.
* Reference Key: 0a2b548d-06eb-4469-9592-ddcdd7dd2089
* Device: Honeywell Honeywell CT60 CT60
* Android Version: 7.1.1 API: 25 LeakCanary: 1.5.1 7719f24
* Durations: watch=5068ms, gc=150ms, heap dump=1048ms, analysis=29199ms

* Details:
* Instance of md5818b2c16ed113ad7c01849f502d834ed.MainActivity
|   static __md_methods = java.lang.String@315073568 (0x12c7a420)
|   static $classOverhead = byte[3612]@315183105 (0x12c95001)
|   refList = null
|   mActionBar = com.android.internal.app.WindowDecorActionBar@315058544 (0x12c76970)
|   mActionModeTypeStarting = 0
|   mActivityInfo = android.content.pm.ActivityInfo@314801456 (0x12c37d30)
|   mActivityTransitionState = android.app.ActivityTransitionState@315020560 (0x12c6d510)
|   mApplication = md587533e3430b96981f8437c51fe610689.MilkRecordApplication@314948000 (0x12c5b9a0)
|   mCalled = true
|   mChangeCanvasToTranslucent = false
|   mChangingConfigurations = false
|   mComponent = android.content.ComponentName@314863840 (0x12c470e0)
|   mConfigChangeFlags = 0
|   mCurrentConfig = android.content.res.Configuration@314994064 (0x12c66d90)
|   mDecor = null
|   mDefaultKeyMode = 0
|   mDefaultKeySsb = null
|   mDestroyed = true
|   mDoReportFullyDrawn = false
|   mEatKeyUpEvent = false
|   mEmbeddedID = null
|   mEnableDefaultActionBarUp = false
|   mEnterTransitionListener = android.app.SharedElementCallback$1@1872743360 (0x6f9fcbc0)
|   mExitTransitionListener = android.app.SharedElementCallback$1@1872743360 (0x6f9fcbc0)
|   mFinished = false
|   mFragments = android.app.FragmentController@315012352 (0x12c6b500)
|   mHandler = android.os.Handler@315187296 (0x12c96060)
|   mHasCurrentPermissionsRequest = false
|   mIdent = 83382499
|   mInstanceTracker = android.os.StrictMode$InstanceTracker@315012368 (0x12c6b510)
|   mInstrumentation = android.app.Instrumentation@314768760 (0x12c2fd78)
|   mIntent = android.content.Intent@314884216 (0x12c4c078)
|   mLastNonConfigurationInstances = null
|   mMainThread = android.app.ActivityThread@314787200 (0x12c34580)
|   mManagedCursors = java.util.ArrayList@315134976 (0x12c89400)
|   mManagedDialogs = null
|   mMenuInflater = android.view.MenuInflater@315619048 (0x12cff6e8)
|   mParent = null
|   mReferrer = null
|   mResultCode = 0
|   mResultData = null
|   mResumed = false
|   mSearchEvent = null
|   mSearchManager = null
|   mStartedActivity = false
|   mStopped = true
|   mTaskDescription = android.app.ActivityManager$TaskDescription@315187328 (0x12c96080)
|   mTemporaryPause = false
|   mTitle = java.lang.String@314822960 (0x12c3d130)
|   mTitleColor = 0
|   mTitleReady = true
|   mToken = android.os.BinderProxy@314840032 (0x12c413e0)
|   mTranslucentCallback = null
|   mUiThread = java.lang.Thread@1955589720 (0x748fee58)
|   mVisibleBehind = false
|   mVisibleFromClient = true
|   mVisibleFromServer = false
|   mVoiceInteractor = null
|   mWindow = com.android.internal.policy.PhoneWindow@314919552 (0x12c54a80)
|   mWindowAdded = true
|   mWindowManager = android.view.WindowManagerImpl@315135672 (0x12c896b8)
|   mInflater = com.android.internal.policy.PhoneLayoutInflater@315124080 (0x12c86970)
|   mOverrideConfiguration = null
|   mResources = android.content.res.Resources@315065256 (0x12c783a8)
|   mTheme = android.content.res.Resources$Theme@315012656 (0x12c6b630)
|   mThemeResource = 2131362181
|   mBase = android.app.ContextImpl@315127728 (0x12c877b0)
|   shadow$_klass_ = md5818b2c16ed113ad7c01849f502d834ed.MainActivity
|   shadow$_monitor_ = -2083693307
* Excluded Refs:
| Field: android.view.textservice.SpellCheckerSession$1.this$0
| Field: android.view.Choreographer$FrameDisplayEventReceiver.mMessageQueue (always)
| Thread:FinalizerWatchdogDaemon (always)
| Thread:main (always)
| Thread:LeakCanary-Heap-Dump (always)
| Class:java.lang.ref.WeakReference (always)
| Class:java.lang.ref.SoftReference (always)
| Class:java.lang.ref.PhantomReference (always)
| Class:java.lang.ref.Finalizer (always)
| Class:java.lang.ref.FinalizerReference (always)

1 Ответ

0 голосов
/ 25 марта 2020

Похоже, что MainActivity сама по себе является G CRoot и, скорее всего, нативным G C Root, поэтому что-то в нативных библиотеках хранит ссылку на него. Вы получите более подробную информацию о природе G C Root, если вы обновите LeakCanary 2 (и вы должны это сделать), при этом, если говорить о нативной утечке, мало что можно выяснить из Java сторона вещей.

...