11

私のプロジェクトには、モデルに関する基本的な情報を保持するモデルがあります。たとえば、モデルが車であるとします。次に、車にはさまざまな種類があり、これらにはさまざまなデータが割り当てられています。すべてのモデルはパーセル可能である必要があります。

異なる車の間の違いは非常に小さく、それはほんのいくつかのデータフィールドかもしれません。したがって、これは、さまざまな車のプレゼンター(データを保持するクラス)を作成することで解決されます。プレゼンターは、保持する必要のある追加データを知ることができます。プレゼンター自体はパーセル可能ではないため、すべてのデータのバンドルがあり、Carクラスがパーセル可能に追加します。プレゼンターを小包にしたくありません。

したがって、Carはプレゼンターからバンドルを取得し、その区画に入れます。

  public void writeToParcel(Parcel parcel, int flags) {
    parcel.writeBundle(getPresenter().getBundle());
  } 

そしてそれはそれからそれを開梱します:

  public Car(Parcel parcel) {    
    getPresenter().setBundle(parcel.readBundle());
  }

これは、パーセル可能なオブジェクトがプレゼンターによってバンドルに追加されるまで正常に機能します。次に、このエラーが発生します。

11-16 15:06:37.255: E/AndroidRuntime(15193): FATAL EXCEPTION: main
11-16 15:06:37.255: E/AndroidRuntime(15193): java.lang.RuntimeException: Unable to start activity ComponentInfo{com.example/com.example.activity}: android.os.BadParcelableException: ClassNotFoundException when unmarshalling: com.example.model.engine
11-16 15:06:37.255: E/AndroidRuntime(15193):         at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2185)
11-16 15:06:37.255: E/AndroidRuntime(15193):         at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2210)
11-16 15:06:37.255: E/AndroidRuntime(15193):         at android.app.ActivityThread.access$600(ActivityThread.java:142)
11-16 15:06:37.255: E/AndroidRuntime(15193):         at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1208)
11-16 15:06:37.255: E/AndroidRuntime(15193):         at android.os.Handler.dispatchMessage(Handler.java:99)
11-16 15:06:37.255: E/AndroidRuntime(15193):         at android.os.Looper.loop(Looper.java:137)
11-16 15:06:37.255: E/AndroidRuntime(15193):         at android.app.ActivityThread.main(ActivityThread.java:4931)
11-16 15:06:37.255: E/AndroidRuntime(15193):         at java.lang.reflect.Method.invokeNative(Native Method)
11-16 15:06:37.255: E/AndroidRuntime(15193):         at java.lang.reflect.Method.invoke(Method.java:511)
11-16 15:06:37.255: E/AndroidRuntime(15193):         at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:791)
11-16 15:06:37.255: E/AndroidRuntime(15193):         at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:558)
11-16 15:06:37.255: E/AndroidRuntime(15193):         at dalvik.system.NativeStart.main(Native Method)
11-16 15:06:37.255: E/AndroidRuntime(15193): Caused by: android.os.BadParcelableException: ClassNotFoundException when unmarshalling: com.example.model.engine
11-16 15:06:37.255: E/AndroidRuntime(15193):         at android.os.Parcel.readParcelable(Parcel.java:2077)
11-16 15:06:37.255: E/AndroidRuntime(15193):         at android.os.Parcel.readValue(Parcel.java:1965)
11-16 15:06:37.255: E/AndroidRuntime(15193):         at android.os.Parcel.readMapInternal(Parcel.java:2226)
11-16 15:06:37.255: E/AndroidRuntime(15193):         at android.os.Bundle.unparcel(Bundle.java:223)
11-16 15:06:37.255: E/AndroidRuntime(15193):         at android.os.Bundle.getString(Bundle.java:1055)
11-16 15:06:37.255: E/AndroidRuntime(15193):         at com.example.cars.CarPresenter.getExtraString(CarPresenter.java:34)
11-16 15:06:37.255: E/AndroidRuntime(15193):         ... 11 more

したがって、どういうわけか、バンドルから何かを読み取ることができません。

これは、readBundle呼び出しを次のように変更することで解決できます。

  public Car(Parcel parcel) {    
    getPresenter().setBundle(parcel.readBundle(engine.class.getClassLoader()));
  }

しかし、これは、バンドルに1種類の小包しか入れられなかったことを意味するのではないでしょうか。たとえば、別のプレゼンターが別のパーセル可能なオブジェクトをバンドルに追加したい場合はどうなりますか?

誰かがこれに光を当てることができますか?

4

4 に答える 4

17

ClassLoaderは、Javaの最も理解されていない機能の1つです。それらは「名前空間」を提供し、実際、クラスが現在のClassLoaderで見つからない場合、「親」ClassLoaderをチェックできるため、これらの名前空間は「ネスト」できます。

Androidアプリケーションの場合、2つのClassLoaderがあります。APKからクラスをロードする方法を知っているものと、Androidフレームワークからクラスをロードする方法を知っているものがあります。前者は後者を「親」クラスローダーとして設定しているため、一般的には気付かないでしょう。ただし、Bundleはフレームワーククラスであり、フレームワーククラスローダーによってロードされるため、APKでクラスを検索するために使用されているClassLoaderについて通知する必要があります。Bundleがデフォルトで使用するクラスローダーはフレームワークClassLoaderであり、APKクラスを含む名前空間よりも「低い」名前空間です。

したがって、使用するClassLoaderをバンドルに指示することにより、APKClassLoaderでクラスをチェックする必要があることをバンドルに指示します。BundleクラスをロードしたClassLoaderであるため、デフォルトでフレームワークAPKをチェックします。したがって、でクラスを検索する方法を知っている唯一の「名前空間」です。

于 2012-11-27T00:19:46.917 に答える
15

あなたはコメントに書いた:

しかし、私の質問は、なぜこの場合にクラスローダーを指定する必要があるのか​​ということです。

AndroidフレームワークエンジニアのDianneHackbornは、次のように書いています。

バンドルがパーセルから読み取るときは、データを抽出するだけです。後でバンドルからデータを取得するまで、実際にはそのデータから分割されません。

バンドルのソースコードsetClassLoader()では、メソッドがフィールドを設定していることがわかりmClassLoaderます。

public void setClassLoader(ClassLoader loader) {
     mClassLoader = loader;
 }

setClassLoader()またはコンストラクターを使用しない場合Bundle(ClassLoader loader)mClassLoaderフィールドはデフォルトに設定されますClassLoader

public Bundle(int capacity) {
     //...
     mClassLoader = getClass().getClassLoader();
 }

mClassLoaderunparcel()次に、メソッド内のパーセルされたデータをアンマーシャリングするために使用されます。

 mParcelledData.readMapInternal(mMap, N, mClassLoader);
 mParcelledData.recycle();
 mParcelledData = null;

ネット、設定しない場合、パーセルされたデータをアンマーシャリングするときClassLoaderにデフォルトでシステムになり、カスタムオブジェクトデータをアンマーシャリングClassLoaderするときにが取得されます。ClassNotFoundExceptionParcelable

于 2012-11-23T00:38:21.610 に答える
5

あなたが書いた:

これは、readBundle呼び出しを次のように変更することで解決できます。

public Car(Parcel parcel) { getPresenter().setBundle(parcel.readBundle(engine.class.getClassLoader())); }

しかし、これは、バンドルに1種類の小包しか入れられなかったことを意味するのではないでしょうか。たとえば、別のプレゼンターが別のパーセル可能なオブジェクトをバンドルに追加したい場合はどうなりますか?

実は違う。classLoaderをに設定するとengine.class.getClassLoader()、実際には、APKからすべてのクラスをロードする方法を知っているクラスローダーが提供されます。したがって、同じクラスローダーを使用して、Parcelableを実装するアプリケーション内の他のすべてのカスタムクラスのパーセルを解除できます。

表示されている問題は、クラスローダーを指定しない場合、(デフォルトの)システムクラスローダーがのパーセル解除に使用されることですBundle。システムクラスローダーは、Androidシステムに認識されているクラスをロードする方法のみを認識しており、アプリケーション固有のクラスをロードする方法を認識していません。

明確にするために、(一般的に)「クラスごとのクラスローダー」はなく、システムクラスローダーがあり、次に「アプリケーションごとのクラスローダー」があります。これがあなたの質問に答えることを願っています。

于 2012-11-23T22:57:11.530 に答える
-4

これは、Parcelableを実装する私のモデルです。

public class ProgramModel implements Parcelable
{
    private int id = -1;
    private String title = "";
    private String time = "";
    private String day = "";
    private String organization = "";
    private String participants = "";
    private String description = "";
    private String descriptionFull = "";
    private String location = "";
    private String locationCaption = "";
    private int locationCode = 0;
    private String locationMap = "";
    private String imageUrl = "";
    private String color = "";
    private int colorId = -1;
    private int columnId = -1;
    private String startTime = "";
    private String endTime = "";
    private Date convertedTimeStart = null;
    private Date convertedTimeEnd = null;
    private String theme = "";
    private ArrayList<TagModel> tags = new ArrayList<TagModel>();
    private ArrayList<Integer> tagsId = new ArrayList<Integer>();

    public ProgramModel()
    {
    }

    private ProgramModel(Parcel in)
    {
        id = in.readInt();
        title = in.readString();
        time = in.readString();
        day = in.readString();
        organization = in.readString();
        participants = in.readString();
        description = in.readString();
        descriptionFull = in.readString();
        location = in.readString();
        locationCaption = in.readString();
        locationCode = in.readInt();
        locationMap = in.readString();
        imageUrl = in.readString();
        color = in.readString();
        colorId = in.readInt();
        columnId = in.readInt();
        startTime = in.readString();
        endTime = in.readString();
        convertedTimeStart = new Date(in.readLong());
        convertedTimeEnd = new Date(in.readLong());
        theme = in.readString();
        in.readTypedList(tags, TagModel.CREATOR);
        in.readList(tagsId, Integer.class.getClassLoader());
    }

    public int getId()
    {
        return id;
    }

    public void setId(int id)
    {
        this.id = id;
    }

    // Other getters and setters

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

    // this is used to regenerate your object
    public static final Parcelable.Creator<ProgramModel> CREATOR = new Parcelable.Creator<ProgramModel>()
    {
        public ProgramModel createFromParcel(Parcel in)
        {
            return new ProgramModel(in);
        }

        public ProgramModel[] newArray(int size)
        {
            return new ProgramModel[size];
        }
    };

    @Override
    public void writeToParcel(Parcel out, int flags)
    {
        out.writeInt(id);
        out.writeString(title);
        out.writeString(time);
        out.writeString(day);
        out.writeString(organization);
        out.writeString(participants);
        out.writeString(description);
        out.writeString(descriptionFull);
        out.writeString(location);
        out.writeString(locationCaption);
        out.writeInt(locationCode);
        out.writeString(locationMap);
        out.writeString(imageUrl);
        out.writeString(color);
        out.writeInt(colorId);
        out.writeInt(columnId);
        out.writeString(startTime);
        out.writeString(endTime);
        out.writeLong(convertedTimeStart.getTime());
        out.writeLong(convertedTimeEnd.getTime());
        out.writeString(theme);
        out.writeTypedList(tags);
        out.writeList(tagsId);
    }
}

アクティビティのインテントにデータを書き込みます。

Intent intent = new Intent(FirstActivity.this, SecondActivity.class);
intent.putExtra("ProgramModel", programDetailsModel);
startActivity(intent);

アクティビティのバンドルからデータを読み取ります:

@Override
public void onCreate(Bundle savedInstanceState)
{
    super.onCreate(savedInstanceState);
    savedInstanceState = getIntent().getExtras();
    if (savedInstanceState != null)
    {
        fromBundleModel = savedInstanceState.getParcelable("ProgramModel");
    }
}
于 2012-11-22T18:45:37.757 に答える