Android Parcel の読出しに関連するエラー java.lang.RuntimeException: Parcel android.os.Parcel: Unmarshalling unknown type…

Android Parcel、Parcelableに関連するエラーにかなりはまったので書いておきます。


FATAL EXCEPTION: main
java.lang.RuntimeException: Unable to start activity ComponentInfo{hogehoge.HogeActivity}: java.lang.RuntimeException: Parcel android.os.Parcel@24940f: Unmarshalling unknown type code 7602259 at offset 2164
	at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3663)
	at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3820)
	at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:103)
	at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135)
	at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95)
	at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2218)
	at android.os.Handler.dispatchMessage(Handler.java:106)
	at android.os.Looper.loopOnce(Looper.java:201)
	at android.os.Looper.loop(Looper.java:288)
	at android.app.ActivityThread.main(ActivityThread.java:8001)
	at java.lang.reflect.Method.invoke(Native Method)
	at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:548)
	at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1039)
Caused by: java.lang.RuntimeException: Parcel android.os.Parcel@24940f: Unmarshalling unknown type code 7602259 at offset 2164
	at android.os.Parcel.readValue(Parcel.java:3306)
	at android.os.Parcel.readArrayMapInternal(Parcel.java:3624)
	at android.os.BaseBundle.initializeFromParcelLocked(BaseBundle.java:292)
	at android.os.BaseBundle.unparcel(BaseBundle.java:236)
	at android.os.BaseBundle.getBoolean(BaseBundle.java:898)

上記のようなエラーが出てしまいます。

細かい説明は割愛しますが、例えばAndroidの画面をまたいで複雑なオブジェクトをやり取りしたい時に、このParcel、Parcelableという仕組みを使います。

Parcelableというインターフェースをimplementsすると、そのクラスがParcelとして扱えるようになります。

例えば次のように作るのですが

public class Hoge implements Parcelable {

    private final int id;
    private final String name;

    public Hoge(int id, String name) {
        this.id = id;
        this.name = name;
    }

    protected Hoge(Parcel in) { 
        id = in.readInt(); // ★この順番と
        name = in.readString();
    }

    @Override
    public void writeToParcel(@NonNull Parcel dest, int flags) {
        dest.writeInt(id); // ★ここの順番は一緒でなくてはならない
        dest.writeString(name);
    }

    public static final Creator<Hoge> CREATOR = new Creator<Hoge>() {
        @Override
        public Hoge createFromParcel(Parcel in) {
            if(in.readInt() == 1) {
                return new Hoge2(in);
            }
            return new Hoge(in);
        }

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

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

}


Parcel inが引数のコンストラクタがParcelを読みだしてコンストラクトします。
writeToParcel()というメソッドで、Parcelに書き出しをします。

あんましここのイメージわきにくかったんですが、BlackHatのYoutubeさんにちょうどよい画像を見つけました。

Android Parcelイメージ
Android Parcelのイメージ

下記からの引用になります。ありがとうございます。
https://www.youtube.com/watch?v=qIzMKfOmIAA&list=LL&index=1

こういう構造だから、読みだしと書き込みが一緒じゃないとダメなんですね。
絵にするとめちゃわかりやすいです!( ˊᵕˋ )

サンプルコードではこの★がついたところが読みだしと書き出しにあたります。

冒頭のエラー はこれが順番通りじゃないと読出しに失敗して起こるエラーだそうです。

何度も目をこすりながら確認しても読出しと書き出しは一緒。
ChatGPTに聞いてもわかりませんでした。

結局、理由はこの部分↓にありました。

    public static final Creator<Hoge> CREATOR = new Creator<Hoge>() {
        @Override
        public Hoge createFromParcel(Parcel in) {
            if(in.readInt() == 1) { // 💀 問題はここだった
                return new Hoge2(in);
            }
            return new Hoge(in);
        }

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

in.readInt() == 1 で、in の1つ目が使われている、ということみたいです。

この部分をほかに移すことでなんとかなりました。

Creatorの中も注意!ということです。

GSON のError java.lang.IllegalArgumentException: class declares multiple JSON fields named ..

val gson = Gson().toJson(hoge)

でJson化をしたときに、下記のようなエラーが出ることがあります。

java.lang.IllegalArgumentException: class hoge.Hoge declares multiple JSON fields named start_time
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.getBoundFields(ReflectiveTypeAdapterFactory.java:172)
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.create(ReflectiveTypeAdapterFactory.java:102)
at com.google.gson.Gson.getAdapter(Gson.java:458)
at com.google.gson.internal.bind.TypeAdapterRuntimeTypeWrapper.write(TypeAdapterRuntimeTypeWrapper.java:56)
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$1.write(ReflectiveTypeAdapterFactory.java:127)
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.write(ReflectiveTypeAdapterFactory.java:245)
at com.google.gson.internal.bind.TypeAdapterRuntimeTypeWrapper.write(TypeAdapterRuntimeTypeWrapper.java:69)
at com.google.gson.internal.bind.CollectionTypeAdapterFactory$Adapter.write(CollectionTypeAdapterFactory.java:97)
at com.google.gson.internal.bind.CollectionTypeAdapterFactory$Adapter.write(CollectionTypeAdapterFactory.java:61)
at com.google.gson.internal.bind.TypeAdapterRuntimeTypeWrapper.write(TypeAdapterRuntimeTypeWrapper.java:69)
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$1.write(ReflectiveTypeAdapterFactory.java:127)
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.write(ReflectiveTypeAdapterFactory.java:245)
at com.google.gson.Gson.toJson(Gson.java:704)
at com.google.gson.Gson.toJson(Gson.java:683)
at com.google.gson.Gson.toJson(Gson.java:638)
at com.google.gson.Gson.toJson(Gson.java:618)

え??なんで?なんでなん?

てなりますが、原因は、例えばこれはHogeというクラスのオブジェクトをJSON化していますが、そのHogeのスーパークラスSuperHogeがあったとして、同じプロパティを宣言していると、これが出ます。

例は次のような感じです。

public class SuperHoge {
    protected String name;
}

class Hoge extends SuperHoge{
    private String name;
}

No overload matches this call. Overload 1 of 3, ‘(object: Hoge, method: never): SpyInstance‘, gave the following error.

Jestのバージョン 29.5.3

class Hoge{

    private validate(start_unix: number, end_unix: number, ids: (number | string)[]): boolean {

	    // 色々バリデーションの処理
	    return true

    }

}

Hogeクラスにvalidate()というメソッドがありますが、テストだとこのバリデーションを使いたくないという場合、validateのモックを作る場合があると思います。

テストコードは次の通り

test('Hoge without validate', () => {
 const hoge = new Hoge()
 const spy = jest.spyOn(hoge, 'validate')
  .mockImplementation(() => true);
}

しかし、これだと次のようなエラーが出てしまいます。

No overload matches this call.       Overload 1 of 3, '(object: Hoge, method: never): SpyInstance', gave the following error.

なんか引数とかの指定が悪いのかな?とか思ってしまいますが、これはspyOnの対象のメソッドがprivateだとこのエラーになるみたいです。

「じゃー、対象のメソッドを public にしよう!」

ではなくて、めちゃ簡単にこれは解決できます。

jest.spyOn()の1個目の引数で呼び出すインスタンスを、as any で呼べばよいです。

 const spy = jest.spyOn(hoge as any, 'validate')
  .mockImplementation(() => true);

下記で教えて頂きました。┌o ペコッ ありがたい!

https://stackoverflow.com/questions/62171602/testing-private-method-using-spyon-and-jest


社員旅行で行った台湾で見かけた派手な百合

jestでgetActivePinia()” was called but there was no active Pinia.

Vue(バージョン2.7)+Jest(バージョン29)+Pinia(バージョン2.1)でJestでテストしていると、次のようなエラーになってしまいました。

import { mount } from '@vue/test-utils'
import DestinationModal from "../../../components/location/poi/organisms/DestinationModal.vue";


describe('DestinationModal', () => {
test('is a Vue instance', () => {
const wrapper = mount(DestinationModal)
expect(wrapper.isVueInstance()).toBeTruthy()
})
})

実行すると

Error: [🍍]: "getActivePinia()" was called but there was no active Pinia. Are you trying to use a store before calling "app.use(pinia)"?

Piniaがないのでエラーになってしまうので

import { mount } from '@vue/test-utils'
import DestinationModal from "../../../components/location/poi/organisms/DestinationModal.vue";
import {createPinia, setActivePinia} from "pinia" //これを追加


describe('DestinationModal', () => {
setActivePinia(createPinia())
//これを追加
test('is a Vue instance', () => {
const wrapper = mount(DestinationModal)
expect(wrapper.isVueInstance()).toBeTruthy()
})
})

パイナップルがかわいいですね!🍍

Vue.js メモリに大きなデータがあるのを解決する

Vueのバージョンは 2.7.15 です。

Vueで作っているところで、明らかにページに描画される部品が多くなると描画が遅くなると感じられるページがありました。

部品が多くなると、古いPCではページがフリーズしてしまうことも(>_<)

Chromeのデベロッパーツールで調査してみました。

Chrome デベロッパーツール でまずNetworkを確認し、サーバー側などで時間がかかっているわけではないのを確認します。(ここは省略)

Chromeの デベロッパーツール で メモリがどれぐらい使われているか確認します。

下記の画面の、一番左上の二重丸をクリックし、Heap Snapshotというのを取ります。

ででん!

遅いVueをChrome開発者ツールを使ってメモリを調査したところ
ん?配列がいっぱい…?

array というのが105万個もあります。( ゚Д゚) そして、これが約120Mもメモリを使っています。

なんだ…?こいつらッ

105万個という数にヤバさを感じますね!!

これ、クリックすると中身が見れるんですが、見るとあんまり関係ないのもあるので辛抱強く見ていくと、なんか見覚えのあるプロパティ名がずらっと出てたりもします。

で、いくつかのパターンでメモリを取得してみると、このarray というのが増えたり減ったりしていて(ある時は半分の50万個ぐらい)ページの中で繰り返し描画されているコンポーネントの関係があるのでは?と思われました。

で、Vueの$dataってリアクティブなので、こうやってメモリ上に確保されているのかなと思い当たり、繰り返しのあるComponentの$dataに大き目のオブジェクトがあったりしたので、それを使わないようにしてみたら、劇的に改善しました。

遅いVueをChrome開発者ツールを使ってメモリを改善したところ
arrayは175個に

arrayの数は 105万個 →175個になり、メモリも約135Kになりました!桁違いすぎる(笑)

array のすぐ上の、compiled codeというのも劇的に少なくなっています。

結果、ページ速度も改善し、動きがよくなりました!

Vueでメモリをいっぱい使っている、で検索すると、メモリリークの話ばっかり出てくるので、ここに書いておきます。

まれにこのChrome デベロッパーツール でメモリを測定する機能が壊れているというかなんというか、おかしくなる時があります。
そういう時は、Chromeを再起動してみましょう。

そんなにVue.jsに詳しいわけではないので、色々間違っていたらすみません。