How to extract 7z archive on Android

By | 09.06.2020

Sometimes there are times when you get stuck on a very simple task. I never had to interact with compressed archives (zip, 7Zip and so on) on Android, and I could not imagine that it would be a difficult task. The difficulty here was not to do this, but to find the normal documentation, which will describe how to do this.

By looking for solutions on the Internet, you can find only old and inappropriate solutions, which at that time were considered simple.

Solution

After some searching, I found a library from Apache called Commons Compress that supports many types of archives: ar, cpio, dump Unix, tar, zip, gzip, XZ, Pack200, bzip2, 7z, arj, lzma, snappy, DEFLATE and Z files . The library is actively updated and is required for its work Java 7.

Installation

In order to add the library to the application, you need to add the following line to the build.gradle file of the app module in the dependencies block:

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

Using

Let’s create a simple application that will extract files from the selected 7Zip archive into the cache folder, and then display their list on the screen.

To do this, place the Button and ListView components on the screen, adding them to the activity_main.xml layout, which is located in the res/layout folder.

<?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="Extract"
      />

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

</LinearLayout>

Then declare these components in the MainActivity.java activity code.

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);
    btn_extract.setOnClickListener(new View.OnClickListener() {
      @Override public void onClick(View view) {
        extractArchive();
        fillList();
      }
    });
  }

...

In the extractArchive() method, we will do the main work of extracting files using the library. The code for this method is shown below:

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;
}

private void extractArchive() {
  String filename = "kittens.7z";
  try {
    SevenZFile sevenZFile = new SevenZFile(getAssetFile(this, "kittens.7z", "tmp"));
    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();
  }
}

IMPORTANT! If your application crashes when executing library methods with the error “java.lang.NoClassDefFoundError: Failed resolution of: Lorg/tukaani/xz/LZMA2Options;“, adding the following library to the build.gradle file may help:

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

Then, when the archive is unpacked, we list the files in the fillList() method, creating a simple adapter and passing it to the ListView.

private void fillList() {
  List<String> files = new ArrayList<>();
  File cache_dir = new File(getCacheDir() + "/kittens");
  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);
}

The activity code as a whole is as follows:

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);
    btn_extract.setOnClickListener(new View.OnClickListener() {
      @Override public void onClick(View view) {
        extractArchive();
        fillList();
      }
    });
  }

  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;
  }

  private void extractArchive() {
    String filename = "kittens.7z";
    try {
      SevenZFile sevenZFile = new SevenZFile(getAssetFile(this, "kittens.7z", "tmp"));
      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() + "/kittens");
    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);
  }
}

So, with just a few lines of code, we created a simple application for extracting files from a 7z archive.

Conclusion

As you can see, this library is very easy to use and has rich functionality. Its drawback may be, perhaps, the fact that it may turn out to be too powerful and heavy for some simple project.

Good luck finding easy solutions!

Leave a Reply

Your email address will not be published.