Android线程池控制并发数多线程下载
作者:Knick_Zhang
这篇文章主要为大家详细介绍了Android线程池控制并发数多线程下载,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
多线程下载并不是并发下载线程越多越好,因为当用户开启太多的并发线程之后,应用程序需要维护每条线程的开销,线程同步的开销。
这些开销反而会导致下载速度降低。因此需要避免在代码中直接开启大量线程执行下载。
主要实现步奏:
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的讲义》的多线程的例子上修改,感谢李刚老师,如有不足之处,欢迎批评指正。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。