I have a tabbar based app using fragments that uses parcelable objects between the fragments/activities. It consists of 4 fragments - the main ones being a mapview (first) and a list view (second). The app goes into the background and loads up again without any problems. However, when the app gets killed after I load up lots of other apps to reduce the memory available, and I restart the app, it crashes with the log output below...
Any guidance would be much appreciated as I find it very difficult to debug this seeing as the debugger isn't running after the app is terminated and restarted, and the crash logs don't tell me much!
Cheers
FATAL EXCEPTION: main
12-16 16:04:36.195 E/AndroidRuntime( 6795): java.lang.RuntimeException: Unable to start activity ComponentInfo{com.appco.myapp/com.myapp.MainActivity}: android.os.BadParcelableException: ClassNotFoundException when unmarshalling: com.braesloantrail.model.Place
12-16 16:04:36.195 E/AndroidRuntime( 6795): at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:1651)
12-16 16:04:36.195 E/AndroidRuntime( 6795): at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:1667)
12-16 16:04:36.195 E/AndroidRuntime( 6795): at android.app.ActivityThread.access$1500(ActivityThread.java:117)
12-16 16:04:36.195 E/AndroidRuntime( 6795): at android.app.ActivityThread$H.handleMessage(ActivityThread.java:935)
12-16 16:04:36.195 E/AndroidRuntime( 6795): at android.os.Handler.dispatchMessage(Handler.java:99)
12-16 16:04:36.195 E/AndroidRuntime( 6795): at android.os.Looper.loop(Looper.java:130)
12-16 16:04:36.195 E/AndroidRuntime( 6795): at android.app.ActivityThread.main(ActivityThread.java:3687)
12-16 16:04:36.195 E/AndroidRuntime( 6795): at java.lang.reflect.Method.invokeNative(Native Method)
12-16 16:04:36.195 E/AndroidRuntime( 6795): at java.lang.reflect.Method.invoke(Method.java:507)
12-16 16:04:36.195 E/AndroidRuntime( 6795): at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:842)
12-16 16:04:36.195 E/AndroidRuntime( 6795): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:600)
12-16 16:04:36.195 E/AndroidRuntime( 6795): at dalvik.system.NativeStart.main(Native Method)
12-16 16:04:36.195 E/AndroidRuntime( 6795): Caused by: android.os.BadParcelableException: ClassNotFoundException when unmarshalling: com.appco.model.Place
12-16 16:04:36.195 E/AndroidRuntime( 6795): at android.os.Parcel.readParcelable(Parcel.java:1958)
12-16 16:04:36.195 E/AndroidRuntime( 6795): at com.appco.model.Places.<init>(Places.java:115)
12-16 16:04:36.195 E/AndroidRuntime( 6795): at com.appco.model.Places.<init>(Places.java:111)
12-16 16:04:36.195 E/AndroidRuntime( 6795): at com.braesloantrail.model.Places$1.createFromParcel(Places.java:121)
12-16 16:04:36.195 E/AndroidRuntime( 6795): at com.braesloantrail.model.Places$1.createFromParcel(Places.java:1)
12-16 16:04:36.195 E/AndroidRuntime( 6795): at android.os.Parcel.readParcelable(Parcel.java:1981)
12-16 16:04:36.195 E/AndroidRuntime( 6795): at android.os.Parcel.readValue(Parcel.java:1846)
12-16 16:04:36.195 E/AndroidRuntime( 6795): at android.os.Parcel.readMapInternal(Parcel.java:2083)
12-16 16:04:36.195 E/AndroidRuntime( 6795): at android.os.Bundle.unparcel(Bundle.java:208)
12-16 16:04:36.195 E/AndroidRuntime( 6795): at android.os.Bundle.getParcelable(Bundle.java:1100)
12-16 16:04:36.195 E/AndroidRuntime( 6795): at com.braesloantrail.fragments.ListViewFragment.readPlacesBundle(ListViewFragment.java:128)
12-16 16:04:36.195 E/AndroidRuntime( 6795): at com.braesloantrail.fragments.ListViewFragment.onCreate(ListViewFragment.java:57)
12-16 16:04:36.195 E/AndroidRuntime( 6795): at android.support.v4.app.Fragment.performCreate(Fragment.java:1455)
12-16 16:04:36.195 E/AndroidRuntime( 6795): at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:893)
12-16 16:04:36.195 E/AndroidRuntime( 6795): at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:1104)
12-16 16:04:36.195 E/AndroidRuntime( 6795): at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:1086)
12-16 16:04:36.195 E/AndroidRuntime( 6795): at android.support.v4.app.FragmentManagerImpl.dispatchCreate(FragmentManager.java:1872)
12-16 16:04:36.195 E/AndroidRuntime( 6795): at android.support.v4.app.FragmentActivity.onCreate(FragmentActivity.java:215)
12-16 16:04:36.195 E/AndroidRuntime( 6795): at com.braesloantrail.MainActivity.onCreate(MainActivity.java:126)
12-16 16:04:36.195 E/AndroidRuntime( 6795): at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1047)
12-16 16:04:36.195 E/AndroidRuntime( 6795): at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:1615)
12-16 16:04:36.195 E/AndroidRuntime( 6795): ... 11 more
My main activity where I assume the error is coming from is below...
public class MainActivity extends FragmentActivity implements MyLocationListener{
// TAB ID CONSTANTS
private final String TAB_MAP_VIEW = "TAB_MAP_VIEW";
private final String TAB_LIST_VIEW = "TAB_LIST_VIEW";
private final String TAB_SONAR = "TAB_SONAR";
private final String TAB_SETTINGS = "TAB_SETTINGS";
public static final String LATITUDE_BUNDLE_EXTRA = "latitude";
public static final String LONGITUDE_BUNDLE_EXTRA = "longitude";
private FragmentTabHost mTabHost;
// Contains all places model
private Places places;
private Bundle placesBundle;
// Identifier for places bundle object
public static final String PLACES_BUNDLE = "places_bundle";
private String current_fragment_id;
private GPSTracker gpsTracker;
final int RQS_GooglePlayServices = 1;
// Keeps current location
private Location location;
// stores previous sonar name between sonar fragment refreshing
private String previousPlaceName = "";
// flag to store google maps and api support
private boolean hasMapSupport = true;
// flag to see if first run to show showgps alert and log how many times shown
SharedPreferences userPrefs = null;
@Override
protected void onPause() {
super.onPause();
if (current_fragment_id != TAB_SONAR) {
gpsTracker.stopUsingGPS();
}
}
@Override
protected void onResume() {
super.onResume();
location = gpsTracker.getLocation();
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
String modelData = Utils.loadAsset(this, "places.txt");
Assert.assertTrue(modelData != null);
current_fragment_id = TAB_MAP_VIEW;
gpsTracker = new GPSTracker(this, this);
this.setLocation(gpsTracker.getLocation());
try {
this.parseModel(modelData);
this.createPlacesBundle();
this.addTabs();
this.setTabColor(mTabHost);
} catch (JSONException je) {
je.printStackTrace();
}
userPrefs = getSharedPreferences("com.appco.myapp", MODE_PRIVATE);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
int menu_layout_id = 0;
if (current_fragment_id.equals(TAB_MAP_VIEW)) {
menu_layout_id = R.menu.menu_mapview;
}else if (current_fragment_id.equals(TAB_LIST_VIEW)){
menu_layout_id = R.menu.menu_listview;
}else if(current_fragment_id.equals(TAB_SONAR)){
menu_layout_id = R.menu.menu_sonar;
}else if(current_fragment_id.equals(TAB_SETTINGS)){
menu_layout_id = R.menu.menu_settings;
}
MenuInflater inflater = getMenuInflater();
inflater.inflate(menu_layout_id, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.action_about:
startActivity(new Intent(this, AboutActivity.class));
return true;
case R.id.action_info:
if(current_fragment_id == TAB_LIST_VIEW){
final Dialog dialog = new Dialog(this);
dialog.setContentView(R.layout.dialog_listview_info);
dialog.setTitle(getResources().getString(R.string.information));
dialog.show();
}else if(current_fragment_id == TAB_MAP_VIEW){
final Dialog dialog = new Dialog(this);
dialog.setContentView(R.layout.dialog_mapview);
dialog.setTitle(getResources().getString(R.string.information));
dialog.show();
}
return true;
default:
return super.onOptionsItemSelected(item);
}
}
private void addTabs() {
mTabHost = (FragmentTabHost) findViewById(android.R.id.tabhost);
mTabHost.setOnTabChangedListener(new OnTabChangeListener() {
@Override
public void onTabChanged(String tabId) {
setTabColor(mTabHost);
current_fragment_id = tabId;
}
});
mTabHost.setup(this, getSupportFragmentManager(), R.id.realtabcontent);
// check if google play installed
int resultCode = GooglePlayServicesUtil.isGooglePlayServicesAvailable(getApplicationContext());
if (resultCode == ConnectionResult.SUCCESS && isGoogleMapsInstalled()){
mTabHost.addTab(
mTabHost.newTabSpec(TAB_MAP_VIEW).setIndicator(
getResources().getString(R.string.MapView)),
MapViewFragment.class, this.placesBundle);
hasMapSupport = true;
}else{
Toast.makeText(getApplicationContext(),
"You must have Google Play Services and Google Maps installed via the Play Store in order to make full use of this app.",
Toast.LENGTH_LONG).show();
hasMapSupport = false;
}
mTabHost.addTab(
mTabHost.newTabSpec(TAB_LIST_VIEW).setIndicator(
getResources().getString(R.string.ListView)),
ListViewFragment.class, this.placesBundle);
mTabHost.addTab(
mTabHost.newTabSpec(TAB_SONAR).setIndicator(
getResources().getString(R.string.Sonar)),
SonarFragment.class, this.placesBundle);
mTabHost.addTab(
mTabHost.newTabSpec(TAB_SETTINGS).setIndicator(
getResources().getString(R.string.Settings)),
SettingsFragment.class, null);
}
private void parseModel(String modelData) throws JSONException {
JSONObject places = new JSONObject(modelData);
this.places = new Places();
this.places.deserialise(places);
}
private void createPlacesBundle(){
this.placesBundle = new Bundle();
this.placesBundle.putParcelable(PLACES_BUNDLE, this.places);
if (this.location != null) {
this.placesBundle.putDouble(LATITUDE_BUNDLE_EXTRA, location.getLatitude());
this.placesBundle.putDouble(LONGITUDE_BUNDLE_EXTRA, location.getLongitude());
}
}
public boolean isGoogleMapsInstalled()
{
try
{
ApplicationInfo info = getPackageManager().getApplicationInfo("com.google.android.apps.maps", 0 );
return true;
}
catch(PackageManager.NameNotFoundException e)
{
return false;
}
}
public OnClickListener getGoogleMapsListener()
{
return new OnClickListener()
{
@Override
public void onClick(DialogInterface dialog, int which)
{
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse("market://details?id=com.google.android.apps.maps"));
startActivity(intent);
//Finish the activity so they can't circumvent the check
finish();
}
};
}
@Override
public void onLocationChanged(Location location) {
this.location = location;
double latitude = (double) (location.getLatitude());
double longitude = (double) (location.getLongitude());
if (current_fragment_id == TAB_LIST_VIEW) {
ListViewFragment listFrag = (ListViewFragment)getSupportFragmentManager().findFragmentByTag(current_fragment_id);
}
else if(current_fragment_id == TAB_SONAR){
SonarFragment sonarFrag = (SonarFragment)getSupportFragmentManager().findFragmentByTag(current_fragment_id);
}
}
@Override
public void onProviderDisabled(String provider) {
}
@Override
public void onProviderEnabled(String provider) {
}
@Override
public void onStatusChanged(String provider, int status, Bundle extras) {
}
@Override
public void onNoLocationProviderFound() {
}
public boolean getMapSupportStatus(){
return hasMapSupport;
}
}
and here is my object that implements parcelable...
public class Place implements ISerializable, Parcelable, Comparable<Place> {
// MEMBER DEFINITION //
private String othercaptions = "";
private String mainphoto = "";
private String description = "";
private String maincaption = "";
private String name = "";
private String otherphotos = "";
private String longitude = "";
private String latitude = "";
private String uniqueid = "";
private float distance = 0;
public Place(){
}
// ISERIALIZABLE DEFINITION //
public void deserialise(JSONObject source) throws JSONException {
othercaptions = source.getString("otherCaptions");
mainphoto = source.getString("mainPhoto");
description = source.getString("description");
maincaption = source.getString("mainCaption");
name = source.getString("name");
otherphotos = source.getString("otherPhotos");
longitude = source.getString("longitude");
latitude = source.getString("latitude");
uniqueid = source.getString("uniqueId");
}
// PARCELABLE IMPLEMENTATION //
@Override
public int describeContents() {
return 1;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(this.othercaptions);
dest.writeString(this.mainphoto);
dest.writeString(this.description);
dest.writeString(this.maincaption);
dest.writeString(this.name);
dest.writeString(this.otherphotos);
dest.writeString(this.longitude);
dest.writeString(this.latitude);
dest.writeString(this.uniqueid);
}
private Place(Parcel in) {
this.othercaptions = in.readString();
this.mainphoto = in.readString();
this.description = in.readString();
this.maincaption = in.readString();
this.name = in.readString();
this.otherphotos = in.readString();
this.longitude = in.readString();
this.latitude = in.readString();
this.uniqueid = in.readString();
}
Discovered the problem - it was in my parcelable class where I stored my data. I was passing in null for a ClassLoader (which I believe selects the default class loader) but this can be problematic -
Whereas it should be the following -