Hi to everyone,
a few days ago I installed my app on my friend's Samsung Galaxy S9 Plus.
Only on this device, the app is slow.
I think it's an absurd thing, considering that it is the last device released by the Samsung company.
I try to explain what happens, considering that in my app you can download multimedia content (2mb/5mb of files).
The problem appears when, after opening the page where it is possible to search and download the files (a list containing two icons and one label per line appears) I'm pressing the icon to start the download of file.
At that moment, the UI of the app freeze while all other Tasks continue to go (eg Task of download be able to download and save the file without problems).
I immediately thought that the problem was caused by too much work on the main thread (maybe caused by an animation that start as soon as the download starts). [Only on Galaxy S9 Plus of my friend?!]
I also tested the app on the Galaxy S9 Plus, available on browserstack.com, and on their devices, the application works perfectly without problems (even fast).
My friend's phone is fast and it's new (no root or similar).
I'll give you some examples with some images.
Steps:
1. List of files (icon on left, file name, icon on right. This list use FastCells)
2. Click on download (appear a new absolute layout with numer of downloads and progress bar, appear with an animation from bottom to top)
3. If you click on download info layout or swipe download info layout appear a list of separate progress for each file)
These animations work on all devices (Android and iOS), except on the Galaxy S9 Plus of my friend.
To manage the global percentage of downloads, I created an event (DownloadProgressChanged).
I show you some code.
Download method inside Android Service (called by DependencyService.Get().DownloadFile)
private void DownloadFile(string fileName, string url, string source, string id, string uniqueId)
{
if (NetworkState == NetworkState.ConnectedData || NetworkState == NetworkState.ConnectedWifi)
{
// Used inside list of current downloads
var downloadInfo = new DownloadInfo
{
FileName = fileName,
Progress = 0,
IsWriting = false, // used to show infinite progress or percentage progress
UniqueId = uniqueId,
DownloadStatus = DownloadStatus.Idle // waiting (infinite progress)
};
// Add to singleton public list of not exist
if (!DownloadHandlers.Instance.AtomicCheck(uniqueId))
{
DownloadHandlers.Instance.AtomicAdd(downloadInfo);
}
else
{
downloadInfo = DownloadHandlers.Instance.CurrentDownloadsInfo.First(u => u.UniqueId == uniqueId);
}
// Invoke DownloadProgressEvent
InvokeDownloadProgress();
try
{
var handler = new NativeMessageHandler();
var httpClient = new HttpClient(handler);
handler.DisableCaching = true;
// Change value of IsWriting field
if (DownloadHandlers.Instance.AtomicCheck(uniqueId))
{
DownloadHandlers.Instance.AtomicWriting(uniqueId, true);
}
var webApiWrapper = new WebApiWrapper();
Uri uri = new Uri(url);
var credentials = XXXXXXXXXXXXXXXXXXXXXXX;
httpClient = webApiWrapper.ExternalApiRequest(credentials?.access_token);
// Only response header, because we download the file only after that the stream is ready to write file on disk
using (var response = httpClient.GetAsync(uri, HttpCompletionOption.ResponseHeadersRead).Result)
{
var filePath = string.Empty;
// If file not exist, write file to disk
if (string.IsNullOrEmpty(fileName)) fileName = "Unknown_" + Guid.NewGuid().ToString("N") + ".xxx";
fileName = Utils.Normalize(WebUtility.HtmlDecode(fileName), true);
// Get full path where to save file
if (!DependencyService.Get<IFilesHandler>().GetFilePath(fileName, out filePath))
{
// Based on exception, throw error or retry if 401 unauthorize (with new token)
if (!response.IsSuccessStatusCode)
{
// Check error code and choose what to do
_webApiWrapper.HandleWebException(response, () =>
{
DownloadFile(fileName, url, source, id, uniqueId);
}, out ErrorResponse error);
if (error != null)
{
throw new ApiException("Unable to download file", error.Code);
}
return;
}
// Open stream to write file
using (var stream = new FileStream(filePath, FileMode.Create, FileAccess.Write))
{
var copyTask = response.Content.CopyToAsync(stream);
// TOP ahah
var length = response.Content.Headers.ContentLength ?? 15000;
while (!copyTask.IsCompleted && !copyTask.IsFaulted && !copyTask.IsCanceled)
{
// While we receive file data, we calculate the percentage
var progress = (double)stream.Position / length;
Device.BeginInvokeOnMainThread(() =>
{
// Set single percentage for current download list
if (DownloadHandlers.Instance.AtomicCheck(uniqueId))
DownloadHandlers.Instance.AtomicProgress(uniqueId, progress);
// Set status of current download
downloadInfo.DownloadStatus = DownloadStatus.Downloading;
// Invoke DownloadProgressEvent
InvokeDownloadProgress();
});
Thread.Sleep(100);
}
// Close stream and Delete file if CopyTask fail
if (stream.Length == 0 || copyTask.IsFaulted || copyTask.IsCanceled)
{
downloadInfo.IsWriting = false;
stream.Close();
if(File.Exists(filePath)) File.Delete(filePath);
throw new Exception("Download Error.");
}
// Max percentage
downloadInfo.Progress = 1d;
// Save other data
DependencyService.Get<IFilesHandler>().WriteInfoJson(fileName, source, id, uniqueId);
}
}
else
{
response?.Dispose();
AlertError("WarningAlertTitle".Translate(), "FileAlreadyExist".Translate(), "OkButton".Translate());
}
}
// Change value of IsWriting and Status field
if (DownloadHandlers.Instance.AtomicCheck(uniqueId))
{
DownloadHandlers.Instance.AtomicWriting(uniqueId, false);
DownloadHandlers.Instance.AtomicStatus(uniqueId, DownloadStatus.Done);
}
// Download finished
// Decrease number of current downloads
Console.WriteLine("INFO: Perfect Download.");
if (_downloadNumber > 0) _downloadNumber--;
if (_downloadNumber == 0) _isDownload = false;
// Invoke DownloadProgressEvent
InvokeDownloadProgress();
}
catch (Exception ex)
{
.............
}
}
else
{
// No internet connection
}
}
InvokeDownloadProgress method
private void InvokeDownloadProgress()
{
// Calculate total progress
var progress = DownloadHandlers.Instance.TotalProgress();
// Invoke with EventArgs
DownloadProgressChanged?.Invoke(null, new DownloadProgressChangedEventArgs
{
DownloadNumber = _downloadNumber,
TotalProgress = progress
});
}
On form side, page code (OnAppearing)
DependencyService.Get<IServerTasks>().DownloadProgressChanged += DownloadProgressChanged;
where DownloadProgressChanged method
private int _downloadNumber = -1;
private void DownloadProgressChanged(object sender, DownloadProgressChangedEventArgs e)
{
var downloadNumber = e.DownloadNumber;
// Invoke main thread async for animation
Device.BeginInvokeOnMainThread(async () =>
{
// Check current downloads
if (Math.Abs(e.TotalProgress) < 0.0000001 || downloadNumber == 0)
{
progressBar.Progress = 0;
return;
}
// Update progress bar value
progressBar.Progress = e.TotalProgress / downloadNumber;
// If there is at least one download, change value of label inside download info layout
if (downloadNumber > 0 && _downloadNumber != downloadNumber)
{
_downloadNumber = downloadNumber;
lbInfoDownload.Text = string.Format("MultipleDownloadInfoText".Translate(), downloadNumber);
// If download info layout is not visible
if (!infoDetailedView.IsVisible)
{
// Animate layout from bottom to top
infoDetailedView.TranslationY = infoDetailedView.Height;
infoDetailedView.IsVisible = true;
await infoDetailedView.TranslateTo(0, infoDetailedView.Height - iosRow.Height.Value, 350, Easing.Linear);
}
// Change margin of List, so that by scrolling we can see all the elements hidden by download info layout
layoutList.Margin = new Thickness(0, 0, 0, iosRow.Height.Value);
}
});
}
There are other methods used to animate the download info layout (touch and swipe) but it is not necessary to show them because the UI freeze before displaying this layout.