Issue
I have been dialing with a back and forth question the i came across in my xamarin.android app.I will try to provide as much detaile as i can to make my question clear.
I have a long running task that i want to perform, when the user does clicks on a button. The task that i'm trying to run is downloading a file, and converting it to other format. The user is aware of the task, as he initiated the download, and wants it to complete.I have already written and tested the code, i just need to place it right.
Some restriction that have to be ruled:
The download and convert task is
async
, since one of the libraries that im using enforces a async approachThe Task must not close when the app is closed, since the user is waiting for the download to finish.
One of the methods must be run from the UI thread (That's what the documentation says),But i believe that it will suffice the method will receive the UI context, e.g. some activity context. If i run the command with the service as the context, i get the following error
Java.Lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare() service
, so i assumed i needed the MainActivity context
I have had quite a debate between using Service/Intentservice/AsyncTask
,and ran into couple of problems, which ONLY occured when i was actually testing on a phone.
Service/Intentservice, can't get the context of the UI thread, i have tried using the following method :
VideoConverter vc = new VideoConverter(); Java.IO.File convertedFile = await vc.ConvertFile(Application.Context, outPutFile, logFunction, progressFunction);
But i still get the same error.
Also this way whenever i report progress, i rebuild the notification with icon, and i got this weird warning show up:09-18 22:47:12.276 W/IconPackHelper(28042): Unable to cache icon /data/system/theme/icons/com.App_7f020002_0.png 09-18 22:47:12.362 W/IconPackHelper(28042): Unable to cache icon /data/system/theme/icons/com.App_7f020002_0.png 09-18 22:47:12.431 W/IconPackHelper(28042): Unable to cache icon /data/system/theme/icons/com.App_7f020002_0.png 09-18 22:47:12.503 W/IconPackHelper(28042): Unable to cache icon /data/system/theme/icons/com.App_7f020002_0.png
Anytime i build the notification, should this matter ?
To sum up, what should i use that fits my needs ? AsyncTask seems like the best option, but since i'm using xamarin i can use await/async, how should i structure my code ? Run all code within the activity or break it down to more compounds ?
Why did this error only occur on my phone and not on the emulator ?
Big thanks for any help! Cheers !
EDIT
From the feedback i received, i think it's best to actually use a service that will run in the foreground, i already implemented this approach, and i got into an error, which i provide more detail.
Here i'm calling my async method from inside the service, since i can't override with async, i must use Task.Factory to run an async function
public override StartCommandResult OnStartCommand(Intent intent, StartCommandFlags flags, int startId)
{
Task.Factory.StartNew(async () => {
await downloadAndConvert(id);
});
}
Here is the call to a class with that will provides a specific conversion. Notice the context.
async downloadAndConvert(string id){
...
VideoConverter vc = new VideoConverter();
Java.IO.File convertedFile = await vc.ConvertFile(Application.Context,
outPutFile,
logFunction,
progressFunction);
// Error is firing here
...
}
Here is the the call from the VideoConverter
class to the library, notice the passage to of the context next.
public async Task<File> ConvertFile(
Context contex,
File inputFile,
Action<string> logger = null,
Action<int, int> onProgress = null)
{
...
await FFMpeg.Xamarin.FFMpegLibrary.Run(
contex,
cmdParams
, (s) => { ... }
});
...
}
Since the ffmpeg xamarin library is open source, i have been able to retrieve the line that causes the error.
// lets try to download
var dlg = new ProgressDialog(context);
So the error is that the library tries to make a ProgressDialog
, with the passed context, but since only an activity context is able to launch the ProgressDialog its crushing on my service/application context.
The call in the FFmpeg library is to open a ProgressDialog
for the first run, since it downloads the library and initializes it.I prefer not to change the library code, since it's easily available through nuget, and i don't really want to mess with building the library myself. If it's the most elegant solution than i will do it, i could just switch the ProgressDialogs
for a notification and it will work fine.
Here is the complete stack trace, that i followed:
Java.Lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw () [0x0000c] in /Users/builder/data/lanes/3511/f4db8a57/source/mono/mcs/class/referencesource/mscorlib/system/runtime/exceptionservices/exceptionservicescommon.cs:143
at Java.Interop.JniEnvironment+InstanceMethods.CallNonvirtualVoidMethod (Java.Interop.JniObjectReference instance, Java.Interop.JniObjectReference type, Java.Interop.JniMethodInfo method, Java.Interop.JniArgumentValue* args) [0x000a7] in /Users/builder/data/lanes/3511/0e59c362/source/Java.Interop/src/Java.Interop/Java.Interop/JniEnvironment.g.cs:12083
at Java.Interop.JniPeerMembers+JniInstanceMethods.FinishCreateInstance (System.String constructorSignature, Java.Interop.IJavaPeerable self, Java.Interop.JniArgumentValue* parameters) [0x00060] in /Users/builder/data/lanes/3511/0e59c362/source/Java.Interop/src/Java.Interop/Java.Interop/JniPeerMembers.JniInstanceMethods.cs:148
at Android.App.ProgressDialog..ctor (Android.Content.Context context) [0x00078] in /Users/builder/data/lanes/3511/0e59c362/source/monodroid/src/Mono.Android/platforms/android-24/src/generated/Android.App.ProgressDialog.cs:48
at FFMpeg.Xamarin.FFMpegLibrary+<Init>d__7.MoveNext () [0x00179] in D:\git
s-github\xamarin-ffmpeg-android\FFMpeg.Xamarin\FFMpegLibrary.cs:94
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw () [0x0000c] in /Users/builder/data/lanes/3511/f4db8a57/source/mono/mcs/class/referencesource/mscorlib/system/runtime/exceptionservices/exceptionservicescommon.cs:143
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess (System.Threading.Tasks.Task task) [0x00047] in /Users/builder/data/lanes/3511/f4db8a57/source/mono/mcs/class/referencesource/mscorlib/system/runtime/compilerservices/TaskAwaiter.cs:187
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification (System.Threading.Tasks.Task task) [0x0002e] in /Users/builder/data/lanes/3511/f4db8a57/source/mono/mcs/class/referencesource/mscorlib/system/runtime/compilerservices/TaskAwaiter.cs:156
at System.Runtime.CompilerServices.TaskAwaiter.ValidateEnd (System.Threading.Tasks.Task task) [0x0000b] in /Users/builder/data/lanes/3511/f4db8a57/source/mono/mcs/class/referencesource/mscorlib/system/runtime/compilerservices/TaskAwaiter.cs:128
at System.Runtime.CompilerServices.TaskAwaiter.GetResult () [0x00000] in /Users/builder/data/lanes/3511/f4db8a57/source/mono/mcs/class/referencesource/mscorlib/system/runtime/compilerservices/TaskAwaiter.cs:113
at FFMpeg.Xamarin.FFMpegLibrary+<Run>d__8.MoveNext () [0x000d0] in D:\git
s-github\xamarin-ffmpeg-android\FFMpeg.Xamarin\FFMpegLibrary.cs:191
--- End of managed Java.Lang.RuntimeException stack trace ---
java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()
at android.os.Handler.<init>(Handler.java:200)
at android.os.Handler.<init>(Handler.java:114)
at android.app.Dialog.<init>(Dialog.java:119)
at android.app.AlertDialog.<init>(AlertDialog.java:200)
at android.app.AlertDialog.<init>(AlertDialog.java:196)
at android.app.AlertDialog.<init>(AlertDialog.java:141)
at android.app.ProgressDialog.<init>(ProgressDialog.java:77)
So is there any way to pass the activity context? Also, Would the application crash if I try to make a ProgressDialog
when the app is closed?
Solution
I Have found the reason the error is thrown.
When you first run the library, it has to download the native library and install it.It shows a progress bar in front of the application (hence why it needed an activity context).
Here is why the emulator worked, since i was using it for all my testing, it already had the library installed from previous runs, when the code where still on t the activity class.
I solved my problem by first checking if the library was installed, and if not let prompt the user to install it, since this is all on the ui thread, i had no problem passing the context.Later when the user downloaded something, it didn't need to redownload the library,so i can pass any context, because it will not use it (The check is without the context, only if the library is missing it will call the init method which he need the context to provide the prograss bar.)
I might fork the xamarin ffmpeg library and add a bit more documentation/ make the code clearer in the future.
Answered By - David Barishev
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.