Android线程池控制并发数多线程下载

Lewa ·
更新时间:2024-11-10
· 813 次阅读

多线程下载并不是并发下载线程越多越好,因为当用户开启太多的并发线程之后,应用程序需要维护每条线程的开销,线程同步的开销。

这些开销反而会导致下载速度降低。因此需要避免在代码中直接开启大量线程执行下载。

主要实现步奏:

1、定义一个DownUtil类,下载工作基本在此类完成,在构造器中初始化UI线程的Handler。用于子线程和UI线程传递下载进度值。

2、所有的下载任务都保存在LinkedList。在init()方法中开启一个后台线程,不断地从LinkedList中取任务交给线程池中的空闲线程执行。

3、每当addTask方法添加一个任务,就向 mPoolThreadHandler发送条消息,就从任务队列中取出一个任务交给线程池执行。这里使用了使用了Semaphore信号量,也就是说只有当一个任务执行完成之后,release()一个信号量,才能从LinkedList中取出一个任务再去执行,否则acquire()方法会一直阻塞线程,直到上一个任务完成。

public class DownUtil { //定义下载资源的路径 private String path; //指定下载文件的保存位置 private String targetFile; //定义下载文件的总大小 private int fileSize; //线程池 private ExecutorService mThreadPool; //线程数量 private static final int DEFAULT_THREAD_COUNT = 5; //任务队列 private LinkedList<Runnable> mTasks; //后台轮询线程 private Thread mPoolThread; //后台线程的handler private Handler mPoolThreadHandler; //UI线程的Handler private Handler mUIThreadHandler; //信号量 private Semaphore semaphore; private Semaphore mHandlerSemaphore = new Semaphore(0); //下载线程数量 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"); //得到文件的大小 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++) { //计算每条线程下载的开始位置 int startPos = i * currentPartSize; //每条线程使用一个RandomAccessFile进行下载 RandomAccessFile currentPart = new RandomAccessFile(targetFile , "rw"); //定位该线程的下载位置 currentPart.seek(startPos); //将任务添加到任务队列中 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 { //当前线程的下载位置 private int startPos; //定义当前线程负责下载的文件大小 private int currentPartSize; //当前线程需要下载的文件块 private RandomAccessFile currentPart; //定义该线程已经下载的字节数 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(); //跳过startPos个字节 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); //累计该线程下载的总大小 length += hasRead; } Log.d("length" , length + ""); //创建消息 Message msg = new Message(); msg.what = 0x123; Bundle bundle = new Bundle(); bundle.putInt("upper" , length); msg.setData(bundle); //向UI线程发送消息 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; } } }

以下是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); //获取界面中的四个界面控件 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(); } }); } }

页面布局比较简单这里一并贴出:

<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"/> <!-- 定义一个水平进度条,用于显示下载进度 --> <ProgressBar android:id="@+id/bar" android:layout_width="match_parent" android:layout_height="wrap_content" android:max="100" style="?android:attr/progressBarStyleHorizontal"/> </LinearLayout>

此例主要是在李刚老师的《疯狂Java的讲义》的多线程的例子上修改,感谢李刚老师,如有不足之处,欢迎批评指正。

您可能感兴趣的文章:Android之线程池ThreadPoolExecutor的简介完全解析Android多线程中线程池ThreadPool的原理和使用浅谈Android中线程池的管理Android编程自定义线程池与用法示例浅谈Android 的线程和线程池的使用Android自带的四种线程池使用总结在Android线程池里运行代码任务实例Android开发经验谈:并发编程(线程与线程池)(推荐)



多线程下载 android线程池 并发数 并发 android线程 程池 多线程 Android

需要 登录 后方可回复, 如果你还没有账号请 注册新账号