Иногда бывают моменты, когда застреваешь на очень простой задаче. Мне никогда не приходилось взаимодействовать со сжатыми архивами (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-архива.
Заключение
Как можно видеть, данная библиотека весьма проста в использовании и обладает богатым функционалом. Недостатком её может стать, разве что, то, что она может оказаться слишком мощной и тяжеловесной для какого-нибудь простого проекта.
Удачи в поисках простых решений!
Большое спасибо, особенно за подсказку насчёт NoClassDefFoundError !