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.