Работа с архивами Zip и 7z

от автора

В мобильной разработке бывает потребность сделать приложение для работы без интернета.
Например словарь или справочник, который будет использоваться в суровых полевых условиях.
Тогда для работы приложения нужно единожды выкачать архив с данными и сохранить его у себя.
Сделать это можно посредством запроса в сеть, но так же можно зашить архив с данными внутрь приложения.
Согласно требованиям Google Play apk файл приложения должен быть не более 50 мб, так же можно прикрепить два файла дополнения .obb по 2 гигабайта. Механизм простой но сложный при эксплуатации, поэтому лучше всего уложиться в 50 мб и возрадоваться.
И в этом нам помогут целых два архивных формата Zip и 7z.

Давайте рассмотрим их работу на примере уже готового тестового приложения ZipExample.
Для тестов была создана sqlite база данных test_data.db.
Она содержит 2 таблицы android_metadata — по традиции и my_test_data с миллионом строчек

Размер полученного файла составляет 198 мб.
Сделаем два архива test_data.zip (10.1 мб) и test_data.7z (3.05).
Как очевидно файлы БД sqlite очень хорошо сжимаются. По опыту могу сказать, что чем проще структура базы, тем лучше сжимается.
Оба этих файла располагаются в папке assets и в процессе работы будут разархивированы.

Внешний вид программы представляет собой окно с текстом и двумя кнопками

Вот метод распаковки zip архива:

  public void onUnzipZip(View v) throws IOException {         SimpleDateFormat sdf = new SimpleDateFormat(" HH:mm:ss.SSS");         String currentDateandTime = sdf.format(new Date());         String log = mTVLog.getText().toString() + "\nStart unzip zip" + currentDateandTime;         mTVLog.setText(log);         InputStream is = getAssets().open("test_data.zip");         File db_path = getDatabasePath("zip.db");         if (!db_path.exists())             db_path.getParentFile().mkdirs();         OutputStream os = new FileOutputStream(db_path);         ZipInputStream zis = new ZipInputStream(new BufferedInputStream(is));         ZipEntry ze;         while ((ze = zis.getNextEntry()) != null) {             byte[] buffer = new byte[1024];             int count;             while ((count = zis.read(buffer)) > -1) {                 os.write(buffer, 0, count);             }             os.close();             zis.closeEntry();         }         zis.close();         is.close();         currentDateandTime = sdf.format(new Date());         log = mTVLog.getText().toString() + "\nEnd unzip zip" + currentDateandTime;         mTVLog.setText(log);      } 

Распаковывающим классом тут является ZipInputStream он входит в пакет java.util.zip, а тот в свою очередь в стандартную Android SDK и поэтому работает «из коробки» т.е. ничего отдельно закачивать не надо.

Вот метод распаковки 7z архива:

public void onUnzip7Zip(View v) throws IOException {         SimpleDateFormat sdf = new SimpleDateFormat(" HH:mm:ss.SSS");         String currentDateandTime = sdf.format(new Date());          String log = mTVLog.getText().toString() + "\nStart unzip 7zip" + currentDateandTime;         mTVLog.setText(log);          File db_path = getDatabasePath("7zip.db");         if (!db_path.exists())             db_path.getParentFile().mkdirs();          SevenZFile sevenZFile = new SevenZFile(getAssetFile(this, "test_data.7z", "tmp"));         SevenZArchiveEntry entry = sevenZFile.getNextEntry();         OutputStream os = new FileOutputStream(db_path);         while (entry != null) {             byte[] buffer = new byte[8192];//             int count;             while ((count = sevenZFile.read(buffer, 0, buffer.length)) > -1) {                  os.write(buffer, 0, count);             }             entry = sevenZFile.getNextEntry();         }         sevenZFile.close();         os.close();         currentDateandTime = sdf.format(new Date());         log = mTVLog.getText().toString() + "\nEnd unzip 7zip" + currentDateandTime;         mTVLog.setText(log);      } 

И его помощник:

 public static File getAssetFile(Context context, String asset_name, String name)             throws IOException {         File cacheFile = new File(context.getCacheDir(), name);         try {             InputStream inputStream = context.getAssets().open(asset_name);             try {                 FileOutputStream outputStream = new FileOutputStream(cacheFile);                 try {                     byte[] buf = new byte[1024];                     int len;                     while ((len = inputStream.read(buf)) > 0) {                         outputStream.write(buf, 0, len);                     }                 } finally {                     outputStream.close();                 }             } finally {                 inputStream.close();             }         } catch (IOException e) {             throw new IOException("Could not open file" + asset_name, e);         }         return cacheFile;     } 

Сначала мы копируем файл архива из asserts , а потом разархивируем при помощи SevenZFile . Он находится в пакете org.apache.commons.compress.archivers.sevenz; и поэтому перед его использованием нужно прописать в build.gradle зависимость: compile ‘org.apache.commons:commons-compress:1.8’.
Android Stuodio сама скачает библиотеки, а если они устарели, то подскажет о наличии обновления.

Вот экран работающего приложения:

Размер отладочной версии приложения получился 6,8 МБ.
А вот его размер в устройстве после распаковки:

Внимание вопрос кто в черном ящике что в кеше?

В заключении хочу сказать, что распаковка архивов занимает продолжительное время и поэтому нельзя его делать в основном (UI) потоке. Это приведет к подвисанию интерфейса. Во избежание этого можно задействовать AsyncTask , а лучше фоновый сервис т.к. пользователь может не дождаться распаковки и выйти, а вы получите ошибку (правда если не поставите костылей в методе onPostExecute)
Буду рад конструктивной критике в комментариях.

ссылка на оригинал статьи http://habrahabr.ru/post/264701/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *