سؤال

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 -

private Places(Parcel in) {
        int length = in.readInt();
        this.places = new Place[length];
        for(int i=0; i<length; i++){
            this.places[i] = in.readParcelable(null);
        }
    }

Whereas it should be the following -

private Places(Parcel in) {
        int length = in.readInt();
        this.places = new Place[length];
        for(int i=0; i<length; i++){
            this.places[i] = in.readParcelable(this.getClass().getClassLoader());
        }
    }
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top