Ukladať či neukladať obrázky do databázy na Androide ?

V jednej Android aplikácii sme riešili dilemu či ukladať obrázky do databázy alebo ich načítavať z interného úložiska. A ako to už býva zvykom tak každý člen tímu mal na vec iný názor. Keďže dohady sú dohady a prax je prax tak sme spravili jednoduchý test výkonnosti jednotlivých riešení takzvaný benchmark.

Postup bol jednoduchý, uložiť sto obrázkov do adresára "assets" a sto obrázkov do databázy. Potom v teste načítať obrázky z adresára a obrázky z databázy a porovnať časovú náročnosť jednotlivých riešení. Obrázky boli jednoduché ikonky o veľkosti cca 20kB.

Tento test je trocha špecifický tým že sme na prístup do databázy použili ORM knižnicu OrmLite. Táto knižnica asi pridala nejaké spomalenie do testu, ale na druhej strane zabezpečuje vačšiu rýchlosť a komfort vývoja.

Takže výsledky testov boli následovné, pseudo kód testu si môžete pozrieť nižšie.

BENCHMARK DATABASE: 362 ms  
BENCHMARK STORAGE: 74 ms  

Z výsledkov vidieť ze rozdiel načítavania z lokálneho úložiska je takmer 5x rýchlejší ako z databázy. Takže víťazom sa jasne stáva lokálne úložisko. Databázové operácie sme sa pokúšali zrýchliť aj pomocou predpripravených dotazov (prepared statements) ale paradoxne sa načítanie súborov z databázy ešte spomalilo.

V nasledujúcom výpise je zobrazený pseudokód ako vyzeral test výkonnosti.

private void benchmark(){  
    new AsyncTask<Void, Void, Void>(){
      @Override
      protected Void doInBackground(Void... params) {
        Bitmap[] bitmapsDb = new Bitmap[100];
        Bitmap[] bitmapsStorage = new Bitmap[100];
        int REPEAT_COUNT = 100;

        // ***** DATABASE BENCHMARK ***** //
        long startTimeDb = System.currentTimeMillis();

        for(int i=1, n=REPEAT_COUNT; i <= n; i++) {
          Bitmap image = loadBitmapFromDb("test" + Integer.toString(i));
          bitmapsDb[i-1] = image;
        }

        long stopTimeDb = System.currentTimeMillis();
        long elapsedTimeDb = stopTimeDb - startTimeDb;

        Log.d("BENCHMARK DATABASE: " + Long.toString(elapsedTimeDb) + " ms");

        // ****** STORAGE BENCHMARK ****** //
        long startTimeStorage = System.currentTimeMillis();

        for(int i=1, n=REPEAT_COUNT; i <= n; i++) {
          Bitmap image = loadBitmapFromStorage("test" + Integer.toString(i));
          bitmapsStorage[i-1] = image;
        }

        long stopTimeStorage = System.currentTimeMillis();
        long elapsedTimeStorage = stopTimeStorage - startTimeStorage;

        Log.d("BENCHMARK STORAGE: " + Long.toString(elapsedTimeStorage) + " ms");

        // compare loaded images, check if they are same
        int match = 0;
        for(int i=0, n=REPEAT_COUNT; i < n; i++) {
            if(bitmapsDb[i] != null && bitmapsStorage[i] != null && bitmapsDb[i].sameAs(bitmapsStorage[i])) {
              match++;
            }
        }
        Log.d("MATCH: " + Integer.toString(match));

        return null;
      }
    }.execute();
  }

public Bitmap loadBitmapFromStorage(String imageName) {  
   try {
      InputStream inStream = context.getAssets().open(filename);

      byte[] bytes = new byte[inStream.available()];
      inStream.read(bytes);

      return BitmapFactory.decodeByteArray(bytes, 0, bytes.length)
    } catch (Exception e) {
      e.printStackTrace();
    }
    return null;
}

public Bitmap loadBitmapFromDb(String imageName) {

    try {
      Dao<DomainInfo, Long> dao = mDatabaseHelper.getImageRecordDao();

      ImageRecord record = dao.queryBuilder()
          .selectColumns("image").where().eq("image_name", imageName).queryForFirst();

      if(record != null) {
        byte[] imageData = record.getImage();
        return BitmapFactory.decodeByteArray(imageData, 0, imageData.length);
      }

    } catch (SQLException e) {
      e.printStackTrace();
    }
    return null;
  }
Show Comments