java.lang.RuntimeException: Android操作系统的Parcel类解包时遇到未知类型代码

15

我似乎在我的应用程序中遇到了一个奇怪的错误(请查看GitHub),这个错误发生在我将实现Parcelable接口的对象传递给不同的活动时。

我已经检查了Stack Overflow上的其他问题和答案,但我无法找到解决方案。例如,我尝试了这里的答案 - 这是参考:

-keepclassmembers class * implements android.os.Parcelable {
    static ** CREATOR;
}

我还确保在writeToParcel中的方法调用是有顺序的。Stack Overflow上关于这个问题的大部分其他问题都没有答案。此外,我之所以要问一个新的问题,是因为我认为我的问题是由于我在应用程序中如何使用接口造成的(我稍后会详细说明这一点)。Stack Overflow上的其他问题并不适合我的特定情况。接下来,我提供了通过GitHub链接到代码的链接,以便您可以在需要时探索更多代码。


当我点击按钮启动一个实现了Parcelable的对象的新活动时,会发生崩溃:launch a new activitythere is a crash:

Process: com.satsuware.flashcards, PID: 4664
java.lang.RuntimeException: Unable to start activity ComponentInfo{com.satsuware.flashcards/com.satsumasoftware.flashcards.ui.FlashCardActivity}: java.lang.RuntimeException: Parcel android.os.Parcel@d2219e4: Unmarshalling unknown type code 6815860 at offset 200
    at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2416)
    ...
 Caused by: java.lang.RuntimeException: Parcel android.os.Parcel@d2219e4: Unmarshalling unknown type code 6815860 at offset 200
at android.os.Parcel.readValue(Parcel.java:2319)
at android.os.Parcel.readListInternal(Parcel.java:2633)
at android.os.Parcel.readArrayList(Parcel.java:1914)
at android.os.Parcel.readValue(Parcel.java:2264)
at android.os.Parcel.readArrayMapInternal(Parcel.java:2592)
at android.os.BaseBundle.unparcel(BaseBundle.java:221)
at android.os.Bundle.getParcelable(Bundle.java:786)
at android.content.Intent.getParcelableExtra(Intent.java:5377)
at com.satsumasoftware.flashcards.ui.FlashCardActivity.onCreate(FlashCardActivity.java:71)
at android.app.Activity.performCreate(Activity.java:6237)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1107)
...

我称这个活动为如此(同样也请看 GitHub):

Intent intent = new Intent(TopicDetailActivity.this, FlashCardActivity.class);
intent.putExtra(FlashCardActivity.EXTRA_TOPIC, mTopic);
intent.putExtra(FlashCardActivity.EXTRA_NUM_CARDS, mSelectedNumCards);
intent.putExtra(FlashCardActivity.EXTRA_CARD_LIST, mFilteredCards);
startActivity(intent);

需要考虑的主要部分是我传递 mTopic 参数。这是我创建的一个Topic 接口

然而,Topic 接口扩展了 Parcelable 接口,因此实现 Topic 接口的对象也包括实现 Parcelable 接口所需的构造函数、CREATOR 字段和方法。

您可以通过 GitHub 链接查看相关类,但我将在下面提供这些类的相关部分。这是 Topic 接口:

public interface Topic extends Parcelable {

    int getId();

    String getIdentifier();

    String getName();

    Course getCourse();


    ArrayList<FlashCard> getFlashCards(Context context);


    class FlashCardsRetriever {

        public static ArrayList<FlashCard> filterStandardCards(ArrayList<FlashCard> flashCards, @StandardFlashCard.ContentType int contentType) {
            ArrayList<FlashCard> filteredCards = new ArrayList<>();
            for (FlashCard flashCard : flashCards) {
                boolean isPaper2 = ((StandardFlashCard) flashCard).isPaper2();
                boolean condition;
                switch (contentType) {
                    case StandardFlashCard.PAPER_1:
                        condition = !isPaper2;
                        break;
                    case StandardFlashCard.PAPER_2:
                        condition = isPaper2;
                        break;
                    case StandardFlashCard.ALL:
                        condition = true;
                        break;
                    default:
                        throw new IllegalArgumentException("content type '" + contentType + "' is invalid");
                }
                if (condition) filteredCards.add(flashCard);
            }
            return filteredCards;
        }

        ...
    }

}

实现 Topic 接口的类(对象):

public class CourseTopic implements Topic {

    ...

    public CourseTopic(int id, String identifier, String name, Course course) {
        ...
    }

    @Override
    public int getId() {
        return mId;
    }

    @Override
    public String getIdentifier() {
        return mIdentifier;
    }

    ...


    protected CourseTopic(Parcel in) {
        mId = in.readInt();
        mIdentifier = in.readString();
        mName = in.readString();
        mCourse = in.readParcelable(Course.class.getClassLoader());
    }

    public static final Parcelable.Creator<CourseTopic> CREATOR = new Parcelable.Creator<CourseTopic>() {
        @Override
        public CourseTopic createFromParcel(Parcel in) {
            return new CourseTopic(in);
        }

        @Override
        public CourseTopic[] newArray(int size) {
            return new CourseTopic[size];
        }
    };

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeInt(mId);
        dest.writeString(mIdentifier);
        dest.writeString(mName);
        dest.writeParcelable(mCourse, flags);
    }

}

在上面的代码的最后一行中,您可以看到我传递了mCourse,它是我创建的一个Course对象。这是它:

public class Course implements Parcelable {

    ...

    public Course(String subject, String examBoard, @FlashCard.CourseType String courseType,
              String revisionGuide) {
        ...
    }


    public String getSubjectIdentifier() {
        return mSubjectIdentifier;
    }

    public String getExamBoardIdentifier() {
        return mBoardIdentifier;
    }

    public ArrayList<Topic> getTopics(Context context) {
        ArrayList<Topic> topics = new ArrayList<>();
        String filename = mSubjectIdentifier + "_" + mBoardIdentifier + "_topics.csv";
        CsvParser parser = CsvUtils.getMyParser();
        try {
            List<String[]> allRows = parser.parseAll(context.getAssets().open(filename));
            for (String[] line : allRows) {
                int id = Integer.parseInt(line[0]);
                topics.add(new CourseTopic(id, line[1], line[2], this));
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return topics;
    }

    ...


    protected Course(Parcel in) {
        mSubjectIdentifier = in.readString();
        mBoardIdentifier = in.readString();
        mCourseType = in.readString();
        mRevisionGuide = in.readString();
    }

    public static final Creator<Course> CREATOR = new Creator<Course>() {
        @Override
        public Course createFromParcel(Parcel in) {
            return new Course(in);
        }

        @Override
        public Course[] newArray(int size) {
            return new Course[size];
        }
    };

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(mSubjectIdentifier);
        dest.writeString(mBoardIdentifier);
        dest.writeString(mCourseType);
        dest.writeString(mRevisionGuide);
    }

我怀疑这里的某些内容可能导致了问题,这也是我场景与其他问题不同的原因。


说实话,我并不确定什么可能会导致错误,所以希望能在答案中得到解释和指导。


编辑

David Wasser 的建议下,我已经更新了代码的部分内容,如下所示:

FlashCardActivity.java - onCreate(...)

Bundle extras = getIntent().getExtras();
extras.setClassLoader(Topic.class.getClassLoader());
mTopic = extras.getParcelable(EXTRA_TOPIC);

Course.java - writeToParcel(...)

dest.writeString(mSubjectIdentifier);
dest.writeString(mBoardIdentifier);
dest.writeString(mCourseType);
dest.writeInt(mRevisionGuide == null ? 0 : 1);
if (mRevisionGuide != null) dest.writeString(mRevisionGuide);

Course.java - Course(Parcel in)

mSubjectIdentifier = in.readString();
mBoardIdentifier = in.readString();
mCourseType = in.readString();
if (in.readInt() != 0) mRevisionGuide = in.readString();

我已经使用Log.d(...)添加了日志消息,以查看在传递到writeToParcel(...)时是否有任何变量为空,并使用了David Wasser的方法来正确处理此问题。

但是,我仍然收到相同的错误消息。


发布与取消发布与 Intent 中放置的实际类相关的打包和解包代码。 - David Wasser
@DavidWasser 我已经更新了我的答案,展示了放置在“Intent”中的类的代码(可以通过提供的GitHub链接完整查看)。 - Farbod Salamat-Zadeh
2个回答

34

你的问题出现在LanguagesFlashCard中。这里是你的打包/解包方法:

protected LanguagesFlashCard(Parcel in) {
    mId = in.readInt();
    mEnglish = in.readString();
    mAnswerPrefix = in.readString();
    mAnswer = in.readString();
    mTier = in.readInt();
    mTopic = in.readParcelable(Topic.class.getClassLoader());
}

你可以看到,它们不匹配。你写入 Parcel 的第二个项目是一个 int,但你从 Parcel 读取的第二个项目是一个 String

@Override
public void writeToParcel(Parcel dest, int flags) {
    dest.writeInt(mId);
    dest.writeInt(mTier);
    dest.writeString(mEnglish);
    dest.writeString(mAnswerPrefix);
    dest.writeString(mAnswer);
    dest.writeParcelable(mTopic, flags);
}

1
如果所有值都是相同类型的,它们的顺序可以改变吗?我的意思是:mEnglish = in.readString(); mAnswerPrefix = in.readString(); mAnswer = in.readString();可以被读作dest.writeString(mAnswer); dest.writeString(mEnglish); dest.writeString(mAnswerPrefix); - Usman Rana
4
不。无论值的类型是什么,您必须按照完全相同的顺序读取和写入它们。您正在将其序列化和反序列化到/从字节数组中。Parcel不是HashMap - David Wasser

0

使用Kotlin编写的子数据类ImagesModel,同时实现了可序列化Parcelable

data class MyPostModel(
    @SerializedName("post_id") val post_id: String? = "",
    @SerializedName("images") val images: ArrayList<ImagesModel>? = null
): Parcelable {
    constructor(parcel: Parcel) : this(
        parcel.writeString(post_id)
        parcel.createTypedArrayList(ImagesModel.CREATOR)
    )

    override fun writeToParcel(parcel: Parcel, flags: Int) {
        parcel.writeString(post_id)
        parcel.writeTypedList(images)
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接