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

Eclipseでプロジェクトを読み込み、まずはZIP書庫解凍用のライブラリlibunzipを使うための設定をAndroid.mkに追加する。
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)

次に
Android NDKでZIP書庫内の画像ファイルを解凍するで作成したZipedImages.hと同じものを追加する。
#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ファイルを追加して、画像ファイルを読み込みビュー表示するクラスを実装する。
基本的に
Android SDKでJPG画像を画像に合わせて拡大縮小表示すると同じだが、画像ファイルが大きくなるとメモリが足りなくなって落ちる問題の修正などをしている。nScaleの計算が間違えているのはご愛嬌。
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クラスを作る。
ZIPファイルから画像ファイルを解凍、ImageFileViewに読み込ませ、画像ファイルを削除という単純な流れにした。
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エミュレーターでは論外なほどの遅さでした。
ファイルをダウンロード