今回はこれまでに実装してきたAndroid NDKでZIP書庫内の画像ファイルを解凍するやAndroid SDKでJPG画像を画像に合わせて拡大縮小表示するを組み合わせて、ZIP書庫内の画像を順々に表示させるアプリを作成する。
まずは雛形となるプロジェクトをEasyProjectGenerator for Androidで作る。C++、HelloJNI、STL有効とする。

LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE := Test123 LOCAL_SRC_FILES := Test123.cpp LOCAL_LDLIBS := -lz -lunzip LOCAL_IS_SUPPORT_LOG := true ifeq ($(LOCAL_IS_SUPPORT_LOG),true) LOCAL_LDLIBS += -llog endif include $(BUILD_SHARED_LIBRARY)

#ifndef _ZIPEDIMAGES_H #define _ZIPEDIMAGES_H #include<string> #include<vector> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <android/log.h> #include "unzip.h" #define MAX_PATH 256 #define bool unsigned int #define true (1) #define false (0) class CZipedImages { protected: unzFile _zipFile; std::vector<unz_file_pos> _vFilePos; public: CZipedImages() { _zipFile = 0; __android_log_write(ANDROID_LOG_DEBUG,TAG,"CZipedImages::CZipedImages()"); } ~CZipedImages() { Close(); __android_log_write(ANDROID_LOG_DEBUG,TAG,"CZipedImages::~CZipedImages()"); } bool IsOpen(void) { __android_log_write(ANDROID_LOG_DEBUG,TAG,"CZipedImages::IsOpen()"); return (_zipFile != 0) ? true : false; } bool OpenZipFile(const char* pszFile) { __android_log_write(ANDROID_LOG_DEBUG,TAG,"CZipedImages::OpenZipFile()"); if(IsOpen() == true) return false; int nRet; _zipFile = unzOpen(pszFile); nRet = unzGoToFirstFile(_zipFile); while(nRet == UNZ_OK) { char pszFileName[MAX_PATH]; unz_file_info info; unz_file_pos pos; std::string strFile; std::string strExt; unzGetCurrentFileInfo(_zipFile,&info,pszFileName,MAX_PATH,NULL,0,NULL,0); strFile = pszFileName; strExt = strFile.substr(strFile.length() - 4,4); //いい加減な方法で拡張子取得 if(strExt == ".JPG" || strExt == ".jpg" || strExt == ".PNG" || strExt == ".png") //いい加減な方法で拡張子チェック { unzGetFilePos(_zipFile,&pos); _vFilePos.push_back(pos); } else { //処理されないファイル/フォルダをログ出力 __android_log_print(ANDROID_LOG_INFO,TAG,"NOT process : (%s)",pszFileName); } nRet = unzGoToNextFile(_zipFile); } return true; } void Close(void) { __android_log_write(ANDROID_LOG_DEBUG,TAG,"CZipedImages::Close()"); if(_zipFile != 0) unzClose(_zipFile); _zipFile = 0; _vFilePos.clear(); } int GetCount(void) { __android_log_write(ANDROID_LOG_DEBUG,TAG,"CZipedImages::GetCount()"); return _vFilePos.size(); } bool Extract(int nIndex,const char* pszFolder,std::string* pstrFile) { __android_log_write(ANDROID_LOG_DEBUG,TAG,"CZipedImages::Extract()"); if(IsOpen() == false) return false; if(nIndex > _vFilePos.size() || nIndex < 0) return false; int nRet; char pszFileName[MAX_PATH]; unz_file_info info; nRet = unzGoToFilePos(_zipFile,&_vFilePos[nIndex]); if(nRet != UNZ_OK) return false; nRet = unzGetCurrentFileInfo(_zipFile,&info,pszFileName,MAX_PATH,NULL,0,NULL,0); if(nRet != UNZ_OK) return false; __android_log_print(ANDROID_LOG_DEBUG,TAG,"CZipedImages::Extract() in process %s",pszFileName); std::string strPath; //保存先ファイルパス作成 { strPath = pszFolder; if(strPath.substr(strPath.length() - 1,1) != "/") strPath += "/"; if(pstrFile != NULL && *pstrFile != "") { if((*pstrFile)[0] != '/') strPath += *pstrFile; else strPath += pstrFile->substr(1,pstrFile->length() - 1); } else { char pszBuff[256]; std::string strExt; strExt = pszFileName; strExt = strExt.substr(strExt.length() - 4,4).c_str(); //いい加減な方法で拡張子取得 sprintf(pszBuff,"%d%s",nIndex,strExt.c_str()); if(pstrFile) *pstrFile = pszBuff; strPath.append(pszBuff); } } __android_log_print(ANDROID_LOG_DEBUG,TAG,"CZipedImages::Extract() : save to %s",strPath.c_str()); //解凍 { FILE *fp; fp = fopen(strPath.c_str(),"w"); if(fp == NULL) return false; char szBuffer[32768]; int dwSizeRead; nRet = unzOpenCurrentFile(_zipFile); if(nRet == UNZ_OK) { while ((dwSizeRead = unzReadCurrentFile(_zipFile,szBuffer,sizeof(szBuffer))) > 0) { fwrite(szBuffer,1,dwSizeRead,fp); } unzCloseCurrentFile(_zipFile); } fclose(fp); } return true; } }; #endif

Javaからネイティブで実装したZIP書庫操作用の関数にアクセスできるようにJNIを実装する。CZipedImagesのメンバー関数を単純にJNIでラッピングした。これでネイティブ側の実装は終りだ。
#if(true) #define LOCAL_LOG #define LOCAL_LOGD #endif #include <string.h> #include <jni.h> #ifdef LOCAL_LOG #include <android/log.h> #endif #define TAG "Test123" #include "ZipedImages.h" CZipedImages g_cZip; extern "C" jint Java_com_Test123_ZipImageFileView_OpenZipFile(JNIEnv* env,jobject thiz,jstring jstrFile) { bool ret; const char* pszFile = env->GetStringUTFChars(jstrFile,NULL); ret = g_cZip.OpenZipFile(pszFile); env->ReleaseStringUTFChars(jstrFile,pszFile); return (ret == true) ? 1 : 0; } extern "C" jint Java_com_Test123_ZipImageFileView_GetInZipCount(JNIEnv* env,jobject thiz) { return g_cZip.GetCount(); } extern "C" jstring Java_com_Test123_ZipImageFileView_Extract(JNIEnv* env,jobject thiz,jint jnIndex,jstring jstrFolder) { std::string strFile; int nIndex = jnIndex; bool ret; const char* pszFolder = env->GetStringUTFChars(jstrFolder,NULL); ret = g_cZip.Extract(nIndex,pszFolder,&strFile); env->ReleaseStringUTFChars(jstrFolder,pszFolder); if(ret) return env->NewStringUTF(strFile.c_str()); else return env->NewStringUTF(""); } extern "C" void Java_com_Test123_ZipImageFileView_Close(JNIEnv* env,jobject thiz) { g_cZip.Close(); }
次にImageFileView.javaファイルを追加して、画像ファイルを読み込みビュー表示するクラスを実装する。
package com.Test123; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.Matrix; import android.graphics.Rect; import android.os.Bundle; import android.view.Display; import android.view.View; import android.view.WindowManager; public class ImageFileView extends View { protected Bitmap _bmpImageFile; //画像ファイル protected Bitmap _bmpResized; //リサイズ画像 protected int _nWidthDisp; protected int _nHeightDisp; public ImageFileView(Context context) { super(context); //画面サイズ取得 WindowManager wmWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); Display display = wmWindowManager.getDefaultDisplay(); _nWidthDisp = display.getWidth(); _nHeightDisp = display.getHeight(); } //設定の保存 public void OnSaveState(Bundle outState) { if(outState == null) return; } //設定の読込 public void OnLoadState(Bundle savedInstanceState) { if(savedInstanceState == null) return; } //画像を大まかに縮小して_bmpImageFileへ読み込み、 //表示用にリサイズして_bmpResizedへ保存する public boolean LoadImageFile(String strFile,boolean bInvalidate) { //画像の解析(大きい画像が開けない対策) BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; _bmpImageFile = BitmapFactory.decodeFile(strFile,options); int nWidth = options.outWidth; int nHeight = options.outHeight; int nScale; //nScaleは2の乗数にする { int nScaleX = (int)(float)(nWidth / _nWidthDisp + 0.5f); int nScaleY = (int)(float)(nHeight / _nHeightDisp + 0.5f); nScale = (nScaleX < nScaleY) ? nScaleX : nScaleY; int i = 1; while(nScale > i) { i *= 2; } nScale = i - 1; if(nScale <= 0) nScale = 1; } //画像の本読み込み options.inJustDecodeBounds = false; options.inSampleSize = nScale; _bmpImageFile = BitmapFactory.decodeFile(strFile,options); ///////////////////////////////// //画像のリサイズ nWidth = _bmpImageFile.getWidth(); nHeight = _bmpImageFile.getHeight(); float fScale; //拡大縮小率取得 if(true) { //画像に合わせる if((long)nWidth * _nHeightDisp > (long)_nWidthDisp * nHeight) fScale = (float)_nWidthDisp / nWidth; else fScale = (float)_nHeightDisp / nHeight; } else if(false) { //高さに合わせる fScale = (float)_nHeightDisp / nHeight; } else { //幅に合わせる fScale = (float)_nWidthDisp / nWidth; } //リサイズ Matrix matrix = new Matrix(); matrix.postScale(fScale,fScale,0,0); _bmpResized = Bitmap.createBitmap(_bmpImageFile,0,0,nWidth,nHeight,matrix,true); if(bInvalidate) invalidate(); //表示更新 return (_bmpImageFile != null) ? true : false; } @Override protected void onDraw(Canvas canvas) { if(_bmpImageFile == null) { super.onDraw(canvas); return; } //描画 Rect rect = new Rect(0,0,_bmpResized.getWidth(),_bmpResized.getHeight()); canvas.drawBitmap(_bmpResized,rect,rect,null); } }
次にImageFileView派生でZipImageFileViewクラスを作る。
package com.Test123; import java.io.File; import android.content.Context; import android.graphics.Canvas; import android.os.Bundle; import android.view.MotionEvent; public class ZipImageFileView extends ImageFileView { private int _nImageIndex = -1; //現在読み込まれている画像のzip内の画像インデックス private int _nImageCount; //zip内の画像数 public ZipImageFileView(Context context) { super(context); //zipファイル読み込み OpenZipFile("/sdcard/desire_img.zip"); _nImageCount = GetInZipCount(); } //設定の保存 @Override public void OnSaveState(Bundle outState) { super.OnSaveState(outState); if(outState == null) return; outState.putInt("_nImageIndex",_nImageIndex); } //設定の読込 @Override public void OnLoadState(Bundle savedInstanceState) { super.OnLoadState(savedInstanceState); if(savedInstanceState == null) return; int nIndex; nIndex = savedInstanceState.getInt("_nImageIndex"); LoadImageFromZip(nIndex,true); } public boolean LoadImageFromZip(int nIndex,boolean bInvalidate) { if(nIndex >= _nImageCount || _nImageCount == 0 || nIndex < 0) return false; if(nIndex == _nImageIndex) return true; //すでに読み込まれている String strFile; //画像解凍 strFile = Extract(nIndex,"/sdcard/"); if(strFile == "") return false; strFile = "/sdcard/" + strFile; //画像読み込み LoadImageFile(strFile,bInvalidate); _nImageIndex = nIndex; //ファイル削除 File file = new File(strFile); file.delete(); return true; } @Override public boolean onTouchEvent(MotionEvent event) { //タッチイベント取得 if(event.getAction() == MotionEvent.ACTION_DOWN) { int nIndex; nIndex = _nImageIndex + 1; if(nIndex >= _nImageCount) nIndex = 0; LoadImageFromZip(nIndex,true); } return true; } @Override protected void onDraw(Canvas canvas) { if(_bmpImageFile == null) { LoadImageFromZip(0,true); return; } super.onDraw(canvas); } private native int OpenZipFile(String strFile); private native int GetInZipCount(); private native String Extract(int nIndex,String strFolder); private native void Close(); static { System.loadLibrary("Test123"); } }

package com.Test123; import android.app.Activity; import android.os.Bundle; import android.view.Window; import android.view.WindowManager; public class Test123Act extends Activity { private ImageFileView _view; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); requestWindowFeature(Window.FEATURE_NO_TITLE); getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); _view = new ZipImageFileView(this); setContentView(_view); } @Override protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); if(_view != null) _view.OnSaveState(outState); } @Override protected void onRestoreInstanceState(Bundle savedInstanceState) { super.onRestoreInstanceState(savedInstanceState); if(_view != null) _view.OnLoadState(savedInstanceState); } }
これで実行するとZIP書庫内の画像ファイルが表示され、画面をタッチすると、、、
先読み処理や別スレッドでの解凍処理などをしていないため、全体的にモッサリ。実機ではまぁまぁ使えるものの、VirtualPC上のAndroidエミュレーターでは論外なほどの遅さでした。
ファイルをダウンロード