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

 先日のZipから画像を読み込む method に不具合があった。
 どうもBitmapFactory の時点で、読み込みミスをしているようす…。

 調べてみると、同様の症状に行き着いた。
http://groups.google.com/group/android-group-japan/browse_thread/thread/197c76b8fb3a8cc6?fwc=1
 どうやら、画像のデータサイズが大きいと、InputStream が読み込みミスをするらしい。
 回避するには、一度どこかに解凍してから、読み込むしかなさそう。

※.キャッシュファイルの保存
https://sites.google.com/a/techdoctranslator.com/jp/android/guide/data-storage

どのくらいだと大きい?

 素朴な疑問。
 サイズが大きいって、どのくらいだと大きいの? (?_?)

 読み込もうとしたのは、800×600のJPG。
 先日の試作アプリでは問題なかったものなんだ。
 じゃ、なにがちがうのかといえば、アプリ。
 不具合が出たのは、作ってるノベルアプリに組み込んでからなんだ。
 だから最初はノベルアプリを疑ったんだけど、そうじゃなさそうなのは前記のとおり。
 ではナニが制約になってるのかというと、たぶんヒープなんじゃないかと思う。

 試作アプリはZIP関係のClass しかないような状態で、ヒープに余裕がある。
 一方、ノベルアプリは色々と組み込んでいるから、ヒープに余裕があまりない。

 どのくらいだと”大きすぎる”かは、一概にいえないんじゃないかな?
 そういうワケで。
 直接メモリーに展開するのは、避けた方がよさそう。
 ”小さい”と思っていても、ヒープからすると「すごく大きいです…」になるかもしれないから。(w

by the way…

 原因を調べている途中で、BitmapFactory.Options なるものを見かけたよ。
 これを使うと、解像度や色数、サイズなんかを指定して、読み込めるらしい。
 GIFやPNG、サムネールなんかにはよさげなオプションだね。

※.コチラのページが詳しいです。
http://d.hatena.ne.jp/hyoromo/20101001/1285943744

Posted in File, 読み込み. Tags: , , , . ZipFile ~画像サイズが大きいと読み込み失敗 はコメントを受け付けていません。 »

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

Posted in File, 読み込み. Tags: , , , , , , , . ZipFile と ZipEntry ~Androidでzipを解凍する はコメントを受け付けていません。 »

RandomAccessFile ~Shif-JISで読み込む

 RandomAccessFile では、文字コードの指定はできない。
 Shift-JISも扱いたいので、別項のソースに手を加えてみたよ。

	public long head_pos = 0;	//現在のseekヘッド位置

	public String readline_RA( String path, long readPos, String code ) {
//	Log.d("TEST", "readline_RA : "+path );

		String result = "";
		//--

		try{
			File file = new File( path );
			RandomAccessFile ra = new RandomAccessFile( file, "r" );
			ra.seek( readPos );

			String str = ra.readLine();
			if ( str!= null ){
				result = str;
			//	}else{
				//	result = "";
				//	Log.d("Err", "// readline_RA : read is null //" );
			}
			head_pos = ra.getFilePointer();

			//
			if ( code != "UTF-8" ){
				// 改行コード判定
				//	CR : 0x0D : \r
				//	LF : 0x0A : \n
				//	UNIX = LF : Mac = CR : DOS = CR+LF

				byte[] buffer = new byte[ 2 ];	// 行末2バイトを読み込む
				ra.seek( head_pos -2 );
				ra.read(buffer);
				long RT = 1;
				if ( ( buffer[0] == 0x0D )&&( buffer[1] == 0x0A ) ){	//DOS
					RT = 2;
				}
				//--

				ra.seek( readPos );
				buffer = new byte[ (int)( head_pos -RT -readPos ) ];	// 読み込む量
				int r = ra.read(buffer);
				if ( -1 != r ){
					result = new String( buffer, code );
				}
			}
			//--

			ra.close();
		}catch( Exception e ){
			Log.d("Err", "// readline_RA : read ERR //" );
		}
		
		//--
		return result;
	}

仕組み

 手を加えた部分のは、23行~46行。

	1. readLine で読み込んだ範囲を、byte 配列で読み込み直す。
	2. byte 配列を文字コードを指定して、String に変換。

 readLine のデータを直接変換するのがシンプルなんだけど…。
 そのデータは、すでにUTF-8のためのbyte を差し挟まれちゃってる。
 そんなbyte 配列をプレーンな状態にするより、もう一度読み込んだ方が、結果的にシンプルなんだよね。
 直接変換するようなものがあるのかもしれないけど。

 だもんで、readLine は「一行の範囲を調べる」だけになってる。(^_^;

読み込み範囲

 最初の位置からgetFilePointer までを読み込むと、改行コードがbyte 配列に含まれてしまう。
 これをそのままTextView へ表示すると、不明文字のトウフが表示されちゃう。
 それは都合がわるいので、改行コードを調べて、読み込み範囲を補正する必要がある。(30~36行目)
 調べた値は long RT に格納し、読み込み範囲の補正に使っている(39行目)

文字コード指定

 指定した文字コードで変換するのは、難しくない。
 byte から、文字コードを指定して、String を作るだけ。(43行目)

	result = new String( buffer, code );

by the way…

 引数 code は、String で文字コードを指定することにしている。
 シフトJISなら、”Shift_JIS”。
 試してないけど、EUC-JPにも対応してるハズ。

Posted in File, 読み込み. Tags: , , . RandomAccessFile ~Shif-JISで読み込む はコメントを受け付けていません。 »

RandomAccessFile ~ファイルの読み込み

 ファイルを扱うclass は色々あるね。
 今回はRandomAccessFile を使ってみたよ。

RandomAccessFile の利点

 RandomAccessFile を使うと、読み込み位置なんかを指定できるんだ。
 これはゲームの再開なんかに、とても都合がいい。

RandomAccessFile での読み込み

 下記サンプルでは、テキスト・ファイルを一行読み込みしている。

	public long head_pos = 0;	//現在のseekヘッド位置

	public String readline_RA( String path, long readPos, String code ) {
//	Log.d("TEST", "readline_RA : "+path );

		String result = "";
		//--

		try{
			File file = new File( path );
			RandomAccessFile ra = new RandomAccessFile( file, "r" );
			ra.seek( readPos );

			String str = ra.readLine();
			if ( str!= null ){
				result = str;
			//	}else{
				//	result = "";
				//	Log.d("Err", "// readline_RA : read is null //" );
			}
			head_pos = ra.getFilePointer();

			ra.close();
		}catch( Exception e ){
			Log.d("Err", "// readline_RA : read ERR //" );
		}
		
		//--
		return result;
	}

モード設定

 RandomAccessFile は書き込み、読み込みで、別class になっていない。
 そのため、オブジェクト生成時に、モードを設定する必要がある。
 下記では、読み込み専用として生成している。

	new RandomAccessFile( file, "r" );

位置設定

 書き込み位置、読み込み位置の設定は、seek method を使う。
 値はlong で指定する。

	seek( readPos );

位置の取得

 書き込み位置、読み込み位置の取得は、getFilePointer method を使う。
 値はlong 。

	getFilePointer();

by the way…

 RandomAccessFile では、文字コードの指定はできない。
 そのためテキストファイルは、デフォルト・コード(UTF-8)で扱われてしまう。
 Shift-JISを扱うには、別途自前で直す必要がある。
 コレに関しては、別項・

Shif-JISで読み込む
にて。

Posted in File, 読み込み. Tags: , . RandomAccessFile ~ファイルの読み込み はコメントを受け付けていません。 »