From ebdb4e1974eeef3900df4cc397217357dedf9571 Mon Sep 17 00:00:00 2001 From: Emmanuel Hansen Date: Tue, 11 Oct 2022 14:31:57 +0000 Subject: [PATCH 1/2] Seperate Avalonia lifecycle from android activity --- .../ControlCatalog.Android/MainActivity.cs | 12 +--- .../ControlCatalog.Android/SplashActivity.cs | 13 +++- .../Avalonia.Android/AvaloniaActivity.cs | 66 +++++-------------- .../AvaloniaSplashActivity.cs | 34 ++++++++++ src/Android/Avalonia.Android/AvaloniaView.cs | 2 +- .../OpenGL/GlPlatformSurface.cs | 6 +- .../Platform/SkiaPlatform/TopLevelImpl.cs | 7 -- .../Avalonia.Android/SingleViewLifetime.cs | 26 ++++++++ 8 files changed, 96 insertions(+), 70 deletions(-) create mode 100644 src/Android/Avalonia.Android/AvaloniaSplashActivity.cs create mode 100644 src/Android/Avalonia.Android/SingleViewLifetime.cs diff --git a/samples/ControlCatalog.Android/MainActivity.cs b/samples/ControlCatalog.Android/MainActivity.cs index 33ca511340..3101deb4a9 100644 --- a/samples/ControlCatalog.Android/MainActivity.cs +++ b/samples/ControlCatalog.Android/MainActivity.cs @@ -5,16 +5,8 @@ using Avalonia.Android; namespace ControlCatalog.Android { - [Activity(Label = "ControlCatalog.Android", Theme = "@style/MyTheme.NoActionBar", Icon = "@drawable/icon", LaunchMode = LaunchMode.SingleInstance, ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize)] - public class MainActivity : AvaloniaActivity + [Activity(Label = "ControlCatalog.Android", Theme = "@style/MyTheme.NoActionBar", Icon = "@drawable/icon", ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize)] + public class MainActivity : AvaloniaActivity { - protected override AppBuilder CustomizeAppBuilder(AppBuilder builder) - { - return base.CustomizeAppBuilder(builder) - .AfterSetup(_ => - { - Pages.EmbedSample.Implementation = new EmbedSampleAndroid(); - }); - } } } diff --git a/samples/ControlCatalog.Android/SplashActivity.cs b/samples/ControlCatalog.Android/SplashActivity.cs index dc292fd37b..908b5f082a 100644 --- a/samples/ControlCatalog.Android/SplashActivity.cs +++ b/samples/ControlCatalog.Android/SplashActivity.cs @@ -1,12 +1,23 @@ using Android.App; using Android.Content; +using Android.Content.PM; using Android.OS; +using Avalonia.Android; namespace ControlCatalog.Android { [Activity(Theme = "@style/MyTheme.Splash", MainLauncher = true, NoHistory = true)] - public class SplashActivity : Activity + public class SplashActivity : AvaloniaSplashActivity { + protected override Avalonia.AppBuilder CustomizeAppBuilder(Avalonia.AppBuilder builder) + { + return base.CustomizeAppBuilder(builder) + .AfterSetup(_ => + { + Pages.EmbedSample.Implementation = new EmbedSampleAndroid(); + }); + } + protected override void OnCreate(Bundle? savedInstanceState) { base.OnCreate(savedInstanceState); diff --git a/src/Android/Avalonia.Android/AvaloniaActivity.cs b/src/Android/Avalonia.Android/AvaloniaActivity.cs index 4ee4bc1375..9d580b2c46 100644 --- a/src/Android/Avalonia.Android/AvaloniaActivity.cs +++ b/src/Android/Avalonia.Android/AvaloniaActivity.cs @@ -1,62 +1,42 @@ +using System; +using Android.App; +using Android.Content; +using Android.Content.Res; using Android.OS; +using Android.Runtime; using AndroidX.AppCompat.App; -using Android.Content.Res; using AndroidX.Lifecycle; -using Avalonia.Controls.ApplicationLifetimes; -using Avalonia.Controls; -using Android.Runtime; -using Android.App; -using Android.Content; -using System; namespace Avalonia.Android { public abstract class AvaloniaActivity : AppCompatActivity { - internal class SingleViewLifetime : ISingleViewApplicationLifetime - { - public AvaloniaView View { get; internal set; } - - public Control MainView - { - get => (Control)View.Content; - set => View.Content = value; - } - } - internal Action ActivityResult; internal AvaloniaView View; internal AvaloniaViewModel _viewModel; - protected abstract AppBuilder CreateAppBuilder(); - protected override void OnCreate(Bundle savedInstanceState) { - var builder = CreateAppBuilder(); + _viewModel = new ViewModelProvider(this).Get(Java.Lang.Class.FromType(typeof(AvaloniaViewModel))) as AvaloniaViewModel; - - var lifetime = new SingleViewLifetime(); - - builder.AfterSetup(x => + View = new AvaloniaView(this); + if (_viewModel.Content != null) { - _viewModel = new ViewModelProvider(this).Get(Java.Lang.Class.FromType(typeof(AvaloniaViewModel))) as AvaloniaViewModel; + View.Content = _viewModel.Content; + } - View = new AvaloniaView(this); - if (_viewModel.Content != null) - { - View.Content = _viewModel.Content; - } + View.Prepare(); - SetContentView(View); + if (Avalonia.Application.Current.ApplicationLifetime is SingleViewLifetime lifetime) + { lifetime.View = View; - - View.Prepare(); - }); - - builder.SetupWithLifetime(lifetime); + } base.OnCreate(savedInstanceState); + + SetContentView(View); } + public object Content { get @@ -90,16 +70,4 @@ namespace Avalonia.Android ActivityResult?.Invoke(requestCode, resultCode, data); } } - - public abstract class AvaloniaActivity : AvaloniaActivity where TApp : Application, new() - { - protected virtual AppBuilder CustomizeAppBuilder(AppBuilder builder) => builder.UseAndroid(); - - protected override AppBuilder CreateAppBuilder() - { - var builder = AppBuilder.Configure(); - - return CustomizeAppBuilder(builder); - } - } } diff --git a/src/Android/Avalonia.Android/AvaloniaSplashActivity.cs b/src/Android/Avalonia.Android/AvaloniaSplashActivity.cs new file mode 100644 index 0000000000..5b5ebd1bd9 --- /dev/null +++ b/src/Android/Avalonia.Android/AvaloniaSplashActivity.cs @@ -0,0 +1,34 @@ +using Android.OS; +using AndroidX.AppCompat.App; +using AndroidX.Lifecycle; + +namespace Avalonia.Android +{ + public abstract class AvaloniaSplashActivity : AppCompatActivity + { + protected abstract AppBuilder CreateAppBuilder(); + + protected override void OnCreate(Bundle? savedInstanceState) + { + base.OnCreate(savedInstanceState); + + var builder = CreateAppBuilder(); + + var lifetime = new SingleViewLifetime(); + + builder.SetupWithLifetime(lifetime); + } + } + + public abstract class AvaloniaSplashActivity : AvaloniaSplashActivity where TApp : Application, new() + { + protected virtual AppBuilder CustomizeAppBuilder(AppBuilder builder) => builder.UseAndroid(); + + protected override AppBuilder CreateAppBuilder() + { + var builder = AppBuilder.Configure(); + + return CustomizeAppBuilder(builder); + } + } +} diff --git a/src/Android/Avalonia.Android/AvaloniaView.cs b/src/Android/Avalonia.Android/AvaloniaView.cs index 94e863210b..34fa121cd7 100644 --- a/src/Android/Avalonia.Android/AvaloniaView.cs +++ b/src/Android/Avalonia.Android/AvaloniaView.cs @@ -74,7 +74,7 @@ namespace Avalonia.Android class ViewImpl : TopLevelImpl { - public ViewImpl(AvaloniaView avaloniaView) : base(avaloniaView) + public ViewImpl(AvaloniaView avaloniaView) : base(avaloniaView, true) { View.Focusable = true; View.FocusChange += ViewImpl_FocusChange; diff --git a/src/Android/Avalonia.Android/OpenGL/GlPlatformSurface.cs b/src/Android/Avalonia.Android/OpenGL/GlPlatformSurface.cs index a9710039f8..e85ed11028 100644 --- a/src/Android/Avalonia.Android/OpenGL/GlPlatformSurface.cs +++ b/src/Android/Avalonia.Android/OpenGL/GlPlatformSurface.cs @@ -1,4 +1,5 @@ -using Avalonia.OpenGL.Egl; +using Avalonia.OpenGL; +using Avalonia.OpenGL.Egl; using Avalonia.OpenGL.Surfaces; namespace Avalonia.Android.OpenGL @@ -19,7 +20,8 @@ namespace Avalonia.Android.OpenGL public static GlPlatformSurface TryCreate(IEglWindowGlPlatformSurfaceInfo info) { - if (EglPlatformOpenGlInterface.TryCreate() is EglPlatformOpenGlInterface egl) + var feature = AvaloniaLocator.Current.GetService(); + if (feature is EglPlatformOpenGlInterface egl) { return new GlPlatformSurface(egl, info); } diff --git a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs index f8eaeba897..1bb74ce897 100644 --- a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs +++ b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs @@ -3,20 +3,14 @@ using System.Collections.Generic; using Android.Content; using Android.Graphics; -using Android.Media.TV; -using Android.OS; using Android.Runtime; -using Android.Text; using Android.Views; using Android.Views.InputMethods; -using Android.Widget; using Avalonia.Android.OpenGL; -using Avalonia.Android.Platform.Input; using Avalonia.Android.Platform.Specific; using Avalonia.Android.Platform.Specific.Helpers; using Avalonia.Android.Platform.Storage; using Avalonia.Controls; -using Avalonia.Controls.Documents; using Avalonia.Controls.Platform; using Avalonia.Controls.Platform.Surfaces; using Avalonia.Input; @@ -29,7 +23,6 @@ using Avalonia.Platform.Storage; using Avalonia.Rendering; using Avalonia.Rendering.Composition; using Java.Lang; -using static System.Net.Mime.MediaTypeNames; namespace Avalonia.Android.Platform.SkiaPlatform { diff --git a/src/Android/Avalonia.Android/SingleViewLifetime.cs b/src/Android/Avalonia.Android/SingleViewLifetime.cs new file mode 100644 index 0000000000..eef763a932 --- /dev/null +++ b/src/Android/Avalonia.Android/SingleViewLifetime.cs @@ -0,0 +1,26 @@ +using Avalonia.Controls; +using Avalonia.Controls.ApplicationLifetimes; + +namespace Avalonia.Android +{ + internal class SingleViewLifetime : ISingleViewApplicationLifetime + { + private AvaloniaView _view; + + public AvaloniaView View + { + get => _view; internal set + { + if (_view != null) + { + _view.Content = null; + _view.Dispose(); + } + _view = value; + _view.Content = MainView; + } + } + + public Control MainView { get; set; } + } +} From 0869827722b2fea0f3af181fb63b3395ed2ee393 Mon Sep 17 00:00:00 2001 From: Emmanuel Hansen Date: Wed, 12 Oct 2022 07:41:06 +0000 Subject: [PATCH 2/2] Address Review --- samples/ControlCatalog.Android/MainActivity.cs | 4 ++-- samples/MobileSandbox.Android/MainActivity.cs | 4 ++-- samples/MobileSandbox.Android/SplashActivity.cs | 4 ++-- ...valoniaActivity.cs => AvaloniaMainActivity.cs} | 15 +++++++-------- src/Android/Avalonia.Android/AvaloniaViewModel.cs | 11 ----------- .../Platform/SkiaPlatform/TopLevelImpl.cs | 2 +- .../Platform/Storage/AndroidStorageProvider.cs | 4 ++-- 7 files changed, 16 insertions(+), 28 deletions(-) rename src/Android/Avalonia.Android/{AvaloniaActivity.cs => AvaloniaMainActivity.cs} (78%) delete mode 100644 src/Android/Avalonia.Android/AvaloniaViewModel.cs diff --git a/samples/ControlCatalog.Android/MainActivity.cs b/samples/ControlCatalog.Android/MainActivity.cs index 3101deb4a9..62c582610c 100644 --- a/samples/ControlCatalog.Android/MainActivity.cs +++ b/samples/ControlCatalog.Android/MainActivity.cs @@ -5,8 +5,8 @@ using Avalonia.Android; namespace ControlCatalog.Android { - [Activity(Label = "ControlCatalog.Android", Theme = "@style/MyTheme.NoActionBar", Icon = "@drawable/icon", ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize)] - public class MainActivity : AvaloniaActivity + [Activity(Label = "ControlCatalog.Android", Theme = "@style/MyTheme.NoActionBar", Icon = "@drawable/icon", LaunchMode = LaunchMode.SingleTop, ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize)] + public class MainActivity : AvaloniaMainActivity { } } diff --git a/samples/MobileSandbox.Android/MainActivity.cs b/samples/MobileSandbox.Android/MainActivity.cs index ac9242dd52..d65f0dec92 100644 --- a/samples/MobileSandbox.Android/MainActivity.cs +++ b/samples/MobileSandbox.Android/MainActivity.cs @@ -5,8 +5,8 @@ using Avalonia.Android; namespace MobileSandbox.Android { - [Activity(Label = "MobileSandbox.Android", Theme = "@style/MyTheme.NoActionBar", Icon = "@drawable/icon", LaunchMode = LaunchMode.SingleInstance, ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize)] - public class MainActivity : AvaloniaActivity + [Activity(Label = "MobileSandbox.Android", Theme = "@style/MyTheme.NoActionBar", Icon = "@drawable/icon", LaunchMode = LaunchMode.SingleTop, ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize)] + public class MainActivity : AvaloniaMainActivity { } } diff --git a/samples/MobileSandbox.Android/SplashActivity.cs b/samples/MobileSandbox.Android/SplashActivity.cs index c26371d6fe..ced092554d 100644 --- a/samples/MobileSandbox.Android/SplashActivity.cs +++ b/samples/MobileSandbox.Android/SplashActivity.cs @@ -1,11 +1,11 @@ using Android.App; using Android.Content; -using Android.OS; +using Avalonia.Android; namespace MobileSandbox.Android { [Activity(Theme = "@style/MyTheme.Splash", MainLauncher = true, NoHistory = true)] - public class SplashActivity : Activity + public class SplashActivity : AvaloniaSplashActivity { protected override void OnResume() { diff --git a/src/Android/Avalonia.Android/AvaloniaActivity.cs b/src/Android/Avalonia.Android/AvaloniaMainActivity.cs similarity index 78% rename from src/Android/Avalonia.Android/AvaloniaActivity.cs rename to src/Android/Avalonia.Android/AvaloniaMainActivity.cs index 9d580b2c46..705fa3c59d 100644 --- a/src/Android/Avalonia.Android/AvaloniaActivity.cs +++ b/src/Android/Avalonia.Android/AvaloniaMainActivity.cs @@ -9,20 +9,19 @@ using AndroidX.Lifecycle; namespace Avalonia.Android { - public abstract class AvaloniaActivity : AppCompatActivity + public abstract class AvaloniaMainActivity : AppCompatActivity { + internal static object ViewContent; + internal Action ActivityResult; internal AvaloniaView View; - internal AvaloniaViewModel _viewModel; protected override void OnCreate(Bundle savedInstanceState) { - _viewModel = new ViewModelProvider(this).Get(Java.Lang.Class.FromType(typeof(AvaloniaViewModel))) as AvaloniaViewModel; - View = new AvaloniaView(this); - if (_viewModel.Content != null) + if (ViewContent != null) { - View.Content = _viewModel.Content; + View.Content = ViewContent; } View.Prepare(); @@ -41,11 +40,11 @@ namespace Avalonia.Android { get { - return _viewModel.Content; + return ViewContent; } set { - _viewModel.Content = value; + ViewContent = value; if (View != null) View.Content = value; } diff --git a/src/Android/Avalonia.Android/AvaloniaViewModel.cs b/src/Android/Avalonia.Android/AvaloniaViewModel.cs deleted file mode 100644 index 1b2c00987a..0000000000 --- a/src/Android/Avalonia.Android/AvaloniaViewModel.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; - -namespace Avalonia.Android -{ - internal class AvaloniaViewModel : AndroidX.Lifecycle.ViewModel - { - public object Content { get; set; } - } -} diff --git a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs index 1bb74ce897..eb11509bfc 100644 --- a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs +++ b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs @@ -52,7 +52,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform _view.Resources.DisplayMetrics.HeightPixels).ToSize(RenderScaling); NativeControlHost = new AndroidNativeControlHostImpl(avaloniaView); - StorageProvider = new AndroidStorageProvider((AvaloniaActivity)avaloniaView.Context); + StorageProvider = new AndroidStorageProvider((AvaloniaMainActivity)avaloniaView.Context); } public virtual Point GetAvaloniaPointFromEvent(MotionEvent e, int pointerIndex) => diff --git a/src/Android/Avalonia.Android/Platform/Storage/AndroidStorageProvider.cs b/src/Android/Avalonia.Android/Platform/Storage/AndroidStorageProvider.cs index 653f450ec8..3a1a9e76ea 100644 --- a/src/Android/Avalonia.Android/Platform/Storage/AndroidStorageProvider.cs +++ b/src/Android/Avalonia.Android/Platform/Storage/AndroidStorageProvider.cs @@ -14,10 +14,10 @@ namespace Avalonia.Android.Platform.Storage; internal class AndroidStorageProvider : IStorageProvider { - private readonly AvaloniaActivity _activity; + private readonly AvaloniaMainActivity _activity; private int _lastRequestCode = 20000; - public AndroidStorageProvider(AvaloniaActivity activity) + public AndroidStorageProvider(AvaloniaMainActivity activity) { _activity = activity; }