ZipFile と ZipEntry ~Androidでzipを解凍する

 いわゆるアドベンチャー・ゲームみたいに、複数のデータファイルを扱う場合、それらをひとつのファイルにまとめることがほとんどだよね。
 その理由は、機密性が第一なのかな?
 でも配布を考えた場合、ひとつにまとまっていた方が都合いいんだ。
 まず、コピーが早い。
 ファイルの取りこぼしがない。
 それと一概にはいえないけど、通信パケットを節約できる。

 で、ひとつにまとめるには、どうしたらいいか?
 色々と方法はあるけど、オイラはzipを使うことにしている。
 なぜかといえば、まとめるツールは作らなくていいし、取り出すルーチンも簡単ですむから。

 というワケで、zipからデータを取り出すmethod を作ってみたよ。

ZipFile と ZipEntry

 zip を扱うのに、使うclass は以下の2つ。

●ZipFile
http://developer.android.com/reference/java/util/zip/ZipFile.html

●ZipEntry
http://developer.android.com/reference/java/util/zip/ZipEntry.html

 ZipFile は、そのままZIP を抽象化したもの。
 ZipEntry は、ZIPの中のファイルを抽象化したもの。

 method を眺めると、ZipEntry と Fileクラス はなんか似ている。
 たぶん、「ZipFile はHD、ZipEntry はファイル」と見立ててるんだろうね。
 取り出すときもInputStream を使うから、ファイル操作と手順はそう変わらなそう。

 ちなみに、ZipInputStream というclass もある。
 今回は使わなかったけど、役割はZipFile と同じみたいだ。
 おそらく、ネットからZIPを取得した時、メモリー上にZIPがある時、なんかに使うんだろね。

ファイル一覧を取得する

 まず、中のファイル一覧を取得してみた。
 下記method は、ZIPのパスを指定すると、中のファイル一覧をString配列として返すよ。

	public int err = 0;	// エラーがあったか? // -1 : エラー有り | other : エラーなし

	public String[] getList( String zipPath ){
		err = 0;
		String[] result = null;
		//--

		try{
			ZipFile zipFile = new ZipFile( zipPath );
			
			List<String> list = new ArrayList<String>();
			Enumeration<? extends ZipEntry>	enu = zipFile.entries();
			while( enu.hasMoreElements() ){
				ZipEntry entry = enu.nextElement();
				//--
				list.add( entry.getName() );
			}
			zipFile.close();
			
			result = (String[])list.toArray(new String[0]);
		}catch( Exception ex ){
			err = -1;
			Log.d( "Err", "// ZipIO : getList err... //" );
		}

		//--
		return result;
	}

 まず、ZIPのパスから、ZipFile オブジェクトを生成する。

 そして、entries() で、Enumeration を取得する。
 このEnumeration は、中のファイル、──つまりZipEntry の塊みたいなモンだと思っていい。
 Enumeration のnextElement() を使うと、順繰りにZipEntry を取り出せるんだ。

 で、取り出したZipEntry にgetName() をして、ファイル名を取得している。
 取得したファイル名は、いったんList オブジェクトに格納するよ。
 List オブジェクトは配列と似たようなものだけど、要素数を増やすことができるんだ。
 お陰でファイルの総数がわからなくても、配列を作ることができる。
 そして、ファイル名の格納が終わったら、List をtoArray()でString配列に変換するんだ。

 以上で、ファイル一覧を取得できた。
 応用として、getName() をgetSize() にすると、容量一覧が取得できるね。
 ※.getSize() は、long を返します。

 注意点は、ZipFile のclose。
 最後にclose してやらないと、メモリーリークする可能性があるから注意。

文字バケ

 一覧を取得できたので、画面に表示してみた。

文字バケ

 …と。なんかバケてるね。(^_^;
 Windowsで作ったZIPなので、ファイル名がShift-JISなのかもしれない。
 それじゃ、Macで作ったZIPならバケない…?

文字バケ

 …見事にバケてるよ。(w

 これはたぶん、Javaレベルの問題。
 1バイト文字専用で、2バイトやUTF-8に対応していないんだね。
 バケ方のちがいからすると、そんな感じ。

 2バイト文字に対応するには、取得したファイル名をデコードしてやらないといけないんだね。
 まぁ、解凍には支障ないんで、今回はパスしておくよ。

ファイルを解凍してみた

 次に、ファイルを指定して、解凍してみた。

 下記method で、指定したファイルを解凍するよ。
 手順は、ファイルのコピーとそう変わらないね。

 引数は、
  ZIPのパスを指定
  解凍するファイル名
  解凍先(Path付ファイル名)

	public int err = 0;	// エラーがあったか? // -1 : エラー有り | other : エラーなし

	public void meltFile( String zipPath, String file, String outFile ){
		err = 0;
		try{
			ZipFile zipFile = new ZipFile( zipPath );
			ZipEntry entry = zipFile.getEntry( file );
			InputStream is = zipFile.getInputStream( entry );
			
			OutputStream os = new FileOutputStream( outFile );

			byte[] buffer = new byte[ 1024*4 ];	// 1024*4 = DEFAULT BUFFER SIZE
			int r = 0;
			while( -1 != ( r = is.read(buffer)) ){
				os.write( buffer, 0, r );
			}

			os.close();
			is.close();
			zipFile.close();
		}catch( Exception ex ){
			err = -1;
			Log.d( "Err", "// ZipIO : meltFile err... //" );
		}

		//--
	}

 ディレクトリの中にファイルがある場合、解凍するファイル名をPath付にしてやる。
 こんな感じ。

	dir/char.gif

 ただし、2バイトの場合、↓ではダメ。

	新しいフォルダ/char.gif

※.UTF-8 のZIPなら解凍できるかも…?

 たぶん、「1バイト化け」した状態で指定してやれば解凍できるんじゃないかな?
 もしくはファイル名以外の方法で、ZipEntry を取得してやればいいと思う。

 ちなみに上記サンプルでは、解凍先にディレクトリは作られない。
 必要があるなら、別途つけくわえてね。

画像データを取得してみた

 データとして取り出すにはどうしたらいいんだろ?
 ここにひとつ問題があるんだ。

 一覧取得でちょっと触れたけど、ファイルサイズの単位は、long なんだ。
 これではbyte配列自体が作れない。
 int サイズに限定すればできるけど、そういう制限はかけたくない。

 となると、InputStream から直接、目的のデータにすることになりそう。
 というワケで。
 画像データ(Bitmap)として取り出すmethod を作ってみた。

	public int err = 0;	// エラーがあったか? // -1 : エラー有り | other : エラーなし

	public Bitmap meltBitmap( String zipPath, String file ){
		err = 0;
		Bitmap result = null;
		//--

		try{
			ZipFile zipFile = new ZipFile( zipPath );
			ZipEntry entry = zipFile.getEntry( file );
			InputStream is = zipFile.getInputStream( entry );
			result = BitmapFactory.decodeStream( is );

			is.close();
			zipFile.close();
		}catch( Exception ex ){
			err = -1;
			Log.d( "Err", "// ZipIO : meltBitmap err... //" );
		}

		//--
		return result;
	}

 BitmapFactory で、InputStream からBitmap にしている。
 この方法は、ネットから画像データを取得するのと同じ方法。

 Drawable で取り出す時には、

	Drawable.createFromStream

 を使えばいいよ。

 取得した画像データをImageView で表示してみた。
 問題なし。(^_^)
※.画像サイズが大きいと、失敗することがあるらしい。
 詳しいくは…

「ZipFile ~画像サイズが大きいと読み込み失敗」

解凍表示

by the way…

 個人的に困ったのは、RandomAccessFile が使えないことだね。
 ヘッダー位置の取得方法がないから、部分的に読み込むことができない。
 できないワケじゃないけど、イロイロと面倒なんだな、これが。(^_^;
 一度テンポラリーに、ファイルとして解凍するのがいいのかな…。

Comments are closed.