Pull to refresh

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

Reading time 4 min
Views 23K
В мобильной разработке бывает потребность сделать приложение для работы без интернета. Например, словарь или справочник, который будет использоваться в суровых полевых условиях. Тогда для работы приложения нужно единожды выкачать архив с данными и сохранить его у себя. Сделать это можно посредством запроса в сеть, но так же можно зашить архив с данными внутрь приложения.

Согласно требованиям 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).

Буду рад конструктивной критике в комментариях.
Tags:
Hubs:
+3
Comments 10
Comments Comments 10

Articles