Android(SQLite3)のBlob利用とパフォーマンスチェック

SQLiteにデータを保存したい

3Gのデータ速度を考えたときにアイコン画像などをBlob列に保存してキャッシュになるかどうかを検討。

結果

結構計測するとばらつきはありますがこんな具合
思ったより使えそうな感じです

プロトコル ヘッダ HTTP取得 DB保存 DB読込 SDカード保存 サイズ
https 非圧縮 800ms 118ms 23ms 12ms 59249B
https  圧縮 210ms 70ms 5ms 5ms 59249B
http 非圧縮 627ms 114ms 21ms 11ms 59249B
http  圧縮 188ms 98ms 21ms 11ms 59249B





サンプルアプリ

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
      package="jp.tackn.defaulthttpclientex"
      android:versionCode="1"
      android:versionName="1.0">
    <application android:label="@string/app_name">
        <activity android:name=".MainActivity"
                  android:label="@string/app_name"
                  android:launchMode="singleInstance">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
            <intent-filter>
                <action android:name="android.intent.action.VIEW" />
                <category android:name="android.intent.category.DEFAULT" />
                <category android:name="android.intent.category.BROWSABLE" />
                <data android:scheme="myapp" android:host="httpclient" />
            </intent-filter>
        </activity>
    </application>

    <uses-sdk android:minSdkVersion="4" />
    <supports-screens android:resizeable="true" />
    
    <uses-permission android:name="android.permission.INTERNET"/>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
</manifest> 

MainActivity.java

package jp.tackn.defaulthttpclientex;

import android.app.Activity;
import android.content.Intent;
import android.content.SharedPreferences;
import android.graphics.Color;
import android.net.Uri;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.*;
import jp.tackn.defaulthttpclientex.dao.TwitterDao;
import oauth.signpost.OAuthProvider;
import oauth.signpost.commonshttp.CommonsHttpOAuthConsumer;

/**
 * DefaultHttpClientを使ったサンプルプログラム
 *
 * @author Tackn
 */
public class MainActivity extends Activity
        implements View.OnClickListener {

    /** Twitterサーバから取得するDAO */
    private TwitterDao twitterDao = null;
    /** OAuth用Consumer */
    private CommonsHttpOAuthConsumer consumer;
    /** OAuth用Provider */
    private OAuthProvider provider;
    
    /** ブラウザのコールバックURL */
    private final static String CALLBACKURL="myapp://httpclient";

    /** ログ用タグ名 */
    private final static String LOG_TAG = "tackn";
    /** 内容に合わせる */
    private final static int WC = LinearLayout.LayoutParams.WRAP_CONTENT;
    /** 親コンテンツに合わせる */
    private final static int FP = LinearLayout.LayoutParams.FILL_PARENT;
    /** URL入力用エディットボックス */
    private EditText url_input;
    /** 結果表示用テキストビュー */
    private TextView result_view;
    /** gzip追加有無 */
    private CheckBox cb_Gzipx;

    /** アクティビティ起動時に呼ばれる */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        //TwitterDaoの準備
        twitterDao = TwitterDao.getInstance(this);
        consumer=twitterDao.getConsumer();
        provider=twitterDao.getProvider();
        
        //コンポーネントの垂直方向に配置
        LinearLayout mainLayout = new LinearLayout(this);
        mainLayout.setBackgroundColor(Color.WHITE);
        mainLayout.setOrientation(LinearLayout.VERTICAL);

        // URL入力欄の生成
        url_input = new EditText(this);
//        url_input.setText("http://www.ugtop.com/spill.shtml", TextView.BufferType.NORMAL);//確認くん
//        url_input.setText("http://www.kojikoji.net/", TextView.BufferType.NORMAL);//GET/POST 確認くん
        url_input.setText("https://api.twitter.com/1/statuses/public_timeline.xml", TextView.BufferType.NORMAL);//GET/POST 確認くん
//        url_input.setText("https://si0.twimg.com/profile_images/66551744/767703_2301518513_normal.jpg", TextView.BufferType.NORMAL);//GET/POST 確認くん
        url_input.setLayoutParams(new LinearLayout.LayoutParams(FP, WC));
        mainLayout.addView(url_input);

        // gzipするかのチェックボックスの生成
        cb_Gzipx = new CheckBox(this);
        cb_Gzipx.setText("ヘッダーにgzipを追加");
        cb_Gzipx.setTextColor(Color.BLACK);
        cb_Gzipx.setChecked(false);
        cb_Gzipx.setLayoutParams(new LinearLayout.LayoutParams(WC, WC));
        mainLayout.addView(cb_Gzipx);

        //実行するボタンの生成
        mainLayout.addView(makeButton("取得", "get"));

        //結果を表示するテキストビューの生成
        result_view = new TextView(this);
        result_view.setTextSize(16f);
        result_view.setTextColor(Color.BLACK);
        result_view.setLayoutParams(new LinearLayout.LayoutParams(FP, FP));
        mainLayout.addView(result_view);

        //ビューのセット
        setContentView(mainLayout);
        
        //認証
//        doOauth(false);
        twitterDao.doOauth(false,CALLBACKURL);
    }

    /**
     * ボタンを押したときの動作
     * 
     * @param v クリックされたView
     */
    public void onClick(View v) {
        if (v.getTag().equals("get")) {
            getFile(url_input.getText().toString());
        } else {
            Toast.makeText(this, "何事!", Toast.LENGTH_SHORT);
        }
    }

    /**
     * ボタンを作成するサブクラス
     *
     * @param text ボタンのラベル
     * @param tag ボタンの識別タグ
     * @return 作成したButtonオブジェクト
     */
    private Button makeButton(String text, String tag) {
        Button button = new Button(this);
        button.setText(text);
        button.setTag(tag);
        button.setOnClickListener(this);
        button.setLayoutParams(new LinearLayout.LayoutParams(FP, WC));
        return button;
    }

    /**
     * URLのパスからSDカード直下にファイルを取得する
     *
     * @param url 取得するURL
     */
    private void getFile(String url) {
        result_view.setText(
                twitterDao.getFile(
                    url,cb_Gzipx.isChecked()
                ));
    }

    /**
     * トークン情報の取得 設定終了時に呼ばれる
     * AndroidManifestのactivityにandroid:launchMode="singleInstance"
     * の記述が必要
     * @param intent
     */
    @Override
    protected void onNewIntent(Intent intent) {
        super.onNewIntent(intent);
        Uri uri = intent.getData();
        if (uri != null && uri.toString().startsWith(CALLBACKURL)) {
            String verier = uri.getQueryParameter(oauth.signpost.OAuth.OAUTH_VERIFIER);
            try {
                provider.retrieveAccessToken(consumer, verier);
                //トークン書き込み
                SharedPreferences prof = getSharedPreferences("token", MODE_PRIVATE);
                SharedPreferences.Editor editor = prof.edit();
                editor.putString("token", consumer.getToken());
                editor.putString("tokenSecret", consumer.getTokenSecret());
                editor.commit();

            } catch (Exception e) {
                Toast.makeText(this, e.getMessage(), Toast.LENGTH_LONG).show();
                Log.e(LOG_TAG, e.getMessage());
            }
        }
    }
}

TwitterDao.java

package jp.tackn.defaulthttpclientex.dao;

import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.net.Uri;
import android.text.TextUtils;
import android.util.Log;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.zip.GZIPInputStream;
import jp.tackn.defaulthttpclientex.R;
import jp.tackn.defaulthttpclientex.db.BlobDao;
import oauth.signpost.OAuthProvider;
import oauth.signpost.basic.DefaultOAuthProvider;
import oauth.signpost.commonshttp.CommonsHttpOAuthConsumer;
import oauth.signpost.exception.OAuthCommunicationException;
import oauth.signpost.exception.OAuthExpectationFailedException;
import oauth.signpost.exception.OAuthMessageSignerException;
import oauth.signpost.http.HttpRequest;
import org.apache.http.Header;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;

/**
 * TwitterにアクセスするDAO
 * @author Tackn
 */
public class TwitterDao {
    /** デバッグ */
    private final static boolean DEBUG = true;
    
    /** ログ用タグ名 */
    private final static String LOG_TAG = "tackn";
    
    /** シングルトン用インスタンス */
    private static TwitterDao instance = null;
    
    /** コンテキスト */
    private Context context;
   
    /** 受信バッファー */
    private static final int BUFFER_SIZE = 1024;
    
    //定数
    /** OAuth CONSUMER KEY */
    private String CONSUMER_KEY="";
    /** OAuth CONSUMER SECRET */
    private String CONSUMER_SECRET="";
    /** OAuth REQUEST_TOKEN URL */
    private String REQUEST_TOKEN_URL="";
    /** OAuth AUTHORIZE URL */
    private String AUTHORIZE_URL="";
    /** OAuth ACCESS TOKEN URL */
    private String ACCESS_TOKEN_URL="";
    /** OAuth Consumer */
    private CommonsHttpOAuthConsumer consumer;
    /** OAuth Provider */
    private OAuthProvider provider;
    
    /** DB保存用 */
    private BlobDao imageDao;
    
    /** ファイル保存用 */
    private ExtrnalStorageDao esdao;
    
    /**
     * シングルトン用コンストラクタ
     */
    private TwitterDao(Context context){
        this.context = context;
        
        //TwitterのOAuth関連の読み込み
        CONSUMER_KEY     =context.getResources().getString(R.string.Consumer_key);
        CONSUMER_SECRET  =context.getResources().getString(R.string.Consumer_secret);
        REQUEST_TOKEN_URL=context.getResources().getString(R.string.Request_token_URL);
        AUTHORIZE_URL    =context.getResources().getString(R.string.Authorize_URL);
        ACCESS_TOKEN_URL =context.getResources().getString(R.string.Access_token_URL);

        consumer = new CommonsHttpOAuthConsumer(CONSUMER_KEY, CONSUMER_SECRET);
        provider = new DefaultOAuthProvider(REQUEST_TOKEN_URL, ACCESS_TOKEN_URL, AUTHORIZE_URL);
    }
    /**
     * シングルトンインスタンス取得
     * @return シングルトンインスタンス
     */
    public static synchronized TwitterDao getInstance(Context context){
        if(instance==null)instance=new TwitterDao(context);
        return instance;
    }

    /**
     * ファイルの取得
     * @param url 取得するURL
     * @param isGzipx gzip圧縮の可否
     * @return ステータス
     */
    public String getFile(String url,boolean isGzipx){
        StringBuilder resultSet = new StringBuilder();
        
        //URLのパースチェック
        URL path = null;
        try {
            path = new URL(url);
        } catch (MalformedURLException ex) {
            return "URLが不正です\n";
        }

        HttpRequest rq = null;        
        
        /** HTTP GETリクエスト */
        HttpGet httpGet = new HttpGet(path.toString());
        try {
            rq= consumer.sign(httpGet);
        } catch (OAuthMessageSignerException ex) {
            Log.e(LOG_TAG,"OAuthMessageSignerException: "+ex.getMessage());
        } catch (OAuthExpectationFailedException ex) {
            Log.e(LOG_TAG,"OAuthExpectationFailedException: "+ex.getMessage());
        } catch (OAuthCommunicationException ex) {
            Log.e(LOG_TAG,"OAuthCommunicationException: "+ex.getMessage());
        }
        if (isGzipx) {
            resultSet.append("gzip圧縮\n");
            httpGet.setHeader("Accept-Encoding", "gzip, deflate");
        } else {
            resultSet.append("gzip非圧縮\n");
        }

        /** 読み込みサイズ */
        int size = 0;
        /** 読み込みバッファ */
        byte[] w = new byte[BUFFER_SIZE];
        /** 入力ストリーム */
        InputStream in = null;
        /** 受信用ストリーム */
        ByteArrayOutputStream out = new ByteArrayOutputStream();

        /** コンテンツ取得用HTTPクライアント */
        DefaultHttpClient httpClient = new DefaultHttpClient();
        /**取得結果 */
        HttpResponse execute = null;

        try {//URLへリクエスト
            execute = httpClient.execute(httpGet);

            switch (execute.getStatusLine().getStatusCode()) {
                case HttpStatus.SC_OK:
                    resultSet.append("Status 200 OK (HTTP/1.0 - RFC 1945)\n");
                    break;
                case HttpStatus.SC_MOVED_PERMANENTLY:
                    resultSet.append("Status 301 Moved Permanently (HTTP/1.0 - RFC 1945)\n");
                    return resultSet.toString();
                case HttpStatus.SC_MOVED_TEMPORARILY:
                    resultSet.append("Status 302 Moved Temporarily (Sometimes Found) (HTTP/1.0 - RFC 1945)\n");
                    return resultSet.toString();
                case HttpStatus.SC_NOT_FOUND:
                    resultSet.append("Status 404 Not Found (HTTP/1.0 - RFC 1945)\n");
                    return resultSet.toString();
                case HttpStatus.SC_INTERNAL_SERVER_ERROR:
                    resultSet.append("Status 500 Server Error (HTTP/1.0 - RFC 1945)\n");
                    return resultSet.toString();
                case HttpStatus.SC_SERVICE_UNAVAILABLE:
                    resultSet.append("Status 503 Service Unavailable (HTTP/1.0 - RFC 1945)\n");
                    return resultSet.toString();
                default:
                    resultSet.append("Status ")
                            .append(execute.getStatusLine().getStatusCode())
                            .append("\n");
                    return resultSet.toString();
            }

        } catch (ClientProtocolException ex) {
            Log.e(LOG_TAG, "ClientProtocolException: " + ex.getMessage());
        } catch (IOException ex) {
            Log.e(LOG_TAG, "IOException: " + ex.getMessage());
        }
        
        try {//HttpStatus.SC_OKの場合取得開始
            /** 取得開始時刻 */
            Long stratTime = System.currentTimeMillis();
            /** 取得終了時刻 */
            Long endTime = 0L;
            
            //gzip転送の有無で切り替え
            if (isGZipHttpResponse(execute)) {
                in = new GZIPInputStream(execute.getEntity().getContent());
            } else {
                in = execute.getEntity().getContent();
            }

            //読み込み処理
            while (true) {
                size = in.read(w);
                if (size <= 0) {
                    break;
                }
                out.write(w, 0, size);
            }
            in.close();
            endTime = System.currentTimeMillis();

            resultSet.append("HTTP取得時間: ")
                    .append(endTime - stratTime)
                    .append("ms\n");
            Log.i(LOG_TAG, "HTTP取得時間: "+(endTime - stratTime) + "ms");
            
            

            stratTime = System.currentTimeMillis();
            imageDao = BlobDao.getInstance();
            long l = BlobDao.insert(context,out.toByteArray());
            endTime = System.currentTimeMillis();

            resultSet.append("DB保存時間: ")
                    .append(endTime - stratTime)
                    .append("ms\n");
            Log.i(LOG_TAG, "DB保存時間: "+(endTime - stratTime) + "ms");
            

            stratTime = System.currentTimeMillis();            
            byte[] temp = imageDao.getBlob(context,"1");
            endTime = System.currentTimeMillis();

            resultSet.append("DB読み込み時間: ")
                    .append(endTime - stratTime)
                    .append("ms\n");
            Log.i(LOG_TAG, "DB読み込み時間: "+(endTime - stratTime) + "ms");

            
            stratTime = System.currentTimeMillis();
            esdao=ExtrnalStorageDao.getInstans();
            esdao.save(url,temp);
            endTime = System.currentTimeMillis();

            resultSet.append("ストレージ保存時間: ")
                    .append(endTime - stratTime)
                    .append("ms\n");
            
            Log.i(LOG_TAG, "ストレージ保存時間: "+(endTime - stratTime) + "ms");
            
            resultSet.append("ファイルサイズ: ")
                    .append(temp.length)
                    .append("B");
            Log.i(LOG_TAG, "ファイルサイズ" +temp.length+"B");
            
        } catch (IOException ex) {
            Log.e(LOG_TAG,"IOException: "+ex.getMessage());
        } catch (IllegalStateException ex) {
            Log.e(LOG_TAG,"IllegalStateException: "+ex.getMessage());
        } finally {
            if (in != null) {
                try {
                    in.close();
                } catch (IOException ex) {
                }
            }
            httpClient.getConnectionManager().shutdown();
        }
        return resultSet.toString();
    }
    
    /**
     * Oauth認証
     *
     * @param setup
     */
    public void doOauth(boolean setup,String callbackuri) {
        try {
            //トークンの読み込み
            SharedPreferences pref = context.getSharedPreferences("token", Context.MODE_PRIVATE);
            String token = pref.getString("token", "");
            String tokenSecret = pref.getString("tokenSecret", "");

            //認証済み
            if (!setup && token.length() > 0 && tokenSecret.length() > 0) {
                consumer.setTokenWithSecret(token, tokenSecret);
            } //認証処理のためブラウザ起動
            else {
                String url = provider.retrieveRequestToken(consumer, callbackuri);
                context.startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(url)));
            }
            
        } catch (Exception e) {
            Log.e(LOG_TAG, "doOauth: "+e.getMessage());
        }
    }

    /**
     * gzipが有効か判定する処理
     *
     * @param response HTTPレスポンス
     * @return gzip圧縮されているかの判定
     */
    private boolean isGZipHttpResponse(HttpResponse response) {
        Header header = response.getEntity().getContentEncoding();
        if (header == null) {
            return false;
        }
        String value = header.getValue();
        return (!TextUtils.isEmpty(value) && value.contains("gzip"));
    }

    /**
     * OAuthコンシュマーの取得
     * @return CommonsHttpOAuthConsumer
     */
    public CommonsHttpOAuthConsumer getConsumer(){
        return consumer;
    }

    /**
     * OAuthプロバイダの取得
     * @return OAuthProvider
     */
    public OAuthProvider getProvider(){
        return provider;
    }
}

ExtrnalStorageDao.java

package jp.tackn.defaulthttpclientex.dao;

import android.os.Environment;
import android.util.Log;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;

/**
 * SDカードに保存するDAO
 * @author Tackn
 */
public class ExtrnalStorageDao {
    /** デバッグ */
    private final static boolean DEBUG = true;
    
    /** ログ用タグ名 */
    private final static String LOG_TAG = "tackn";
    
    /** シングルトンインスタンス*/
    private static ExtrnalStorageDao instans;

    /** ファイル保存用ストリーム */
    FileOutputStream writefile = null;

    /**
     * シングルトンコンストラクタ
     */
    private ExtrnalStorageDao() {
        if(DEBUG)Log.v(LOG_TAG, "コンストラクタ:ExtrnalStorageDao()");
    }

    /**
     * インスタンス生成
     *
     * @return 生成したシングルトン取得
     */
    public static synchronized ExtrnalStorageDao getInstans() {
        if (instans == null)instans = new ExtrnalStorageDao();
        return instans;
    }
    /**
     * ファイルを外部ストレージに保存
     * @param url 取得URL。ファイル名に利用
     * @param out 保存するbyteアレイ
     * @throws IOException 保存失敗
     */
    public void save(String url, byte[] out) throws IOException {
        try{
            //ファイルに保存
            if (checkSDCard()) {
                /** 外部ストレージパス */
//                File dir = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + "/jp/tackn/tmp/");
                File dir = Environment.getExternalStorageDirectory();
                //フォルダの生成
                if(!dir.exists())dir.mkdirs();
                /** 保存ファイル */
                File file = null;
                if (url.length() - 1 == url.lastIndexOf("/")) {
                    file = File.createTempFile("temp", ".html", dir);
                } else {
                    file = new File(dir.getAbsolutePath() + url.substring(url.lastIndexOf("/")));
                }
                writefile = new FileOutputStream(file);
            }else{
                return;
            }
            Log.i(LOG_TAG, url);
            
            writefile.write(out);
            writefile.flush();
            writefile.close();
        }catch(IOException e) {
            Log.e(LOG_TAG, "save: " + e.getMessage());
            throw e;
        }finally{
            try{
                if(writefile!=null)writefile.close();
            }catch(Exception e){
            }
            
        }
    }

    /**
     * SDカードの状態をチェック
     *
     * @return SDカードが着込み可かどうか
     */
    private boolean checkSDCard() {
        String status = Environment.getExternalStorageState();

        if (status.equalsIgnoreCase(Environment.MEDIA_MOUNTED)) {
            if(DEBUG)Log.d(LOG_TAG, "checkSDCard: SDカードが装着されている");
            //この状態が返ってきた場合は、読み書きが可能です。
            return true;
        } else if (status.equalsIgnoreCase(Environment.MEDIA_MOUNTED_READ_ONLY)) {
            Log.e(LOG_TAG, "checkSDCard: SDカードが装着されていますが、読み取り専用・書き込み不可です");
            return false;
        } else if (status.equalsIgnoreCase(Environment.MEDIA_REMOVED)) {
            Log.e(LOG_TAG, "checkSDCard: SDカードが装着されていません");
            return false;
        } else if (status.equalsIgnoreCase(Environment.MEDIA_SHARED)) {
            Log.e(LOG_TAG, "checkSDCard: SDカードが装着されていますが、"
                    +"USBストレージとしてPCなどにマウント中です");
           return false;
        } else if (status.equalsIgnoreCase(Environment.MEDIA_BAD_REMOVAL)) {
            Log.e(LOG_TAG, "checkSDCard: SDカードのアンマウントをする前に、取り外しました");
            return false;
        } else if (status.equalsIgnoreCase(Environment.MEDIA_CHECKING)) {
            Log.e(LOG_TAG, "checkSDCard: SDカードのチェック中です");
            return false;
        } else if (status.equalsIgnoreCase(Environment.MEDIA_NOFS)) {
            Log.e(LOG_TAG, "checkSDCard: SDカードは装着されていますが、ブランクであるか、"
                    + "またはサポートされていないファイルシステムを利用しています");
            return false;
        } else if (status.equalsIgnoreCase(Environment.MEDIA_UNMOUNTABLE)) {
            Log.e(LOG_TAG, "checkSDCard: SDカードは装着されていますが、マウントすることができません");
            return false;
        } else if (status.equalsIgnoreCase(Environment.MEDIA_UNMOUNTED)) {
            Log.e(LOG_TAG, "checkSDCard: SDカードは存在していますが、マウントすることができません");
            return false;
        } else {
            Log.e(LOG_TAG, "checkSDCard: その他の要因で利用不可能");
            return false;
        }
    }
}

BlobDao.java

package jp.tackn.defaulthttpclientex.db;

import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;


/**
 * Blobに保存するDAO
 *
 * @author Tackn
 */
public class BlobDao {
    /** デバッグ */
    private final static boolean DEBUG = true;
    /** ログ用タグ名 */
    private final static String LOG_TAG = "tackn";
    /** シングルトンインスタンス*/
    private static BlobDao instance = null;
    /** テーブル名 */
    final static String DB_TABLE = "test";
    /** 現在のDBバージョン */
    final static int DB_VERSION = 1;
    /** ID */
    private final static String COLUMN_ID = "id";
    /** 画像保存用BLOB */
    private final static String COLUMN_BLOB = "object_blob";
    /** TABLE作成用SQL */
    final static String TABLE_SAMPLE =
            "create table if not exists "
            + DB_TABLE + "("
            + COLUMN_ID + " integer primary key,"
            + COLUMN_BLOB + " blob)";
    
    /** TABLEアップデート用SQL */
    private static String TABLE_UPDATE = "drop table if exists "
            + DB_TABLE;
    
    /** 古いバージョン */
    private int oldVersion = 1;
    /** 現在のバージョン */
    private int newVersion = DB_VERSION;
    

    /**
     * シングルトンコンストラクタ
     */
    private BlobDao() {
    }

    /**
     * シングルトン取得
     *
     * @return ImageDao
     */
    public static synchronized BlobDao getInstance() {
        if (instance == null) {
            instance = new BlobDao();
        }
        return instance;
    }

    /**
     * 古いバージョン
     *
     * @param oldVersion
     */
    void setOldVersion(int oldVersion) {
        if(1>oldVersion) //TODO:エラー処理
        this.oldVersion = oldVersion;
    }

    /**
     * 新しいバージョン
     *
     * @param newViersion
     */
    void setNewVersion(int newViersion) {
        if(DB_VERSION<newViersion) //TODO:エラー処理
        this.newVersion = newViersion;
    }

    /**
     * バージョン差異にあわせたUpdate文を発行
     * <Strong>事前にsetOldVersion,newVersionをセットが必要</Strong>
     *
     * @return TableUpdateのSQL文
     */
    String getTableUpdate() {
        //TODO:バージョンアップ処理
        return TABLE_UPDATE;
    }
    
    /**
     * インサート処理
     * @param context コンテキスト
     * @param blob byte配列
     * @return 
     */
    public static long insert(Context context,byte[] blob){
       SQLiteDatabase db = null;
       try{
            DBHelper helper = new DBHelper(context);
            db = helper.getWritableDatabase();
            ContentValues values = new ContentValues();
            values.put(COLUMN_BLOB, blob);

            return db.insert(DB_TABLE, null, values);
        }finally{
            if(db!=null)db.close();
        }
    }
    /**
     * Blob列からデータを取得
     * @param context コンテキスト
     * @param id ID
     * @return バイト配列
     */
    public static byte[] getBlob(Context context,String id){
       SQLiteDatabase db = null;
       Cursor cursor = null;
       try{
           DBHelper helper = new DBHelper(context);
           db = helper.getReadableDatabase();
           String sql = String.format("select %s from %s where %s = ?"
                   ,COLUMN_BLOB,DB_TABLE,COLUMN_ID);
           String [] args = {id};
           
           cursor = db.rawQuery(sql, args);
           
           if(cursor.moveToFirst()){
               return cursor.getBlob(0);
           }
           return null;
       }finally{
           if(cursor != null)cursor.close();
           if(db != null)db.close();
       }
    }
    
}

DBHelper.java

package jp.tackn.defaulthttpclientex.db;

import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.util.Log;

/**
 * DBヘルパークラス
 * @author Tackn
 */
public class DBHelper extends SQLiteOpenHelper {
    /** デバッグ用 */
    final static boolean DEBUG  = true;
    /** ログ用タグ */
    final static String LOG_TAG = "tackn";
    /** 画像処理用DAO */
    private BlobDao dao;
    
    /**
     * コンストラクタ
     * @param context コンテキスト
     */
    public DBHelper(Context context) {
        super(context, BlobDao.DB_TABLE, null, BlobDao.DB_VERSION);
        //画像処理用DAOの取得
        dao = BlobDao.getInstance();
        if(DEBUG)Log.d(LOG_TAG,"DBhelper コンストラクタ");
    }

    /**
     * データベース生成
     * @param db
     */
    @Override
    public void onCreate(SQLiteDatabase db) {
        if(DEBUG)Log.v(LOG_TAG,"DBhelper onCreate(): " + BlobDao.DB_TABLE + " start");
        db.execSQL(BlobDao.TABLE_SAMPLE);
        if(DEBUG)Log.v(LOG_TAG, BlobDao.TABLE_SAMPLE);
        if(DEBUG)Log.v(LOG_TAG ,"onCreate() " + db);
    }

    /**
     * データベースのアップグレード
     * @param db
     * @param oldVersion
     * @param newViersion
     */
    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newViersion) {
        if(DEBUG)Log.v(LOG_TAG,"DBhelper onUpgrade(): " + BlobDao.DB_TABLE + " start");
        if(DEBUG)Log.v(LOG_TAG,"DBhelper onUpgrade(): " + oldVersion);
        if(DEBUG)Log.v(LOG_TAG,"DBhelper onUpgrade(): " + newViersion);
        if(DEBUG)Log.v(LOG_TAG,"DBhelper onUpgrade(): " + dao.getTableUpdate());
        dao.setOldVersion(oldVersion);
        dao.setNewVersion(newViersion);
        db.execSQL(dao.getTableUpdate());
        onCreate(db);
    }
}