Иногда бывают моменты, когда застреваешь на очень простой задаче. Мне никогда не приходилось взаимодействовать со сжатыми архивами (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 !