I have an android (4.1) application which reportedly (can't recreate) chrashes with the message "app as stopped". The problem, however, is that the user has to press "OK" in the alert that pops up. The chrash only occur when the app is not active (on screen). This indicates that Android kills of my app because of memory or naughtiness. I'f been investigating for memory leaks, because i handle bitmaps in the app, that did not pay off.
I have a catch and log all default handler like this:
Thread.setDefaultUncaughtExceptionHandler(new UncaughtExceptionHandler() {
public void uncaughtException(Thread thread, Throwable ex) {
Log.e(StaticData.LogTag, "Unhandled exception app", ex);
}
});
To log all exceptions. Afterwards i call original exceptionhandler. This handler is put on the Apps main activity. The method is never called when the "stopped" chrash happens, but is in other cases.
My app uses IntentService to send data to a server in background. This is not a long running service, 1-10s. I will try to put a default exception handler on the service as well. I mention the service because the app is killed when "off screen", so I thought that might have a connection to the problem, but the cause evades me.
Furthermore I use BroadcastReceiver to notify the apps main activity about network connection changes, because the app is used in turbulent network conditions. This is relevant because I'f seen BroadcastReceiver mentioned when people talk about possible memory leak issues. My implementation of BradcastReceiver goes like this:
Serivce side:
sendOrderedBroadcast(uploadedIntent, null);
Activity side:
public static class NetworkStateReceiver extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent in) {
// super.onReceive(context, intent);
Log.d(StaticData.LogTag, "Network connectivity change");
if (in.getExtras() != null) {
NetworkInfo ni = (NetworkInfo) in.getExtras().get(ConnectivityManager.EXTRA_NETWORK_INFO);
if (ni != null && ni.getState() == NetworkInfo.State.CONNECTED) {
Log.i(StaticData.LogTag, "Network " + ni.getTypeName() + " connected");
...
}
}
if (in.getExtras().getBoolean(ConnectivityManager.EXTRA_NO_CONNECTIVITY, Boolean.FALSE)) {
Log.d(StaticData.LogTag, "There's no network connectivity");
}
}
}
As i mentioned in the beginning, the problem is mainly an annoyance for the user because he has to press ok on a popup when looking at mails or taking a call. The app is robust enough to handle that it gets killed from time to time, however, I would like to figure out why my app is knocked out.
PS. I have tried to get the users to send be bug reports via mx log logcollector, but no cigar.
I seem to have solved this problem. I think the culprit was a static reference to the main activity in the application. I was using this static to get a reference to the applications Context. This however this is unnecessary in Service and BroadcastReceiver, because they have their own ref to a "Context".
Why the long face?
A static reference to a Android view, like Activity, is kept in memory and can't be freed by the garbage collector. This was the idea behind making it static. I wanted to have a future reference to the object, but is the wrong approach.
When changing orientation (eg.), Android recreates the current activity (and children views), and the old is freed for GC. When you have a static ref to a view, its not collected, and now you have 2 instances of the Activity. Do the math and realize that this obviously fills memory over time (leak) and Android will nuke the app at some point.
When facing this problem you have to realize that you have to carry your data/states over to the new Activity, for example by using shared memory, a local database or a savedInstanceState:
#Override
public void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (savedInstanceState != null) {
int value = savedInstanceState.getInteger("key");
}
}
#Override
protected void onSaveInstanceState(final Bundle outState) {
outState.putInteger("key", value);
}
Related
I have an Activity and a Service.
In my Activity, a button interacts with the Service to start/stop GPS logging.
My Service has 3 state indicators: One for being connected to Google Play Services, one for actively logging GPS, and one for processing what was logged.
When connected to Google Play Services the Service flow is this:
Ready -> Logging -> Processing -> Ready
The Service will broadcast these states as follows:
private void UpdateStatusBroadcast() {
//Save status variables to intent
Intent intent = new Intent(this.getString(R.string.BroadcastStatusIntent));
intent.putExtra(getString(R.string.BroadcastIsConnected), mIsConnected);
intent.putExtra(getString(R.string.BroadcastIsTripActive), mIsTripActive);
intent.putExtra(getString(R.string.BroadcastIsProcessing), mIsProcessing);
//Send the broadcast
LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
}
My Activity receives the states as follows:
private class StatusReceiver extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
mIsConnected = intent.getBooleanExtra(getString(R.string.BroadcastIsConnected), false);
mIsTripActive = intent.getBooleanExtra(getString(R.string.BroadcastIsTripActive), false);
mIsProcessing = intent.getBooleanExtra(getString(R.string.BroadcastIsProcessing), false);
HandleConnectionStatus();
HandleTripStatus();
}
}
Then comes my problem. In HandleTripStatus(), posted below, i change the text and background of a button to reflect what the Service is currently doing. This works fine for the first and the third case. I never see the second background drawn however, in spite of receiving the correct boolean values.
private void HandleTripStatus() {
Button tripButton = (Button) findViewById(R.id.TripButton);
Button liveMapButton = (Button) findViewById(R.id.LiveMapButton);
if (mIsTripActive) {
tripButton.setText(R.string.TripButtonTitleStop);
tripButton.setBackground(ContextCompat.getDrawable(mContext, R.drawable.trip_button_stop_shape));
liveMapButton.setEnabled(true);
} else if (mIsProcessing) {
tripButton.setText(R.string.TripButtonTitleStopping);
tripButton.setBackground(ContextCompat.getDrawable(mContext, R.drawable.trip_button_stopping_shape));
liveMapButton.setEnabled(false);
} else {
tripButton.setText(R.string.TripButtonTitleStart);
tripButton.setBackground(ContextCompat.getDrawable(mContext, R.drawable.trip_button_start_shape));
liveMapButton.setEnabled(false);
}
}
To debug the issue i verified the following:
Text and background resource is correctly defined (i.e. trying to use
it instead of the first and third case works)
The if-else conditions runs when expected (i.e. the "else if" condition actually runs when I expect it to. Verified by breakpoint.)
No other if-else condition is used in the process. (i.e, only the correct condition is run.)
Some other code that could possibly be relevant:
This is how the Activity requests that the GPS logging should stop (Leading to the processing step before finishing)
private void EndTrip() {
//Create message to TripService with intent to run case for END_TRIP
Message message = Message.obtain(null, TripService.END_TRIP, 0, 0);
//Send the Message to the Service
try {
mMessenger.send(message);
Toast.makeText(mContext, R.string.TripStopToast, Toast.LENGTH_SHORT).show();
} catch (RemoteException e) {
Log.e("Debug", "Failed to contact TripService");
}
}
This is the structure of what happens in the Service after receiving the message from the Activity.
private void EndTrip() {
//Stop retrieving location updates
//Broadcast the updated status and begin processing the trip
mIsTripActive = false;
mIsProcessing = true;
UpdateStatusBroadcast();
//Processing the collected data
//Finish up
mIsProcessing = false;
UpdateStatusBroadcast();
stopForeground(true);
}
I am all out of ideas. What can the cause be? Why does the button background not change in the else-if?
After too many hours of trial and error, I found the cause to be thread-related.
What I learned:
My service doing its work (Processing) would hang up the UI thread until done
This was quite simply because the service was running on the UI thread
Android does not automatically run services in a thread seperate from the rest of your application.
It is possible to run your service on a different thread. To do this, add the following to your AndroidManifest, inside your service:
android:process=":WhateverNameYouLikeForYourThread"
Note that this of course broke the broadcasts i relied on. This was however easy to fix; The consequence is that I can no longer use LocalBroadcastManager
By example - Instead of
LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
i now use
sendBroadcast(intent);
instead. This does however mean that the broadcasts are less private.
I have a Service like this (this is not the actual Service, it's just for describing my problem).
public class UploadService {
private BlockingQueue<UploadData> queue = null;
private UploadInfoReceiver receiver = null;
public void onStart(...) {
queue = new LinkedBlockingQueue<UploadData>();
(new Processor()).start();
// creating and reigtering receiver
}
public void onDestroy() {
queue.add(new ServiceDestroyedData());
// unregistering the receiver
}
private class Processor extends Thread() {
public void run() {
while (true) {
UploadData data = queue.take();
if (data instanceof ServiceDestroyedData) {
return;
}
// processing data
}
}
}
private class UploadInfoReceiver extends BroadcastReceiver {
public void onReceive(Context context, Intent intent) {
queue.add(new UploadData(/* getting data from intent */));
}
}
}
And my problem is that if I do something like this in my App:
if (!isUploadServiceRunning()) {
// start the Service
}
Then it starts the Service, but when I move my App to the background and open task manager (android 4.2.2), and kill the app, Android restart my Service, and I can see that it creates a whole new instance of it, and I can see that onDestroy never gets called for the previous Service instance. And I also can see that the instance of the previous Processor Thread is no longer running. How can this be? If onDestroy never gets called how does Android know that it should stop my Thread?
Thanks for your answers.
Android will kill off anything that it finds that is attached to your apps classloader when you select force stop from the menu. Think kill -9 on Linux. There will be no nice callbacks to any onDestroy methods, the system will just end everything.
Now for your service:
while(true) should really NEVER be used. It will instantly kill the battery and will not do any work 99% of the time anyway.
You area already using a receiver, you can just put your while logic into there and once the upload is done call the next upload and so on. There is absolutely no need for the loop.
(working code extract added below)
My app needs to be notified of all inserts and deletes (and maybe updates, but less important) of contacts. This means when the app is started it will need a list of changes. While it is running it should be notified immediately (is it even possible to make changes to contacts outside the app while it is running?).
Should I be using a ContentObserver? Do I need a Service? Is there a way at app startup to get a list of changes that occurred since the last time the app ran?
Thanks.
ContentObserver does indeed work. However, for contacts, it does much less than I hoped for. You only get a notification that something has changed (in fact, you may get several notifications). You wont know what changed. Better than no notification though, I guess.
When you receive the notificaton, you'll have to run queries to find out if any of the contacts you are interested in have changed. If you need to check all of them, I think you'll be better off using a SyncAdapter.
Here's the code I ended up using. First a ContentObserver subclass; this receives notifications from whatever provider you register with (see next block of code):
class MainContentObserver extends ContentObserver
{
public MainContentObserver (Handler handler)
{
super (handler);
}
#Override
public void onChange (boolean selfChange)
{
Message msg = handler.obtainMessage();
msg.what = CONTACTS_CHANGED; // const int declared elsewhere
msg.obj = null;
handler.sendMessage (msg);
}
}
Here's the sceond block - this is the onCreate from your activity (or it could be in onResume). There are two important parts. One, I implement and instantiate a handler. This will receive "messages" from the observer, which runs in a separate thread, and relay them to my activity. The second piece is the creation of the observer, which happens through the register call.
#Override
public void onCreate (Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
// receive notices from our background threads.
handler = new Handler()
{
#Override
public void handleMessage (Message msg)
{
if (msg.what == CONTACTS_CHANGED) // const int declared elsewhere
System.out.println ("handler: contacts changed");
else
throw new IllegalArgumentException ("unrecognized handler message source: " + msg.what);
}
};
// register content observer for contact changes
contactsObserver = new MainContentObserver (handler);
getContentResolver().registerContentObserver (ContactsContract.AUTHORITY_URI, true,
contactsObserver);
... other initialization ...
}
Finally, one more block of code - you need to unregister the observer or (I've read) you'll have a memory leak. (If you regsiter in onResume, be sure to unregister in onPause.)
#Override
public void onDestroy ()
{
super.onDestroy();
getContentResolver().unregisterContentObserver (contactsObserver);
}
I know there is no broadcast for what you want to do. ContentObserver is what you have to go with. Also check:
Native contact change notification
I think ContentObserver is better option, you can refer following ContentOberver
dealing with contacts.
i think you will have to look into the Broadcast Receiver for your question..
I have spent a lot of hours trying to reproduce and understand the cause of this problem, with no success in either of these goals.
I have tried to leave only the code related to the problem, but I believe a few minutes are still necessary to understand the problem and context. I hope that someone will be able to spot the problem in my implementation or at least help me understand the cause.
Description of the application:
Word game where you play against the computer. After the computer has laid a word on the board, the definition of this word is fetched online in an AsyncTask and displayed in a TextView
How I discovered the issue:
I use ACRA for crash and error reporting (great free tool by the way). It sends me reports for each unexpected situtation (this one does not lead to a crash). I have been receiving many reports of errors 1,2,3 and 4 (see code)
Some bad reviews on Google Play tend to show that some users do not see the definition even though they are connected to Internet. (I am pretty sure this functional bug is related to the previously mentioned errors, though I cannot prove it)
A word on the code design:
After reading a lot on memory leaks in Android, I have decided to make the AsyncTask that retrieves the definition online a static inner class (even though my main activty currently does not support rotations, which are the main causes of leaks: I put in my Manifest android:screenOrientation="portrait").
I need access to the parent Activity from this AsyncTask because I retrieve strings from the resources, and perform some changes on the UI in onPostExecute().
Hence, I use a WeakReference in the AsyncTask which is pointing to the parent Activity. This should prevent memory leaks in case the Activity is recreated or killed while theAsyncTask` is still running.
What exactly is the problem:
The WeakReference or the return of its get() method is null in
some unexplained situations (I suspect it impacts more than 1% of the games or
players) (see code)
All kinds of devices and Android versions are impacted, and I often see several occurences coming from the same device)
I have never been able to reproduce these errors (the most obvious try was exiting the activity while the definition is being downloaded, but this didn't cause any error)
Meaningful parts of my code:
public class GameActivity extends Activity {
private TextView _definition; //inflated from XML in onCreate()
private ProgressDialog _pDialog; //created in onCreate()
private Handler _handlerToDelayDroidMove = new Handler();
private Handler _handlerToDelayProgressDialog = new Handler();
private Handler _handlerToDelayDefinitionClosure = new Handler();
public void onClickValidatePlayerMoveAndTriggerDroidMove(View v) {
int score = _arbitre.validatePlayerMoveAndReturnScore(_listOfLetters);
toast(String.format(getResources().getString(R.string.player_word_score), score));
// ***** Only start Droid move when previous toast has been displayed ****
timedDroidPlayWithSpinner();
}
private void timedDroidPlayWithSpinner() {
_handlerToDelayProgressDialog.removeCallbacks(_droidThinkingDialogRunnable);
_handlerToDelayDroidMove.removeCallbacks(_droidPlayRunnable);
_handlerToDelayProgressDialog.postDelayed(_droidThinkingDialogRunnable, 1500);
_handlerToDelayDroidMove.postDelayed(_droidPlayRunnable, 1500 + DUMMY_DELAY);
}
private Runnable _droidThinkingDialogRunnable = new Runnable() { //Show a "Droid is thinking spinner dialog"
public void run() {
_pDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
_pDialog.setMessage(getResources().getString(R.string.droid_thinking));
_pDialog.setCancelable(false);
_pDialog.show();
}
};
private Runnable _droidPlayRunnable = new Runnable() {
public void run() {
String word = playBestMoveAndUpdateGUI(); // Droid move (CPU intensive, can take several seconds)
saveGameStateToPrefs();
_pDialog.dismiss(); //Hide "Thinking dialog")
new SearchDefinitionTask(GameActivity.this).execute(word);
}
};
private Runnable _hideDefinitionRunnable = new Runnable() {
public void run() {
_definition.startAnimation(_slideUpAnim);
_definition.setVisibility(View.GONE);
}
};
// Made static so we are sure if does not reference the Activity (risk of leak)
public static class SearchDefinitionTask extends AsyncTask<String, Void, String[]> {
private WeakReference<GameActivity> weakRefToGameActivity;
public SearchDefinitionTask(GameActivity context) { //Save a weak reference to the Activity
super();
weakRefToGameActivity = new WeakReference<GameActivity>(context);
}
protected String[] doInBackground(String... words) {
try {
DefFetcherInterface defFetcher = null;
Language l = weakRefToGameActivity.get()._dictionaryId;
defFetcher = new OnlineDefinitionFetcher(l);
return defFetcher.getDefinition(words[0]);
} catch (Exception e) { // Typical exceptions are due to lack of internet connectivity
Log.e("Definition fetch error: ", e.toString());
String[] ret = { "", "" };
ret[0] = mots[0];
if (weakRefToGameActivity == null) { // !!! This occurs in ~0.3% of the games !!!
ErrorReporter.getInstance().handleSilentException(new Exception("Silent ERROR 1: weakRef is NULL"));
return ret;
}
if (weakRefToGameActivity.get() == null) { !!! This occurs in ~1% of the games !!!
ErrorReporter.getInstance().handleSilentException(new Exception("Silent ERROR 2: weakRef.get() is NULL"));
return ret;
}
// If we get here we still have a reference on our Activit/context, so let's show a decent error message
ret[1] = weakRefToGameActivity.get().getResources().getString(R.string.no_connection);
return ret;
}
}
protected void onPostExecute(String[] result) {
if (result[0] != "") { //Don't send another error report if WeakRef was already NULL in doInBackground()
if (weakRefToGameActivity == null) { !!! This occurs in ~0.5% of the games !!!
ErrorReporter.getInstance().handleSilentException(new Exception("Silent ERROR 3: weakRef is NULL"));
} else if (weakRefToGameActivity.get() == null) { !!!!!!!! This occurs in ~1% of the games !!!!!!!!
ErrorReporter.getInstance().handleSilentException(new Exception("Silent ERROR 4: weakRef.get() is NULL"));
} else {
// Everything is fine, show a box with the definition of the word for a few seconds
//(with animation to make the box appearing from the top of the screen)
weakRefToGameActivity.get()._definition.setVisibility(ImageView.VISIBLE);
weakRefToGameActivity.get()._handlerToDelayDefinitionClosure.removeCallbacks(weakRefToGameActivity.get()._hideDefinitionRunnable);
weakRefToGameActivity.get()._definition.setText(Html.fromHtml("<b>" + result[0].toUpperCase() + "</b> " + result[1]));
weakRefToGameActivity.get()._definition.startAnimation(weakRefToGameActivity.get()._slideDownAnim);
weakRefToGameActivity.get()._handlerToDelayDefinitionClosure.postDelayed(weakRefToGameActivity.get()._hideDefinitionRunnable,
DURATION_OF_DEFINITION);
}
}
}
}
}
Any idea of what could go wrong or how to reproduce?
Sebastien, maybe you can try to check the onDestroy is never called for your Activity... The activity can be restarted when the screen is rotated (which you already handle), but there are other configuration changes that may cause the same behavior.
Another pretty common one is to take the keyboard out on some phones, but there are others that are even more obscure to me. You can see the list there
Beside that, I really don't see anything wrong in your code and cannot imagine what else could cause your trouble.
The worst ones are your errors 1 and 3. Can you check in the constructor that weakRefToGameActivity is not null after it is created? (and if it is null, what about the context argument).
Please post updates once you find the root cause of your problem.
Bonne chance.
I have a service which works with LongPoll and when I receive my data everything is OK, but when I don't receive data, rather I receive empty result (long polling max time == 25 sec) my service sometimes turning off manually (and I don't see it in list of services).
So, how to keep this service (..always..) running?
Recursive function, which works with long polling and at first calls in service's onCreate() (structure):
//"u" is "new utils()".
public class myservice extends Service {
public static boolean started=false;
#Override
public void onCreate() {
super.onCreate();
Toast.makeText(this, "created qweqwe", Toast.LENGTH_LONG).show();
longpoll();
this.started=true;
}
#Override
public void onDestroy() {
super.onDestroy();
this.started=false;
}
private String url = "http://example.com/lp.php";
private void longpoll() {
try {
String resp = u.getData(url); //max time of working u.getData(lpurl) - 25s.
if (resp.length()>0) doSmthWithData(resp); //It works fine
} catch(Exception e) {}
longpoll();
}
}
So, how to keep this service (..always..) running?
Tactically, based on the "15 seconds" in your question title, my guess is that you are doing this long poll on the main application thread. You need to do it on a background thread.
Strategically, you cannot keep a service "always running". You can use startForeground() to reduce the odds of your service being automatically destroyed, but the user and the OS can still get rid of your process (along with its service) at any time for any reason. Many users do not like services that are "always running" because of the resources they waste, and therefore will attack developers of such services with task killers and low ratings on the Play Store.