Skip to content

Commit 11318bf

Browse files
committed
fix memory leak on Android CollectionView
1 parent d23709b commit 11318bf

27 files changed

+484
-432
lines changed

README.md

Lines changed: 58 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,61 @@ For example, you can add a shadow to a rounded button:
107107

108108
<img src="Docs/rounded_button.png" height="180"/>
109109

110+
### Choose your Shade Collection
111+
112+
You can use several type of `IEnumerable<Shade>`:
113+
114+
#### 1. ReadOnlyCollection<Shade>
115+
116+
This is what you want to use most of the time.
117+
All the different `IMarkupExtension` like `ImmutableShades, NeumorphismShades, SingleShade`, return a `ReadOnlyCollection<Shade>`.
118+
If you use a `ReadOnlyCollection<Shade>`, all shades will be cloned to be sure the immutability is respected.
119+
It means, you can specify shades as static objects in your `ResourceDictionary`, it won't create any leak or view hierarchy issues.
120+
121+
```xml
122+
<ResourceDictionary xmlns="http://xamarin.com/schemas/2014/forms"
123+
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
124+
xmlns:sh="clr-namespace:Sharpnado.Shades;assembly=Sharpnado.Shadows">
125+
<sh:SingleShade x:Key="ShadowTop"
126+
BlurRadius="6"
127+
Opacity="0.15"
128+
Offset="0,-8"
129+
Color="{StaticResource ShadowsColor}" />
130+
131+
<sh:SingleShade x:Key="ShadowBottom"
132+
BlurRadius="6"
133+
Opacity="0.1"
134+
Offset="0,5"
135+
Color="{StaticResource ShadowsColor}" />
136+
137+
<sh:SingleShade x:Key="ShadowAccentBottom"
138+
BlurRadius="6"
139+
Opacity="0.4"
140+
Offset="0,4"
141+
Color="{StaticResource AccentColor}" />
142+
143+
<sh:ImmutableShades x:Key="ShadowNone" />
144+
145+
<sh:NeumorphismShades x:Key="ShadowNeumorphism" />
146+
147+
<sh:NeumorphismShades x:Key="ShadowThinNeumorphism"
148+
LowerOffset="8, 6"
149+
UpperOffset="-8,-6" />
150+
</ResourceDictionary>
151+
```
152+
153+
#### 2. ObservableCollection<Shade>
154+
155+
Only if you want to dynamically add or remove shade during the view lifetime.
156+
157+
#### 3. All other IEnumerable<Shade>
158+
159+
If you want to modify a shade property during the view lifetime.
160+
161+
**IMPORTANT**: if you don't use a `ReadOnlyCollection<Shade>` please be sure to declare your `Shade` as transient.
162+
It means you should declare a new instance of `Shade` for each `Shadows` views. For example, in code-behind with `new Shade()`, or in xaml with `Shades` property.
163+
Just don't reference static instances of shade from `ResourceDictionary` with `StaticResource` references, or even in a C# class.
164+
110165
### Shades
111166

112167
The `Shadows` component has only 2 properties:
@@ -252,8 +307,8 @@ Have a look at the `BeCreative.xaml` file and its code-behind.
252307

253308
To have a better control of your shades, `Shadows` provides 2 kinds of `MarkupExtension`:
254309

255-
1. One immutable collection of shades: `ImmutableShades`
256-
2. One mutable collection: `ShadesStack`
310+
1. One immutable collection of shades: `ImmutableShades` (readonly type)
311+
2. One mutable collection: `ShadeStack` (observable collection type)
257312

258313
Use the first one if the shade collection will not change and the second one if you want to dynamically add or remove shades.
259314

@@ -297,7 +352,7 @@ It will remove some xaml elements:
297352

298353
## Performance
299354

300-
* On `Android`, shadows are created thanks to `RenderScript`. Bitmaps are cached and only recreated when needed
355+
* On `Android`, shadows are created thanks to `RenderScript`. Bitmaps are cached in a global `BitmapCache`. For a particular color, size and blur, you will only have one instance alive.
301356
* On `iOS`, a `Shade` is implemented with a simple `CALayer`
302357
* On `UWP`, `Shade` is implemented with `SpriteVisual` drop shadows.
303358
* On `Tizen`, `Shade` is implemented with `SkiaSharp`.

Sample/ShadowsSample.Android/CollectionViewLeakRenderer.cs

Lines changed: 15 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,8 @@
1-
using System;
2-
using System.Collections.Generic;
3-
using System.Linq;
4-
using System.Text;
5-
6-
using Android.App;
7-
using Android.Content;
8-
using Android.OS;
9-
using Android.Runtime;
10-
using Android.Views;
11-
using Android.Widget;
1+
using Android.Content;
122

133
using ShadowsSample.Droid;
144

155
using Sharpnado.Presentation.Forms.Droid.Helpers;
16-
using Sharpnado.Shades.Droid;
176

187
using Xamarin.Forms;
198
using Xamarin.Forms.Platform.Android;
@@ -29,24 +18,26 @@ public CollectionViewLeakRenderer(Context context)
2918
{
3019
}
3120

32-
protected override void Dispose(bool disposing)
33-
{
34-
System.Diagnostics.Debug.WriteLine(ItemsViewAdapter);
21+
//protected override void Dispose(bool disposing)
22+
//{
23+
// System.Diagnostics.Debug.WriteLine(ItemsViewAdapter);
3524

36-
if (!disposing)
37-
{
38-
base.Dispose(false);
39-
return;
40-
}
25+
// if (!disposing)
26+
// {
27+
// base.Dispose(false);
28+
// return;
29+
// }
4130

42-
var children = GetChildren();
43-
base.Dispose(true);
44-
DisposeChildren(children);
45-
}
31+
// var children = GetChildren();
32+
// base.Dispose(true);
33+
// DisposeChildren(children);
34+
//}
4635

4736
private ItemContentView[] GetChildren()
4837
{
4938
System.Diagnostics.Debug.WriteLine($"GetChildren() => count: {ChildCount}");
39+
40+
5041
var result = new ItemContentView[ChildCount];
5142
for (int childCount = ChildCount, i = 0; i < childCount; ++i)
5243
{

Sample/ShadowsSample.Android/MainActivity.cs

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -34,15 +34,15 @@ protected override void OnCreate(Bundle savedInstanceState)
3434

3535
LoadApplication(new App());
3636

37-
TaskMonitor.Create(
38-
async () =>
39-
{
40-
while (true)
41-
{
42-
BitmapCache.Instance?.Log();
43-
await Task.Delay(10000);
44-
}
45-
});
37+
//TaskMonitor.Create(
38+
// async () =>
39+
// {
40+
// while (true)
41+
// {
42+
// BitmapCache.Instance?.Log();
43+
// await Task.Delay(10000);
44+
// }
45+
// });
4646
}
4747

4848
public override void OnRequestPermissionsResult(int requestCode, string[] permissions, [GeneratedEnum] Android.Content.PM.Permission[] grantResults)

Sample/ShadowsSample.Android/ShadowsSample.Android.csproj

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,11 @@
3333
<ErrorReport>prompt</ErrorReport>
3434
<WarningLevel>4</WarningLevel>
3535
<AndroidLinkMode>None</AndroidLinkMode>
36+
<AotAssemblies>false</AotAssemblies>
37+
<EnableLLVM>false</EnableLLVM>
38+
<AndroidEnableProfiledAot>false</AndroidEnableProfiledAot>
39+
<BundleAssemblies>false</BundleAssemblies>
40+
<EmbedAssembliesIntoApk>true</EmbedAssembliesIntoApk>
3641
</PropertyGroup>
3742
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
3843
<DebugSymbols>true</DebugSymbols>

Sample/ShadowsSample.sln

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Nuget", "Nuget", "{3D7E0A15
2525
ProjectSection(SolutionItems) = preProject
2626
..\Shadows\AssemblyInfo.targets = ..\Shadows\AssemblyInfo.targets
2727
..\make-package.ps1 = ..\make-package.ps1
28+
..\README.md = ..\README.md
2829
..\Sharpnado.Shadows.nuspec = ..\Sharpnado.Shadows.nuspec
2930
EndProjectSection
3031
EndProject

Sample/ShadowsSample/App.xaml.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ public App()
1717
MainPage = new NavigationPage(new MainPage());
1818
}
1919

20-
Sharpnado.Shades.Initializer.Initialize(true, true, filter: "Renderer");
20+
Sharpnado.Shades.Initializer.Initialize(true, true, filter: "ShadowsRenderer");
2121

2222
}
2323

Sample/ShadowsSample/ListItemCustomEasing.xaml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,7 @@
44
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
55
xmlns:effects="clr-namespace:Sharpnado.Presentation.Forms.Effects;assembly=Sharpnado.Presentation.Forms"
66
xmlns:images="clr-namespace:ShadowsSample.Images;assembly=ShadowsSample"
7-
xmlns:materialFrame="clr-namespace:Sharpnado.MaterialFrame;assembly=Sharpnado.MaterialFrame"
8-
xmlns:shades="clr-namespace:Sharpnado.Shades;assembly=Sharpnado.Shadows">
7+
xmlns:materialFrame="clr-namespace:Sharpnado.MaterialFrame;assembly=Sharpnado.MaterialFrame">
98

109
<!--<shades:Shadows.Shades>
1110
<shades:ImmutableShades>

Sample/ShadowsSample/MainPage.xaml

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
xmlns:d="http://xamarin.com/schemas/2014/forms/design"
66
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
77
mc:Ignorable="d"
8-
xmlns:shades="clr-namespace:Sharpnado.Shades;assembly=Sharpnado.Shadows"
8+
xmlns:sho="http://sharpnado.com"
99
xmlns:views="clr-namespace:ShadowsSample.Views;assembly=ShadowsSample"
1010
BackgroundColor="{DynamicResource DynamicBackgroundColor}"
1111
BackgroundImageSource="{DynamicResource DynamicBackgroundImageSource}">
@@ -25,24 +25,29 @@
2525
<views:Logo HeightRequest="80"
2626
Margin="0,0,0,10"
2727
HorizontalOptions="Center"
28-
VerticalOptions="Center" />
28+
VerticalOptions="Center">
29+
<views:Logo.GestureRecognizers>
30+
<TapGestureRecognizer Tapped="LogoOnTapped" />
31+
</views:Logo.GestureRecognizers>
32+
</views:Logo>
2933

3034
<Label Style="{StaticResource TextBodySecondary}"
3135
Margin="0,0,0,10"
3236
HorizontalOptions="End"
3337
FontFamily="{StaticResource FontItalic}"
3438
Text="The above logo is made of 7 different shades." />
3539

36-
<shades:Shadows CornerRadius="10" Shades="{shades:NeumorphismShades}">
40+
<sho:Shadows CornerRadius="10" Shades="{sho:NeumorphismShades}">
3741
<Button Style="{StaticResource TextHeadline}"
42+
Padding="10"
3843
HorizontalOptions="Center"
3944
VerticalOptions="Center"
4045
BackgroundColor="{DynamicResource DynamicFrameBackgroundColor}"
4146
Clicked="OnNavigateToShadowsListClicked"
4247
CornerRadius="10"
4348
Text="Navigate to List of Shadows"
4449
TextColor="Gray" />
45-
</shades:Shadows>
50+
</sho:Shadows>
4651

4752
<Label Style="{StaticResource TextHeadline}"
4853
Margin="{StaticResource HeaderMargin}"

Sample/ShadowsSample/MainPage.xaml.cs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System;
22
using System.ComponentModel;
3+
using System.Threading.Tasks;
34

45
using Sharpnado.Tasks;
56

@@ -24,11 +25,33 @@ protected override void OnAppearing()
2425
{
2526
base.OnAppearing();
2627
BeCreative.OnAppearing();
28+
29+
Device.BeginInvokeOnMainThread(async () =>
30+
{
31+
GC.Collect();
32+
GC.WaitForPendingFinalizers();
33+
GC.Collect();
34+
GC.WaitForPendingFinalizers();
35+
GC.Collect();
36+
GC.WaitForPendingFinalizers();
37+
await Task.Delay(500);
38+
GC.Collect();
39+
GC.WaitForPendingFinalizers();
40+
GC.Collect();
41+
GC.WaitForPendingFinalizers();
42+
GC.Collect();
43+
GC.WaitForPendingFinalizers();
44+
});
2745
}
2846

2947
private void OnNavigateToShadowsListClicked(object sender, EventArgs e)
3048
{
3149
Navigation.PushAsync(new ShadowList());
3250
}
51+
52+
private void LogoOnTapped(object sender, EventArgs e)
53+
{
54+
Navigation.PopAsync();
55+
}
3356
}
3457
}

0 commit comments

Comments
 (0)