Hi all developers,
I wrote a native code wrapper for a library that manages Zip files
https://github.com/srikanth-lingala/zip4j
https://jar-download.com/artifacts/net.lingala.zip4j/zip4j/2.6.1/documentation
This library permit to do some interesting things with Zip files.
The wrapper I wrote seem to work well, but the only problem I had is that it blocks the main thread while do operations on Zip files.
This is not a big problem with small files, but is a real problem with large files where the main thread is blocked, I cannot raise
events on main thread to log the operation progress, change a value of a progressbar etc.
The library has _Progress event, this is fired on the Main thread, the user can print it on the log, set a progressbar value etc...
this currently happen but only when operation on files is done.
On library every command can thrown an exception and return a boolean value to inform the user if operation was successfully completed or error occoured.
So the user can do eg, something like this:
I searched on the forum and found this good tutorial by Erel:
https://www.b4x.com/android/forum/threads/java-libraries-and-multithreading.12697/#content
I managed it to work in background using a Runnable as Erel suggested, the problem here is that Runnable cannot return a value, it's signature is 'void' and it cannot thrown an exception I catch with try-catch.
Searching on the web I found that a better solution is to use Callable, so I've read #post2 in the same Erel thread:
I do not yet tried it, but the problem is that as Erel said on same thread, it can only return the value when the task completes:
What I want is to return a boolean value from functions and even the _Error event where I pass the message string of exception occoured.
Naturally even raise _Progress event....
Because I'm not very Java expert I accept any suggestion to solve this, here I post just a function that use this mechanism, but my library
has 12 of these that I need to change to work the right way.
Any suggestion appreciated
Many Thanks
Max
I wrote a native code wrapper for a library that manages Zip files
https://github.com/srikanth-lingala/zip4j
https://jar-download.com/artifacts/net.lingala.zip4j/zip4j/2.6.1/documentation
This library permit to do some interesting things with Zip files.
The wrapper I wrote seem to work well, but the only problem I had is that it blocks the main thread while do operations on Zip files.
This is not a big problem with small files, but is a real problem with large files where the main thread is blocked, I cannot raise
events on main thread to log the operation progress, change a value of a progressbar etc.
The library has _Progress event, this is fired on the Main thread, the user can print it on the log, set a progressbar value etc...
this currently happen but only when operation on files is done.
Events:
@Events(values={"Progress (Method As String, Percent As Int, Complete As Boolean)", "Error (Method As String, Error As String)"})
On library every command can thrown an exception and return a boolean value to inform the user if operation was successfully completed or error occoured.
So the user can do eg, something like this:
B4X:
Dim Success = Zip.ExtractZip(..., ..., ...)
If Success Then
... open here an extracted file
End If
I searched on the forum and found this good tutorial by Erel:
https://www.b4x.com/android/forum/threads/java-libraries-and-multithreading.12697/#content
I managed it to work in background using a Runnable as Erel suggested, the problem here is that Runnable cannot return a value, it's signature is 'void' and it cannot thrown an exception I catch with try-catch.
Searching on the web I found that a better solution is to use Callable, so I've read #post2 in the same Erel thread:
Java:
public static void runAsync(final BA ba, final Object Sender, String FullEventName,
final Object[] errorResult, final Callable<Object[]> callable)
public void Initialize(String Dir, String FileName) throws IOException, BiffException {
InputStream in = File.OpenInput(Dir, FileName).getObject();
Workbook w = Workbook.getWorkbook(in, getDefaultSettings(Encoding));
in.close();
setObject(w);
}
public void InitializeAsync(BA ba, String EventName, final String Dir, final String FileName) {
BA.runAsync(ba, this, EventName + "_ready", new Object[] {false}, new Callable<Object[]>() {
@Override
public Object[] call() throws Exception {
Initialize(Dir, FileName);
return new Object[] {true};
}
});
}
I do not yet tried it, but the problem is that as Erel said on same thread, it can only return the value when the task completes:
... so the problem here seem to be I cannot anyway raise the _Progress event in the middle.Just to make sure that it is clear, the event is raised when the task completes.
You can use ba.raiseEventFromDifferentThread if you want to raise intermediate events.
What I want is to return a boolean value from functions and even the _Error event where I pass the message string of exception occoured.
Naturally even raise _Progress event....
Because I'm not very Java expert I accept any suggestion to solve this, here I post just a function that use this mechanism, but my library
has 12 of these that I need to change to work the right way.
Any suggestion appreciated
Many Thanks
Max
Original blocking function:
public boolean ExtractZip(final String ZipPath, final String DestFolder, final String Password) {
// Initiate ZipFile object with the path/name of the zip file.
ZipFile zipFile = new ZipFile(ZipPath);
final String evt = eventName + "_progress";
final String errEvt = eventName + "_error";
final int Percent, oldPercent;
try {
Percent = 0; oldPercent = 0;
// Set true to monitor progress state
// This work on different thread
zipFile.setRunInThread(true);
// Check to see if the zip file is password protected
if (Password.length() > 0) {
try {
// If yes, then set the password for the zip file
zipFile.setPassword(Password.toCharArray());
} catch (Exception e) {
BA.Log("Zip4j: ExtractZip -> Cannot set Password for file " + ZipPath);
if (ba.subExists(errEvt)) {
ba.raiseEvent(this, errEvt, "ExtractZip", "Cannot set Password for file " + ZipPath);
}
return false;
}
}
// Extracts all files to the path specified
zipFile.extractAll(DestFolder);
// Get progress
if (ba.subExists(evt)) {
ProgressMonitor progressMonitor = zipFile.getProgressMonitor();
while (progressMonitor.getState().equals(ProgressMonitor.State.BUSY)) { // New syntax
//BA.Log("Extracting. Percent done: " + progressMonitor.getPercentDone());
Percent = progressMonitor.getPercentDone();
if (oldPercent != Percent) {
//BA.Log("Zip4j: RAISE EVENT: " + evt);
ba.raiseEvent(this, evt, "ExtractZip", Percent, false);
}
oldPercent = Percent;
}
// HERE OPERATION IS DONE
BA.Log("Zip4j: ExtractZip -> Done extracted content of file " + ZipPath +
" into Folder " + DestFolder + " Result: " + getResultString(progressMonitor.getResult()));
ba.raiseEvent(this, evt, "ExtractZip", 100, true);
} else {
BA.Log("Zip4j: ExtractZip -> Sub " + OriginalEventName +
"_Progress does not exists. Use Async Mode to extract file " + ZipPath +
" into Folder " + DestFolder);
}
return true;
} catch (ZipException e) {
String err = e.getMessage().replace("net.lingala.zip4j.exception.ZipException: net.lingala.zip4j.exception.ZipException: ", "");
err = err.replaceAll(":", "");
//BA.Log("Zip4j: ExtractZip -> " + err);
if (ba.subExists(errEvt)) {
ba.raiseEvent(this, errEvt, "ExtractZip", err);
}
return false;
} finally {
try {
zipFile.close();
} catch (IOException e) {
e.printStackTrace();
if (ba.subExists(errEvt)) {
ba.raiseEvent(this, errEvt, "ExtractZip", e.getMessage());
}
return false;
};
}
}
Unblocking. But cannot return value:
public void ExtractZip(final String ZipPath, final String DestFolder, final String Password) {
Runnable r = new Runnable() {
@Override
public void run() {
// Initiate ZipFile object with the path/name of the zip file.
ZipFile zipFile = new ZipFile(ZipPath);
final String evt = eventName + "_progress";
final String errEvt = eventName + "_error";
final int Percent, oldPercent;
try {
Percent = 0; oldPercent = 0;
// Set true to monitor progress state
// Work on background thread
zipFile.setRunInThread(true);
// Set if the zip file is password protected
if (Password.length() > 0) {
try {
// if yes, then set the password for the zip file
zipFile.setPassword(Password.toCharArray());
} catch (Exception e) {
BA.Log("Zip4j: ExtractZip -> Cannot set Password for file " + ZipPath);
if (ba.subExists(errEvt)) {
ba.raiseEventFromDifferentThread(this, null, 0, errEvt, false,
new Object[] {"ExtractZip", "Cannot set Password for file " + ZipPath});
}
return;
}
}
// Extracts all files to the path specified
zipFile.extractAll(DestFolder);
// Get progress
if (ba.subExists(evt)) {
ProgressMonitor progressMonitor = zipFile.getProgressMonitor();
while (progressMonitor.getState().equals(ProgressMonitor.State.BUSY)) {
//BA.Log("Extracting. Percent done: " + progressMonitor.getPercentDone());
Percent = progressMonitor.getPercentDone();
if (oldPercent != Percent) {
//BA.Log("Zip4j: RAISE EVENT: " + evt);
ba.raiseEventFromDifferentThread(this, null, 0, evt, false, new Object[] {"ExtractZip", Percent, false});
}
oldPercent = Percent;
}
// HERE OPERATION IS DONE
BA.Log("Zip4j: ExtractZip -> Done extracted content of file " + ZipPath +
" into Folder " + DestFolder + " Result: " + getResultString(progressMonitor.getResult()));
BA.Log("Zip4j: RAISE FINAL EVENT: " + evt);
ba.raiseEventFromDifferentThread(this, null, 0, evt, false, new Object[] {"ExtractZip", 100, true});
} else {
BA.Log("Zip4j: ExtractZip -> Sub " + OriginalEventName +
"_Progress does not exists. Use Async Mode to extract file " + ZipPath +
" into Folder " + DestFolder);
}
return;
} catch (ZipException e) {
String err = e.getMessage().replace("net.lingala.zip4j.exception.ZipException: net.lingala.zip4j.exception.ZipException: ", "");
err = err.replaceAll(":", "");
//BA.Log("Zip4j: ExtractZip -> " + err);
if (ba.subExists(errEvt)) {
ba.raiseEventFromDifferentThread(this, null, 0, errEvt, false, new Object[] {"ExtractZip", err});
}
return;
} finally {
try {
zipFile.close();
} catch (IOException e) {
e.printStackTrace();
if (ba.subExists(errEvt)) {
ba.raiseEventFromDifferentThread(this, null, 0, errEvt, false, new Object[] {"ExtractZip", e.getMessage()});
}
return;
};
}
}
};
BA.submitRunnable(r, this, 0);
}
Last edited: