I have been trying to find guidance on how to make a loading splash screen in Android, and have found tutorials such like A Simple Android Splash Screen. Because this tutorials glosses over certain things, one thing that's a bit unclear to me is how do you programmatically determine whether the app is trying to load (before the onCreate() method for your activity executes and your activity loads)?
You might be interested in the Instrumentation Class. It is used to monitor such things, and should provide you with whatever you want know. Also, putting the Dalvik messages to Verbose in LogCat will give you an idea of everything the system is doing prior to calling your Activity's onCreate() method.
The onCreate() method of android is called when its time for it to render the UI. The condition that you are specifying by
one thing that's a bit unclear to me is how do you programmatically
determine whether the app is trying to load (before the onCreate
method for your activity executes and your activity loads)?
would be using someting in manifest like splashScreen="#drawable/Splash" but this unfortunately doesnt exist
So the solution would be like this
Calling a temporary XML file before loading your actual content
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.splash_screen);
// Do your loading code here
// Create an AsyncTask if the task is time consuming
//Now Load your actual UI
setContentView(R.layout.activity_main);
}
you said
The issue is for my app while onCreate() is executing there is a black
screen for some time
The short black screen is for very few seconds and there is no way around for it.
Related
After user inputs parameters in MainActivity for my app (shown below), he taps Search, which calls MatchesActivity, which generates output on a new screen (shown further below), which is exited via tapping back.
But with MatchesActivity active, every time the device is rotated, Search is again executed because the Activity restarts. In the screenshot below, I rotated device from vertical to horizontal to vertical to horizontal back to vertical.
It looks silly.
The output is generated in MatchesActivity that is invoked in onCreate in MainActivity like so:
Intent matchesIntent;
matchesIntent = new Intent(MainActivity.this, MatchesActivity.class);
startActivity(matchesIntent);
Here's the essence of onCreate for MatchesActivity:
#Override protected void onCreate(Bundle savedInstanceState)
{
MainActivity.dbc.setDbProcesslistener(this); // to know txaMatches has been defined
MainActivity.dbc.findDBMatches(); // generate output
}
I did research. I found some complicated ways of preventing an activity from restarting when the device is rotated. For example .
I'm hoping for a simpler solution. Any ideas?
As you have found, one option is to prevent the activity from being recreated on configuration changes all together. This is not always the best option, as this will prevent other things depending on the configuration from being recreated/reloaded too (e.g. resources overridden with the "-land" qualifier).
Another option is to cache the result of the DB search somehow. This could be done by adding a wrapper around your database that memorizes the term and results of the last search. Another way to cache the results would be to use a fragment, and reuse that fragment across activity recreations. Whether a fragment is recreated along with its activity is controlled by this method:
http://developer.android.com/reference/android/app/Fragment.html#setRetainInstance(boolean).
My solution was simple.
Introduce boolean variable outputIsShowing, set it to true in onCreate as MatchesActivity terminates, set it for false when onCreate or onResume are executed in MainActivity (i.e., when MatchesActivity terminates), and return immediately in onCreate for MatchesActivity if outputIsShowing is true.
So if MatchesActivity is active when device is rotated, outputIsShowing will be true, so don't execute again.
It may not be best practice, but I've extensively tested it under normal conditions and am happy enough so far. Not sure if anything is lurking out there as a "gotcha".
I plan to go back and study the suggestions made so far since the more general situation is definitely important. And I will have to do so if someone finds fault with what I've done.
#Override protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
// usual details prior to asking for matches
if(outputIsShowing)
return;
MainActivity.dbc.setDbProcesslistener(this); // to know matches was defined
MainActivity.dbc.findDBMatches();
outputIsShowing = true;
}
* EDIT *
Strangely, after embedding TextView txaMatches in a ScrollView to accomplish smooth, accelerated scrolling, I had to remove the references to outputIsShowing in order to see output after two device orientation changes.
And now, maybe I'll submit another question to address the fact that, very infrequently after screensaver forces waking the device, the output does NOT show if that is where the focus was when screensaver became active. Tapping 'back' to get to user input screen and then immediately tapping Search restores all to normal until about 100 (give or take) screensaver instances later, the output is again missing.
Such a bug makes me think I ought to follow the advice above.
If I do, or when I figure out the problem, I'll edit this again.
Given a one-activity app like this:
public class MyActivity extends Activity {
protected void onCreate(Bundle savedInstanceState) {
loadingScreen = new RelativeLayout(this);
// add a bitmap to loadingScreen
setContentView(loadingScreen);
Dict dict = new Dict();
dict.init();
// this method takes ~10 seconds
}
}
In theory, it looks like this would display a loading screen while the dictionary initializes. In practice, nothing is displayed until onCreate() returns, so the user sees a blank white screen for 10 seconds. What's the best way to display a please wait while this app loads screen?
I believe I could create a separate thread for dict.init(), but that seems like overkill in this case because I don't want the app to be usable or interactive while dict.init() runs. I'd like it to run on the main thread (and hang the rest of the app while it executes), I just want to display something on the screen first.
PS, I tried moving dict.init() to onStart(), that appeared to have no effect.
edit: Perhaps I should have clarified this to avoid getting "You're doing it wrong" type answers, but the init takes 2 or 3 seconds on modern phones and tables, 10 seconds is a worst-case on old phones. This app is a word game, it can't be used without the dictionary. Moving dict.init() to an async task will not improve the user's experience, and the question I asked is whether it's possible to display a splash screen without doing that. I gather that the answer is "No."
I'd like it to run on the main thread (and hang the rest of the app while it executes)
No, no wouldn't and your users wouldn't like you much either
I believe I could create a separate thread for dict.init(), but that seems like overkill in this case because I don't want the app to be usable or interactive while dict.init() runs.
This is exactly what you should do. You can use a variety of ways including an AsyncTask
and show a ProgressDialog while the work is being done.
so the user sees a blank white screen for 10 seconds.
If it is taking this long then you might want to rethink your flow. Even if you go with a separate Thread and show a ProgressBar, most users aren't going to want to stare at that for 10 seconds. You should load the data in the background and allow them to do something else while it loads, if possible. You could use something like an IntentService depending on how you are getting the data.
Example of AsyncTask
Painless Threading
I have a problem that causes me some problems when a user (or another app, like the phone-application) pushes my application to the background.
My application does following:
A User can enter some information that is supposed to be pushed to a server.
When the user clicks "Send" i open a managed ProgressDialog and start an AsyncTask that performs the server communication.
When server communication is complete the AsyncTask reports back to my Activity where i perform a dismissDialog().
Directly after dismissDialog(), I will show another managed dialog using showDialog() that will inform the user about whether the submission was ok or if it failed.
This all works perfectly without any issues; however, when a call happens to come while the AsyncTask is running I get (seemingly random) one of these results:
The activity holding the managed dialog is dismissed completely and the previous view from the stack is presented when I come back.
The activity holding the managed dialog is still on screen, but it is grayed out without showing a dialog. The only way to fix this is to rotate the phone at which point it shows the "Submission sent"-dialog exactly the way it should and everything is ok after that.
All this happens without any warning messages so I get absolutely no clues as to why Android is behaving this way.
I know a way around this and that is to cancel the AsyncTask (so no dialogs are shown at the end). However, in this very use-case the requirements are that the app has to try to complete the server transaction so that there is as little confusion as possible (i.e. the user wondering if it was really sent or not).
Has anybody else had this issue and knows a way around?
I see recommendations to hold a reference to the asynch task in onRetainNonConfigurationInstance
What to do with AsyncTask in onPause()?
Or implement a bus:
https://github.com/commonsguy/cwac-bus/tree
EDIT: The complexity of your challenge is two fold:
1) saving and restoring state of your app on a kill such as when there is an incoming phone call
https://sites.google.com/site/jalcomputing/home/mac-osx-android-programming-tutorial/saving-instance-state
2) somehow continuing the asyncTask on kill instead of canceling it onPause
https://sites.google.com/site/jalcomputing/home/mac-osx-android-programming-tutorial/asynch
Both of these are significant challenges alone, and trying to fix both at the same time would give me a headache. In fact, I am getting a headache just thinking on it :) One clue is that you say the dialog returns on orientation change. This MAY be due to the fact that using the standard architecture for dialogs, the OS handles saving and restoring the state of dialogs for you on orientation change.
[EDIT] See CommonsWare
#Override
public Object onRetainNonConfigurationInstance() {
task.detach();
return(task);
}
and
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
bar=(ProgressBar)findViewById(R.id.progress);
task=(RotationAwareTask)getLastNonConfigurationInstance();
if (task==null) {
task=new RotationAwareTask(this);
task.execute();
}
else {
task.attach(this);
updateProgress(task.getProgress());
if (task.getProgress()>=100) {
markAsDone();
}
}
}
where task is an instance of
static class RotationAwareTask extends AsyncTask<Void, Void, Void> {
I see no reason why this would not work for all types of soft kills, but on a hard kill, well, you get killed. Dead is dead :)
Without looking at your code it is slightly difficult to say what the problem is. However, here is something you could use to help get around the problem. You can override the onPause() method of your Activity.
This is taken directly from the Android Acitivy javadoc:
onPause() is where you deal with the user leaving your activity. Most importantly, any changes made by the user should at this point be committed (usually to the ContentProvider holding the data)
I've read a few posts on the topic, . Here is my strategy; I have a drawing app that shows the user-created drawings in a ListView. Once the user selects a drawing, the DrawingEdit Activity is fired up, with the drawing being loaded in onCreate, roughly as follows:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// set the layout for the activity
setContentView(R.layout.drawing_view);
// do potentially expensive loading of drawing here
loadDrawing();
}
Where loadDrawing() is loading the individual shapes of the user's drawing from a database. Now, for 95% of the drawings, and for the 'typical' use case, this works fine, as the Activity loads quickly with little to no delay. However, for very very complex drawings, the delay can be up to two seconds. To help resolve this issue, I've tried an AsyncTask, which works great for the 5% of drawings that are excessively large, but for the other 95% seems like overkill, and actually makes the loading a little slower due to having to fire up the AsyncTask. It's not the worst solution, but I'm looking at alternatives.
Now, in addition to the drawing data that I have stored in the database, I also have a static PNG version of the drawing that I can make use of... so the solution that I'd like to use is along the lines of:
Load a temporary 'splash' screen view at the beginning of onCreate(), using the static drawing as a placeholder that the user can immediately review while the drawing is loading.
Start the sometimes expensive loadDrawing() routine.
Upon completion switch the View and show the secondary view with the fully interactive drawing canvas that includes the shapes from loadDrawing().
My problem is that if I model the above solution roughly as below, the 'splash' layout is never displayed, and there is still the same delay until loadDrawing() is complete and then the final layout is displayed. Any info on what is happening here? Should I move the loadDrawing() to onResume so that the initial splash UI has a chance to load before loadDrawing() is triggered?
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// set the layout for the activity
setContentView(R.layout.splash_view);
// do potentially expensive loading of drawing here
if (loadDrawing()) {
// set the layout for the activity
setContentView(R.layout.drawing_view);
}
}
If loadDrawing is a method within your Activity, it will block the UI thread until it is complete. In short, the UI isn't displayed until after the startup steps (create/start/resume) have finished. It wouldn't matter if you put loadDrawing in onResume as it will still block the UI creation.
You need to use some sort of background asynchronous thread to get this to work - this is why AsyncTask is useful to allow the UI to be drawn/manipulated while something else needs to happen at the same time.
My app may launch a sub-activity for a specific purpose. When that activity finishes, I get the results in onActivityResult. These results are then processed in the subsequent onResume. This consists of a setContentView and also starting an AsyncTask that puts up a ProgressDialog.
This all works well when initiated the normal way, which is via a user request (i.e., menu selection) after the app is up and running. However, under some conditions I need to do this right as the app is starting up, so I initiate this sequence right from my onCreate. What then happens is that I get fatal ResourceNotFound errors within any o/s call that implicitly calls the layout inflater. I got around this with setContentView by pre-inflating the view in my onCreate method, but the AsyncTask's onPreExecute still fails on ProgressDialog.show() as it "fails to find" Android's own progress_dialog.xml!
Anyone know what's happening here?
I suspect it's something to do with the timing, where this is occurring before the main activity has even had a chance to display its screen. These calls are all being made on the main UI thread, but maybe something hasn't completed within the o/s under these conditions.
As a closeout, the problem turned out to be totally unrelated to what I described in my post. Turns out it was due to blindly using some code that had been posted in some online forum showing how to get and use AssetManager. Trouble is, at the end of the block of code he had put "assMan.close()". Well, this closes the asset manager for the entire activity and resources can no longer be accessed!
It took a while to find it since it was not something that I did via my own understanding.