Android thread pool controls concurrent multithreaded downloads

  • 2021-10-27 08:56:44
  • OfStack

Multi-threaded downloading is not that the more concurrent download threads, the better, because when the user opens too many concurrent threads, the application needs to maintain the overhead of each thread and the overhead of thread synchronization.

These overhead will lead to lower download speed. Therefore, it is necessary to avoid opening a large number of threads directly in the code to perform downloads.

Mainly realize step playing:

1. Define an DownUtil class, in which the download work is basically completed, and initialize the Handler of UI thread in the constructor. Used by child threads and UI threads to pass download progress values.

2. All download tasks are saved in LinkedList. Open a background thread in the init () method, and continuously take tasks from LinkedList and hand them to idle threads in the thread pool for execution.

3. Whenever addTask method adds a task, it sends a message to mPoolThreadHandler, and takes out a task from the task queue and gives it to the thread pool for execution. Here, Semaphore semaphore is used, that is to say, only when one task is completed, release () one semaphore can take out one task from LinkedList and then execute it, otherwise acquire () method will block the thread until the last task is completed.


public class DownUtil
{
 // Define the path to download resources 
 private String path;
 // Specify where to save the downloaded file 
 private String targetFile;
 // Define the total size of downloaded files 
 private int fileSize;

 // Thread pool 
 private ExecutorService mThreadPool;
 // Number of threads 
 private static final int DEFAULT_THREAD_COUNT = 5;
 // Task queue 
 private LinkedList<Runnable> mTasks;

 // Background polling thread 
 private Thread mPoolThread;
 // Background thread handler
 private Handler mPoolThreadHandler;
 //UI Threaded Handler
 private Handler mUIThreadHandler;
 // Semaphore 
 private Semaphore semaphore;
 private Semaphore mHandlerSemaphore = new Semaphore(0);
 // Number of download threads 
 private int threadNum;

 public DownUtil(String path , String targetFile , int threadNum , final ProgressBar bar)
 {
  this.path = path;
  this.targetFile = targetFile;
  this.threadNum = threadNum;
  init();

  mUIThreadHandler = new Handler()
  {
   int sumSize = 0;
   @Override
   public void handleMessage(Message msg)
   {
    if (msg.what == 0x123)
    {
     int size = msg.getData().getInt("upper");
     sumSize += size;
     Log.d("sumSize" , sumSize + "");
     bar.setProgress((int) (sumSize * 1.0 / fileSize * 100));
    }
   }
  };
 }

 private void init()
 {
  mPoolThread = new Thread()
  {
   public void run()
   {
    Looper.prepare();
    mPoolThreadHandler = new Handler()
    {
     public void handleMessage(Message msg)
     {
      if (msg.what == 0x111)
      {
       mThreadPool.execute(getTask());
       try
       {
        semaphore.acquire();
       }
       catch (InterruptedException e)
       {
        e.printStackTrace();
       }
      }
     }
    };
    mHandlerSemaphore.release();
    Looper.loop();
   }
  };
  mPoolThread.start();

  mThreadPool = Executors.newFixedThreadPool(DEFAULT_THREAD_COUNT);
  mTasks = new LinkedList<>();
  semaphore = new Semaphore(DEFAULT_THREAD_COUNT);
 }

 public void downLoad()
 {


  try {
   URL url = new URL(path);
   HttpURLConnection conn = (HttpURLConnection) url.openConnection();
   conn.setConnectTimeout(5 * 1000);
   conn.setRequestMethod("GET");
   conn.setRequestProperty(
     "Accept",
     "image/gif, image/jpeg, image/pjpeg, image/pjpeg, "
       + "application/x-shockwave-flash, application/xaml+xml, "
       + "application/vnd.ms-xpsdocument, application/x-ms-xbap, "
       + "application/x-ms-application, application/vnd.ms-excel, "
       + "application/vnd.ms-powerpoint, application/msword, */*");
   conn.setRequestProperty("Accept-Language", "zh-CN");
   conn.setRequestProperty("Charset", "UTF-8");
   conn.setRequestProperty("Connection", "Keep-Alive");

   // Get the size of the file 
   fileSize = conn.getContentLength();
   conn.disconnect();

   int currentPartSize = fileSize / threadNum + 1;
   RandomAccessFile file = new RandomAccessFile(targetFile , "rw");
   file.setLength(fileSize);
   file.close();


   for (int i = 0 ; i < threadNum ; i++)
   {
    // Calculate the starting position of each thread download 
    int startPos = i * currentPartSize;
    // Each thread uses 1 A RandomAccessFile Download 
    RandomAccessFile currentPart = new RandomAccessFile(targetFile , "rw");
    // Locate the download location of this thread 
    currentPart.seek(startPos);

    // Add a task to the task queue 
    addTask(new DownThread(startPos , currentPartSize , currentPart));
   }
  }
  catch (IOException e)
  {
   e.printStackTrace();
  }
 }

 private Runnable getTask()
 {
  if (!mTasks.isEmpty())
  {
   return mTasks.removeFirst();
  }
  return null;
 }

 private synchronized void addTask(Runnable task)
 {
  mTasks.add(task);
  try
  {
   if (mPoolThreadHandler == null)
   {
    mHandlerSemaphore.acquire();
   }
  }
  catch (InterruptedException e)
  {
   e.printStackTrace();
  }
  mPoolThreadHandler.sendEmptyMessage(0x111);
 }

 private class DownThread implements Runnable
 {
  // Download location of the current thread 
  private int startPos;
  // Defines the file size that the current thread is responsible for downloading 
  private int currentPartSize;
  // The file block that the current thread needs to download 
  private RandomAccessFile currentPart;
  // Defines the number of bytes that the thread has downloaded 
  private int length;

  public DownThread(int startPos , int currentPartSize , RandomAccessFile currentPart)
  {
   this.startPos = startPos;
   this.currentPartSize = currentPartSize;
   this.currentPart = currentPart;
  }

  @Override
  public void run()
  {
   try
   {
    URL url = new URL(path);
    HttpURLConnection conn = (HttpURLConnection) url.openConnection();
    conn.setConnectTimeout(5 * 1000);
    conn.setRequestMethod("GET");
    conn.setRequestProperty(
      "Accept",
      "image/gif, image/jpeg, image/pjpeg, image/pjpeg, "
        + "application/x-shockwave-flash, application/xaml+xml, "
        + "application/vnd.ms-xpsdocument, application/x-ms-xbap, "
        + "application/x-ms-application, application/vnd.ms-excel, "
        + "application/vnd.ms-powerpoint, application/msword, */*");
    conn.setRequestProperty("Accept-Language", "zh-CN");
    conn.setRequestProperty("Charset", "UTF-8");
    conn.setRequestProperty("Connection", "Keep-Alive");

    InputStream inStream = conn.getInputStream();
    // Skip startPos Bytes 
    skipFully(inStream , this.startPos);

    byte[] buffer = new byte[1024];
    int hasRead = 0;
    while (length < currentPartSize && (hasRead = inStream.read(buffer)) > 0)
    {
     currentPart.write(buffer , 0 , hasRead);
     // Accumulate the total download size of this thread 
     length += hasRead;
    }

    Log.d("length" , length + "");

    // Create a message 
    Message msg = new Message();
    msg.what = 0x123;
    Bundle bundle = new Bundle();
    bundle.putInt("upper" , length);
    msg.setData(bundle);
    // Toward UI Threads send messages 
    mUIThreadHandler.sendMessage(msg);

    semaphore.release();
    currentPart.close();
    inStream.close();
   }
   catch (Exception e)
   {
    e.printStackTrace();
   }
  }
 }

 public static void skipFully(InputStream in , long bytes) throws IOException
 {
  long remaining = bytes;
  long len = 0;
  while (remaining > 0)
  {
   len = in.skip(remaining);
   remaining -= len;
  }
 }
}

Here is the code for MainActivity:


public class MainActivity extends Activity
{
 EditText url;
 EditText target;
 Button downBn;
 ProgressBar bar;
 DownUtil downUtil;
 private String savePath;

 @Override
 protected void onCreate(Bundle savedInstanceState)
 {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.main);
  // Object in the interface 4 Interface controls 
  url = (EditText) findViewById(R.id.address);
  target = (EditText) findViewById(R.id.target);
  try
  {
   File sdCardDir = Environment.getExternalStorageDirectory();
   savePath = sdCardDir.getCanonicalPath() + "/d.chm";
  }
  catch (Exception e)
  {
   e.printStackTrace();
  }
  target.setText(savePath);
  downBn = (Button) findViewById(R.id.down);
  bar = (ProgressBar) findViewById(R.id.bar);
  downBn.setOnClickListener(new View.OnClickListener()
  {
   @Override
   public void onClick(View view)
   {
    downUtil = new DownUtil(url.getText().toString() , target.getText().toString() , 7 , bar);
    new Thread()
    {
     @Override
     public void run()
     {
      try
      {
       downUtil.downLoad();
      }
      catch (Exception e)
      {
       e.printStackTrace();
      }
     }
    }.start();
   }
  });
 }
}

Page layout is relatively simple here 1 and post:


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
 android:layout_width="match_parent"
 android:layout_height="match_parent"
 android:orientation="vertical">

 <TextView
  android:layout_width="match_parent"
  android:layout_height="wrap_content"
  android:text="@string/title1"/>

 <EditText
  android:id="@+id/address"
  android:layout_width="match_parent"
  android:layout_height="wrap_content"
  android:text="@string/address"/>

 <TextView
  android:layout_width="match_parent"
  android:layout_height="wrap_content"
  android:text="@string/targetAddress"/>

 <EditText
  android:id="@+id/target"
  android:layout_width="match_parent"
  android:layout_height="wrap_content"/>

 <Button
  android:id="@+id/down"
  android:layout_width="match_parent"
  android:layout_height="wrap_content"
  android:text="@string/down"/>

 <!--  Definition 1 Horizontal progress bars for displaying download progress  -->
 <ProgressBar
  android:id="@+id/bar"
  android:layout_width="match_parent"
  android:layout_height="wrap_content"
  android:max="100"
  style="?android:attr/progressBarStyleHorizontal"/>

</LinearLayout>

This example is mainly in teacher Li Gang's "crazy Java handout" on the multi-thread example modification, thank teacher Li Gang, if there are deficiencies, welcome criticism and correction.


Related articles: