アセットバンドルの基礎

確認済のバージョン: 5.3

-

難易度: Advanced

この記事は Unity 5 におけるアセット、リソース、およびリソース管理について解説したシリーズの第 4 章です。

この章では AssetBundle について解説していきます。AssetBundle の構造の基礎となる仕組みや、AssetBundle と相互作用する core API についてご紹介します。とくに、AssetBundle 自体のロード(読み込み)とアンロード、そして AssetBundle からの特定の Asset と Object のロード・アンロードについて説明しています。

AssetBundle の使用パターンや推奨される使用方法についてのさらなる解説は、このつぎの章をご参照ください。

3.1. 概要

AssetBundle システムは、Unity がインデックスできるアーカイバル形式にひとつ、または複数のファイルを保存できるようにするシステムです。このシステムの目的は、Unity のシリアライズの仕組みと互換性のあるデータ送信方式を提供することです。AssetBundle は、インストール後に非コードのコンテンツを配信および更新するために使われる、Unity の主要ツールです。これによって、アセットのサイズを縮小したり、ランタイムのメモリー負荷を最小化したり、エンドユーザーのデバイス向けに最適化されたコンテンツを選択的に読み込ませることが可能になります。

モバイルデバイス向けに Unity プロジェクトをビルドするには、AssetBundle の仕組みを理解することが必要不可欠です。

3.2. AssetBundle に含まれる内容

AssetBundle は、ヘッダーとデータセグメントのふたつの部分から構成されます。

ヘッダーは、AssetBundle がビルドされる際に Unity によって生成されます。ヘッダーには AssetBundle の識別子や、圧縮・非圧縮の識別情報、マニフェストなど、AssetBundle に関する情報が含まれています。

マニフェストは、特定の Object の名前を使用したルックアップテーブルです。各エントリーが、特定の Object の AssetBundle のデータセグメント内における場所を表す、バイト・インデックスを提供します。ほとんどのプラットフォームでは、このルックアップテーブルが STL std::multimap として実装されます。プラットフォームによって STL の実装に使用されるアルゴリズムはことなりますが、ほとんどの場合は、各種の安定した検索ツリーです。Windows や OSX から派生したプラットフォーム(iOS を含む)では、赤黒ツリーを採用しています。そのため、マニフェストの構築に必要な時間の長さは、AssetBundle 内の Asset の数が増加するに従って、線形増加を超える率で伸びていきます。

データセグメントには、AssetBundle 内での Asset のシリアライズによって生成された生データが含まれています。データセグメントが圧縮されている場合は、シリアライズされたバイトの総体的なシーケンスに LZMA アルゴリズムが適用されているということです。つまり、すべてのアセットがシリアライズ済みで、バイト配列全体が圧縮されているということです。

Unity 5.3 になる前は、AssetBundle 内で Object を個々に圧縮することはできませんでした。従って 5.3 より前のバージョンでは、圧縮された AssetBundle からひとつ、または複数の Object を読み込む命令を受け取ると、Unity は AssetBundle 全体を解凍する必要がありました。そのため、通常は同じ AssetBundle についての後続の読み込みリクエストに対する読み込み性能を向上させるように、Unity は解凍された AssetBundle の複製をキャッシュしていました。

Unity 5.3 では、LZ 4 圧縮が利用可能になりました。LZ 4 圧縮を使用してビルドされた AssetBundle は、そのなかにある個々の Object を圧縮するので、圧縮された AssetBundle をディスクに保存することが可能になります。また、AssetBundle 全体を解凍せずに 個々の Object の解凍もできるようになりました。

3.3. AssetBundle Manager

Unity は、Bitbucket 上に AssetBundle Manager のリファレンス実装を開発・維持しています。このマネージャーには本章で解説されているコンセプトや API が多く用いられており、プロジェクトの開発において、AssetBundle をリソース管理のワークフローに統合する必要がある場合に有益なツールとなっています。

なかでも注目に値する機能のひとつが「シミュレーションモード」です。これが Unity エディターでアクティブになっていると、AssetBundle 内にタグ付けされた Asset へのリクエストが、プロジェクトの /Assets/ フォルダー内にあるもともとの Asset に透過的にリダイレクトされます。これにより、開発者は AssetBundle を再ビルドせずにプロジェクトの作業を進められるようになっています。

AssetBundle Manager はオープンソースのプロジェクトで、こちらで入手することができます。

3.4. AssetBundle の読み込み

Unity 5 では、AssetBundle の読み込みは 4 つの特殊な API によって行なわれます。これらの API の動作は、つぎのふたつの条件によってことなります。

  1. AssetBundle が LZMA 圧縮か、LZ 4 圧縮または非圧縮か
  2. AssetBundle が読み込まれているプラットフォームは何か

API は以下の 4 種類です。

3.4.1. AssetBundle.LoadFromMemoryAsync

Unity では、この API の使用を推奨していません。

【Unity 5.3.3の時点での追記】Unity 5.3.3 では、この API の名前が変更されています。Unity 5.3.2(または、それ以前のバージョン)では、AssetBundle.CreateFromMemory と呼ばれていました。機能に変更はありません。

AssetBundle.LoadFromMemoryAsync は、マネージドのコードの配列(C# では byte[])から AssetBundle をひとつ呼び出します。つぎに、マネージドのコードの配列から新しく割り当てられた、ネイティブメモリーの隣接ブロックにソースデータをコピーします。AssetBundle が LZMA 圧縮されている場合は、コピー中に AssetBundle を解凍します。非圧縮の AssetBundle や LZ 4 圧縮された AssetBundle は逐語的にコピーされます。

この API によって消費されたメモリーの最大サイズは、AssetBundle のサイズの 2 倍以上になります。つまり、API 内で作成された複製がネイティブのメモリー上にひとつと、API に渡される複製がマネージドのバイト配列内にひとつです。したがって、API によって作成されて AssetBundle から読み込まれる Asset は、メモリー上で合計 3 回コピーされます。その内訳は、マネージドコードの配列内で 1 回、AssetBundle のネイティブメモリーの複製内で 1 回、そして 3 回目は GPU あるいはアセット自体のシステムメモリー内で行なわれます。

3.4.2. AssetBundle.LoadFromFile

【Unity 5.3 時点での追記】Unity 5.3 では、この API の名前が変更されています。Unity 5.2(または、それ以前のバージョン)では、この API は AssetBundle.CreateFromFile と呼ばれています。機能に変更はありません。

AssetBundle.LoadFromFile は、ハードディスクや SD カードなどのローカルストレージから非圧縮 AssetBundle を読み込むための高性能 API です。AssetBundles が非圧縮の場合や LZ 4 圧縮されている場合、この API は以下のような処理を行ないます。

モバイルデバイスの場合 この API は AssetBundle のヘッダーのみを読み込み、ディスク上の残りのデータは読み込みません。AssetBundle の Object は、読み込みメソッド(例.AssetBundle.Load)の呼び出しか、その InstanceID の間接参照に応じて、オンデマンドで読み込まれます。この場合、余分なメモリーは消費されません。

Unityエディターの場合 API は、AssetBundle 全体をメモリーに読み込みます。これは、バイトがディスクから読み出されて AssetBundle.LoadFromMemoryAsync が使用された場合と同様です。プロジェクトが Unity エディター上でプロファイリングされている場合は、この API によって AssetBundle の読み込み中にメモリースパイクが引き起こされる可能性があります。これがデバイス上でパフォーマンスに悪影響を与えることはないはずです。改善措置を取る前に、スパイクに関してデバイス上で再テストを行なう必要があります。

Android デバイスでUnity 5.3 以降のバージョンをお使いの場合、この API で Streaming Assets パスから AssetBundle の読み込みを試みると失敗します。これは、そのパスの内容が圧縮 .jar ファイル内にあるためです。くわしくは『AssetBundle の使用パターン』の章の配信―プロジェクトと共に配信するの項をご参照ください。Unity 5.4 では、この問題は解決されています。Unity 5.4 以降のバージョンを使ってビルドされたゲームでは、この API を使用して Streaming Assets から Asset Bundle を読み込むことができます。

LZMA 圧縮の AssetBundle の場合、AssetBundle.LoadFromFile の呼び出しは失敗します。

3.4.3. WWW.LoadFromCacheOrDownload

WWW.LoadFromCacheOrDownload は、リモートサーバーやローカルストレージから Object を呼び出すときに便利な API です。file:// URL 経由でローカルストレージからファイルを読み込むことができます。AssetBundle が Unity キャッシュに存在する場合は、この API の動作は AssetBundle.LoadFromFile とまったく同じになります。

キャッシュされていない AssetBundle がある場合、WWW.LoadFromCacheOrDownload がその AssetBundle をソースから読み出します。その AssetBundle が圧縮されている場合はワーカースレッドを使って解凍され、キャッシュに書き込まれます。そうでない場合は、ワーカースレッドから直接キャッシュに書き込まれます。

一旦 AssetBundle がキャッシュされると、キャッシュにある解凍された AssetBundle からWWW.LoadFromCacheOrDownload がヘッダー情報を読み込みます。その後、API の動作は AssetBundle.LoadFromFile で読み込まれた AssetBundle とまったく同じになります。

データが解凍されて固定サイズバッファ経由でキャッシュに書き込まれるあいだ、WWW オブジェクトは AssetBundle のバイトの完全な複製をネイティブメモリーに保持します。この AssetBundle の追加複製は、WWW.bytes プロパティに対応するために行ないます。

AssetBundle のバイトを WWW オブジェクト内にキャッシュするときは、メモリのオーバーヘッドが生じてしまうため、すべてのケースで WWW.LoadFromCacheOrDownload を使い、AssetBundle のサイズを小さく(最大 2~3 メガバイトに)抑えることをお勧めします。また、モバイルデバイスなどのメモリー制限のあるプラットフォームの場合は、メモリースパイクが起こるのを避けるため、コード 1 回にひとつの AssetBundle だけをダウンロードするような設定にすることを推奨します。AssetBundle のサイズに関する詳細は、AssetBundle の使用パターンの章のAsset の割り当て方法の項をご参照ください。

この API を呼び出すたびに、新しいワーカースレッドが生成されます。そのため、何度も API を呼び出すと、過剰な数のスレッドが生成される可能性がありますので、ご注意ください。AssetBundle を 5~10 個以上ダウンロードする場合は、同時にダウンロードする AssetBundle を 2~3 個に抑えるようにコードを書かれることをお勧めします。

3.4.4. AssetBundleDownloadHandler

Unity 5.3 からモバイルプラットフォーム向けに提供が開始された UnityWebRequest API は、Unity の WWW API に、より適応性のある選択肢を提供します。UnityWebRequest を使用すると、ダウンロードしたデータを Unity に「どのように扱わせるか」を具体的に指定できるので、不要なメモリーの消費を回避することができます。UnityWebRequest で AssetBundle をダウンロードするもっとも簡単な方法は UnityWebRequest.GetAssetBundle API です。

このガイドの目的上、興味のあるクラスは[DownloadHandlerAssetBundle](http://docs.unity3d.com/ScriptReference/Networking.DownloadHandlerAssetBundle.html)です。 このダウンロードハンドラを使用すると、* WWW.LoadFromCacheOrDownload *と同様に動作します。 ワーカースレッドを使用して、ダウンロードされたデータを固定サイズのバッファにストリーミングし、ダウンロードハンドラがどのように構成されているかに応じて、バッファリングされたデータを一時記憶域またはAssetBundleキャッシュにスプールします。 LZMAで圧縮されたAssetBundleは、ダウンロード中に解凍され、非圧縮でキャッシュされます。

これらのすべての処理はネイティブのコード内で実行されるので、マネージドのヒープが大きくなってしまうリスクを回避できます。また、この Download Handler は、ダウンロード済みのすべてのバイトのネイティブコードの複製を保持するわけではないので、AssetBundle をダウンロードする際のメモリーのオーバーヘッドがさらに削減されます。

ダウンロードが完了すると、Download Handler の assetBundle プロパティによってダウンロード済み AssetBundle へのアクセスが可能になります。これは、ダウンロードされた AssetBundle に AssetBundle.LoadFromFile が呼び出された場合と同様です。

UnityWebRequest API も、WWW.LoadFromCacheOrDownload とまったく同じキャッシング方法に対応しています。UnityWebRequest オブジェクトにキャッシング情報が提供されており、リクエストされた AssetBundle がすでにUnityのキャッシュ内に存在している場合は、AssetBundle がすぐに利用可能になり、API が AssetBundle.LoadFromFile とまったく同じ処理を行ないます。

Unity AssetBundle キャッシュは、WWW.LoadFromCacheOrDownloadUnityWebRequest とのあいだで共有されます。ある特定の API によってダウンロードされた AssetBundle はすべて、ほかの API でも利用可能になります。

WWW とはことなり、UnityWebRequest システムは開発者が過剰な数の同時ダウンロードを開始できないようにするため、ワーカースレッドの内部的プールと内部的なジョブシステムをひとつずつ持っています。スレッドプールのサイズの設定は、現時点では行なえません。

3.4.5. 推奨事項

基本的には、可能な限り AssetBundle.LoadFromFile をつねに使用することが推奨されます。この API は、速度、ディスクの使用率、ランタイムのメモリ使用率の観点から見て、もっとも効率的な API です。

AssetBundle のダウンロードやパッチが必要なプロジェクトの場合、Unity 5.3 以降のバージョンを使用したプロジェクトにはUnityWebRequestを使うことを、Unity 5.2 以前のバージョンを使用したプロジェクトには WWW.LoadFromCacheOrDownload を使うことを強くお勧めします。つぎの章の配信の項で解説されているとおり、Bundle がプロジェクトのインストーラー内に含まれた状態で AssetBundle Cache の事前準備を行なうことも可能です。

WWW.LoadFromCacheOrDownload をご使用の際は、プロジェクトの AssetBundle のサイズを、プロジェクトの最大許容メモリーサイズの 2~3%未満に収めることを強くお勧めします。これは、メモリー使用のスパイクが原因でアプリケーションが終了してしまうのを防ぐためです。ほとんどのプロジェクトでは、AssetBundle のファイルサイズが 5MB を超えないようにし、同時にダウンロードされる AssetBundle の数をふたつ以内に抑える必要があります。

WWW.LoadFromCacheOrDownload または UnityWebRequest を使う場合は、AssetBundle の読み込み後にダウンローダーのコードによって Dispose が正しく呼び出されるようにしてください。そのほかの方法としては、C# の using ステートメントを使用すると、WWWUnityWebRequest をもっとも簡単に、かつ安全・確実に処理することができます。

エンジニアリングチームのレベルが高く、キャッシングやダウンロードに関して独自の必要条件があるプロジェクトでは、カスタムのダウンローダーが求められることもあります。カスタムのダウンローダーを書くのは簡単なことではありません。どんなカスタム・ダウンローダーにも、かならず AssetBundle.LoadFromFile との互換性を持たせる必要があります。くわしくは、次章の配信の項をご参照ください。

3.5. AssetBundle から Asset を読み込む

UnityEngine.Object は、AssetBundle オブジェクトに付属した 3 種類の特殊な API(LoadAsset , LoadAllAssets , LoadAssetWithSubAssets)によって AssetBundle から読み込むことが可能です。またこれらの API はすべて、-Async という拡張子付きの非同期バリアント(LoadAssetAsync , LoadAllAssetsAsync , LoadAssetWithSubAssetsAsync)を持っています。

同期 API は、非同期 API と比較してつねに 1 フレーム以上速くなります。これは、とくに Unity 5.1 以前のバージョンで顕著です。Unity 5.2 になる前、すべての非同期 API は 1 フレームにつき最大でもひとつの Object だけしか読み込めませんでした。つまり、LoadAllAssetsAsyncLoadAssetWithSubAssetAsync が、それらに相当する同期 API とくらべて著しく遅かったのです。この現象は、Unity 5.2 で修正されています。現時点では、非同期の読み込みは 1 フレームごとに複数(タイムスライスの限界内で)の Object を読み込むようになっています。この挙動の背景にある技術的な理由やタイムスライスに関する詳細は読み込みに関する低水準の詳細の項をご参照ください。

個別の UnityEngine.Object を複数読み込む際には、LoadAllAssets を使用する必要があります。これは、AssetBundle 内の大部分、またはすべての Object を読み込む必要がある場合にのみ使用してください。ほかのふたつのAPIとくらべると、LoadAllAssets は個別の LoadAsset を複数呼び出すよりも若干速くなります。従って、読み込むアセットの数が多く、一度に読み込む必要のある数が AssetBundle コンテンツ全体の 3 分の 2 に満たない場合は、AssetBundle を複数の小さなバンドルに分割して LoadAllAssets を使用するのもひとつの方法です。

複数の埋め込み Object を含む複合 Asset(埋め込みアニメーションを含む FBX モデルや、複数のスプライトが埋め込まれたスプライトアトラスなど)を読み込む場合は、 LoadAssetWithSubAssets を使用してください。読み込む必要のある Object がすべて同じ Asset から来ていて、それらが関連性のないほかの Ocject と一緒に同じ AssetBundle 内に保存されている場合は、この API を使ってください。

上記以外のケースでは、LoadAssetLoadAssetAsync を使用してください。

3.5.1. 読み込みに関する低水準の詳細

UnityEngine.Object 読み込みは、メインスレッドの外で実行されます。Object のデータは、ワーカースレッド上のストレージから読み出されます。Unity のシステムにおいてスレッドに影響されない部分(スクリプト、グラフィックス)に触れないものは、ワーカースレッド上で変換されます。例えば、VBO はメッシュから作成され、テクスチャは解凍されます。

Unity 5.3 より前のバージョンでは、Object の読み込みは順番に処理され、Object の読み込みの一部はメインスレッドのみで行なわれます。これは「統合(integration)」と呼ばれます。ワーカースレッドは Object のデータの読み込み完了後に一時停止し、新しく読み込まれた Object をメインスレッドに統合します。そして、メインスレッドの統合が完了するまで一時停止の状態(つぎの Object を読み込まない状態)が維持されます。

Unity 5.3 からは、Object の読み込みが並行処理になりました。複数の Object のデシリアライズや処理や統合がワーカースレッドで行なえるようになりました。Object の読み込みが完了すると、その Awake コールバックが実行され、Unity Engine 全体がつぎのフレーム中にそのObjectを利用できるようになります。

同期の AssetBundle.Load 方式は、Object の読み込みが完了するまでメインスレッドを一時停止します。5.3 より前のバージョンの Unity では、非同期の AssetBundle.LoadAsync は、Objectのメインスレッドへの統合が必要になるまでメインスレッドを一時停止することはありません。また、Object の統合によって一定ミリ秒数を越えるフレーム時間が使用されるのを防ぐため、Object の読み込みがタイムスライスされます。このミリ秒数は、Application.backgroundLoadingPriority プロパティで設定します。

  • ThreadPriority.High : 1 フレーム最大 50 ミリ秒
  • ThreadPriority.Normal : 1 フレーム最大 10 ミリ秒
  • ThreadPriority.BelowNormal : 1 フレーム最大 4 ミリ秒
  • ThreadPriority.Low : 1 フレーム最大 2 ミリ秒

Unity 5.1 以前のバージョンでは、非同期 API はフレームごとにひとつの Object のみを統合します。これは不具合と考えられ、Unity 5.2 で修正されました。Unity 5.2 からは、Object の読み込みのフレーム時間の限界まで複数の Object が読み込まれます。AssetBundle.LoadAsync は同等の同期 API と比較すると(他の条件がすべて同じである場合)、より長い時間が掛かります。これは、LoadAsync の呼び出しがなされてからエンジン上で Object が利用可能になるまでに最小 1 フレームの遅延が生じるためです。

実際のObjectとAssetでテストを行なうと、このちがいがわかります。Unity 5.2 より前のバージョンでは、一定サイズの大きなテクスチャを低性能デバイスで読み込むためには、7ms 同期か 70ms 非同期である必要がありました。対してバージョン5.2以降は、観測可能な差異はゼロに近くなっています。

3.5.2. AssetBundle の依存関係

Unity 5 の AssetBundleシステムでは、AssetBundle 間の依存関係はランタイムの環境に応じて、2 種類のことなる API によって自動的にトラッキングされます。Unity エディターでは、AssetDatabase API によって AssetBundle の依存関係のクエリーが行なわれます。AssetBundle の割り当てと依存関係には、AssetImporter API からアクセスできます。ランタイムでは、AssetBundle のビルド中に生成された依存関係情報を読み込むための API が Unity によってオプションで提供されます。ScriptableObject がベースになった AssetBundleManifest API です。

ひとつ以上の親AssetBundle の UnityEngine.Objects がひとつ以上のほかの AssetBundle の UnityEngine.Objects を参照している場合、AssetBundle はほかの AssetBundle に「依存」している状態になります。Object 同士の参照に関する詳細は、アセット、オブジェクト、およびシリアライゼーションの章のObject 同士の参照 の項をご参照ください。

同じ章のシリアライゼーションとインスタンスの項に解説されているとおり、AssetBundle はその AssetBundle 内に含まれる各 Object の FileGUID と LocalID によって識別されるソースデータのソースとしての役割を持っています。

Object は、その Instance ID が最初に間接参照されたときに読み込まれます。また、Object が有効な Instance ID に割り当てられるのは、その AssetBundle が読み込まれたときです。従って、AssetBundle が読み込まれる順序は重要ではありません。それよりも重要なのは、Object 自体を読み込む前に、その Object の依存関係が含まれるすべての AssetBundle を読み込むことです。Unity は、親 AssetBundle が読み込まれたときに自動で子 AssetBundle の読み込みを行なうことはありません。

マテリアル Aテクスチャ B を参照していると仮定します。マテリアル A は AssetBundle 1 にパッケージされており、テクスチャ B は AssetBundle 2 にパッケージされています。

description

この場合、マテリアル A が AssetBundle 1 から読み込まれる に、AssetBundle 2 が読み込まれる必要があります。

これは、AssetBundle 2 が AssetBundle 1 の前に読み込まれる必要があるということではありません。また、Texture B がAssetBundle 2 から明示的に読み込まれなければいけないわけでもありません。AssetBundle 1 からマテリアル A を読み込むのに先立って、AssetBundle 2を読み込んでおくだけでこと足ります。

Unity は、AssetBundle 1 が読み込まれている場合に自動的で AssetBundle 2 を 読み込むことはありません 。これはスクリプトから手動で行なう必要があります。AssetBundles 1 と 2 の読み込みに使われた AssetBundle API の種類は関係ありません。WWW.LoadFromCacheOrDownload によって読み込まれた AssetBundle は、AssetBundle.LoadFromFileAssetBundle.LoadFromMemoryAsync によって読み込まれた AssetBundle と自由に組み合わせることができます。

3.5.3. AssetBundle のマニフェスト

BuildPipeline.BuildAssetBundles API から AssetBundle ビルドのパイプラインを実行する場合、各 AssetBundle の依存関係情報が含まれた Object が Unity によってシリアライズされます。このデータは、AssetBundleManifest タイプの Object をひとつ含んだ別の AssetBundle に保存されます。

このアセットは、AssetBundle がビルドされる親ディレクトリと同じ名前を持つ AssetBundle 内に保存されます。プロジェクトによって (projectroot)/build/Client/にあるフォルダー内に AssetBundle がビルドされた場合、マニフェストが含まれる AssetBundle は(projectroot)/build/Client/Client.manifestという形で保存されます。

マニフェストが含まれる AssetBundle は、ほかの AssetBundle と同じように読み込んだりキャッシュしたりアンロードしたりできます。

AssetBundleManifest Object 自体が、マニフェストと同時にビルドされたすべての AssetBundle をリスト化するための GetAllAssetBundles API を提供しています。また、AssetBundleManifest Object は、特定の AssetBundle の依存関係にクエリーを行なうための以下のふたつのメソッドを提供しています。

AssetBundleManifest.GetAllDependencies は、特定の AssetBundle のすべての依存関係を返します。これには AssetBundle の直接の子や、子の子なども含まれます。

AssetBundleManifest.GetDirectDependencies は、特定の AssetBundle の直接の子のみを返します。

これらの API の両方ともが、文字列の配列を割り当てることに注意してください。使用はなるべく控え、アプリケーションの存続期間においてパフォーマンスに影響しやすい部分ではまったく使用しないほうが賢明でしょう。

3.5.4. 推奨される使用方法

ユーザーが、アプリケーションのなかでパフォーマンスが絶対的に重要な部分(主要ゲームのステージやゲーム世界など)に入る前に、必要な Object をできるだけ多く読み込むのが理想的です。これは、とくにモバイルプラットフォームにおいて極めて重要です。モバイルプラットフォームの場合、ローカルストレージへのアクセス速度が遅く、プレイ時間における Object のロード・アンロードによってガベージコレクターがトリガーされる可能性があるからです。

アプリケーションがインタラクティブ状態のときに Object のロードやアンロードが必要とされるプロジェクトの場合は、AssetBundle の使用パターン の章の読込み済みアセットの管理の項で Object と AssetBundle の読み込みとアンロードに関する解説をご参照ください。