Imagine you have an enterprise application running on hundreds or thousands of mobile devices in field. Your application is being used every day, and it is rarely being restarted (users prefer leaving the application in the foreground all the time).
While we all tend to create applications that are memory responsible, the reality is that there would be memory leaks. With intensive usage of the application described in the example above, even small memory leaks would eventually cause the issues (application malfunctions or breaks).
Our Android application is developed using Xamarin.Forms. Our server runs on Azure and exposes REST API developed using ASP.NET Web API. Server communicates with the mobile devices by sending messages through Azure Notification Hub and Google Cloud Messaging (GCM).
When we experienced memory related issues on devices, we started brainstorming for possible solutions. Besides fixing all noticeable memory leaks, the best way to make sure that memory is in a good state is to restart the device. This method is intrusive, not easy to implement on Android and is discouraged. But it turns out that there are a lot of applications on Google Play Store which perform “fast rebooting”. We chose FastReboot, which is lightweight and “simulates a reboot by closing/restarting all core and user processes and thus frees up memory”.
Solution
Azure Notification Hub and GCM implementation is not subject of this blog post. If you are not familiar with it, you can find more info here. Keep in mind that Firebase Cloud Messaging (FCM) is now recommended over GCM, so you should use it for new implementations.
For easier understanding the code is simplified (removed try-catch blocks, filtering logic etc.).
Server side logic
We implemented simple Web API method which only job it to invoke the logic (in our case implemented inside worker task):
[HttpGet] public HttpResponseMessage FastReboot(string upn, string token) { this.SecurePublicMethod(token); var connectionString = this.SettingProvider.ServiceBusConnectionString(); WorkerTaskPump.Send(connectionString, WorkerTaskPump.WorkerTaskQueue, new FastRebootTask(upn)); return new HttpResponseMessage(HttpStatusCode.Accepted); }
Depending on your logic, Web API method can accept other parameters. Below is simplified implementation of FastRebootTask: obtaining mobile registration records and sending message to every device. Typically, here you would implement filtering logic, so you could send messages only to specific devices.
[Serializable] [DataContract] public class FastRebootTask : WorkerTaskBase { protected override void ExecuteInternal(IKernel kernel) { var mailboxCenter = kernel.Get<IMailboxCenter>(); var mailboxRegistrationRecords = mailboxCenter.GetMobileDeviceRegistrationsAsync().Result; foreach (var mailboxRegistrationRecord in mailboxRegistrationRecords.AsParallel()) { this.SendFastRebootMessage(mailboxRegistrationRecord.Tag, mailboxCenter); } } private string SendFastRebootMessage(string destinationTag, IMailboxCenter mailboxCenter) { var mailboxMessageId = Guid.NewGuid().ToString("N").Substring(1, 8); var fastRebootMessage = new FastRebootMessage( mailboxMessageId, ServerManager.FastRebootTaskSourceTag, destinationTag, MailboxMessageStatus.None); mailboxCenter.PushAsync(fastRebootMessage); return mailboxMessageId; } }
Azure Scheduler
In Azure portal simply create scheduler job which will be invoking your Web API method URL. If you want it to be executed nightly, set recurring job:
You could have filtering logic in place, you could create multiple tasks each invoking different set of devices in different times.
Mobile solution
Fast Reboot frees up the memory of all the applications in the background, leaving the one in the foreground working as expected. We wanted to make sure that the memory of our application is cleared as well, so we figured out we should close our application before executing Fast Reboot. Solution could not be straightforward, as killing Android process removes all the intents that process created. But we were able to utilize Android Alarm Manager, which purpose is to schedule some action in some time in future. So we implemented following:
1. Schedule Fast Reboot intent at Now + 2 seconds
2. Schedule the application intent in Now + 10 seconds
3. Kill the application
This way we let Fast Reboot to clean all unnecessary memory left by processes (including the application which is dead by that moment), and then start fresh instance of the application.
protected override async Task<bool> ProcessInternalAsync(FastRebootMessage mailboxMessage) { this.ActivityService.LaunchFastReboot(); this.ActivityService.RelaunchApp(); return true; } public bool LaunchFastReboot() { var alarmManager = (AlarmManager)Application.Context.GetSystemService(Context.AlarmService); var intent = Application.Context.PackageManager.GetLaunchIntentForPackage("com.greatbytes.fastreboot"); if (intent != null) { var pendingServiceIntent = PendingIntent.GetActivity(Application.Context, 0, intent, PendingIntentFlags.CancelCurrent); alarmManager.Set(AlarmType.RtcWakeup, SystemClock.CurrentThreadTimeMillis() + ServiceManager.FastRebootPendingIntentDelay, pendingServiceIntent); } return intent != null; } public bool RelaunchApp() { var alarmManager = (AlarmManager)Application.Context.GetSystemService(Context.AlarmService); var intent = Application.Context.PackageManager.GetLaunchIntentForPackage(Application.Context.PackageName); if (intent != null) { intent.AddFlags(ActivityFlags.ClearTask | ActivityFlags.NewTask); var pendingIntentId = ServiceManager.RelaunchNimbusPendingIntentId; var pendingServiceIntent = PendingIntent.GetActivity(Application.Context, pendingIntentId, intent, PendingIntentFlags.CancelCurrent); alarmManager.Set(AlarmType.ElapsedRealtimeWakeup, SystemClock.ElapsedRealtime() + ServiceManager.RelaunchNimbusPendingIntentDelay, pendingServiceIntent); Process.KillProcess(Process.MyPid()); } return intent != null; }
This solution works in all cases:
– The application in the foreground
– The application in the background
– Device is in sleep mode