首页 > 系统 > Android > 正文

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

2019-10-21 21:30:54
字体:
来源:转载
供稿:网友

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

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

主要实现步奏:

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的讲义》的多线程的例子上修改,感谢李刚老师,如有不足之处,欢迎批评指正。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持VEVB武林网。


注:相关教程知识阅读请移步到Android开发频道。
发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表