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

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

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值