我在Xamarin.Android应用程序中出现内存泄漏。它们很小,为2-5kbs,但是很明显,加起来时,会因OOM崩溃而崩溃。通过打开一个新活动,我可以为我的一个活动重新创建内存泄漏。我尝试如下清除OnDestroy()函数中的资源,甚至尝试强制执行我不想执行的GC。但是我仍然遇到内存泄漏。试图理解和发现泄漏的任何帮助将不胜感激:
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");
}
}
}
我得到一个非常模糊的堆栈跟踪,除非我读错了它,否则不会告诉我很多:
GC根md5818b2c16ed113ad7c01849f502d834ed.MainActivity实例
* 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)
看起来MainActivity本身是GCRoot,并且很可能是本机GC Root,因此本机库中的某些内容正在对其进行引用。如果您更新到LeakCanary 2(您应该),则将获得有关GC根目录性质的更多详细信息,也就是说,如果这是本机泄漏,那么从Java方面可以发现的内容不多。