Как извлечь 7z архив на Android

Автор: | 28.01.2019

Иногда бывают моменты, когда застреваешь на очень простой задаче. Мне никогда не приходилось взаимодействовать со сжатыми архивами (zip, 7Zip и так далее) на Android, и я не мог себе представить, что это окажется сложной задачей. Трудность здесь заключалась не в том, чтобы сделать это, а в том, чтобы найти нормальную документацию, в которой будет написано о том, как это сделать.

Поискав решения в Интернете, можно найти только старые и неуместные решения, которые на тот момент считались простыми.

Решение

После некоторых поисков я обнаружил библиотеку от Apache под названием Commons Compress, которая поддерживает множество типов архивов:  ar, cpio, дамп Unix, tar, zip, gzip, XZ, Pack200, bzip2, 7z, arj, lzma, snappy, DEFLATE и Z файлы. Библиотека активно обновляется и требуется для своей работы Java 7.

Установка

Для того, чтобы добавить библиотеку в приложение, нужно добавить следующую строку в файл build.gradle модуля приложения в блок dependencies:

implementation 'org.apache.commons:commons-compress:1.18'

Использование

Создадим простое приложение, которое будет извлекать файлы из выбранного 7Zip-архива в папку для кэша, а затем выводить их список на экран.

Для этого разместим на экране компоненты Button и ListView, добавив их в разметку activity_main.xml, которая находится в папке res/layout.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity"
    >

  <Button
      android:id="@+id/btn_extract"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:layout_gravity="center_horizontal"
      android:layout_marginTop="12dp"
      android:text="Извлечь"
      />

  <ListView
      android:id="@+id/listview"
      android:layout_width="match_parent"
      android:layout_height="match_parent"
      android:layout_marginTop="6dp"
      android:visibility="gone"
      />

</LinearLayout>

Затем объявим эти компоненты в коде активности MainActivity.java.

public class MainActivity extends AppCompatActivity {
  private ListView listView;

  @Override protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    listView = findViewById(R.id.listview);
    Button btn_extract = findViewById(R.id.btn_extract);
  }

...

Поскольку архив будет находиться на SD-карте устройства, сначала нужно будет проверить наличие прав на чтение. Если прав нет - нужно запросить их.

private static final int REQUEST_PERMISSION_READ = 101;

@Override protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);

  ...

  btn_extract.setOnClickListener(new View.OnClickListener() {
    @Override public void onClick(View view) {
      if (ContextCompat.checkSelfPermission(MainActivity.this,
          android.Manifest.permission.WRITE_EXTERNAL_STORAGE)
          == PackageManager.PERMISSION_GRANTED) {
        extractArchive();
        fillList();
      } else {
        ActivityCompat.requestPermissions(MainActivity.this,
            new String[] { android.Manifest.permission.WRITE_EXTERNAL_STORAGE },
            REQUEST_PERMISSION_READ);
      }
    }
  });
}

@Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
    @NonNull int[] grantResults) {
  switch (requestCode) {
    case REQUEST_PERMISSION_READ: {
      if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
        extractArchive();
        fillList();
      }
      break;
    }
  }
}

В методе extractArchive() мы будем выполнять основную работу по извлечению файлов с использованием библиотеки. Код этого метода показан ниже:

private void extractArchive() {
  String path = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
      .getAbsolutePath();
  String filename = "test.7z";
  try {
    SevenZFile sevenZFile = new SevenZFile(new File(path, filename));
    SevenZArchiveEntry entry = sevenZFile.getNextEntry();
    File cache_dir = new File(getCacheDir() + "/" + filename.substring(0, filename.indexOf(".")));
    if (!cache_dir.exists()) cache_dir.mkdirs();
    while (entry != null) {
      FileOutputStream out = new FileOutputStream(cache_dir + "/" + entry.getName());
      byte[] content = new byte[(int) entry.getSize()];
      sevenZFile.read(content, 0, content.length);
      out.write(content);
      out.close();
      entry = sevenZFile.getNextEntry();
    }
    sevenZFile.close();
  } catch (IOException e) {
    e.printStackTrace();
  }
}

ВАЖНО! Если у вас при выполнении методов библиотеки приложение падает с ошибкой "java.lang.NoClassDefFoundError: Failed resolution of: Lorg/tukaani/xz/LZMA2Options;", то может помочь добавление в файл build.gradle модуля приложения следующей библиотеки:

implementation group: 'org.tukaani', name: 'xz', version: '1.5'

Затем, когда архив распакован, выведем список файлов в методе fillList(), создав простой адаптер и передав его в ListView.

private void fillList() {
  List<String> files = new ArrayList<>();
  File cache_dir = new File(getCacheDir() + "/test");
  for (File f : cache_dir.listFiles()) {
    files.add(f.getAbsolutePath());
  }
  ArrayAdapter<String> itemsAdapter =
      new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, files);
  listView.setAdapter(itemsAdapter);
  listView.setVisibility(View.VISIBLE);
}

Код активности целом выглядит следующим образом:

public class MainActivity extends AppCompatActivity {
  private static final int REQUEST_PERMISSION_READ = 101;
  private ListView listView;

  @Override protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    listView = findViewById(R.id.listview);
    Button btn_extract = findViewById(R.id.btn_extract);
    btn_extract.setOnClickListener(new View.OnClickListener() {
      @Override public void onClick(View view) {
        if (ContextCompat.checkSelfPermission(MainActivity.this,
            android.Manifest.permission.WRITE_EXTERNAL_STORAGE)
            == PackageManager.PERMISSION_GRANTED) {
          extractArchive();
          fillList();
        } else {
          ActivityCompat.requestPermissions(MainActivity.this,
              new String[] { android.Manifest.permission.WRITE_EXTERNAL_STORAGE },
              REQUEST_PERMISSION_READ);
        }
      }
    });
  }

  @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
      @NonNull int[] grantResults) {
    switch (requestCode) {
      case REQUEST_PERMISSION_READ: {
        if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
          extractArchive();
          fillList();
        }
        break;
      }
    }
  }

  private void extractArchive() {
    String path = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
        .getAbsolutePath();
    String filename = "test.7z";
    try {
      SevenZFile sevenZFile = new SevenZFile(new File(path, filename));
      SevenZArchiveEntry entry = sevenZFile.getNextEntry();
      File cache_dir = new File(getCacheDir() + "/" + filename.substring(0, filename.indexOf(".")));
      if (!cache_dir.exists()) cache_dir.mkdirs();
      while (entry != null) {
        FileOutputStream out = new FileOutputStream(cache_dir + "/" + entry.getName());
        byte[] content = new byte[(int) entry.getSize()];
        sevenZFile.read(content, 0, content.length);
        out.write(content);
        out.close();
        entry = sevenZFile.getNextEntry();
      }
      sevenZFile.close();
    } catch (IOException e) {
      e.printStackTrace();
    }
  }

  private void fillList() {
    List<String> files = new ArrayList<>();
    File cache_dir = new File(getCacheDir() + "/test");
    for (File f : cache_dir.listFiles()) {
      files.add(f.getAbsolutePath());
    }
    ArrayAdapter<String> itemsAdapter =
        new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, files);
    listView.setAdapter(itemsAdapter);
    listView.setVisibility(View.VISIBLE);
  }
}

Так, с помощью всего нескольких строчек кода, мы создали простое приложение для извлечения файлов из 7z-архива.

Заключение

Как можно видеть, данная библиотека весьма проста в использовании и обладает богатым функционалом. Недостатком её может стать, разве что, то, что она может оказаться слишком мощной и тяжеловесной для какого-нибудь простого проекта.

Удачи в поисках простых решений!

Как извлечь 7z архив на Android: 1 комментарий

  1. Иван

    Большое спасибо, особенно за подсказку насчёт NoClassDefFoundError !

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

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