B4A Library AsyncTask

This library wraps the android AsyncTask class.

AsyncTask enables proper and easy use of the UI thread.
This class allows you to perform background operations and publish results on the UI thread without having to manipulate threads and/or handlers.

An asynchronous task is defined by a computation that runs on a background thread and whose result is published on the UI thread.
An asynchronous task is defined by 3 generic types, called Params, Progress and Result, and 4 steps, called PreExecute, DoInBackground, ProgressUpdate and PostExecute.

Here's the documentation for my b4a implementation of AsyncTask:

AsyncTask
Author:
Martin Pearman
Version: 1
  • AsyncTask
    Events:
    • Cancelled (Result As Object)
    • DoInBackground (Params() As Object) As Object
    • PostExecute (Result As Object)
    • PreExecute
    • ProgressUpdate (Progress() As Object)
    Fields:
    • STATUS_FINISHED As Status
      Indicates that event 'PostExecute' has been raised and has completed execution.
    • STATUS_PENDING As Status
      Indicates that the task has not been executed yet.
    • STATUS_RUNNING As Status
      Indicates that the task is running.
    Methods:
    • Cancel (MayInterruptIfRunning As Boolean) As Boolean
      Attempts to cancel execution of this task.
      This attempt will fail if the task has already completed, already been cancelled, or could not be cancelled for some other reason.
      If successful, and this task has not started when cancel is called, this task should never run.
      If the task has already started, then the MayInterruptIfRunning parameter determines whether the thread executing this task should be interrupted in an attempt to stop the task.

      Calling this method will result in the 'Cancelled' event being raised once your 'DoInBackground' callback has completed.
      Your 'DoInBackground' callback can call the IsCancelled method to determine if the task has been cancelled whilst in progress.
      Calling this method ensures that the 'PostExecute' event is never raised.

      Cancel returns True if the task was cancelled, otherwise False.
    • Execute (Params() As Object)
      Executes the task with the specified parameters.
    • Get As Object
      Waits if necessary for the computation to complete, and then retrieves its result.
    • Get2 (TimeoutMillis As Long) As Object
      Waits if necessary for at most the given time for the computation to complete, and then retrieves its result.
    • GetStatus As Status
      Returns the current status of this task.
    • Initialize (EventName As String)
      Initialize the AsyncTask.
      A MissingCallbackException will be raised if no callback sub is found for the 'DoInBackground' event.
    • IsCancelled As Boolean
      Returns True if the task was cancelled before it completed normally.
    • IsInitialized As Boolean
    • PublishProgress (Progress() As Object)
      This method can be invoked from the 'DoInBackground' callback to publish updates on the UI thread while the background computation is still running.
      The 'ProgressUpdate' callback will be called on the UI thread and passed the Progress array.

For anyone that's interested, here's the source code for the library's only class:

B4X:
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

import anywheresoftware.b4a.AbsObjectWrapper;
import anywheresoftware.b4a.BA;

@BA.Events(values={
  "Cancelled(Result As Object)",
  "DoInBackground(Params() As Object) As Object",
  "PostExecute(Result As Object)",
  "PreExecute",
  "ProgressUpdate(Progress() As Object)"
})
@BA.ShortName("AsyncTask")
public class AsyncTask extends AbsObjectWrapper<AsyncTask.AsyncTaskImpl> {

  /**
  * Indicates that event 'PostExecute' has been raised and has completed execution.
  */
  public static final android.os.AsyncTask.Status STATUS_FINISHED=android.os.AsyncTask.Status.FINISHED;

  /**
  * Indicates that the task has not been executed yet.
  */
  public static final android.os.AsyncTask.Status STATUS_PENDING=android.os.AsyncTask.Status.PENDING;

  /**
  * Indicates that the task is running.
  */
  public static final android.os.AsyncTask.Status STATUS_RUNNING=android.os.AsyncTask.Status.RUNNING;

  /**
  * Attempts to cancel execution of this task.
  * This attempt will fail if the task has already completed, already been cancelled, or could not be cancelled for some other reason.
  * If successful, and this task has not started when cancel is called, this task should never run.
  * If the task has already started, then the MayInterruptIfRunning parameter determines whether the thread executing this task should be interrupted in an attempt to stop the task.
  *
  * Calling this method will result in the 'Cancelled' event being raised once your 'DoInBackground' callback has completed.
  * Your 'DoInBackground' callback can call the IsCancelled method to determine if the task has been cancelled whilst in progress.
  * Calling this method ensures that the 'PostExecute' event is never raised.
  *
  * Cancel returns True if the task was cancelled, otherwise False.
  */
  public boolean Cancel(boolean MayInterruptIfRunning){
  return getObject().cancel(MayInterruptIfRunning);
  }

  /**
  * Executes the task with the specified parameters.
  */
  public void Execute(Object[] Params){
  getObject().execute(Params);
  }

  /**
  * Waits if necessary for the computation to complete, and then retrieves its result.
  */
  public Object Get() throws ExecutionException, InterruptedException {
  return getObject().get();
  }

  /**
  * Waits if necessary for at most the given time for the computation to complete, and then retrieves its result.
  */
  public Object Get2(long TimeoutMillis) throws InterruptedException, ExecutionException, TimeoutException {
  return getObject().get(TimeoutMillis, TimeUnit.MILLISECONDS);
  }

  /**
  * Returns the current status of this task.
  */
  public android.os.AsyncTask.Status GetStatus(){
  return getObject().getStatus();
  }

  /**
  * Initialize the AsyncTask.
  * A MissingCallbackException will be raised if no callback sub is found for the 'DoInBackground' event.
  */
  public void Initialize(BA ba, String EventName) throws MissingCallbackException {
  setObject(new AsyncTaskImpl(ba, EventName));
  }

  /**
  * Returns True if the task was cancelled before it completed normally.
  */
  public boolean IsCancelled(){
  return getObject().isCancelled();
  }

  /**
  * This method can be invoked from the 'DoInBackground' callback to publish updates on the UI thread while the background computation is still running.
  * The 'ProgressUpdate' callback will be called on the UI thread and passed the Progress array.
  */
  public void PublishProgress(Object[] Progress){
  getObject().publishProgress2(Progress);
  }

  @BA.Hide
  public static final class AsyncTaskImpl extends android.os.AsyncTask<Object, Object, Object>{

  private static final String EVENT_NAME_CANCELLED="Cancelled";
  private static final String EVENT_NAME_DO_IN_BACKGROUND="DoInBackground";
  private static final String EVENT_NAME_POST_EXECUTE="PostExecute";
  private static final String EVENT_NAME_PRE_EXECUTE="PreExecute";
  private static final String EVENT_NAME_PROGRESS_UPDATE="ProgressUpdate";

  private static final String EXCEPTION_MESSAGE_MISSING_CALLBACK="DoInBackground callback sub not found: ";

  private final BA ba;

  private final String eventNameCancelled;
  private final String eventNameDoInBackground;
  private final String eventNamePostExecute;
  private final String eventNamePreExecute;
  private final String eventNameProgressUpdate;

  private final boolean subExistsCancelled;
  private final boolean subExistsPostExecute;
  private final boolean subExistsPreExecute;
  private final boolean subExistsProgressUpdate;

  public AsyncTaskImpl(BA ba, String EventName) throws MissingCallbackException {
  super();
  this.ba=ba;

  eventNameCancelled=getEventName(EventName, EVENT_NAME_CANCELLED);
  subExistsCancelled=ba.subExists(eventNameCancelled);

  eventNameDoInBackground=getEventName(EventName, EVENT_NAME_DO_IN_BACKGROUND);

  eventNamePostExecute=getEventName(EventName, EVENT_NAME_POST_EXECUTE);
  subExistsPostExecute=ba.subExists(eventNamePostExecute);

  eventNamePreExecute=getEventName(EventName, EVENT_NAME_PRE_EXECUTE);
  subExistsPreExecute=ba.subExists(eventNamePreExecute);

  eventNameProgressUpdate=getEventName(EventName, EVENT_NAME_PROGRESS_UPDATE);
  subExistsProgressUpdate=ba.subExists(eventNameProgressUpdate);

  if(ba.subExists(eventNameDoInBackground)==false){
  throw new MissingCallbackException(new StringBuilder(EXCEPTION_MESSAGE_MISSING_CALLBACK).append(eventNameDoInBackground).toString());
  }
  }

  private String getEventName(String baseEventName, String event){
  return new StringBuilder(baseEventName).append('_').append(event).toString().toLowerCase(BA.cul);
  }

  @Override
  protected Object doInBackground(Object... params) {
  return ba.raiseEvent(AsyncTaskImpl.this, eventNameDoInBackground, new Object[]{params});
  }

/*  @Override
  protected void onCancelled() {}*/

  @Override
  protected void onCancelled(Object result) {
  if(subExistsCancelled) {
  ba.raiseEvent(AsyncTaskImpl.this, eventNameCancelled, new Object[]{result});
  }
  }

  @Override
  protected void onPostExecute(Object result) {
  if(subExistsPostExecute) {
  ba.raiseEvent(AsyncTaskImpl.this, eventNamePostExecute, new Object[]{result});
  }
  }

  @Override
  protected void onPreExecute() {
  if(subExistsPreExecute){
  ba.raiseEvent(AsyncTaskImpl.this, eventNamePreExecute, new Object[0]);
  }
  }


  @Override
  protected void onProgressUpdate(Object... progress) {
  if(subExistsProgressUpdate) {
  ba.raiseEvent(AsyncTaskImpl.this, eventNameProgressUpdate, new Object[]{progress});
  }
  }

  public void publishProgress2(Object[] values){
  publishProgress(values);
  }

  }

  @BA.Hide
  public static final class MissingCallbackException extends Exception{
  public MissingCallbackException(String message){
  super(message);
  }
  }
}

I implement AsyncTask as AsyncTaskImpl and then wrap instances of AsyncTaskImpl in the library.
AsyncTaskImpl simply relays the android callbacks to b4a by raising it's various events.

I've put together a basic usage example, here i created a class named MyAsyncTask:

B4X:
#Event: TaskCancelled(ValueReached as Long)
#Event: TaskComplete(ValueReached as Long)
#Event: TaskProgress(PercentProgress as Int)

Sub Class_Globals
   Private mAsyncTask As AsyncTask
   Private mEventName As String
   Private mParent As Object
End Sub

public Sub Cancel(MayInterruptIfRunning As Boolean)
   mAsyncTask.Cancel(MayInterruptIfRunning)
End Sub

Public Sub Initialize(Parent As Object, EventName As String)
   mAsyncTask.Initialize("mAsyncTask")
   mEventName=EventName
   mParent=Parent
End Sub

'   execute the async task, start counting from zero up to the Target value
public Sub Execute(Target As Long, PublishProgress As Boolean)
   mAsyncTask.Execute(Array As Object(Target, PublishProgress))
End Sub

private Sub mAsyncTask_Cancelled(Result As Object)
   '   this sub is called on the Main UI thread
   '   adding this callback sub is optional
   Log("mAsyncTask_Cancelled")
 
   Dim ValueReached As Long=Result
   RaiseEvent("TaskCancelled", ValueReached)
End Sub

private Sub mAsyncTask_DoInBackground(Params() As Object) As Object
   '   this sub is called on a background thread
   '   any other methods called from this method will execute in the same background thread
   '   do not attempt to update the UI from this sub
   '   adding this callback sub is NOT optional
   Log("mAsyncTask_DoInBackground")
 
   Dim PercentProgress As Int=0
   Dim PublishProgress As Boolean=Params(1)
   Dim Start As Long=0
   Dim Stop As Long=Params(0)
 
   Do While Start<Stop And Not(mAsyncTask.IsCancelled)
     Start=Start+1
     Dim NewPercentProgress As Int=(Start/Stop)*100
     If PublishProgress And PercentProgress<>NewPercentProgress Then
       '   publish periodical progress
       PercentProgress=NewPercentProgress
       mAsyncTask.PublishProgress(Array As Object(PercentProgress))
     End If
   Loop
 
   Dim Result As Object=Start
   Return Result
End Sub

private Sub mAsyncTask_PostExecute(Result As Object)
   '   this sub is called on the Main UI thread
   '   adding this callback sub is optional
   Log("mAsyncTask_PostExecute")
 
   '   the AsyncTask library is compatible with android API 13+
   '   if you are targetting older versions of the API then apply this (commented out) workaround before executing any further code in this sub:
 
'   If mAsyncTask.IsCancelled Then
'     mAsyncTask_Cancelled(Result)
'     Return
'   End If
 
   Dim ValueReached As Long=Result
   RaiseEvent("TaskComplete", ValueReached)
End Sub

private Sub mAsyncTask_PreExecute
   '   this sub is called on the Main UI thread
   '   adding this callback sub is optional
   Log("mAsyncTask_PreExecute")
End Sub

private Sub mAsyncTask_ProgressUpdate(Progress() As Object)
   '   this sub is called on the Main UI thread
   '   adding this callback sub is optional
   Log("mAsyncTask_ProgressUpdate")
 
   Dim PercentProgress As Int=Progress(0)
   RaiseEvent("TaskProgress", PercentProgress)
End Sub

private Sub RaiseEvent(Event As String, Value As Object)
   Dim EventName As String=mEventName&"_"&Event
   If SubExists(mParent, EventName) Then
     CallSub2(mParent, EventName, Value)
   Else
     Log("Ignoring event "&EventName&" as no callback sub SubExists")
   End If
End Sub

Then in my activity:

B4X:
Sub Process_Globals
   Private COUNT_TO_VALUE As Long=99000000
   Private PUBLISH_PROGRESS As Boolean=True
   Private MyAsyncTask1 As MyAsyncTask
End Sub

Sub Globals
 
   Private LabelProgress As Label
   Private LabelResult As Label
End Sub

Sub Activity_Create(FirstTime As Boolean)
   Activity.LoadLayout("Layout1")
End Sub

Sub Activity_Resume

End Sub

Sub Activity_Pause (UserClosed As Boolean)

End Sub

Sub ButtonCancel_Click
   If MyAsyncTask1.IsInitialized Then
     MyAsyncTask1.Cancel(True)
   End If
End Sub

Sub ButtonStart_Click
   LabelProgress.Text=""
   LabelResult.Text=""
   If Not(MyAsyncTask1.IsInitialized) Then
     MyAsyncTask1.Initialize(Me, "MyAsyncTask1")
     MyAsyncTask1.Execute(COUNT_TO_VALUE, PUBLISH_PROGRESS)
   End If
End Sub

Sub MyAsyncTask1_TaskCancelled(ValueReached As Long)
   LabelResult.Text="The task was cancelled, a value of "&ValueReached&" was reached, target was "&COUNT_TO_VALUE
   Dim MyAsyncTask1 As MyAsyncTask
End Sub

Sub MyAsyncTask1_TaskComplete(ValueReached As Long)
   LabelResult.Text="The task completed, a value of "&ValueReached&" was reached, target was "&COUNT_TO_VALUE
   Dim MyAsyncTask1 As MyAsyncTask
End Sub

Sub MyAsyncTask1_TaskProgress(PercentProgress As Int)
   LabelProgress.Text = PercentProgress & "%"
End Sub

I click start and my class starts counting from zero to 99000000.
A label displays the current percentage counted from zero to 99000000.
I can let the task complete or i can cancel it.
Upon completion or cancellation another label shows how far the task counted to.

Let the task complete and the log will show something like:
mAsyncTask_PreExecute
mAsyncTask_DoInBackground
mAsyncTask_ProgressUpdate (repeated 100 times)
mAsyncTask_PostExecute

Cancel the task and you'll see:
mAsyncTask_PreExecute
mAsyncTask_DoInBackground
mAsyncTask_ProgressUpdate (repeated ? times)
mAsyncTask_Cancelled

Adding callback subs for the library's events is optional except for DoInBackground - you must implement a callback for this event.
I'll put together another (more useful) example and post that shortly.

Library files and b4a example project are attached.

Check out my new ThreadExtras to get the AsyncTask library.

Martin.
 

Attachments

  • AsyncTaskExample.zip
    8.6 KB · Views: 350
Last edited:

warwound

Expert
Licensed User
Longtime User
Here's a more useful example of AsyncTask.

I wrapped some android classes that handle ZIP archives and some additonal InputStream / OutputStream related classes and these are in the attached ZipInputStream library files.

ZipInputStream
Version:
0
  • BufferedOutputStream
    Methods:
    • Close
      Closes this output stream and releases any system resources associated with the stream.
    • Flush
      Flushes this buffered output stream.
    • Initialize (OutputStream1 As OutputStream)
      Creates a new buffered output stream to write data to the specified underlying output stream.
    • Initialize2 (OutputStream1 As OutputStream, Size As Int)
      Creates a new buffered output stream to write data to the specified underlying output stream with the specified buffer size.
    • IsInitialized As Boolean
    • Write (Buffer() As Byte, Offset As Int, Length As Int)
      Writes Length bytes from the specified byte array starting at Offset to this buffered output stream.
    • Write2 (Byte1 As Int)
      Writes the specified byte to this buffered output stream.
  • SequenceInputStream
    Methods:
    • Available As Int
      Returns an estimate of the number of bytes that can be read (or skipped over) from the current underlying input stream
      without blocking by the next invocation of a method for the current underlying input stream.
    • Close
      Closes this input stream and releases any system resources associated with the stream.
    • Initialize (InputStreams As List)
      Initialize this SequenceInputStream with a List of InputStreams.
    • IsInitialized As Boolean
    • Read (Buffer() As Byte, Offset As Int, Length As Int) As Int
      Reads up to Length bytes of data from this input stream into Buffer at Offset.
    • Read2 As Int
      Reads the next byte of data from this input stream.
  • ZipEntry
    Methods:
    • HashCode As Int
      Returns the hash code value for this entry.
    • IsDirectory As Boolean
      Returns true if this is a directory entry.
    • IsInitialized As Boolean
    Properties:
    • Comment As String
      Get or Set the comment string for the entry.
    • CompressedSize As Long
      Get or Set the size of the compressed entry data.
    • Crc As Long
      Get or Set the CRC-32 checksum of the uncompressed entry data.
    • Extra()() As Byte
      Get or Set the extra field data for the entry.
    • Method As Int
      Get or Set the compression method of the entry.
    • Name As String [read only]
      Get the name of the entry.
    • Size As Long
      Get or Set the uncompressed size of the entry data.
    • Time As Long
      Get or Set the last modification time of the entry.
  • ZipFile
    Methods:
    • Close
      Closes the ZIP file.
    • Entries As List
      Returns a List of ZipEntry objects representing the content of this ZipFile.
    • GetEntry (EntryName As String) As ZipEntry
    • GetInputStream (ZipEntry1 As ZipEntry) As InputStream
    • Initialize (Dir As String, Filename As String)
    • IsInitialized As Boolean
    • Size As Int
      Returns the number of entries in the ZIP file.
    Properties:
    • Comment As String [read only]
      Returns the zip file comment, or Null if none.
    • Name As String [read only]
      Returns the path name of the ZIP file.
  • ZipInputStream
    Methods:
    • Available As Int
      Returns 0 after EOF has reached for the current entry data, otherwise always return 1.
    • Close
      Closes this input stream and releases any system resources associated with the stream.
    • CloseEntry
      Closes the current ZIP entry and positions the stream for reading the next entry.
    • GetNextEntry As ZipEntry
      Reads the next ZIP file entry and positions the stream at the beginning of the entry data.
    • Initialize (InputStream1 As InputStream)
    • IsInitialized As Boolean
    • Read (Buffer() As Byte, Offset As Int, Length As Int) As Int
      Reads from the current ZIP entry into an array of bytes.
      Returns the actual number of bytes read, or -1 if the end of the entry is reached.
    • Skip (NumberBytes As Long) As Long
      Skips specified number of bytes in the current ZIP entry.
      Returns the actual number of bytes skipped.

My goal was to unzip very large archives, archives of 4GB and more typically.
Doing this on the Main UI thread was not feasible, the app would soon be force closed.
I could have updated my ZipInputStream library to perform it's reading in a background thread and then raise an event once complete - that would be my usual solution.
But i wanted more control over the process from b4a, i didn't want to hardcode the 'extract and write' code into the library.

So with the help of AsyncTask i created this class:

B4X:
'   ZipExtractor class

#Event: PostExecute(ExtractResult1 As ExtractResult)
#Event: ProgressUpdate(ExtractUpdate1 As ExtractUpdate)

Sub Class_Globals
   '   a custom type used to pass initialization parameters to the Execute method of theis class
   Type ExtractJob( _
     BufferSize As Int, _
     DestDir As String, _
     OverwriteExiting As Boolean, _
     SrcDir As String, _
     SrcNames As List _
   )
   
   '   a custom type to publish result from DoInBackground callback to PostExecute callback
   Type ExtractResult( _
     ExtractTime As Long, _
     Error As String, _
     Success As Boolean _
   )
   
   '   a custom type to publish updates from DoInBackground callback to ProgressUpdate callback
   Type ExtractUpdate( _
     BytesExtracted As Long, _
     BytesExtractedPercent As Int, _
     BytesTotal As Long _
   )
   
   Private mAsyncTask As AsyncTask
   Private mEventNamePostExecute As String
   Private mEventNameProgressUpdate As String
   Private mOwner As Object
   Private mProgressPercent As Int=0
End Sub

public Sub Cancel
   mAsyncTask.Cancel(True)
End Sub

public Sub GetProgressPercent As Int
   Return mProgressPercent
End Sub

public Sub Execute(ExtractJob1 As ExtractJob)
   mAsyncTask.Execute(Array As Object(ExtractJob1))
End Sub

Public Sub Initialize(Owner As Object, EventName As String)
   mAsyncTask.Initialize("mAsyncTask")
   mEventNamePostExecute=EventName&"_PostExecute"
   mEventNameProgressUpdate=EventName&"_ProgressUpdate"
   mOwner=Owner
   
   
End Sub

private Sub mAsyncTask_Cancelled(Result As Object)
   Log("mAsyncTask_Cancelled")
   Dim ExtractResult1 As ExtractResult=Result
   CallSub2(mOwner, mEventNamePostExecute, ExtractResult1)
End Sub

private Sub mAsyncTask_DoInBackground(Params() As Object) As Object
   Log("mAsyncTask_DoInBackground")
   
   Dim ExtractResult1 As ExtractResult
   ExtractResult1.Initialize

   Try
     Dim ExtractJob1 As ExtractJob=Params(0)
     
     Dim ExtractedBytesCount As Long=0
     Dim Buffer(ExtractJob1.BufferSize) As Byte
     Dim ProgressPercent As Int=0
     Dim SrcBytesCount As Long
     Dim SrcFileStream As InputStream
     Dim StartTime As Long=DateTime.Now
     Dim ZipEntry1 As ZipEntry
     Dim ZipInputStream1 As ZipInputStream
     
     If ExtractJob1.SrcNames.Size=1 Then
       '   extract a single file zip archive
       SrcBytesCount=File.Size(ExtractJob1.SrcDir, ExtractJob1.SrcNames.Get(0))
       SrcFileStream=File.OpenInput(ExtractJob1.SrcDir, ExtractJob1.SrcNames.Get(0))
     Else
       '   extract a multi file zip archive
       SrcBytesCount=0
       Dim SrcFileStreams As List
       SrcFileStreams.Initialize
       For Each SrcName As String In ExtractJob1.SrcNames
         SrcBytesCount=SrcBytesCount+File.Size(ExtractJob1.SrcDir, SrcName)
         SrcFileStreams.Add(File.OpenInput(ExtractJob1.SrcDir, SrcName))
       Next
       Dim SequenceInputStream1 As SequenceInputStream
       SequenceInputStream1.Initialize(SrcFileStreams)
       SrcFileStream=SequenceInputStream1
     End If
     
     If Not(File.Exists(ExtractJob1.DestDir, "")) Then
       File.MakeDir(ExtractJob1.DestDir, "")
     End If
     
     '   publish 0% progress
     Dim ExtractUpdate1 As ExtractUpdate
     ExtractUpdate1.Initialize
     ExtractUpdate1.BytesExtracted=ExtractedBytesCount
     ExtractUpdate1.BytesExtractedPercent=ProgressPercent
     ExtractUpdate1.BytesTotal=SrcBytesCount
     mAsyncTask.PublishProgress(Array As Object(ExtractUpdate1))
     
     ZipInputStream1.Initialize(SrcFileStream)
     ZipEntry1=ZipInputStream1.GetNextEntry
     Do While ZipEntry1.IsInitialized And mAsyncTask.IsCancelled=False
       
       ExtractedBytesCount=ExtractedBytesCount+ZipEntry1.CompressedSize
       Dim NewProgressPercent As Int=(ExtractedBytesCount/SrcBytesCount)*100
       If NewProgressPercent>ProgressPercent Then
         '   publish progress
         ProgressPercent=NewProgressPercent
         Dim ExtractUpdate1 As ExtractUpdate
         ExtractUpdate1.Initialize
         ExtractUpdate1.BytesExtracted=ExtractedBytesCount
         ExtractUpdate1.BytesExtractedPercent=ProgressPercent
         ExtractUpdate1.BytesTotal=SrcBytesCount
         mAsyncTask.PublishProgress(Array As Object(ExtractUpdate1))
       End If
       
       If ZipEntry1.IsDirectory Then
         If Not(File.Exists(ExtractJob1.DestDir, ZipEntry1.Name)) Then
           File.MakeDir(ExtractJob1.DestDir, ZipEntry1.Name)
           ZipEntry1=ZipInputStream1.GetNextEntry
           Continue
         End If
       End If
       
       '   ** note we have a bug if OverwriteExisting is True! **
       If ExtractJob1.OverwriteExiting=False And File.Exists(ExtractJob1.DestDir, ZipEntry1.Name) Then
         '   Log("Skipping compressed file "&File.Combine(ExtractJob1.DestDir, ZipEntry1.Name)&", destination file already exists")
         ZipEntry1=ZipInputStream1.GetNextEntry
         Continue
       End If
       
       '   Log("Extracting: "&File.Combine(ExtractJob1.DestDir, ZipEntry1.Name))
       
       Dim Count As Int=0
       Dim OutputStream1 As OutputStream=File.OpenOutput(ExtractJob1.DestDir, ZipEntry1.Name, False)
       Dim BufferedOutputStream1 As BufferedOutputStream
       
       BufferedOutputStream1.Initialize2(OutputStream1, ExtractJob1.BufferSize)
       Count=ZipInputStream1.Read(Buffer, 0, ExtractJob1.BufferSize)
       Do While Count<>-1
         BufferedOutputStream1.Write(Buffer, 0, Count)
         Count=ZipInputStream1.Read(Buffer, 0, ExtractJob1.BufferSize)
       Loop
       
       BufferedOutputStream1.Flush
       BufferedOutputStream1.Close
       
       ZipEntry1=ZipInputStream1.GetNextEntry
     Loop
     
     ZipInputStream1.Close
     SrcFileStream.Close
     
     '   publish 100% progress
     Dim ExtractUpdate1 As ExtractUpdate
     ExtractUpdate1.Initialize
     ExtractUpdate1.BytesExtracted=ExtractedBytesCount
     ExtractUpdate1.BytesExtractedPercent=100
     ExtractUpdate1.BytesTotal=SrcBytesCount
     mAsyncTask.PublishProgress(Array As Object(ExtractUpdate1))
     '   Log("All files in archive processed")
     
     Dim EndTime As Long=DateTime.Now
     '   Log("Processing took: "&(EndTime-StartTime)&" millis")
   
     ExtractResult1.ExtractTime=EndTime-StartTime
     If mAsyncTask.IsCancelled Then
       ExtractResult1.Error="Task cancelled"
       ExtractResult1.Success=False
     Else
       ExtractResult1.Success=True
     End If
   Catch
     ExtractResult1.Success=False
     If LastException.IsInitialized Then
       ExtractResult1.Error=LastException.Message
       '   Log(LastException.Message)
     Else
       ExtractResult1.Error="Unknown Exception occurred"
     End If
   End Try
   
   Return ExtractResult1
End Sub

private Sub mAsyncTask_PostExecute(Result As Object)
   Log("mAsyncTask_PostExecute")
   Dim ExtractResult1 As ExtractResult=Result
'   If ExtractResult1.Success Then
'     Log("All files extracted in "&ExtractResult1.ExtractTime&" millis")
'   Else
'     Log("Failed to extract files: "&ExtractResult1.Error)
'   End If
   CallSub2(mOwner, mEventNamePostExecute, ExtractResult1)
End Sub

private Sub mAsyncTask_PreExecute
   Log("mAsyncTask_PreExecute")
   '   this callback is not used or required, included just as example
End Sub

private Sub mAsyncTask_ProgressUpdate(Progress() As Object)
   Log("mAsyncTask_ProgressUpdate")
   Dim ExtractUpdate1 As ExtractUpdate=Progress(0)
   '   Log("Progress: "&ExtractUpdate1.BytesExtractedPercent&"%")
   CallSub2(mOwner, mEventNameProgressUpdate, ExtractUpdate1)
End Sub

I've created 3 custom Types:
  • ExtractJob defines the parameters required by the Execute method.
  • ExtractUpdate contains fields used to publish progress from the background thread to the Main UI thread.
  • ExtractResult is passed from the DoInBackground sub to the PostExecute sub.

A simple activity is used to start the task and monitor it's progress:

B4X:
Sub Process_Globals
   '   this process global is an instance of the new ZipExtractor class
   Private ZipExtractor1 As ZipExtractor
End Sub

Sub Globals
   Private ArchiveSelectSpinner As Spinner
   Private BufferSizeSpinner As Spinner
   Private ExtractButton As Button
   Private OverwriteCheckBox As CheckBox
   Private ProgressBar1 As ProgressBar
   Private CancelButton As Button
End Sub

Sub Activity_Create(FirstTime As Boolean)
   Activity.LoadLayout("Main")
   
   '   here add the names of any zip archives on sdcard root
   ArchiveSelectSpinner.AddAll(Array As String("my_archive1.zip", "my_archive2.zip", "my_split_archive.zip"))
   
   '   here are some buffer sizes to experiment with
   '   most examples i've seen don't use a buffer larger than 2048 bytes
   BufferSizeSpinner.AddAll(Array As String("2048", "4096", "8192", "16384", "32768"))
End Sub

Sub Activity_Resume
   UpdateUI(True)
End Sub

Sub Activity_Pause (UserClosed As Boolean)

End Sub

Sub CancelButton_Click
   ZipExtractor1.Cancel
End Sub

Sub ExtractButton_Click
   Dim ExtractJob1 As ExtractJob
   ExtractJob1.Initialize
   ExtractJob1.BufferSize=BufferSizeSpinner.SelectedItem
   ExtractJob1.DestDir=File.DirRootExternal
   ExtractJob1.OverwriteExiting=OverwriteCheckBox.Checked
   ExtractJob1.SrcDir=File.DirRootExternal
   
   ExtractJob1.SrcNames.Initialize
   If ArchiveSelectSpinner.SelectedItem="my_split_archive.zip" Then
     '   if you have a multi part archive then you must pass the filenames of all parts
     ExtractJob1.SrcNames.AddAll(Array As String("my_split_archive.001.zip", "my_split_archive.002", "my_split_archive.003"))
   Else
     ExtractJob1.SrcNames.Add(ArchiveSelectSpinner.SelectedItem)
   End If
   
   ZipExtractor1.Initialize(Me, "ZipExtractor1")
   ZipExtractor1.Execute(ExtractJob1)
   
   UpdateUI(True)
End Sub

Sub UpdateUI(UpdateProgressBar As Boolean)
   CancelButton.Enabled=ZipExtractor1.IsInitialized
   ExtractButton.Enabled=Not(ZipExtractor1.IsInitialized)
   If UpdateProgressBar And ZipExtractor1.IsInitialized Then
     ProgressBar1.Progress=ZipExtractor1.GetProgressPercent
   End If
End Sub

Sub ZipExtractor1_PostExecute(ExtractResult1 As ExtractResult)
   Log("ZipExtractor1_PostExecute")
   Dim ZipExtractor1 As ZipExtractor   '   uninitialize the process global now it's completed it's task
   ProgressBar1.Progress=100
   UpdateUI(False)
   If ExtractResult1.Success=False Then
     Log(ExtractResult1.Error)
   End If
End Sub

Sub ZipExtractor1_ProgressUpdate(ExtractUpdate1 As ExtractUpdate)
   ProgressBar1.Progress=ExtractUpdate1.BytesExtractedPercent
   UpdateUI(False)
End Sub

So far in my tests, the code hasn't failed once.
It extracts single and multi part zip archives of many GBs to the device's external storage.

More info on android's built in support for zip files can be found here: https://developer.android.com/reference/java/util/zip/package-summary.html

ZipInputStream library files and b4a example project are attached.

Martin.
 

Attachments

  • ZipInputStream_library_files.zip
    6.2 KB · Views: 186
  • ZipInputStream_b4a_example.zip
    17.9 KB · Views: 194

peacemaker

Expert
Licensed User
Longtime User
Thanks for the lib !
Trying the sample under Android 4.4 emulator, debug mode, get error:
B4X:
** Activity (main) Create, isFirst = true **
** Activity (main) Resume **
Unexpected event (missing RaiseSynchronousEvents): masynctask_preexecute
Check the unfiltered logs for the full stack trace.
mAsyncTask_PreExecute
mAsyncTask_DoInBackground
mAsyncTask_PostExecute
Error occurred on line: 74 (MyAsyncTask)
java.lang.NumberFormatException: Invalid long: "null"
    at java.lang.Long.invalidLong(Long.java:124)
    at java.lang.Long.parse(Long.java:361)
    at java.lang.Long.parseLong(Long.java:352)
    at java.lang.Long.parseLong(Long.java:318)
    at anywheresoftware.b4a.BA.ObjectToLongNumber(BA.java:654)
    at b4a.example.asynctask.myasynctask._masynctask_postexecute(myasynctask.java:224)
    at java.lang.reflect.Method.invokeNative(Native Method)
    at java.lang.reflect.Method.invoke(Method.java:515)
    at anywheresoftware.b4a.shell.Shell.runMethod(Shell.java:710)
    at anywheresoftware.b4a.shell.Shell.raiseEventImpl(Shell.java:342)
    at anywheresoftware.b4a.shell.Shell.raiseEvent(Shell.java:249)
    at java.lang.reflect.Method.invokeNative(Native Method)
    at java.lang.reflect.Method.invoke(Method.java:515)
    at anywheresoftware.b4a.ShellBA.raiseEvent2(ShellBA.java:139)
    at anywheresoftware.b4a.BA.raiseEvent(BA.java:166)
    at ifims.android.os.AsyncTask$AsyncTaskImpl.onPostExecute(AsyncTask.java:173)
    at android.os.AsyncTask.finish(AsyncTask.java:632)
    at android.os.AsyncTask.access$600(AsyncTask.java:177)
 

warwound

Expert
Licensed User
Longtime User
Are you referring to this:

Unexpected event (missing RaiseSynchronousEvents): masynctask_preexecute

That's usually related to using debug mode - i've no idea how AsyncTask's DoInBackground sub will be handled in debug mode.

mAsyncTask_PostExecute
Error occurred on line: 74 (MyAsyncTask)
java.lang.NumberFormatException: Invalid long: "null"

That sounds like your DoInBackground sub has not returned an Object that can be converted to a Long type.
The return value of DoInBackground is passed as a parameter to the PostExecute sub.

If you're using debug mode then try release.
If debug mode causes a problem it may be that your DoInBackground sub 'looks like' it'll return a value but an incompatibility with debug mode prevents the passing of the value.
 

peacemaker

Expert
Licensed User
Longtime User
Thanks, yes, in Release mode works OK, it's your example project as is.
 

warwound

Expert
Licensed User
Longtime User
is there any difference between this library, CallSubExtended library and Thread library ?

Let's start by agreeing that AsyncTask is officially android's recommended way to execute basic background tasks.
It's structure encourages good coding techniques.

The b4a Threading library looks to be better suited for more elaborate background taks - it supports Locks and give a finer grain of control over the process.

I've got no idea what CallSubExtended offers so can't comment there.

Neither is any better than the other i guess and neither is the same as the other...
 

Informatix

Expert
Licensed User
Longtime User
is there any difference between this library, CallSubExtended library and Thread library ?
CallSubExtended uses internally BA.submitRunnable, which seems to be the function to use with B4A to run something in the background.
 
Top