nicht zu finden Ich baute diese ganze App dachte, dass die Garbage Collector behandelt Speicherbereinigung ganz gut, die war unglaublich dumm und naiv von mir, aber hey, es war mein erstes Mal alle Ich benutze Xamarin, um eine App zu erstellen, und das erste Mal, dass ich eine App erstelle, also was soll ich machen? Jeder Bildschirm scheint Speicher zu verlieren, aber die Bildschirme, die die meisten sind Bildschirme auslaufen, die Bitmaps haben, um ein Speicherabbild zu erzeugen und es in MAT Analyse, fand ich folgendes:Ursache des Speicherverlusts in Android App
So gibt es vier potentielle Täter , 2 sind Bitmaps, 2 sind Byte-Arrays. Dies ist ein Heap-Dump für das Hauptmenü der App, wenn ich in meine List-View-Aktivität zum Auflisten von Elementen gehe, bekomme ich 5 potentielle Lecks aus Bitmaps. Hier ist der Code für die Aktivität:
AssetManager assets = Assets;
Window.AddFlags(WindowManagerFlags.DrawsSystemBarBackgrounds);
var topPanel = FindViewById<TextView>(Resource.Id.topPanel);
topPanel.Text = service.GetLanguageValue("use recommendations - top bar heading");
topPanel.Dispose();
var lowerPanel = FindViewById<TextView>(Resource.Id.recommendationsPanel);
lowerPanel.Text = service.GetLanguageValue("title upper - recommendations by variety");
Shared.ScaleTextToOneLine(lowerPanel, lowerPanel.Text, Shared.ScaleFloatToDensityPixels(Shared.GetViewportWidthInDp()), 1.0f);
lowerPanel.Dispose();
// Read html file and replace it's contents with apple data
string html = "";
using (StreamReader sr = new StreamReader(Assets.Open("apple-variety-detail.html")))
{
html = sr.ReadToEnd();
}
html = ReplaceAppleDetailsHtml(html);
var webview = FindViewById<WebView>(Resource.Id.recommendationsMessage);
CleanWebView();
webview.LoadDataWithBaseURL("file:///android_asset/",
html,
"text/html", "UTF-8", null);
if (Shared.currentApple != null)
{
// Setup apple image
using (var imageView = FindViewById<ImageView>(Resource.Id.recommendationsImage))
{
var apple = this.apples.Where(a => a.Id == Shared.currentApple.AppleId).Select(a => a).First();
var imgName = apple.Identifier.First().ToString().ToUpper() + apple.Identifier.Substring(1);
var fullImageName = "SF_" + imgName;
using (var bitmap = Shared.decodeSampledBitmapFromResource(ApplicationContext.Resources,
Resources.GetIdentifier(fullImageName.ToLower(), "drawable", PackageName),
200, 200))
{
imageView.SetImageBitmap(bitmap);
}
}
// Setup apple name
FindViewById<TextView>(Resource.Id.appleNameTextView).Text = Shared.currentApple.Name;
}
else
{
FindViewById<TextView>(Resource.Id.appleNameTextView).Text = "Not Found!";
}
// Setup list menu for apples
AppleListView = FindViewById<ListView>(Resource.Id.ApplesListMenu);
// Scale details and list to fit on the same screen if the screen size permits
if (Shared.GetViewportWidthInDp() >= Shared.minPhoneLandscapeWidth)
{
var listViewParams = AppleListView.LayoutParameters;
// Scales list view to a set width
listViewParams.Width = Shared.ScaleFloatToDensityPixels(240);
listViewParams.Height = Shared.ScaleFloatToDensityPixels(Shared.GetViewportHeightInDp());
AppleListView.LayoutParameters = listViewParams;
}
else
{
// Here, we either need to hide the list view if an apple was selected,
// or set it to be 100% of the screen if it wasn't selected.
if(!Shared.appleSelected)
{
var listViewParams = AppleListView.LayoutParameters;
// Scales list view to a set width
listViewParams.Width = Shared.ScaleFloatToDensityPixels(Shared.GetViewportWidthInDp());
listViewParams.Height = Shared.ScaleFloatToDensityPixels(Shared.GetViewportHeightInDp());
AppleListView.LayoutParameters = listViewParams;
}
else
{
var listViewParams = AppleListView.LayoutParameters;
// Scales list view to a set width
listViewParams.Width = Shared.ScaleFloatToDensityPixels(0);
listViewParams.Height = Shared.ScaleFloatToDensityPixels(Shared.GetViewportHeightInDp());
AppleListView.LayoutParameters = listViewParams;
}
}
// Set listview adapter
if(AppleListView.Adapter == null)
{
AppleListView.Adapter = new Adapters.AppleListAdapter(this, (List<Apple>)apples, this);
}
AppleListView.FastScrollEnabled = true;
// Set the currently active view for the slide menu
var frag = (SlideMenuFragment)FragmentManager.FindFragmentById<SlideMenuFragment>(Resource.Id.SlideMenuFragment);
frag.SetSelectedLink(FindViewById<TextView>(Resource.Id.SlideMenuRecommendations));
// Replace fonts for entire view
Typeface tf = Typeface.CreateFromAsset(assets, "fonts/MuseoSansRounded-300.otf");
FontCrawler fc = new FontCrawler(tf);
fc.replaceFonts((ViewGroup)this.FindViewById(Android.Resource.Id.recommendationsRootLayout));
tf.Dispose();
}
Der wichtige Teil, um dies zu beachten ist die Art und Weise dieser Tätigkeit arbeitet, ist es einen Adapter lädt, und wenn es zeigt es eine Liste der Elemente, wenn ein Element geklickt wird , es lädt die gleiche Aktivität neu und berechnet die Bildschirmgröße, verkleinert die Liste, um nur die Seitenansicht zur Seite zu zeigen, und zeigt Details über das Element an, so dass 2 Bildschirme simuliert werden, der Grund dafür ist der Bildschirm Die Größe ist größer, es muss alles in einer einzigen Ansicht angezeigt werden. Auf größeren Bildschirmen werden also sowohl die Listenansicht als auch die Webansicht angezeigt, aber die Aktivität wird erneut geladen, um neue Daten zu laden.
Die Code-Adapter ist wahrscheinlich das, was mir eine harte Zeit zu geben, aber ich bin nicht sicher, ich habe ein paar Dinge ganz versucht, aber nichts scheint zu helfen, hier ist der Adapter Code:
public class AppleListAdapter : BaseAdapter<Apple>
{
List<Apple> items;
Activity context;
ApplicationService service = AgroFreshApp.Current.ApplicationService;
private Context appContext;
private Typeface tf;
static AppleRowViewHolder holder = null;
public AppleListAdapter(Activity context, List<Apple> items, Context appContext): base()
{
this.context = context;
this.items = items;
this.appContext = appContext;
context.FindViewById<ListView>(Resource.Id.ApplesListMenu).ChoiceMode = ChoiceMode.Single;
tf = Typeface.CreateFromAsset(context.Assets, "fonts/MuseoSansRounded-300.otf");
}
public override long GetItemId(int position)
{
return position;
}
public override Apple this[int position]
{
get { return items[position]; }
}
public override int Count
{
get
{
return items.Count;
}
}
public override View GetView(int position, View convertView, ViewGroup parent)
{
var item = items[position];
var view = convertView;
var imgName = item.Identifier.First().ToString().ToUpper() + item.Identifier.Substring(1);
var fullImageName = "SF_" + imgName;
if (view == null)
{
view = context.LayoutInflater.Inflate(Resource.Layout.appleRowView, null);
}
if (view != null)
{
holder = view.Tag as AppleRowViewHolder;
}
if(holder == null)
{
holder = new AppleRowViewHolder();
view = context.LayoutInflater.Inflate(Resource.Layout.appleRowView, null);
holder.AppleImage = view.FindViewById<ImageView>(Resource.Id.iconImageView);
holder.AppleName = view.FindViewById<TextView>(Resource.Id.nameTextView);
view.Tag = holder;
}
using (var bitmap = Shared.decodeSampledBitmapFromResource(context.Resources,
context.Resources.GetIdentifier(fullImageName.ToLower(), "drawable", context.PackageName),
25, 25))
{
holder.AppleImage.SetImageBitmap(bitmap);
}
holder.AppleName.Text = AgroFreshApp.Current.AppleDetailManager.GetAll().Where(a => a.AppleId == item.Id).Select(a => a.Name).FirstOrDefault();
holder.AppleName.SetTypeface(tf, TypefaceStyle.Normal);
view.Click += (object sender, EventArgs e) =>
{
var apple = AgroFreshApp.Current.AppleManager.Get(item.Id);
Shared.currentApple = AgroFreshApp.Current.AppleDetailManager.GetAll().Where(a=>a.AppleId == item.Id && a.LanguageId == service.UserSettings.LanguageId).Select(a=>a).FirstOrDefault();
Shared.appleSelected = true;
Intent intent = new Intent(appContext, typeof(RecommendationsActivity));
intent.SetFlags(flags: ActivityFlags.NoHistory | ActivityFlags.NewTask);
appContext.StartActivity(intent);
};
return view;
}
}
Also verwende ich hier das Viewholder-Muster und weise jedem Listenelement Click-Events zu, wenn sie erzeugt werden, mit nohistory und newtask als Intentions-Flags, damit die Seiten korrekt aktualisiert werden. Um die Bitmaps aufzuräumen, ich habe diese beiden Methoden unter Verwendung von:
Diese reinigt das große Bild auf dem Details Webansicht:
public void CleanBitmap()
{
// Clean recommendations bitmap
ImageView imageView = (ImageView)FindViewById(Resource.Id.recommendationsImage);
Drawable drawable = imageView.Drawable;
if (drawable is BitmapDrawable)
{
BitmapDrawable bitmapDrawable = (BitmapDrawable)drawable;
if (bitmapDrawable.Bitmap != null)
{
Bitmap bitmap = bitmapDrawable.Bitmap;
if (!bitmap.IsRecycled)
{
imageView.SetImageBitmap(null);
bitmap.Recycle();
bitmap = null;
}
}
}
Java.Lang.JavaSystem.Gc();
}
Und das reinigt die gespeicherten Bitmaps in jedem Listenansicht Artikel:
public void CleanListViewBitmaps()
{
var parent = FindViewById<ListView>(Resource.Id.ApplesListMenu);
// Clean listview bitmaps
for (int i = 0; i < parent.ChildCount; i++)
{
var tempView = parent.GetChildAt(i);
// If the tag is null, this no longer holds a reference to the view, so
// just leave it.
if(tempView.Tag != null)
{
AppleRowViewHolder tempHolder = (AppleRowViewHolder)tempView.Tag;
var imageView = tempHolder.AppleImage;
var drawable = imageView.Drawable;
if (drawable is BitmapDrawable)
{
BitmapDrawable bitmapDrawable = (BitmapDrawable)drawable;
if (bitmapDrawable.Bitmap != null)
{
Bitmap bitmap = bitmapDrawable.Bitmap;
if (!bitmap.IsRecycled)
{
imageView.SetImageBitmap(null);
bitmap.Recycle();
bitmap = null;
}
}
}
}
}
Java.Lang.JavaSystem.Gc();
}
Sie dann wie so in den Aktivitäten OnDestroy Methode aufgerufen:
protected override void OnDestroy()
{
base.OnDestroy();
CleanBitmap();
CleanListViewBitmaps();
Shared.appleSelected = false;
}
Ich verwende auch eine gemeinsam genutzte Klasse mit statischen Variablen, um im Wesentlichen Ansichtszustände zu verfolgen, wie wenn etwas ausgewählt wurde oder nicht, aber es speichert nur Grundelemente, es speichert keine Ansichtsobjekte oder ähnliches, also denke ich nicht Das ist das Problem, wie ich sagte, es sieht so aus, als ob Bitmaps nicht richtig gereinigt werden, und es scheint bei jeder Ansicht zu passieren, aber diese ist besonders schlecht.
Ich auch auf jeder Ansicht laden 2 Fragmente, eine ist ein Dia-Menü Fragment in einem Rahmenlayout, und die andere ist ein Navbar-Fragment, das nur 2 Bitmaps für ein Logo und Menü enthält, so dass auch Täter ich sein könnte annehmen. Hier ist das Navbar-Fragment:
public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
// Use this to return your custom view for this Fragment
// return inflater.Inflate(Resource.Layout.YourFragment, container, false);
var view = inflater.Inflate(Resource.Layout.navbar, container, false);
var navLogo = view.FindViewById(Resource.Id.navbarLogo);
var menuHandle = view.FindViewById(Resource.Id.menuHandle);
var navSpacer = view.FindViewById(Resource.Id.navSpacer);
((ImageButton)(menuHandle)).SetMaxWidth(Shared.GenerateProportionalWidth(.25f, 50));
((ImageButton)(menuHandle)).SetMaxHeight(Shared.GenerateProportionalHeight(.25f, 50));
((ImageButton)(menuHandle)).Click += (object sender, EventArgs e) =>
{
var slideMenu = FragmentManager.FindFragmentById(Resource.Id.SlideMenuFragment);
if (slideMenu.IsHidden)
{
FragmentManager.BeginTransaction().Show(slideMenu).Commit();
}
else if (!slideMenu.IsHidden)
{
FragmentManager.BeginTransaction().Hide(slideMenu).Commit();
}
};
var navLogoParams = navLogo.LayoutParameters;
// Account for the padding offset of the handle to center logo truly in the center of the screen
navLogoParams.Width = global::Android.Content.Res.Resources.System.DisplayMetrics.WidthPixels - (((ImageButton)(menuHandle)).MaxWidth * 2);
navLogoParams.Height = (Shared.GenerateProportionalHeight(.25f, 30));
navLogo.LayoutParameters = navLogoParams;
// Spacer puts the logo in the middle of the screen, by making it's size the same as the handle on the opposite side to force-center the logo
((Button)(navSpacer)).SetMaxWidth(Shared.GenerateProportionalWidth(.25f, 50));
((Button)(navSpacer)).SetMaxHeight(Shared.GenerateProportionalHeight(.25f, 50));
return view;
}
Seht jemand irgendeinen offensichtlichen oder dummen Fehler, den ich mache? Ich habe das Gefühl, dass es reine Unerfahrenheit sein muss, die mich dazu bringt, etwas wirklich Offensichtliches zu verpassen, oder ich mache etwas völlig Falsches, egal wie.
EDIT # 1:
1 der Bitmaps undichten das Menü-Handle-Button in dem Navigations Fragmente war, so dass das Leck nach unten von 300kb auf 200kb fällt, aber ich muß noch herausfinden, wie es zu reinigen richtig .
EDIT # 2:
Hier ist mein Code, die Bitmaps nach unten
public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId,
int reqWidth, int reqHeight)
{
// First decode with inJustDecodeBounds=true to check dimensions
BitmapFactory.Options options = new BitmapFactory.Options();
options.InJustDecodeBounds = true;
BitmapFactory.DecodeResource(res, resId, options);
// Calculate inSampleSize
options.InSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
// Decode bitmap with inSampleSize set
options.InJustDecodeBounds = false;
return BitmapFactory.DecodeResource(res, resId, options);
}
public static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight)
{
// Raw height and width of image
int height = options.OutHeight;
int width = options.OutWidth;
int inSampleSize = 1;
if (height > reqHeight || width > reqWidth)
{
int halfHeight = height/2;
int halfWidth = width/2;
// Calculate the largest inSampleSize value that is a power of 2 and keeps both
// height and width larger than the requested height and width.
while ((halfHeight/inSampleSize) >= reqHeight
&& (halfWidth/inSampleSize) >= reqWidth)
{
inSampleSize *= 2;
}
}
return inSampleSize;
}
Das Problem ist, dass die Bildansichten Garbage Collector Roots sind, so dass sie nicht wirklich Müll gesammelt bekommen, weil etwas auf sie verweist, funktioniert meine Methode der Reinigung Bitmaps wie oben gezeigt, durch explizites Nullen und recycling sie, aber dann kann ich Verwende die Bitmaps nicht, weil sie recycelt wurden. –
Die andere Option, um zu verhindern, dass das Leck erzeugt wird, ist das Laden des Bildes im Hintergrund, das heißt, Sie müssen eine asynchrone Aufgabe erstellen, die im Hintergrund den Image-Bitmap-Prozess bekommt und bei der Ausführung aktualisiert die Ansicht, die Sie hier rot machen können Sie können im Hintergrund tun, um es effizient zu machen https://developer.android.com/topic/performance/graphics/load-bitmap.html –
Werft einen Blick, danke für den Tipp! –