I have a quiz app that loads a question database from a plist file. When the app is freshly loaded it runs a thread to load the question file.
After some inactivity, when you open the app again and try to run a new quiz, it loads no questions.
I imagine its some sort of garbage collector that the OS uses to clear up some memory, it's doing its job so thats ok.
But what would you recommended for reload the file? I'm thinking overriding savedInstanceState?
This is a splash screen and loader combination that is the first activity to run
public class SplashScreen extends Activity {
/** Called when the activity is first created. **/
ProgressDialog dialog;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
new LoadViewTask().execute();
}
private class LoadViewTask extends AsyncTask<Void, Integer, Void>{
//Before running code in separate thread
#Override
protected void onPreExecute(){
dialog = new ProgressDialog(SplashScreen.this);
dialog.setMessage("Loading...");
dialog.setTitle("Loading File");
dialog.setIndeterminate(true);
dialog.setCancelable(false);
dialog.show();
}
//The code to be executed in a background thread.
#Override
protected Void doInBackground(Void... params){
try{
//Get the current thread's token
synchronized (this){
QuestionsLoader.getInstance(SplashScreen.this);
}
}
catch (Exception e){
e.printStackTrace();
Log.d("TAG", "exception" + e);
}
return null;
}
//Update the progress
#Override
protected void onProgressUpdate(Integer... values){
}
//after executing the code in the thread
#Override
protected void onPostExecute(Void result){
dialog.dismiss();
Intent i = new Intent("com.project.AnActivity");
startActivity(i);
finish(); //Finish the splash screen
}
}
}
Is SplashScreen an Activity? If so, you should not attempt to retain a reference to it.
Android destroys activities to free memory when needed. It is indeterminate when this happens and you should not interfere with the mechanism. It doesn't use the garbage collector to do this (it destroys the Activity's process) so if you hold a reference to it, you will leak the entire activity which is not good. You see many apps on the Play Store with this particular bug, or a variant of it. Just rotate the device 5 or 10 times and the app will crash with out of memory.
Without seeing the code, it's impossible to know but usually the correct approach is to load external resources in onResume() or later such that they are available whether the Activity is a new instance (e.g. onCreate() has been called) or the same instance returning to the foreground.
If you need to persist some state regardless of whether the activity is destroyed or not, save it (usually to SharedPreferences) in onPause() and retrieve it in onResume().
Related
Edit
After moving my loading / creation code to an Async Task (see below) - I still have the initial problems that I had with my original splashscreen.
Those being that:
1) On starting the Async task in onCreate, everything is loaded but my Dialog can only be shown when onStart() is called which makes the whole process kind of pointless as there is a long pause with a blank screen, then after everything has loaded, the 'loading' dialog flashes up for a split second before disappearing.
2) I can't move object loading / creation etc to onStart because a) it will be run again even when the app is resumed after being sent to the background which I don't want to happen, and b) when when calling restoring the savedInstanceState in onCreate() I'll get a nullPointerException because i'm restoring properties of objects that won't have yet been created.
Would really appreciate if someone could advise how to get around these problems and create a simple splashscreen. Thanks!
Background
My app uses only one activity and I would like to keep it that way if possible.
I've struggled with this for over a week so really hope someone can help me out.
All I want to do is use a splashscreen with a simple 'loading' message displayed on the screen while my resources load (and objects are created etc.) There are a couple of points:
Conditions
1) The splashscreen should not have it's own activity - everything needs to be contained in a single-activity
2) The splashscreen should not use an XML layout (I have created a Splashscreen class which uses View to display a loading PNG)
3) My app is openGL ES 2.0 so the textures need to be loaded on the OpenGL Thread (creation of objects etc that don't use GL calls are OK to go on another thread if necessary).
What I've attempted so far
What I did so far was to create a dialog and display it in my onStart() method with:
Dialog.show();
then let everything load in my onSurfaceCreated method before getting rid of it with:
Dialog.dismiss();
However I needed to change this for varioius reasons so now I create my objects from a call within my onCreate() method and just let the textures load in my GL Renderer's onSurfaceCreated method.
However, this means that because the dialogue isn't displayed until after onCreate, I still get a delay (blank screen) while everything is created before the splash-screen is shown, this then stays on the screen until the textures have loaded. There are other issues with this too which can wait for another day!
So my approach is obviouly very wrong. I read every tutorial I could and every splash-screen related question I could find on SO and Gamedev.SE but I still can't find an explanation (that makes sense to me), of how this can be achieved.
I'm hope someone here can explain.
You should be able to use AsyncTask to load resources in the background and then just dismiss your splash
Here's an AsyncTask that I use to load data from a remote db. This displays a loading progress circle until the task is complete but should be easily re-purposed to display your splash
AsyncTask that runs in the background
private class SyncList extends AsyncTask<Void, ULjException, Void> {
private static final String TAG = "SyncList";
private final class ViewHolder {
LinearLayout progress;
LinearLayout list;
}
private ViewHolder m;
/**
* Setup everything
*/
protected void onPreExecute() {
Log.d(TAG, "Preparing ASyncTask");
m = new ViewHolder();
m.progress = (LinearLayout) findViewById(R.id.linlaHeaderProgress);
m.list = (LinearLayout) findViewById(R.id.listContainer);
m.list.setVisibility(View.INVISIBLE); //Set the ListView that contains data invisible
m.progress.setVisibility(View.VISIBLE); //Set the loading circle visible you can sub in Dialog.show() here
}
/**
* Async execution performs the loading
*/
#Override
protected Void doInBackground(Void... arg0) {
try {
Log.d(TAG, "Syncing list in background");
dba.open(ListActivity.this);
dba.sync();
} catch (ULjException e) {
publishProgress(e);
}
return null;
}
/**
* Display exception toast on the UI thread
*/
protected void onProgressUpdate(ULjException... values) {
Log.e(TAG, values[0].getMessage());
Toast.makeText(ListActivity.this, "Sync failed", Toast.LENGTH_LONG).show();
}
/**
* Finish up
*/
protected void onPostExecute(Void result) {
Log.d(TAG, "ASyncTask completed, cleaning up and posting data");
fillData();
m.list.setVisibility(View.VISIBLE); //Show the list with data in it
m.progress.setVisibility(View.INVISIBLE); //Hide the loading circle sub in Dialog.dismiss()
}
}
Calling the Task
protected void onStart() {
super.onStart();
// Init the dba
dba = DBAccessor.getInstance();
new SyncList().execute();
}
It should be noted that the AsyncTask is an inner class of the Activity its related to here
Edit
onCreate Method
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_layout);
Dialog.show();
//This launches a new thread meaning execution will continue PAST this call
//to onStart and your loading will be done concurrently
//Make sure to not try to access anything that you're waiting to be loaded in onStart or onResume let your game start from onPostExectue
new AsyncTask.execute();
}
doInBackground
protected Void doInBackground(Void... arg0) {
Load all resources here
}
onPostExecute
protected void onPostExecute(Void result) {
Dialog.dismiss();
Call a method that starts your game logic using your newly loaded resources
}
Strange effect in displaying an AlertDialog inside AsyncTask: if the application is minimized during the execution of the AsyncTask:
private class CheckDeviceConfiguration extends AsyncTask<Void, Void, Boolean> {
private ProgressDialog progressDialog;
#Override
protected void onPreExecute() {
super.onPreExecute();
progressDialog = ProgressDialog.show(ActivitySIPCountrySelection.this, "Title", "working...", true);
}
#Override
protected void onPostExecute(Boolean result) {
super.onPostExecute(result);
progressDialog.dismiss(); //hide progress dialog previously shown
if (!result) {
AlertDialog.Builder dialog = new AlertDialog.Builder(ActivitySIPCountrySelection.this);
dialog.setCancelable(false);
dialog.setMessage("Message");
dialog.setPositiveButton("OK", new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int arg1) {
//do something
}
});
dialog.show();
}
}
#Override
protected Boolean doInBackground(Void... params)
Thread.sleep(5000);
return false;
}
}
If I click on my app icon to restore, the UI is not responding and the activity looks a bit darkened (inactive?). Back button has no effect.
Edit:
Someone asks where I call AsyncTask. Well, from the Activity onCreate().
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_sip_country_selection);
new CheckDeviceConfiguration().execute();
}
The async task shows correctly a progress dialog and hide it in onPostExecute.
NOTE: AsyncTask manages a thread pool, created with ThreadPoolExecutor. It will have from 5 to 128 threads. If there are more than 5 threads, those extra threads will stick around for at most 10 seconds before being removed. (note: these figures are for the presently-visible open source code and vary by Android release).
Leave the AsyncTask threads alone, please.
Pressing the Home switches you from the app to the home screen, whilst leaving your app running in the background.
When your phone is running low on resources like memory it will start to close apps that are running in the background, so that your phone has enough resources for what you're trying to do now. Games are often amongst the first apps the phone will "kill" to save resources as they often use a lot more memory and CPU than other apps. This is why sometimes your game is still running paused, and sometimes Android has closed it for you.
http://developer.android.com/guide/components/tasks-and-back-stack.html. For more details check tasks and back task.
You can cancel asynctask by calling cancel(true), an interrupt will be sent to the background thread, which may help interruptible tasks. Otherwise, you should simply make sure to check isCancelled() regularly in your doInBackground() method.
#Override
protected void onPause() {
// TODO Auto-generated method stub
super.onPause();
d.cancel(true);
if(d.isCancelled())
{
System.out.println("Destroyed....");
}
}
#Override
protected void onDestroy() {
// TODO Auto-generated method stub
super.onDestroy();
d.cancel(true);
if(d.isCancelled())
{
System.out.println("Destroyed....");
}
}
SO when your activity resumes create a new instance of asynctask and execute again.
http://code.google.com/p/shelves/. Check shelves project by Romain Guy.
http://developer.android.com/reference/android/content/AsyncTaskLoader.html. Also check asynctask loader.
An alternative to asynctask is RoboSpice.
https://github.com/octo-online/robospice.
FAQ's https://github.com/octo-online/robospice/wiki/Advanced-RoboSpice-Usages-and-FAQ.
I have an AsyncTask which does a lot of JSON calculations.
public class InitializationTask extends AsyncTask<Void, Void, InitializationResult> {
private ProcessController processController = new ProcessController();
private ProgressDialog progressDialog;
private MainActivity mainActivity;
public InitializationTask(MainActivity mainActivity) {
this.mainActivity = mainActivity;
}
#Override
protected void onPreExecute() {
super.onPreExecute();
progressDialog = new ProgressDialog(mainActivity);
progressDialog.setMessage("Die Daten werden aufbereitet.\nBitte warten...");
progressDialog.setIndeterminate(true); //means that the "loading amount" is not measured.
progressDialog.setCancelable(false);
progressDialog.show();
};
#Override
protected InitializationResult doInBackground(Void... params) {
return processController.initializeData();
}
#Override
protected void onPostExecute(InitializationResult result) {
super.onPostExecute(result);
progressDialog.dismiss();
if (result.isValid()) {
mainActivity.finalizeSetup();
}
else {
AlertDialog.Builder dialog = new AlertDialog.Builder(mainActivity);
dialog.setTitle("Initialisierungsfehler");
dialog.setMessage(result.getReason());
dialog.setPositiveButton("Ok",
new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int which) {
dialog.cancel();
mainActivity.finish();
}
});
dialog.show();
}
}
}
processController.initializeData() runs for about 20 seconds. All this works. It even works when I install the application, and pressing home button while application is initializing. The AsyncTask is working in the background. When I restart the application from Android device again after the AsyncTask has been finished, the application still works.
But the application cannot handle this use case: When I deploy the application (or start it when no data is initialized), so that it really takes about 20sec to initialize the data and when I hit home to close the application while initialization (the AsyncTask) runs in the background and start the application again, it leads to unexpected behavior as RuntimExceptions and so on. It seems that the device wants to load the application twice, but none of them can start successfully. How to deal with that?
I thought about checking if there is a running AsyncTask in MainActivity to avoid starting it again:
private InitializationTask initializationTask = new InitializationTask(this);
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
if (!AsyncTask.Status.RUNNING.equals(initializationTask.getStatus())) {
initializationTask.execute((Void[])null);
}
initializeMap();
}
Unfortunately this does do nothing. Moreover I'm not sure if such use case is possible at all, because when I start the same application twice, they cannot "share" an AsyncTask. Should I somehow avoid starting the application twice or something? I'm not sure what options do I have on this? Any ideas?
I usually do it a bit more bluntly. I set my AsyncTask reference to null when I'm not using it. When the onClick fires, I check if it's not null, which means I started it. If it is null, I create and execute a new AsyncTask right there. It is plenty fast and it's clean enough. One bonus of this approach is that an AsyncTask can only be executed once anyway, so it fits in well with that. At the end of onPostExecute, you can set the reference back to null again if you intend to stay in the same Activity.
While you're perfecting your AsyncTask flow, make sure that it survives orientation changes as well.
I found the solution: It's actually not an AsyncTask issue. The problem was that my parse method of JSONParser (that does the most of the work) which is invoked in processController.initializeData(), was not synchronized.
As I've asken on another question HERE it seems that the PackageManager.getInstalledPackages() doesn't play nice with Threading. As CommonsWare stated HERE:
Perhaps the PackageManager needs to be invoked on the main application
thread, for some reason
So having it on a thread gets undesired behavior, entering and exiting a few times in my Activity makes the list of displayed apps sometimes with items, sometimes empty. Having everything in the UI Thread works like a dream, loads fine everytime. The thing is, the user expects some sort of feedback and I need to provide one. As I start the activity, the screen remains black for 3-4-5-6 seconds (depending on the device and apps installed). How can I provide some sort of feedback ? I am thinking of a ProgressDialog but I don't know how can I start it. Thank you.
As discovered, the loop to work through the applications takes awhile (which can be done in a separate thread), compared to the call to PackageManager.getInstalledPackages() (which has to be done on the UI thread).
Use Async to do background work and show indicator while loading data.
in you onCreate(). call new AsyncCommonDataFetcher(this).execute();
public class AsyncCommonDataFetcher extends AsyncTask<Void, Void, Void> {
Context mContext = null;
private ProgressDialog mProgressIndicator = null;
public AsyncCommonDataFetcher(Context ctx) {
mContext = ctx;
}
protected void onPreExecute() {
mProgressIndicator = ProgressDialog.show(((Activity) mContext), null,
"Please wait", true);
}
#Override
protected Void doInBackground(Void... voids) {
// Do what ever work you like to do. It will do this in backgound and show user a indicator to wait.
}
#Override
protected void onPostExecute(Void voidInstance) {
try {
if (mProgressIndicator != null) {
mProgressIndicator.hide();
mProgressIndicator.dismiss();
}
} catch (Exception e2) {
e2.printStackTrace();
}
}
}
}
Try the following for ProgressDialog in the onCreate() of your activity
this.requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
ProgressDialog LoadingDialog = ProgressDialog.show(this, "", "Loading..", true);
And then dismiss it when the process causing the delay is over
LoadingDialog.dismiss();
The documentation at http://developer.android.com/reference/android/database/sqlite/SQLiteOpenHelper.html#getWritableDatabase%28%29 states:
Database upgrade may take a long time,
you should not call this method
[getWritableDatabase] from the
application main thread, including
from ContentProvider.onCreate().
This begs the question: for best practice, where should getWritableDatabase be called from?
My feeling is that, perhaps, it should be called once upon application launch with a callback to mark the database as ready. Is this correct?
For small and agile databases I imagine this isn't much of an issue.
Otherwise, I'd use an always-wonderful AsyncTask, called from onCreate.
It can be called from anywhere, but it should not be called from the UI thread because you don't know how long the process will take (especially with the different file systems in use). Even if you know the database should be small, you don't know about the file system (can it perform more than one job at a time? are there are thousand other jobs waiting in line already?). You can use an AsyncTask or a Thread to call getWriteableDatabase.
It seems that the intended use of the open helper framework, is to open the db on activity start, and close it when the Activity is destroyed.
In an AsyncTask from within onCreate()...
new StartupTask().execute();
The AsyncTask Thread.sleep() below is just to give enough time to show the dialog so that you can see it work. Obviously take that out when you're done playing. ;)
private class StartupTask extends AsyncTask
{
private ProgressDialog progressDialog;
#Override
protected Object doInBackground(final Object... objects)
{
openHelperRef.getWritableDatabase();
try
{
Thread.sleep(5000);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
return null;
}
#Override
protected void onPreExecute()
{
super.onPreExecute();
runOnUiThread(new Runnable()
{
public void run()
{
progressDialog = ProgressDialog.show(
MyActivity.this, "Title",
"Opening/Upgrading the database, please wait", true);
}
});
}
#Override
protected void onPostExecute(Object object)
{
super.onPostExecute(object);
progressDialog.dismiss();
}
}
in onDestroy()...
openHelper.close();