Android線程池控制并發(fā)數(shù)多線程下載
多線程下載并不是并發(fā)下載線程越多越好,因?yàn)楫?dāng)用戶開(kāi)啟太多的并發(fā)線程之后,應(yīng)用程序需要維護(hù)每條線程的開(kāi)銷,線程同步的開(kāi)銷。
這些開(kāi)銷反而會(huì)導(dǎo)致下載速度降低。因此需要避免在代碼中直接開(kāi)啟大量線程執(zhí)行下載。
主要實(shí)現(xiàn)步奏:
1、定義一個(gè)DownUtil類,下載工作基本在此類完成,在構(gòu)造器中初始化UI線程的Handler。用于子線程和UI線程傳遞下載進(jìn)度值。
2、所有的下載任務(wù)都保存在LinkedList。在init()方法中開(kāi)啟一個(gè)后臺(tái)線程,不斷地從LinkedList中取任務(wù)交給線程池中的空閑線程執(zhí)行。
3、每當(dāng)addTask方法添加一個(gè)任務(wù),就向 mPoolThreadHandler發(fā)送條消息,就從任務(wù)隊(duì)列中取出一個(gè)任務(wù)交給線程池執(zhí)行。這里使用了使用了Semaphore信號(hào)量,也就是說(shuō)只有當(dāng)一個(gè)任務(wù)執(zhí)行完成之后,release()一個(gè)信號(hào)量,才能從LinkedList中取出一個(gè)任務(wù)再去執(zhí)行,否則acquire()方法會(huì)一直阻塞線程,直到上一個(gè)任務(wù)完成。
public class DownUtil
{
//定義下載資源的路徑
private String path;
//指定下載文件的保存位置
private String targetFile;
//定義下載文件的總大小
private int fileSize;
//線程池
private ExecutorService mThreadPool;
//線程數(shù)量
private static final int DEFAULT_THREAD_COUNT = 5;
//任務(wù)隊(duì)列
private LinkedList<Runnable> mTasks;
//后臺(tái)輪詢線程
private Thread mPoolThread;
//后臺(tái)線程的handler
private Handler mPoolThreadHandler;
//UI線程的Handler
private Handler mUIThreadHandler;
//信號(hào)量
private Semaphore semaphore;
private Semaphore mHandlerSemaphore = new Semaphore(0);
//下載線程數(shù)量
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++)
{
//計(jì)算每條線程下載的開(kāi)始位置
int startPos = i * currentPartSize;
//每條線程使用一個(gè)RandomAccessFile進(jìn)行下載
RandomAccessFile currentPart = new RandomAccessFile(targetFile , "rw");
//定位該線程的下載位置
currentPart.seek(startPos);
//將任務(wù)添加到任務(wù)隊(duì)列中
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
{
//當(dāng)前線程的下載位置
private int startPos;
//定義當(dāng)前線程負(fù)責(zé)下載的文件大小
private int currentPartSize;
//當(dāng)前線程需要下載的文件塊
private RandomAccessFile currentPart;
//定義該線程已經(jīng)下載的字節(jié)數(shù)
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();
//跳過(guò)startPos個(gè)字節(jié)
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);
//累計(jì)該線程下載的總大小
length += hasRead;
}
Log.d("length" , length + "");
//創(chuàng)建消息
Message msg = new Message();
msg.what = 0x123;
Bundle bundle = new Bundle();
bundle.putInt("upper" , length);
msg.setData(bundle);
//向UI線程發(fā)送消息
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);
//獲取界面中的四個(gè)界面控件
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();
}
});
}
}
頁(yè)面布局比較簡(jiǎn)單這里一并貼出:
<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"/> <!-- 定義一個(gè)水平進(jìn)度條,用于顯示下載進(jìn)度 --> <ProgressBar android:id="@+id/bar" android:layout_width="match_parent" android:layout_height="wrap_content" android:max="100" style="?android:attr/progressBarStyleHorizontal"/> </LinearLayout>
此例主要是在李剛老師的《瘋狂Java的講義》的多線程的例子上修改,感謝李剛老師,如有不足之處,歡迎批評(píng)指正。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
使用Android開(kāi)發(fā)接入第三方原生SDK實(shí)現(xiàn)微信登錄
這篇文章主要介紹了使用Android開(kāi)發(fā)接入第三方原生SDK實(shí)現(xiàn)微信登錄,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-03-03
AndroidStudio中AVD虛擬機(jī)設(shè)備空間不足調(diào)試過(guò)程出現(xiàn)的黑屏問(wèn)題及解決方案
這篇文章主要介紹了解決AndroidStudio中AVD虛擬機(jī)設(shè)備空間不足調(diào)試過(guò)程出現(xiàn)的黑屏問(wèn)題,本文通過(guò)圖文并茂的形式給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-04-04
Android 微信搖一搖功能實(shí)現(xiàn)詳細(xì)介紹
這篇文章主要介紹了Android 微信搖一搖功能實(shí)現(xiàn)詳細(xì)介紹的相關(guān)資料,并附實(shí)例代碼及實(shí)現(xiàn)微信搖一搖的思路,需要的朋友可以參考下2016-11-11
Android開(kāi)發(fā)注解排列組合出啟動(dòng)任務(wù)ksp
這篇文章主要為大家介紹了Android開(kāi)發(fā)注解排列組合出啟動(dòng)任務(wù)ksp示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-06-06
Android監(jiān)聽(tīng)系統(tǒng)來(lái)電并彈出提示窗口
本篇文章主要介紹了Android監(jiān)聽(tīng)系統(tǒng)來(lái)電并彈出提示窗口,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-10-10
Android帶進(jìn)度條的下載圖片示例(AsyncTask異步任務(wù))
本文主要介紹Android帶進(jìn)度條的下載圖片示例(AsyncTask異步任務(wù))的方法解析。具有很好的參考價(jià)值。下面跟著小編一起來(lái)看下吧2017-04-04
Android控件實(shí)現(xiàn)直播App點(diǎn)贊飄心動(dòng)畫
這篇文章主要為大家詳細(xì)介紹了FlowLikeView控件實(shí)現(xiàn)直播App特效之點(diǎn)贊飄心動(dòng)畫,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-03-03
Android便攜式熱點(diǎn)的開(kāi)啟狀態(tài)檢測(cè)和SSID的獲取方法
WIFI熱點(diǎn)的開(kāi)啟狀態(tài)和開(kāi)啟后的SSID如何獲取呢?接下來(lái)通過(guò)本文給大家分享Android便攜式熱點(diǎn)的開(kāi)啟狀態(tài)檢測(cè)和SSID的獲取方法,需要的朋友參考下吧2017-01-01
Android View與Compose互相調(diào)用實(shí)例探究
這篇文章主要介紹了Android View與Compose互相調(diào)用,Compose 具有超強(qiáng)的兼容性,兼容現(xiàn)有的所有代碼,Compose 能夠與現(xiàn)有 View 體系并存,可實(shí)現(xiàn)漸進(jìn)式替換2023-01-01

