├── .gitignore
├── Demo
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── java
│ └── cn
│ │ └── aigestudio
│ │ └── downloader
│ │ └── demo
│ │ ├── DLService.java
│ │ ├── MainActivity.java
│ │ └── NotificationUtil.java
│ └── res
│ ├── drawable-hdpi
│ └── ic_launcher.png
│ ├── drawable-mdpi
│ └── ic_launcher.png
│ ├── drawable-xhdpi
│ └── ic_launcher.png
│ ├── drawable-xxhdpi
│ └── ic_launcher.png
│ ├── drawable-xxxhdpi
│ └── ic_launcher.png
│ ├── layout
│ └── activity_main.xml
│ └── values
│ └── strings.xml
├── Downloader
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ └── main
│ ├── AndroidManifest.xml
│ └── java
│ └── cn
│ └── aigestudio
│ └── downloader
│ ├── bizs
│ ├── DLCons.java
│ ├── DLDBHelper.java
│ ├── DLDBManager.java
│ ├── DLError.java
│ ├── DLException.java
│ ├── DLHeader.java
│ ├── DLInfo.java
│ ├── DLManager.java
│ ├── DLTask.java
│ ├── DLThread.java
│ ├── DLThreadInfo.java
│ ├── DLUtil.java
│ ├── IDLThreadListener.java
│ ├── ITaskDAO.java
│ ├── IThreadDAO.java
│ ├── TaskDAO.java
│ └── ThreadDAO.java
│ └── interfaces
│ ├── AsyncDListener.java
│ ├── DLTaskListener.java
│ ├── IDListener.java
│ └── SimpleDListener.java
├── MultiThreadDownloader.iml
├── README.md
├── build.gradle
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── preview1.gif
├── preview2.gif
└── settings.gradle
/.gitignore:
--------------------------------------------------------------------------------
1 | .gradle
2 | /local.properties
3 | /.idea
4 | *.iml
5 | .DS_Store
6 | /build
7 |
--------------------------------------------------------------------------------
/Demo/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/Demo/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion 22
5 | buildToolsVersion "22.0.1"
6 |
7 | defaultConfig {
8 | applicationId "cn.aigestudio.downloader.demo"
9 | minSdkVersion 4
10 | targetSdkVersion 22
11 | versionCode 1
12 | versionName "1.0"
13 | }
14 | buildTypes {
15 | release {
16 | minifyEnabled false
17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
18 | }
19 | }
20 | }
21 |
22 | dependencies {
23 | compile fileTree(dir: 'libs', include: ['*.jar'])
24 | compile project(':Downloader')
25 | compile 'com.android.support:support-v4:22.1.1'
26 | // compile 'cn.aigestudio.downloader:Downloader:1.4.1'
27 | }
28 |
--------------------------------------------------------------------------------
/Demo/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this dlLocalFile are appended to flags specified
3 | # in H:\Programming\Android\sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include dirPath and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
--------------------------------------------------------------------------------
/Demo/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
14 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/Demo/src/main/java/cn/aigestudio/downloader/demo/DLService.java:
--------------------------------------------------------------------------------
1 | package cn.aigestudio.downloader.demo;
2 |
3 | import android.app.NotificationManager;
4 | import android.app.Service;
5 | import android.content.Context;
6 | import android.content.Intent;
7 | import android.os.IBinder;
8 | import android.support.v4.app.NotificationCompat;
9 |
10 | import java.io.File;
11 |
12 | import cn.aigestudio.downloader.bizs.DLManager;
13 | import cn.aigestudio.downloader.interfaces.SimpleDListener;
14 |
15 | /**
16 | * 执行下载的Service
17 | *
18 | * @author AigeStudio 2015-05-18
19 | */
20 | public class DLService extends Service {
21 |
22 | @Override
23 | public int onStartCommand(Intent intent, int flags, int startId) {
24 | String url = intent.getStringExtra("url");
25 | String path = intent.getStringExtra("path");
26 | final int id = intent.getIntExtra("id", -1);
27 | final NotificationManager nm = (NotificationManager) getSystemService(Context
28 | .NOTIFICATION_SERVICE);
29 | final NotificationCompat.Builder builder = new NotificationCompat.Builder(this)
30 | .setSmallIcon(R.drawable.ic_launcher);
31 |
32 | final int[] length = new int[1];
33 | DLManager.getInstance(this).dlStart(url, path, null, null, new SimpleDListener() {
34 | @Override
35 | public void onStart(String fileName, String realUrl, int fileLength) {
36 | builder.setContentTitle(fileName);
37 | length[0] = fileLength;
38 | }
39 |
40 | @Override
41 | public void onProgress(int progress) {
42 | builder.setProgress(length[0], progress, false);
43 | nm.notify(id, builder.build());
44 | }
45 |
46 | @Override
47 | public void onFinish(File file) {
48 | nm.cancel(id);
49 | }
50 | });
51 | return super.onStartCommand(intent, flags, startId);
52 | }
53 |
54 | @Override
55 | public IBinder onBind(Intent intent) {
56 | return null;
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/Demo/src/main/java/cn/aigestudio/downloader/demo/MainActivity.java:
--------------------------------------------------------------------------------
1 | package cn.aigestudio.downloader.demo;
2 |
3 | import android.app.Activity;
4 | import android.os.Bundle;
5 | import android.os.Environment;
6 | import android.view.View;
7 | import android.widget.Button;
8 | import android.widget.ProgressBar;
9 |
10 | import cn.aigestudio.downloader.bizs.DLManager;
11 | import cn.aigestudio.downloader.interfaces.SimpleDListener;
12 |
13 | public class MainActivity extends Activity {
14 | private static final String[] URLS = {
15 | "http://china35.newhua.com/down/FetionNew2015September.zip",
16 | "http://download.chinaunix.net/down.php?id=10608&ResourceID=5267&site=1",
17 | "http://down.tech.sina.com.cn/download/d_load.php?d_id=49535&down_id=1&ip=42.81.45.159",
18 | "http://dlsw.baidu.com/sw-search-sp/soft/7b/33461/freeime.1406862029.exe",
19 | "http://113.207.16.84/dd.myapp.com/16891/2E53C25B6BC55D3330AB85A1B7B57485.apk?mkey=5630b43973f537cf&f=cf87&fsname=com.htshuo.htsg_3.0.1_49.apk&asr=02f1&p=.apk"
20 | };
21 |
22 | private static final int[] RES_ID_BTN_START = {
23 | R.id.main_dl_start_btn1,
24 | R.id.main_dl_start_btn2,
25 | R.id.main_dl_start_btn3,
26 | R.id.main_dl_start_btn4,
27 | R.id.main_dl_start_btn5,
28 | R.id.main_dl_start_btn6};
29 | private static final int[] RES_ID_BTN_STOP = {
30 | R.id.main_dl_stop_btn1,
31 | R.id.main_dl_stop_btn2,
32 | R.id.main_dl_stop_btn3,
33 | R.id.main_dl_stop_btn4,
34 | R.id.main_dl_stop_btn5,
35 | R.id.main_dl_stop_btn6};
36 | private static final int[] RES_ID_PB = {
37 | R.id.main_dl_pb1,
38 | R.id.main_dl_pb2,
39 | R.id.main_dl_pb3,
40 | R.id.main_dl_pb4,
41 | R.id.main_dl_pb5,
42 | R.id.main_dl_pb6};
43 | private static final int[] RES_ID_NOTIFY = {
44 | R.id.main_notify_btn1,
45 | R.id.main_notify_btn2,
46 | R.id.main_notify_btn3,
47 | R.id.main_notify_btn4,
48 | R.id.main_notify_btn5,
49 | R.id.main_notify_btn6};
50 |
51 | private String saveDir;
52 |
53 | private ProgressBar[] pbDLs;
54 |
55 | @Override
56 | protected void onCreate(Bundle savedInstanceState) {
57 | super.onCreate(savedInstanceState);
58 | setContentView(R.layout.activity_main);
59 |
60 | DLManager.getInstance(MainActivity.this).setMaxTask(2);
61 | Button[] btnStarts = new Button[RES_ID_BTN_START.length];
62 | for (int i = 0; i < btnStarts.length; i++) {
63 | btnStarts[i] = (Button) findViewById(RES_ID_BTN_START[i]);
64 | final int finalI = i;
65 | btnStarts[i].setOnClickListener(new View.OnClickListener() {
66 | @Override
67 | public void onClick(View v) {
68 | DLManager.getInstance(MainActivity.this).dlStart(URLS[finalI], saveDir,
69 | null, null, new SimpleDListener(){
70 | @Override
71 | public void onStart(String fileName, String realUrl, int fileLength) {
72 | pbDLs[finalI].setMax(fileLength);
73 | }
74 |
75 | @Override
76 | public void onProgress(int progress) {
77 | pbDLs[finalI].setProgress(progress);
78 | }
79 | });
80 | }
81 | });
82 | }
83 |
84 | Button[] btnStops = new Button[RES_ID_BTN_STOP.length];
85 | for (int i = 0; i < btnStops.length; i++) {
86 | btnStops[i] = (Button) findViewById(RES_ID_BTN_STOP[i]);
87 | final int finalI = i;
88 | btnStops[i].setOnClickListener(new View.OnClickListener() {
89 | @Override
90 | public void onClick(View v) {
91 | DLManager.getInstance(MainActivity.this).dlStop(URLS[finalI]);
92 | }
93 | });
94 | }
95 |
96 | pbDLs = new ProgressBar[RES_ID_PB.length];
97 | for (int i = 0; i < pbDLs.length; i++) {
98 | pbDLs[i] = (ProgressBar) findViewById(RES_ID_PB[i]);
99 | pbDLs[i].setMax(100);
100 | }
101 |
102 | Button[] btnNotifys = new Button[RES_ID_NOTIFY.length];
103 | for (int i = 0; i < btnNotifys.length; i++) {
104 | btnNotifys[i] = (Button) findViewById(RES_ID_NOTIFY[i]);
105 | final int finalI = i;
106 | btnNotifys[i].setOnClickListener(new View.OnClickListener() {
107 | @Override
108 | public void onClick(View v) {
109 | NotificationUtil.notificationForDLAPK(MainActivity.this, URLS[finalI]);
110 | }
111 | });
112 | }
113 |
114 | saveDir = Environment.getExternalStorageDirectory() + "/AigeStudio/";
115 | }
116 |
117 | @Override
118 | protected void onDestroy() {
119 | for (String url : URLS) {
120 | DLManager.getInstance(this).dlStop(url);
121 | }
122 | super.onDestroy();
123 | }
124 | }
125 |
--------------------------------------------------------------------------------
/Demo/src/main/java/cn/aigestudio/downloader/demo/NotificationUtil.java:
--------------------------------------------------------------------------------
1 | package cn.aigestudio.downloader.demo;
2 |
3 | import android.content.Context;
4 | import android.content.Intent;
5 | import android.os.Environment;
6 |
7 | /**
8 | * 通知工具类
9 | *
10 | * @author AigeStudio 2015-05-18
11 | */
12 | public final class NotificationUtil {
13 | public static void notificationForDLAPK(Context context, String url) {
14 | notificationForDLAPK(context, url, Environment.getExternalStorageDirectory() + "/AigeStudio/");
15 | }
16 |
17 | public static void notificationForDLAPK(Context context, String url, String path) {
18 | Intent intent = new Intent(context, DLService.class);
19 | intent.putExtra("url", url);
20 | intent.putExtra("path", path);
21 | intent.putExtra("id", (int) (Math.random() * 1024));
22 | context.startService(intent);
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/Demo/src/main/res/drawable-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devaige/MultiThreadDownloader/21bf649b47ffb6830a2d15a2b7438a41bf3bca70/Demo/src/main/res/drawable-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/Demo/src/main/res/drawable-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devaige/MultiThreadDownloader/21bf649b47ffb6830a2d15a2b7438a41bf3bca70/Demo/src/main/res/drawable-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/Demo/src/main/res/drawable-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devaige/MultiThreadDownloader/21bf649b47ffb6830a2d15a2b7438a41bf3bca70/Demo/src/main/res/drawable-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/Demo/src/main/res/drawable-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devaige/MultiThreadDownloader/21bf649b47ffb6830a2d15a2b7438a41bf3bca70/Demo/src/main/res/drawable-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/Demo/src/main/res/drawable-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devaige/MultiThreadDownloader/21bf649b47ffb6830a2d15a2b7438a41bf3bca70/Demo/src/main/res/drawable-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/Demo/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
9 |
10 |
13 |
14 |
19 |
20 |
26 |
27 |
33 |
34 |
35 |
38 |
39 |
44 |
45 |
51 |
52 |
58 |
59 |
60 |
63 |
64 |
69 |
70 |
76 |
77 |
83 |
84 |
85 |
88 |
89 |
94 |
95 |
101 |
102 |
108 |
109 |
110 |
113 |
114 |
119 |
120 |
126 |
127 |
133 |
134 |
135 |
138 |
139 |
144 |
145 |
151 |
152 |
158 |
159 |
160 |
164 |
165 |
172 |
173 |
180 |
181 |
188 |
189 |
196 |
197 |
204 |
205 |
212 |
213 |
214 |
--------------------------------------------------------------------------------
/Demo/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | MultiThreadDownloader
5 |
6 | Start1
7 | Stop1
8 | Start2
9 | Stop2
10 | Start3
11 | Stop3
12 | Start4
13 | Stop4
14 | Start5
15 | Stop5
16 | Start6
17 | Stop6
18 |
19 | Notify1
20 | Notify2
21 | Notify3
22 | Notify4
23 | Notify5
24 | Notify6
25 |
26 |
27 |
--------------------------------------------------------------------------------
/Downloader/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/Downloader/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 |
3 | version = "1.4.3"
4 |
5 | android {
6 | compileSdkVersion 23
7 | buildToolsVersion "23.0.1"
8 |
9 | defaultConfig {
10 | minSdkVersion 1
11 | targetSdkVersion 23
12 | versionCode 8
13 | versionName version
14 | }
15 | buildTypes {
16 | release {
17 | minifyEnabled false
18 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
19 | }
20 | }
21 | }
--------------------------------------------------------------------------------
/Downloader/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this dlLocalFile are appended to flags specified
3 | # in H:/Programming/Android/sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include dirPath and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
--------------------------------------------------------------------------------
/Downloader/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/Downloader/src/main/java/cn/aigestudio/downloader/bizs/DLCons.java:
--------------------------------------------------------------------------------
1 | package cn.aigestudio.downloader.bizs;
2 |
3 | import android.provider.BaseColumns;
4 |
5 | final class DLCons {
6 | private DLCons() {
7 | }
8 |
9 | static boolean DEBUG = true;
10 |
11 | static final class Base {
12 | static final int DEFAULT_TIMEOUT = 20000;
13 | static final int MAX_REDIRECTS = 5;
14 | static final int LENGTH_PER_THREAD = 10485760;
15 | }
16 |
17 | static final class Code {
18 | static final int HTTP_CONTINUE = 100;
19 | static final int HTTP_SWITCHING_PROTOCOLS = 101;
20 | static final int HTTP_PROCESSING = 102;
21 |
22 | static final int HTTP_OK = 200;
23 | static final int HTTP_CREATED = 201;
24 | static final int HTTP_ACCEPTED = 202;
25 | static final int HTTP_NOT_AUTHORITATIVE = 203;
26 | static final int HTTP_NO_CONTENT = 204;
27 | static final int HTTP_RESET = 205;
28 | static final int HTTP_PARTIAL = 206;
29 | static final int HTTP_MULTI_STATUS = 207;
30 |
31 | static final int HTTP_MULT_CHOICE = 300;
32 | static final int HTTP_MOVED_PERM = 301;
33 | static final int HTTP_MOVED_TEMP = 302;
34 | static final int HTTP_SEE_OTHER = 303;
35 | static final int HTTP_NOT_MODIFIED = 304;
36 | static final int HTTP_USE_PROXY = 305;
37 | static final int HTTP_TEMP_REDIRECT = 307;
38 |
39 | static final int HTTP_BAD_REQUEST = 400;
40 | static final int HTTP_UNAUTHORIZED = 401;
41 | static final int HTTP_PAYMENT_REQUIRED = 402;
42 | static final int HTTP_FORBIDDEN = 403;
43 | static final int HTTP_NOT_FOUND = 404;
44 | static final int HTTP_BAD_METHOD = 405;
45 | static final int HTTP_NOT_ACCEPTABLE = 406;
46 | static final int HTTP_PROXY_AUTH = 407;
47 | static final int HTTP_CLIENT_TIMEOUT = 408;
48 | static final int HTTP_CONFLICT = 409;
49 | static final int HTTP_GONE = 410;
50 | static final int HTTP_LENGTH_REQUIRED = 411;
51 | static final int HTTP_PRECON_FAILED = 412;
52 | static final int HTTP_ENTITY_TOO_LARGE = 413;
53 | static final int HTTP_REQ_TOO_LONG = 414;
54 | static final int HTTP_UNSUPPORTED_TYPE = 415;
55 | static final int HTTP_REQUESTED_RANGE_NOT_SATISFIABLE = 416;
56 | static final int HTTP_EXPECTATION_FAILED = 417;
57 | static final int HTTP_UNPROCESSABLE_ENTITY = 422;
58 | static final int HTTP_LOCKED = 423;
59 | static final int HTTP_FAILED_DEPENDENCY = 424;
60 |
61 | static final int HTTP_INTERNAL_ERROR = 500;
62 | static final int HTTP_NOT_IMPLEMENTED = 501;
63 | static final int HTTP_BAD_GATEWAY = 502;
64 | static final int HTTP_UNAVAILABLE = 503;
65 | static final int HTTP_GATEWAY_TIMEOUT = 504;
66 | static final int HTTP_VERSION = 505;
67 | static final int HTTP_INSUFFICIENT_STORAGE = 507;
68 | }
69 |
70 | static final class DBCons {
71 | static final String TB_TASK = "task_info";
72 | static final String TB_TASK_URL_BASE = "base_url";
73 | static final String TB_TASK_URL_REAL = "real_url";
74 | static final String TB_TASK_DIR_PATH = "file_path";
75 | static final String TB_TASK_CURRENT_BYTES = "currentBytes";
76 | static final String TB_TASK_TOTAL_BYTES = "totalBytes";
77 | static final String TB_TASK_FILE_NAME = "file_name";
78 | static final String TB_TASK_MIME_TYPE = "mime_type";
79 | static final String TB_TASK_ETAG = "e_tag";
80 | static final String TB_TASK_DISPOSITION = "disposition";
81 | static final String TB_TASK_LOCATION = "location";
82 |
83 | static final String TB_THREAD = "thread_info";
84 | static final String TB_THREAD_URL_BASE = "base_url";
85 | static final String TB_THREAD_START = "start";
86 | static final String TB_THREAD_END = "end";
87 | static final String TB_THREAD_ID = "id";
88 |
89 | static final String TB_TASK_SQL_CREATE = "CREATE TABLE " +
90 | DLCons.DBCons.TB_TASK + "(" +
91 | BaseColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " +
92 | DLCons.DBCons.TB_TASK_URL_BASE + " CHAR, " +
93 | DLCons.DBCons.TB_TASK_URL_REAL + " CHAR, " +
94 | DLCons.DBCons.TB_TASK_DIR_PATH + " CHAR, " +
95 | DLCons.DBCons.TB_TASK_FILE_NAME + " CHAR, " +
96 | DLCons.DBCons.TB_TASK_MIME_TYPE + " CHAR, " +
97 | DLCons.DBCons.TB_TASK_ETAG + " CHAR, " +
98 | DLCons.DBCons.TB_TASK_DISPOSITION + " CHAR, " +
99 | DLCons.DBCons.TB_TASK_LOCATION + " CHAR, " +
100 | DLCons.DBCons.TB_TASK_CURRENT_BYTES + " INTEGER, " +
101 | DLCons.DBCons.TB_TASK_TOTAL_BYTES + " INTEGER)";
102 | static final String TB_THREAD_SQL_CREATE = "CREATE TABLE " +
103 | DLCons.DBCons.TB_THREAD + "(" +
104 | BaseColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " +
105 | DLCons.DBCons.TB_THREAD_URL_BASE + " CHAR, " +
106 | DLCons.DBCons.TB_THREAD_START + " INTEGER, " +
107 | DLCons.DBCons.TB_THREAD_END + " INTEGER, " +
108 | DLCons.DBCons.TB_THREAD_ID + " CHAR)";
109 |
110 | static final String TB_TASK_SQL_UPGRADE = "DROP TABLE IF EXISTS " +
111 | DLCons.DBCons.TB_TASK;
112 | static final String TB_THREAD_SQL_UPGRADE = "DROP TABLE IF EXISTS " +
113 | DLCons.DBCons.TB_THREAD;
114 | }
115 | }
--------------------------------------------------------------------------------
/Downloader/src/main/java/cn/aigestudio/downloader/bizs/DLDBHelper.java:
--------------------------------------------------------------------------------
1 | package cn.aigestudio.downloader.bizs;
2 |
3 | import android.content.Context;
4 | import android.database.sqlite.SQLiteDatabase;
5 | import android.database.sqlite.SQLiteOpenHelper;
6 |
7 | import static cn.aigestudio.downloader.bizs.DLCons.DBCons.TB_TASK_SQL_CREATE;
8 | import static cn.aigestudio.downloader.bizs.DLCons.DBCons.TB_TASK_SQL_UPGRADE;
9 | import static cn.aigestudio.downloader.bizs.DLCons.DBCons.TB_THREAD_SQL_CREATE;
10 | import static cn.aigestudio.downloader.bizs.DLCons.DBCons.TB_THREAD_SQL_UPGRADE;
11 |
12 | final class DLDBHelper extends SQLiteOpenHelper {
13 | private static final String DB_NAME = "dl.db";
14 | private static final int DB_VERSION = 3;
15 |
16 | DLDBHelper(Context context) {
17 | super(context, DB_NAME, null, DB_VERSION);
18 | }
19 |
20 | @Override
21 | public void onCreate(SQLiteDatabase db) {
22 | db.execSQL(TB_TASK_SQL_CREATE);
23 | db.execSQL(TB_THREAD_SQL_CREATE);
24 | }
25 |
26 | @Override
27 | public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
28 | db.execSQL(TB_TASK_SQL_UPGRADE);
29 | db.execSQL(TB_THREAD_SQL_UPGRADE);
30 | onCreate(db);
31 | }
32 | }
--------------------------------------------------------------------------------
/Downloader/src/main/java/cn/aigestudio/downloader/bizs/DLDBManager.java:
--------------------------------------------------------------------------------
1 | package cn.aigestudio.downloader.bizs;
2 |
3 | import android.content.Context;
4 |
5 | import java.util.List;
6 |
7 | final class DLDBManager implements ITaskDAO, IThreadDAO {
8 | private static DLDBManager sManager;
9 |
10 | private TaskDAO daoTask;
11 | private ThreadDAO daoThread;
12 |
13 | private DLDBManager(Context context) {
14 | daoTask = new TaskDAO(context);
15 | daoThread = new ThreadDAO(context);
16 | }
17 |
18 | static DLDBManager getInstance(Context context) {
19 | if (null == sManager) {
20 | sManager = new DLDBManager(context);
21 | }
22 | return sManager;
23 | }
24 |
25 | @Override
26 | public synchronized void insertTaskInfo(DLInfo info) {
27 | daoTask.insertTaskInfo(info);
28 | }
29 |
30 | @Override
31 | public synchronized void deleteTaskInfo(String url) {
32 | daoTask.deleteTaskInfo(url);
33 | }
34 |
35 | @Override
36 | public synchronized void updateTaskInfo(DLInfo info) {
37 | daoTask.updateTaskInfo(info);
38 | }
39 |
40 | @Override
41 | public synchronized DLInfo queryTaskInfo(String url) {
42 | return daoTask.queryTaskInfo(url);
43 | }
44 |
45 | @Override
46 | public synchronized void insertThreadInfo(DLThreadInfo info) {
47 | daoThread.insertThreadInfo(info);
48 | }
49 |
50 | @Override
51 | public synchronized void deleteThreadInfo(String id) {
52 | daoThread.deleteThreadInfo(id);
53 | }
54 |
55 | @Override
56 | public synchronized void deleteAllThreadInfo(String url) {
57 | daoThread.deleteAllThreadInfo(url);
58 | }
59 |
60 | @Override
61 | public synchronized void updateThreadInfo(DLThreadInfo info) {
62 | daoThread.updateThreadInfo(info);
63 | }
64 |
65 | @Override
66 | public synchronized DLThreadInfo queryThreadInfo(String id) {
67 | return daoThread.queryThreadInfo(id);
68 | }
69 |
70 | @Override
71 | public synchronized List queryAllThreadInfo(String url) {
72 | return daoThread.queryAllThreadInfo(url);
73 | }
74 | }
--------------------------------------------------------------------------------
/Downloader/src/main/java/cn/aigestudio/downloader/bizs/DLError.java:
--------------------------------------------------------------------------------
1 | package cn.aigestudio.downloader.bizs;
2 |
3 | public final class DLError {
4 | private DLError() {
5 | }
6 |
7 | /**
8 | * 没有网络
9 | */
10 | public static final int ERROR_NOT_NETWORK = 0;
11 | /**
12 | * 创建文件失败
13 | */
14 | public static final int ERROR_CREATE_FILE = 1;
15 | /**
16 | * 无效Url
17 | */
18 | public static final int ERROR_INVALID_URL = 2;
19 | /**
20 | * 重复的下载地址
21 | */
22 | public static final int ERROR_REPEAT_URL = 101;
23 | /**
24 | * 无法获取真实下载地址
25 | */
26 | public static final int ERROR_CANNOT_GET_URL = 137;
27 | /**
28 | * 建立连接出错
29 | */
30 | public static final int ERROR_OPEN_CONNECT = 138;
31 | /**
32 | * 未能处理的重定向错误
33 | */
34 | public static final int ERROR_UNHANDLED_REDIRECT = 333;
35 | }
--------------------------------------------------------------------------------
/Downloader/src/main/java/cn/aigestudio/downloader/bizs/DLException.java:
--------------------------------------------------------------------------------
1 | package cn.aigestudio.downloader.bizs;
2 |
3 | class DLException extends Exception {
4 | DLException() {
5 | super();
6 | }
7 |
8 | DLException(String detailMessage) {
9 | super(detailMessage);
10 | }
11 |
12 | DLException(String detailMessage, Throwable throwable) {
13 | super(detailMessage, throwable);
14 | }
15 |
16 | DLException(Throwable throwable) {
17 | super(throwable);
18 | }
19 | }
--------------------------------------------------------------------------------
/Downloader/src/main/java/cn/aigestudio/downloader/bizs/DLHeader.java:
--------------------------------------------------------------------------------
1 | package cn.aigestudio.downloader.bizs;
2 |
3 | public class DLHeader {
4 | public final String key;
5 | public final String value;
6 |
7 | public DLHeader(String key, String value) {
8 | this.key = key;
9 | this.value = value;
10 | }
11 | }
--------------------------------------------------------------------------------
/Downloader/src/main/java/cn/aigestudio/downloader/bizs/DLInfo.java:
--------------------------------------------------------------------------------
1 | package cn.aigestudio.downloader.bizs;
2 |
3 | import java.io.File;
4 | import java.util.ArrayList;
5 | import java.util.List;
6 |
7 | import cn.aigestudio.downloader.interfaces.IDListener;
8 |
9 | /**
10 | * 下载实体类
11 | * Download entity.
12 | *
13 | * @author AigeStudio 2015-05-16
14 | */
15 | public class DLInfo {
16 | public int totalBytes;
17 | public int currentBytes;
18 | public String fileName;
19 | public String dirPath;
20 | public String baseUrl;
21 | public String realUrl;
22 |
23 | int redirect;
24 | boolean hasListener;
25 | boolean isResume;
26 | boolean isStop;
27 | String mimeType;
28 | String eTag;
29 | String disposition;
30 | String location;
31 | List requestHeaders;
32 | final List threads;
33 | IDListener listener;
34 | File file;
35 |
36 | DLInfo() {
37 | threads = new ArrayList<>();
38 | }
39 |
40 | synchronized void addDLThread(DLThreadInfo info) {
41 | threads.add(info);
42 | }
43 |
44 | synchronized void removeDLThread(DLThreadInfo info) {
45 | threads.remove(info);
46 | }
47 | }
--------------------------------------------------------------------------------
/Downloader/src/main/java/cn/aigestudio/downloader/bizs/DLManager.java:
--------------------------------------------------------------------------------
1 | package cn.aigestudio.downloader.bizs;
2 |
3 | import android.content.Context;
4 | import android.text.TextUtils;
5 | import android.util.Log;
6 |
7 | import java.io.File;
8 | import java.util.ArrayList;
9 | import java.util.Collections;
10 | import java.util.List;
11 | import java.util.concurrent.BlockingQueue;
12 | import java.util.concurrent.ConcurrentHashMap;
13 | import java.util.concurrent.ExecutorService;
14 | import java.util.concurrent.LinkedBlockingQueue;
15 | import java.util.concurrent.ThreadFactory;
16 | import java.util.concurrent.ThreadPoolExecutor;
17 | import java.util.concurrent.TimeUnit;
18 | import java.util.concurrent.atomic.AtomicInteger;
19 |
20 | import cn.aigestudio.downloader.interfaces.IDListener;
21 |
22 | import static cn.aigestudio.downloader.bizs.DLCons.DEBUG;
23 | import static cn.aigestudio.downloader.bizs.DLError.ERROR_INVALID_URL;
24 | import static cn.aigestudio.downloader.bizs.DLError.ERROR_NOT_NETWORK;
25 | import static cn.aigestudio.downloader.bizs.DLError.ERROR_REPEAT_URL;
26 |
27 | /**
28 | * 下载管理器
29 | * Download manager
30 | * 执行具体的下载操作
31 | *
32 | * @author AigeStudio 2015-05-09
33 | * 开始一个下载任务只需调用{@link #dlStart}方法即可
34 | * 停止某个下载任务需要调用{@link #dlStop}方法 停止下载任务仅仅会将对应下载任务移除下载队列而不删除相应数据 下次启动相同任务时会自动根据上一次停止时保存的数据重新开始下载
35 | * 取消某个下载任务需要调用{@link #dlCancel}方法 取消下载任务会删除掉相应的本地数据库数据但文件不会被删除
36 | * 相同url的下载任务视为相同任务
37 | * Use {@link #dlStart} for a new download task.
38 | * Use {@link #dlStop} to stop a download task base on url.
39 | * Use {@link #dlCancel} to cancel a download task base on url.
40 | * By the way, the difference between {@link #dlStop} and {@link #dlCancel} is whether the data in database would be deleted or not,
41 | * for example, the state of download like local file and data in database will be save when you use {@link #dlStop} stop a download task,
42 | * if you use {@link #dlCancel} cancel a download task, anything related to download task would be deleted.
43 | * @author AigeStudio 2015-05-26
44 | * 对不支持断点下载的文件直接使用单线程下载 该操作将不会插入数据库
45 | * 对转向地址进行解析
46 | * 更改下载线程分配逻辑
47 | * DLManager will download with single thread if server does not support break-point, and it will not insert to database
48 | * Support url redirection.
49 | * Change download thread size dispath.
50 | * @author AigeStudio 2015-05-29
51 | * 修改域名重定向后无法多线程下载问题
52 | * 修改域名重定向后无法暂停问题
53 | * Bugfix:can not start multi-threads to download file when we in url redirection.
54 | * Bugfix:can not stop a download task when we in url redirection.
55 | * @author zhangchi 2015-10-13
56 | * Bugfix:修改多次触发任务时的并发问题,防止同时触发多个相同的下载任务;修改任务队列为线程安全模式;
57 | * 修改多线程任务的线程数量设置机制,每个任务可以自定义设置下载线程数量;通过同构方法dlStart(String url, String dirPath, DLTaskListener listener,int threadNum);
58 | * 添加日志开关及日志记录,开关方法为setDebugEnable,日志TAG为DLManager;方便调试;
59 | * @author AigeStudio 2015-10-23
60 | * 修复大部分已知Bug
61 | * 优化代码逻辑适应更多不同的下载情况
62 | * 完善错误码机制,使用不同的错误码标识不同错误的发生,详情请参见{@link DLError}
63 | * 不再判断网络类型只会对是否联网做一个简单的判断
64 | * 修改{@link #setDebugEnable(boolean)}方法
65 | * 新增多个不同的{@link #dlStart}方法便于回调
66 | * 新增{@link #setMaxTask(int)}方法限制多个下载任务的并发数
67 | * @author AigeStudio 2015-11-05
68 | * 修复较大文件下载暂停后无法续传问题
69 | * 修复下载无法取消问题
70 | * 优化线程分配
71 | * 优化下载逻辑提升执行效率
72 | * @author AigeStudio 2015-11-27
73 | * 新增{@link #getDLInfo(String)}方法获取瞬时下载信息
74 | * 新增{@link #getDLDBManager()}方法获取数据库管理对象
75 | * @author AigeStudio 2015-12-16
76 | * 修复非断点下载情况下无法暂停问题
77 | * 修复非断点下载情况下载完成后无法获得文件的问题
78 | */
79 | public final class DLManager {
80 | private static final String TAG = DLManager.class.getSimpleName();
81 |
82 | private static final int CORES = Runtime.getRuntime().availableProcessors();
83 | private static final int POOL_SIZE = CORES + 1;
84 | private static final int POOL_SIZE_MAX = CORES * 2 + 1;
85 |
86 | private static final BlockingQueue POOL_QUEUE_TASK = new LinkedBlockingQueue<>(56);
87 | private static final BlockingQueue POOL_QUEUE_THREAD = new LinkedBlockingQueue<>(256);
88 |
89 | private static final ThreadFactory TASK_FACTORY = new ThreadFactory() {
90 | private final AtomicInteger COUNT = new AtomicInteger(1);
91 |
92 | @Override
93 | public Thread newThread(Runnable runnable) {
94 | return new Thread(runnable, "DLTask #" + COUNT.getAndIncrement());
95 | }
96 | };
97 |
98 | private static final ThreadFactory THREAD_FACTORY = new ThreadFactory() {
99 | private final AtomicInteger COUNT = new AtomicInteger(1);
100 |
101 | @Override
102 | public Thread newThread(Runnable runnable) {
103 | return new Thread(runnable, "DLThread #" + COUNT.getAndIncrement());
104 | }
105 | };
106 |
107 | private static final ExecutorService POOL_TASK = new ThreadPoolExecutor(POOL_SIZE,
108 | POOL_SIZE_MAX, 3, TimeUnit.SECONDS, POOL_QUEUE_TASK, TASK_FACTORY);
109 | private static final ExecutorService POOL_Thread = new ThreadPoolExecutor(POOL_SIZE * 5,
110 | POOL_SIZE_MAX * 5, 1, TimeUnit.SECONDS, POOL_QUEUE_THREAD, THREAD_FACTORY);
111 |
112 | private static final ConcurrentHashMap TASK_DLING = new ConcurrentHashMap<>();
113 | private static final List TASK_PREPARE =
114 | Collections.synchronizedList(new ArrayList());
115 | private static final ConcurrentHashMap TASK_STOPPED = new ConcurrentHashMap<>();
116 |
117 | private static DLManager sManager;
118 |
119 | private Context context;
120 |
121 | private int maxTask = 10;
122 |
123 | private DLManager(Context context) {
124 | this.context = context;
125 | }
126 |
127 | public static DLManager getInstance(Context context) {
128 | if (null == sManager) {
129 | sManager = new DLManager(context);
130 | }
131 | return sManager;
132 | }
133 |
134 | /**
135 | * 设置并发下载任务最大值
136 | * The max task of DLManager.
137 | *
138 | * @param maxTask ...
139 | * @return ...
140 | */
141 | public DLManager setMaxTask(int maxTask) {
142 | this.maxTask = maxTask;
143 | return sManager;
144 | }
145 |
146 | /**
147 | * 设置是否开启Debug模式 默认不开启
148 | * Is debug mode, default is false.
149 | *
150 | * @param isDebug ...
151 | * @return ...
152 | */
153 | public DLManager setDebugEnable(boolean isDebug) {
154 | DLCons.DEBUG = isDebug;
155 | return sManager;
156 | }
157 |
158 | /**
159 | * @see #dlStart(String, String, String, List, IDListener)
160 | */
161 | public void dlStart(String url) {
162 | dlStart(url, "", "", null, null);
163 | }
164 |
165 | /**
166 | * @see #dlStart(String, String, String, List, IDListener)
167 | */
168 | public void dlStart(String url, IDListener listener) {
169 | dlStart(url, "", "", null, listener);
170 | }
171 |
172 | /**
173 | * @see #dlStart(String, String, String, List, IDListener)
174 | */
175 | public void dlStart(String url, String dir, IDListener listener) {
176 | dlStart(url, dir, "", null, listener);
177 | }
178 |
179 | /**
180 | * @see #dlStart(String, String, String, List, IDListener)
181 | */
182 | public void dlStart(String url, String dir, String name, IDListener listener) {
183 | dlStart(url, dir, name, null, listener);
184 | }
185 |
186 | /**
187 | * 开始一个下载任务
188 | * Start a download task.
189 | *
190 | * @param url 文件下载地址
191 | * Download url.
192 | * @param dir 文件下载后保存的目录地址,该值为空时会默认使用应用的文件缓存目录作为保存目录地址
193 | * The directory of download file. This parameter can be null, in this case we
194 | * will use cache dir of app for download path.
195 | * @param name 文件名,文件名需要包括文件扩展名,类似“AigeStudio.apk”的格式。该值可为空,为空时将由程
196 | * 序决定文件名。
197 | * Name of download file, include extension like "AigeStudio.apk". This
198 | * parameter can be null, in this case the file name will be decided by program.
199 | * @param headers 请求头参数
200 | * Request header of http.
201 | * @param listener 下载监听器
202 | * Listener of download task.
203 | */
204 | public void dlStart(String url, String dir, String name, List headers, IDListener listener) {
205 | boolean hasListener = listener != null;
206 | if (TextUtils.isEmpty(url)) {
207 | if (hasListener) listener.onError(ERROR_INVALID_URL, "Url can not be null.");
208 | return;
209 | }
210 | if (!DLUtil.isNetworkAvailable(context)) {
211 | if (hasListener) listener.onError(ERROR_NOT_NETWORK, "Network is not available.");
212 | return;
213 | }
214 | if (TASK_DLING.containsKey(url)) {
215 | if (null != listener) listener.onError(ERROR_REPEAT_URL, url + " is downloading.");
216 | } else {
217 | DLInfo info;
218 | if (TASK_STOPPED.containsKey(url)) {
219 | if (DEBUG) Log.d(TAG, "Resume task from memory.");
220 | info = TASK_STOPPED.remove(url);
221 | } else {
222 | if (DEBUG) Log.d(TAG, "Resume task from database.");
223 | info = DLDBManager.getInstance(context).queryTaskInfo(url);
224 | if (null != info) {
225 | info.threads.clear();
226 | info.threads.addAll(DLDBManager.getInstance(context).queryAllThreadInfo(url));
227 | }
228 | }
229 | if (null == info) {
230 | if (DEBUG) Log.d(TAG, "New task will be start.");
231 | info = new DLInfo();
232 | info.baseUrl = url;
233 | info.realUrl = url;
234 | if (TextUtils.isEmpty(dir)) dir = context.getCacheDir().getAbsolutePath();
235 | info.dirPath = dir;
236 | info.fileName = name;
237 | } else {
238 | info.isResume = true;
239 | for (DLThreadInfo threadInfo : info.threads) {
240 | threadInfo.isStop = false;
241 | }
242 | }
243 | info.redirect = 0;
244 | info.requestHeaders = DLUtil.initRequestHeaders(headers, info);
245 | info.listener = listener;
246 | info.hasListener = hasListener;
247 | if (TASK_DLING.size() >= maxTask) {
248 | if (DEBUG) Log.w(TAG, "Downloading urls is out of range.");
249 | TASK_PREPARE.add(info);
250 | } else {
251 | if (DEBUG) Log.d(TAG, "Prepare download from " + info.baseUrl);
252 | if (hasListener) listener.onPrepare();
253 | TASK_DLING.put(url, info);
254 | POOL_TASK.execute(new DLTask(context, info));
255 | }
256 | }
257 | }
258 |
259 | /**
260 | * 根据Url暂停一个下载任务
261 | * Stop a download task according to url.
262 | *
263 | * @param url 文件下载地址
264 | * Download url.
265 | */
266 | public void dlStop(String url) {
267 | if (TASK_DLING.containsKey(url)) {
268 | DLInfo info = TASK_DLING.get(url);
269 | info.isStop = true;
270 | if (!info.threads.isEmpty()) {
271 | for (DLThreadInfo threadInfo : info.threads) {
272 | threadInfo.isStop = true;
273 | }
274 | }
275 | }
276 | }
277 |
278 | /**
279 | * 根据Url取消一个下载任务
280 | * Cancel a download task according to url.
281 | *
282 | * @param url 文件下载地址
283 | * Download url.
284 | */
285 | public void dlCancel(String url) {
286 | dlStop(url);
287 | DLInfo info;
288 | if (TASK_DLING.containsKey(url)) {
289 | info = TASK_DLING.get(url);
290 | } else {
291 | info = DLDBManager.getInstance(context).queryTaskInfo(url);
292 | }
293 | if (null != info) {
294 | File file = new File(info.dirPath, info.fileName);
295 | if (file.exists()) file.delete();
296 | }
297 | DLDBManager.getInstance(context).deleteTaskInfo(url);
298 | DLDBManager.getInstance(context).deleteAllThreadInfo(url);
299 | }
300 |
301 | public DLInfo getDLInfo(String url) {
302 | return DLDBManager.getInstance(context).queryTaskInfo(url);
303 | }
304 |
305 | @Deprecated
306 | public DLDBManager getDLDBManager() {
307 | return DLDBManager.getInstance(context);
308 | }
309 |
310 | synchronized DLManager removeDLTask(String url) {
311 | TASK_DLING.remove(url);
312 | return sManager;
313 | }
314 |
315 | synchronized DLManager addDLTask() {
316 | if (!TASK_PREPARE.isEmpty()) {
317 | POOL_TASK.execute(new DLTask(context, TASK_PREPARE.remove(0)));
318 | }
319 | return sManager;
320 | }
321 |
322 | synchronized DLManager addStopTask(DLInfo info) {
323 | TASK_STOPPED.put(info.baseUrl, info);
324 | return sManager;
325 | }
326 |
327 | synchronized DLManager addDLThread(DLThread thread) {
328 | POOL_Thread.execute(thread);
329 | return sManager;
330 | }
331 |
332 | }
--------------------------------------------------------------------------------
/Downloader/src/main/java/cn/aigestudio/downloader/bizs/DLTask.java:
--------------------------------------------------------------------------------
1 | package cn.aigestudio.downloader.bizs;
2 |
3 |
4 | import android.content.Context;
5 | import android.os.Process;
6 | import android.text.TextUtils;
7 | import android.util.Log;
8 |
9 | import java.io.File;
10 | import java.io.FileOutputStream;
11 | import java.io.IOException;
12 | import java.io.InputStream;
13 | import java.net.HttpURLConnection;
14 | import java.net.URL;
15 | import java.util.UUID;
16 |
17 | import static cn.aigestudio.downloader.bizs.DLCons.Base.DEFAULT_TIMEOUT;
18 | import static cn.aigestudio.downloader.bizs.DLCons.Base.LENGTH_PER_THREAD;
19 | import static cn.aigestudio.downloader.bizs.DLCons.Base.MAX_REDIRECTS;
20 | import static cn.aigestudio.downloader.bizs.DLCons.Code.HTTP_MOVED_PERM;
21 | import static cn.aigestudio.downloader.bizs.DLCons.Code.HTTP_MOVED_TEMP;
22 | import static cn.aigestudio.downloader.bizs.DLCons.Code.HTTP_NOT_MODIFIED;
23 | import static cn.aigestudio.downloader.bizs.DLCons.Code.HTTP_OK;
24 | import static cn.aigestudio.downloader.bizs.DLCons.Code.HTTP_PARTIAL;
25 | import static cn.aigestudio.downloader.bizs.DLCons.Code.HTTP_SEE_OTHER;
26 | import static cn.aigestudio.downloader.bizs.DLCons.Code.HTTP_TEMP_REDIRECT;
27 | import static cn.aigestudio.downloader.bizs.DLError.ERROR_OPEN_CONNECT;
28 |
29 | class DLTask implements Runnable, IDLThreadListener {
30 | private static final String TAG = DLTask.class.getSimpleName();
31 |
32 | private DLInfo info;
33 | private Context context;
34 |
35 | private int totalProgress;
36 | private int count;
37 | private long lastTime = System.currentTimeMillis();
38 |
39 | DLTask(Context context, DLInfo info) {
40 | this.info = info;
41 | this.context = context;
42 | this.totalProgress = info.currentBytes;
43 | if (!info.isResume) DLDBManager.getInstance(context).insertTaskInfo(info);
44 | }
45 |
46 | @Override
47 | public synchronized void onProgress(int progress) {
48 | totalProgress += progress;
49 | long currentTime = System.currentTimeMillis();
50 | if (currentTime - lastTime > 1000) {
51 | Log.d(TAG, totalProgress + "");
52 | if (info.hasListener) info.listener.onProgress(totalProgress);
53 | lastTime = currentTime;
54 | }
55 | }
56 |
57 | @Override
58 | public synchronized void onStop(DLThreadInfo threadInfo) {
59 | if (null == threadInfo) {
60 | DLManager.getInstance(context).removeDLTask(info.baseUrl);
61 | DLDBManager.getInstance(context).deleteTaskInfo(info.baseUrl);
62 | if (info.hasListener) {
63 | info.listener.onProgress(info.totalBytes);
64 | info.listener.onStop(info.totalBytes);
65 | }
66 | return;
67 | }
68 | DLDBManager.getInstance(context).updateThreadInfo(threadInfo);
69 | count++;
70 | if (count >= info.threads.size()) {
71 | Log.d(TAG, "All the threads was stopped.");
72 | info.currentBytes = totalProgress;
73 | DLManager.getInstance(context).addStopTask(info).removeDLTask(info.baseUrl);
74 | DLDBManager.getInstance(context).updateTaskInfo(info);
75 | count = 0;
76 | if (info.hasListener) info.listener.onStop(totalProgress);
77 | }
78 | }
79 |
80 | @Override
81 | public synchronized void onFinish(DLThreadInfo threadInfo) {
82 | if (null == threadInfo) {
83 | DLManager.getInstance(context).removeDLTask(info.baseUrl);
84 | DLDBManager.getInstance(context).deleteTaskInfo(info.baseUrl);
85 | if (info.hasListener) {
86 | info.listener.onProgress(info.totalBytes);
87 | info.listener.onFinish(info.file);
88 | }
89 | return;
90 | }
91 | info.removeDLThread(threadInfo);
92 | DLDBManager.getInstance(context).deleteThreadInfo(threadInfo.id);
93 | Log.d(TAG, "Thread size " + info.threads.size());
94 | if (info.threads.isEmpty()) {
95 | Log.d(TAG, "Task was finished.");
96 | DLManager.getInstance(context).removeDLTask(info.baseUrl);
97 | DLDBManager.getInstance(context).deleteTaskInfo(info.baseUrl);
98 | if (info.hasListener) {
99 | info.listener.onProgress(info.totalBytes);
100 | info.listener.onFinish(info.file);
101 | }
102 | DLManager.getInstance(context).addDLTask();
103 | }
104 | }
105 |
106 | @Override
107 | public void run() {
108 | Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
109 | while (info.redirect < MAX_REDIRECTS) {
110 | HttpURLConnection conn = null;
111 | try {
112 | conn = (HttpURLConnection) new URL(info.realUrl).openConnection();
113 | conn.setInstanceFollowRedirects(false);
114 | conn.setConnectTimeout(DEFAULT_TIMEOUT);
115 | conn.setReadTimeout(DEFAULT_TIMEOUT);
116 |
117 | addRequestHeaders(conn);
118 |
119 | final int code = conn.getResponseCode();
120 | Log.d("AigeStudio", code+"");
121 | switch (code) {
122 | case HTTP_OK:
123 | case HTTP_PARTIAL:
124 | dlInit(conn, code);
125 | return;
126 | case HTTP_MOVED_PERM:
127 | case HTTP_MOVED_TEMP:
128 | case HTTP_SEE_OTHER:
129 | case HTTP_NOT_MODIFIED:
130 | case HTTP_TEMP_REDIRECT:
131 | final String location = conn.getHeaderField("location");
132 | if (TextUtils.isEmpty(location))
133 | throw new DLException(
134 | "Can not obtain real url from location in header.");
135 | info.realUrl = location;
136 | info.redirect++;
137 | continue;
138 | default:
139 | if (info.hasListener)
140 | info.listener.onError(code, conn.getResponseMessage());
141 | DLManager.getInstance(context).removeDLTask(info.baseUrl);
142 | return;
143 | }
144 | } catch (Exception e) {
145 | if (info.hasListener) info.listener.onError(ERROR_OPEN_CONNECT, e.toString());
146 | DLManager.getInstance(context).removeDLTask(info.baseUrl);
147 | return;
148 | } finally {
149 | if (null != conn) conn.disconnect();
150 | }
151 | }
152 | throw new RuntimeException("Too many redirects");
153 | }
154 |
155 | private void dlInit(HttpURLConnection conn, int code) throws Exception {
156 | readResponseHeaders(conn);
157 | DLDBManager.getInstance(context).updateTaskInfo(info);
158 | if (!DLUtil.createFile(info.dirPath, info.fileName))
159 | throw new DLException("Can not create file");
160 | info.file = new File(info.dirPath, info.fileName);
161 | if (info.file.exists() && info.file.length() == info.totalBytes) {
162 | Log.d(TAG, "The file which we want to download was already here.");
163 | return;
164 | }
165 | if (info.hasListener) info.listener.onStart(info.fileName, info.realUrl, info.totalBytes);
166 | switch (code) {
167 | case HTTP_OK:
168 | dlData(conn);
169 | break;
170 | case HTTP_PARTIAL:
171 | if (info.totalBytes <= 0) {
172 | dlData(conn);
173 | break;
174 | }
175 | if (info.isResume) {
176 | for (DLThreadInfo threadInfo : info.threads) {
177 | DLManager.getInstance(context)
178 | .addDLThread(new DLThread(threadInfo, info, this));
179 | }
180 | break;
181 | }
182 | dlDispatch();
183 | break;
184 | }
185 | }
186 |
187 | private void dlDispatch() {
188 | int threadSize;
189 | int threadLength = LENGTH_PER_THREAD;
190 | if (info.totalBytes <= LENGTH_PER_THREAD) {
191 | threadSize = 2;
192 | threadLength = info.totalBytes / threadSize;
193 | } else {
194 | threadSize = info.totalBytes / LENGTH_PER_THREAD;
195 | }
196 | int remainder = info.totalBytes % threadLength;
197 | for (int i = 0; i < threadSize; i++) {
198 | int start = i * threadLength;
199 | int end = start + threadLength - 1;
200 | if (i == threadSize - 1) {
201 | end = start + threadLength + remainder - 1;
202 | }
203 | DLThreadInfo threadInfo =
204 | new DLThreadInfo(UUID.randomUUID().toString(), info.baseUrl, start, end);
205 | info.addDLThread(threadInfo);
206 | DLDBManager.getInstance(context).insertThreadInfo(threadInfo);
207 | DLManager.getInstance(context).addDLThread(new DLThread(threadInfo, info, this));
208 | }
209 | }
210 |
211 | private void dlData(HttpURLConnection conn) throws IOException {
212 | InputStream is = conn.getInputStream();
213 | FileOutputStream fos = new FileOutputStream(info.file);
214 | byte[] b = new byte[4096];
215 | int len;
216 | while (!info.isStop && (len = is.read(b)) != -1) {
217 | fos.write(b, 0, len);
218 | onProgress(len);
219 | }
220 | if (!info.isStop) {
221 | onFinish(null);
222 | } else {
223 | onStop(null);
224 | }
225 | fos.close();
226 | is.close();
227 | }
228 |
229 | private void addRequestHeaders(HttpURLConnection conn) {
230 | for (DLHeader header : info.requestHeaders) {
231 | conn.addRequestProperty(header.key, header.value);
232 | }
233 | }
234 |
235 | private void readResponseHeaders(HttpURLConnection conn) {
236 | info.disposition = conn.getHeaderField("Content-Disposition");
237 | info.location = conn.getHeaderField("Content-Location");
238 | info.mimeType = DLUtil.normalizeMimeType(conn.getContentType());
239 | final String transferEncoding = conn.getHeaderField("Transfer-Encoding");
240 | if (TextUtils.isEmpty(transferEncoding)) {
241 | try {
242 | info.totalBytes = Integer.parseInt(conn.getHeaderField("Content-Length"));
243 | } catch (NumberFormatException e) {
244 | info.totalBytes = -1;
245 | }
246 | } else {
247 | info.totalBytes = -1;
248 | }
249 | if (info.totalBytes == -1 && (TextUtils.isEmpty(transferEncoding) ||
250 | !transferEncoding.equalsIgnoreCase("chunked")))
251 | throw new RuntimeException("Can not obtain size of download file.");
252 | if (TextUtils.isEmpty(info.fileName))
253 | info.fileName = DLUtil.obtainFileName(info.realUrl, info.disposition, info.location);
254 | }
255 | }
--------------------------------------------------------------------------------
/Downloader/src/main/java/cn/aigestudio/downloader/bizs/DLThread.java:
--------------------------------------------------------------------------------
1 | package cn.aigestudio.downloader.bizs;
2 |
3 | import android.os.Process;
4 | import android.util.Log;
5 |
6 | import java.io.IOException;
7 | import java.io.InputStream;
8 | import java.io.RandomAccessFile;
9 | import java.net.HttpURLConnection;
10 | import java.net.URL;
11 |
12 | import static cn.aigestudio.downloader.bizs.DLCons.Base.DEFAULT_TIMEOUT;
13 |
14 | class DLThread implements Runnable {
15 | private static final String TAG = DLThread.class.getSimpleName();
16 |
17 | private DLThreadInfo dlThreadInfo;
18 | private DLInfo dlInfo;
19 | private IDLThreadListener listener;
20 |
21 | public DLThread(DLThreadInfo dlThreadInfo, DLInfo dlInfo, IDLThreadListener listener) {
22 | this.dlThreadInfo = dlThreadInfo;
23 | this.listener = listener;
24 | this.dlInfo = dlInfo;
25 | }
26 |
27 | @Override
28 | public void run() {
29 | Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
30 |
31 | HttpURLConnection conn = null;
32 | RandomAccessFile raf = null;
33 | InputStream is = null;
34 | try {
35 | conn = (HttpURLConnection) new URL(dlInfo.realUrl).openConnection();
36 | conn.setConnectTimeout(DEFAULT_TIMEOUT);
37 | conn.setReadTimeout(DEFAULT_TIMEOUT);
38 |
39 | addRequestHeaders(conn);
40 |
41 | raf = new RandomAccessFile(dlInfo.file, "rwd");
42 | raf.seek(dlThreadInfo.start);
43 |
44 | is = conn.getInputStream();
45 |
46 | byte[] b = new byte[4096];
47 | int len;
48 | while (!dlThreadInfo.isStop && (len = is.read(b)) != -1) {
49 | dlThreadInfo.start += len;
50 | raf.write(b, 0, len);
51 | listener.onProgress(len);
52 | }
53 | if (dlThreadInfo.isStop) {
54 | Log.d(TAG, "Thread " + dlThreadInfo.id + " will be stopped.");
55 | listener.onStop(dlThreadInfo);
56 | } else {
57 | Log.d(TAG, "Thread " + dlThreadInfo.id + " will be finished.");
58 | listener.onFinish(dlThreadInfo);
59 | }
60 | } catch (IOException e) {
61 | listener.onStop(dlThreadInfo);
62 | e.printStackTrace();
63 | } finally {
64 | try {
65 | if (null != is) is.close();
66 | if (null != raf) raf.close();
67 | } catch (IOException e) {
68 | e.printStackTrace();
69 | }
70 | if (null != conn) conn.disconnect();
71 | }
72 | }
73 |
74 | private void addRequestHeaders(HttpURLConnection conn) {
75 | for (DLHeader header : dlInfo.requestHeaders) {
76 | conn.addRequestProperty(header.key, header.value);
77 | }
78 | conn.setRequestProperty("Range", "bytes=" + dlThreadInfo.start + "-" + dlThreadInfo.end);
79 | }
80 | }
--------------------------------------------------------------------------------
/Downloader/src/main/java/cn/aigestudio/downloader/bizs/DLThreadInfo.java:
--------------------------------------------------------------------------------
1 | package cn.aigestudio.downloader.bizs;
2 |
3 | class DLThreadInfo {
4 | String id;
5 | String baseUrl;
6 | int start, end;
7 | boolean isStop;
8 |
9 | DLThreadInfo(String id, String baseUrl, int start, int end) {
10 | this.id = id;
11 | this.baseUrl = baseUrl;
12 | this.start = start;
13 | this.end = end;
14 | }
15 | }
--------------------------------------------------------------------------------
/Downloader/src/main/java/cn/aigestudio/downloader/bizs/DLUtil.java:
--------------------------------------------------------------------------------
1 | package cn.aigestudio.downloader.bizs;
2 |
3 | import android.content.Context;
4 | import android.net.ConnectivityManager;
5 | import android.net.NetworkInfo;
6 | import android.net.Uri;
7 | import android.os.Build;
8 | import android.text.TextUtils;
9 |
10 | import java.io.File;
11 | import java.io.IOException;
12 | import java.util.ArrayList;
13 | import java.util.List;
14 | import java.util.UUID;
15 |
16 | final class DLUtil {
17 | public static final String DEFAULT_USER_AGENT;
18 |
19 | static {
20 | final StringBuilder builder = new StringBuilder();
21 |
22 | final boolean validRelease = !TextUtils.isEmpty(Build.VERSION.RELEASE);
23 | final boolean validId = !TextUtils.isEmpty(Build.ID);
24 | final boolean includeModel = "REL".equals(Build.VERSION.CODENAME)
25 | && !TextUtils.isEmpty(Build.MODEL);
26 |
27 | builder.append("MultiThreadDownloader");
28 | if (validRelease) {
29 | builder.append("/").append(Build.VERSION.RELEASE);
30 | }
31 | builder.append(" (Linux; U; Android");
32 | if (validRelease) {
33 | builder.append(" ").append(Build.VERSION.RELEASE);
34 | }
35 | if (includeModel || validId) {
36 | builder.append(";");
37 | if (includeModel) {
38 | builder.append(" ").append(Build.MODEL);
39 | }
40 | if (validId) {
41 | builder.append(" Build/").append(Build.ID);
42 | }
43 | }
44 | builder.append(")");
45 |
46 | DEFAULT_USER_AGENT = builder.toString();
47 | }
48 |
49 | private DLUtil() {
50 | }
51 |
52 | static String normalizeMimeType(String type) {
53 | if (type == null) {
54 | return null;
55 | }
56 | type = type.trim().toLowerCase();
57 | final int semicolonIndex = type.indexOf(';');
58 | if (semicolonIndex != -1) {
59 | type = type.substring(0, semicolonIndex);
60 | }
61 | return type;
62 | }
63 |
64 | static List initRequestHeaders(List headers, DLInfo info) {
65 | if (null == headers || headers.isEmpty()) {
66 | headers = new ArrayList<>();
67 | headers.add(new DLHeader("Accept", "image/gif, image/jpeg, image/pjpeg, image/pjpeg," +
68 | "application/x-shockwave-flash, application/xaml+xml," +
69 | "application/vnd.ms-xpsdocument, application/x-ms-xbap," +
70 | "application/x-ms-application, application/vnd.ms-excel," +
71 | "application/vnd.ms-powerpoint, application/msword, */*"));
72 | headers.add(new DLHeader("Accept-Ranges", "bytes"));
73 | headers.add(new DLHeader("Charset", "UTF-8"));
74 | headers.add(new DLHeader("Connection", "Keep-Alive"));
75 | headers.add(new DLHeader("Accept-Encoding", "identity"));
76 | headers.add(new DLHeader("Range", "bytes=" + 0 + "-"));
77 | }
78 | if (!hasRequestHeader("User-Agent", headers)) {
79 | headers.add(new DLHeader("User-Agent", DEFAULT_USER_AGENT));
80 | }
81 | if (!TextUtils.isEmpty(info.eTag)) {
82 | headers.add(new DLHeader("If-Match", info.eTag));
83 | }
84 | return headers;
85 | }
86 |
87 | private static boolean hasRequestHeader(String key, List headers) {
88 | for (DLHeader header : headers) {
89 | if (header.key.equalsIgnoreCase(key)) {
90 | return true;
91 | }
92 | }
93 | return false;
94 | }
95 |
96 | static String obtainFileName(String url, String contentDisposition, String contentLocation) {
97 | String fileName = null;
98 | if (null != contentDisposition) {
99 | fileName = parseContentDisposition(contentDisposition);
100 | if (null != fileName) {
101 | int index = fileName.lastIndexOf('/') + 1;
102 | if (index > 0) {
103 | fileName = fileName.substring(index);
104 | }
105 | }
106 | }
107 | if (fileName == null && contentLocation != null) {
108 | String decodedContentLocation = Uri.decode(contentLocation);
109 | if (decodedContentLocation != null && !decodedContentLocation.endsWith("/")
110 | && decodedContentLocation.indexOf('?') < 0) {
111 | int index = decodedContentLocation.lastIndexOf('/') + 1;
112 | if (index > 0) {
113 | fileName = decodedContentLocation.substring(index);
114 | } else {
115 | fileName = decodedContentLocation;
116 | }
117 | }
118 | }
119 | if (fileName == null) {
120 | String decodedUrl = Uri.decode(url);
121 | if (decodedUrl != null && !decodedUrl.endsWith("/") && decodedUrl.indexOf('?') < 0) {
122 | int index = decodedUrl.lastIndexOf('/') + 1;
123 | if (index > 0) {
124 | fileName = decodedUrl.substring(index);
125 | }
126 | }
127 | }
128 | if (fileName == null) {
129 | fileName = UUID.randomUUID().toString();
130 | }
131 | fileName = replaceInvalidVFATCharacters(fileName);
132 | return fileName;
133 | }
134 |
135 | private static String parseContentDisposition(String contentDisposition) {
136 | int index = contentDisposition.indexOf("=");
137 | if (index > 0) {
138 | return contentDisposition.substring(index + 1);
139 | }
140 | return null;
141 | }
142 |
143 | private static String replaceInvalidVFATCharacters(String fileName) {
144 | final char END_CTRLCODE = 0x1f;
145 | final char QUOTEDBL = 0x22;
146 | final char ASTERISK = 0x2A;
147 | final char SLASH = 0x2F;
148 | final char COLON = 0x3A;
149 | final char LESS = 0x3C;
150 | final char GREATER = 0x3E;
151 | final char QUESTION = 0x3F;
152 | final char BACKSLASH = 0x5C;
153 | final char BAR = 0x7C;
154 | final char DEL = 0x7F;
155 | final char UNDERSCORE = 0x5F;
156 |
157 | StringBuilder sb = new StringBuilder();
158 | char ch;
159 | boolean isRepetition = false;
160 | for (int i = 0; i < fileName.length(); i++) {
161 | ch = fileName.charAt(i);
162 | if (ch <= END_CTRLCODE || ch == QUOTEDBL || ch == ASTERISK || ch == SLASH ||
163 | ch == COLON || ch == LESS || ch == GREATER || ch == QUESTION ||
164 | ch == BACKSLASH || ch == BAR || ch == DEL) {
165 | if (!isRepetition) {
166 | sb.append(UNDERSCORE);
167 | isRepetition = true;
168 | }
169 | } else {
170 | sb.append(ch);
171 | isRepetition = false;
172 | }
173 | }
174 | return sb.toString();
175 | }
176 |
177 | static synchronized boolean createFile(String path, String fileName) {
178 | boolean hasFile = false;
179 | try {
180 | File dir = new File(path);
181 | boolean hasDir = dir.exists() || dir.mkdirs();
182 | if (hasDir) {
183 | File file = new File(dir, fileName);
184 | hasFile = file.exists() || file.createNewFile();
185 | }
186 | } catch (IOException e) {
187 | e.printStackTrace();
188 | }
189 | return hasFile;
190 | }
191 |
192 | static boolean isNetworkAvailable(Context context) {
193 | try {
194 | ConnectivityManager cm =
195 | (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
196 | NetworkInfo info = cm.getActiveNetworkInfo();
197 | return null != info && info.isConnected();
198 | } catch (Exception e) {
199 | e.printStackTrace();
200 | }
201 | return false;
202 | }
203 | }
--------------------------------------------------------------------------------
/Downloader/src/main/java/cn/aigestudio/downloader/bizs/IDLThreadListener.java:
--------------------------------------------------------------------------------
1 | package cn.aigestudio.downloader.bizs;
2 |
3 | interface IDLThreadListener {
4 | void onProgress(int progress);
5 |
6 | void onStop(DLThreadInfo threadInfo);
7 |
8 | void onFinish(DLThreadInfo threadInfo);
9 | }
--------------------------------------------------------------------------------
/Downloader/src/main/java/cn/aigestudio/downloader/bizs/ITaskDAO.java:
--------------------------------------------------------------------------------
1 | package cn.aigestudio.downloader.bizs;
2 |
3 | interface ITaskDAO {
4 | void insertTaskInfo(DLInfo info);
5 |
6 | void deleteTaskInfo(String url);
7 |
8 | void updateTaskInfo(DLInfo info);
9 |
10 | DLInfo queryTaskInfo(String url);
11 | }
--------------------------------------------------------------------------------
/Downloader/src/main/java/cn/aigestudio/downloader/bizs/IThreadDAO.java:
--------------------------------------------------------------------------------
1 | package cn.aigestudio.downloader.bizs;
2 |
3 | import java.util.List;
4 |
5 | interface IThreadDAO {
6 | void insertThreadInfo(DLThreadInfo info);
7 |
8 | void deleteThreadInfo(String id);
9 |
10 | void deleteAllThreadInfo(String url);
11 |
12 | void updateThreadInfo(DLThreadInfo info);
13 |
14 | DLThreadInfo queryThreadInfo(String id);
15 |
16 | List queryAllThreadInfo(String url);
17 | }
--------------------------------------------------------------------------------
/Downloader/src/main/java/cn/aigestudio/downloader/bizs/TaskDAO.java:
--------------------------------------------------------------------------------
1 | package cn.aigestudio.downloader.bizs;
2 |
3 | import android.content.Context;
4 | import android.database.Cursor;
5 | import android.database.sqlite.SQLiteDatabase;
6 |
7 | import static cn.aigestudio.downloader.bizs.DLCons.DBCons.TB_TASK;
8 | import static cn.aigestudio.downloader.bizs.DLCons.DBCons.TB_TASK_CURRENT_BYTES;
9 | import static cn.aigestudio.downloader.bizs.DLCons.DBCons.TB_TASK_DIR_PATH;
10 | import static cn.aigestudio.downloader.bizs.DLCons.DBCons.TB_TASK_DISPOSITION;
11 | import static cn.aigestudio.downloader.bizs.DLCons.DBCons.TB_TASK_ETAG;
12 | import static cn.aigestudio.downloader.bizs.DLCons.DBCons.TB_TASK_FILE_NAME;
13 | import static cn.aigestudio.downloader.bizs.DLCons.DBCons.TB_TASK_LOCATION;
14 | import static cn.aigestudio.downloader.bizs.DLCons.DBCons.TB_TASK_MIME_TYPE;
15 | import static cn.aigestudio.downloader.bizs.DLCons.DBCons.TB_TASK_TOTAL_BYTES;
16 | import static cn.aigestudio.downloader.bizs.DLCons.DBCons.TB_TASK_URL_BASE;
17 | import static cn.aigestudio.downloader.bizs.DLCons.DBCons.TB_TASK_URL_REAL;
18 |
19 | class TaskDAO implements ITaskDAO {
20 | private final DLDBHelper dbHelper;
21 |
22 | TaskDAO(Context context) {
23 | dbHelper = new DLDBHelper(context);
24 | }
25 |
26 | @Override
27 | public void insertTaskInfo(DLInfo info) {
28 | SQLiteDatabase db = dbHelper.getWritableDatabase();
29 | db.execSQL("INSERT INTO " + TB_TASK + "(" +
30 | TB_TASK_URL_BASE + ", " +
31 | TB_TASK_URL_REAL + ", " +
32 | TB_TASK_DIR_PATH + ", " +
33 | TB_TASK_FILE_NAME + ", " +
34 | TB_TASK_MIME_TYPE + ", " +
35 | TB_TASK_ETAG + ", " +
36 | TB_TASK_DISPOSITION + ", " +
37 | TB_TASK_LOCATION + ", " +
38 | TB_TASK_CURRENT_BYTES + ", " +
39 | TB_TASK_TOTAL_BYTES + ") values (?,?,?,?,?,?,?,?,?,?)",
40 | new Object[]{info.baseUrl, info.realUrl, info.dirPath, info.fileName,
41 | info.mimeType, info.eTag, info.disposition, info.location,
42 | info.currentBytes, info.totalBytes});
43 | db.close();
44 | }
45 |
46 | @Override
47 | public void deleteTaskInfo(String url) {
48 | SQLiteDatabase db = dbHelper.getWritableDatabase();
49 | db.execSQL("DELETE FROM " + TB_TASK + " WHERE " + TB_TASK_URL_BASE + "=?",
50 | new String[]{url});
51 | db.close();
52 | }
53 |
54 | @Override
55 | public void updateTaskInfo(DLInfo info) {
56 | SQLiteDatabase db = dbHelper.getWritableDatabase();
57 | db.execSQL("UPDATE " + TB_TASK + " SET " +
58 | TB_TASK_DISPOSITION + "=?," +
59 | TB_TASK_LOCATION + "=?," +
60 | TB_TASK_MIME_TYPE + "=?," +
61 | TB_TASK_TOTAL_BYTES + "=?," +
62 | TB_TASK_FILE_NAME + "=?," +
63 | TB_TASK_CURRENT_BYTES + "=? WHERE " +
64 | TB_TASK_URL_BASE + "=?", new Object[]{info.disposition, info.location,
65 | info.mimeType, info.totalBytes, info.fileName, info.currentBytes, info.baseUrl});
66 | db.close();
67 | }
68 |
69 | @Override
70 | public DLInfo queryTaskInfo(String url) {
71 | DLInfo info = null;
72 | SQLiteDatabase db = dbHelper.getWritableDatabase();
73 | Cursor c = db.rawQuery("SELECT " +
74 | TB_TASK_URL_BASE + ", " +
75 | TB_TASK_URL_REAL + ", " +
76 | TB_TASK_DIR_PATH + ", " +
77 | TB_TASK_FILE_NAME + ", " +
78 | TB_TASK_MIME_TYPE + ", " +
79 | TB_TASK_ETAG + ", " +
80 | TB_TASK_DISPOSITION + ", " +
81 | TB_TASK_LOCATION + ", " +
82 | TB_TASK_CURRENT_BYTES + ", " +
83 | TB_TASK_TOTAL_BYTES + " FROM " +
84 | TB_TASK + " WHERE " +
85 | TB_TASK_URL_BASE + "=?", new String[]{url});
86 | if (c.moveToFirst()) {
87 | info = new DLInfo();
88 | info.baseUrl = c.getString(0);
89 | info.realUrl = c.getString(1);
90 | info.dirPath = c.getString(2);
91 | info.fileName = c.getString(3);
92 | info.mimeType = c.getString(4);
93 | info.eTag = c.getString(5);
94 | info.disposition = c.getString(6);
95 | info.location = c.getString(7);
96 | info.currentBytes = c.getInt(8);
97 | info.totalBytes = c.getInt(9);
98 | }
99 | c.close();
100 | db.close();
101 | return info;
102 | }
103 | }
--------------------------------------------------------------------------------
/Downloader/src/main/java/cn/aigestudio/downloader/bizs/ThreadDAO.java:
--------------------------------------------------------------------------------
1 | package cn.aigestudio.downloader.bizs;
2 |
3 | import android.content.Context;
4 | import android.database.Cursor;
5 | import android.database.sqlite.SQLiteDatabase;
6 |
7 | import java.util.ArrayList;
8 | import java.util.List;
9 |
10 | import static cn.aigestudio.downloader.bizs.DLCons.DBCons.TB_THREAD;
11 | import static cn.aigestudio.downloader.bizs.DLCons.DBCons.TB_THREAD_END;
12 | import static cn.aigestudio.downloader.bizs.DLCons.DBCons.TB_THREAD_ID;
13 | import static cn.aigestudio.downloader.bizs.DLCons.DBCons.TB_THREAD_START;
14 | import static cn.aigestudio.downloader.bizs.DLCons.DBCons.TB_THREAD_URL_BASE;
15 |
16 | class ThreadDAO implements IThreadDAO {
17 | private final DLDBHelper dbHelper;
18 |
19 | ThreadDAO(Context context) {
20 | dbHelper = new DLDBHelper(context);
21 | }
22 |
23 | @Override
24 | public void insertThreadInfo(DLThreadInfo info) {
25 | SQLiteDatabase db = dbHelper.getWritableDatabase();
26 | db.execSQL("INSERT INTO " + TB_THREAD + "(" +
27 | TB_THREAD_URL_BASE + ", " +
28 | TB_THREAD_START + ", " +
29 | TB_THREAD_END + ", " +
30 | TB_THREAD_ID + ") VALUES (?,?,?,?)",
31 | new Object[]{info.baseUrl, info.start, info.end, info.id});
32 | db.close();
33 | }
34 |
35 | @Override
36 | public void deleteThreadInfo(String id) {
37 | SQLiteDatabase db = dbHelper.getWritableDatabase();
38 | db.execSQL("DELETE FROM " + TB_THREAD + " WHERE " + TB_THREAD_ID + "=?", new String[]{id});
39 | db.close();
40 | }
41 |
42 | @Override
43 | public void deleteAllThreadInfo(String url) {
44 | SQLiteDatabase db = dbHelper.getWritableDatabase();
45 | db.execSQL("DELETE FROM " + TB_THREAD + " WHERE " + TB_THREAD_URL_BASE + "=?",
46 | new String[]{url});
47 | db.close();
48 | }
49 |
50 | @Override
51 | public void updateThreadInfo(DLThreadInfo info) {
52 | SQLiteDatabase db = dbHelper.getWritableDatabase();
53 | db.execSQL("UPDATE " + TB_THREAD + " SET " +
54 | TB_THREAD_START + "=? WHERE " +
55 | TB_THREAD_URL_BASE + "=? AND " +
56 | TB_THREAD_ID + "=?", new Object[]{info.start, info.baseUrl, info.id});
57 | db.close();
58 | }
59 |
60 | @Override
61 | public DLThreadInfo queryThreadInfo(String id) {
62 | DLThreadInfo info = null;
63 | SQLiteDatabase db = dbHelper.getWritableDatabase();
64 | Cursor c = db.rawQuery("SELECT " +
65 | TB_THREAD_URL_BASE + ", " +
66 | TB_THREAD_START + ", " +
67 | TB_THREAD_END + " FROM " +
68 | TB_THREAD + " WHERE " +
69 | TB_THREAD_ID + "=?", new String[]{id});
70 | if (c.moveToFirst()) info = new DLThreadInfo(id, c.getString(0), c.getInt(1), c.getInt(2));
71 | c.close();
72 | db.close();
73 | return info;
74 | }
75 |
76 | @Override
77 | public List queryAllThreadInfo(String url) {
78 | List info = new ArrayList<>();
79 | SQLiteDatabase db = dbHelper.getWritableDatabase();
80 | Cursor c = db.rawQuery("SELECT " +
81 | TB_THREAD_URL_BASE + ", " +
82 | TB_THREAD_START + ", " +
83 | TB_THREAD_END + ", " +
84 | TB_THREAD_ID + " FROM " +
85 | TB_THREAD + " WHERE " +
86 | TB_THREAD_URL_BASE + "=?", new String[]{url});
87 | while (c.moveToNext())
88 | info.add(new DLThreadInfo(c.getString(3), c.getString(0), c.getInt(1), c.getInt(2)));
89 | c.close();
90 | db.close();
91 | return info;
92 | }
93 | }
--------------------------------------------------------------------------------
/Downloader/src/main/java/cn/aigestudio/downloader/interfaces/AsyncDListener.java:
--------------------------------------------------------------------------------
1 | package cn.aigestudio.downloader.interfaces;
2 |
3 | import android.os.Bundle;
4 | import android.os.Handler;
5 | import android.os.Message;
6 |
7 | import java.io.File;
8 | import java.lang.ref.SoftReference;
9 |
10 | /**
11 | * Created by yjwfn on 16-3-2.
12 | */
13 | public final class AsyncDListener implements IDListener {
14 |
15 | private static final String EXTRA_ARGS_1 = "extra_args_1";
16 | private static final String EXTRA_ARGS_2 = "extra_args_2";
17 | private static final String EXTRA_ARGS_3 = "extra_args_3";
18 |
19 | private static final int OP_PREPARE = 0x0;
20 | private static final int OP_START = 0x1;
21 | private static final int OP_PROGRESS = 0x2;
22 | private static final int OP_STOP = 0x3;
23 | private static final int OP_FINISH = 0x4;
24 | private static final int OP_ERROR = 0x5;
25 |
26 | private final SoftReference mRef;
27 |
28 |
29 | private static final SoftReference mHandlerRef;
30 |
31 | static {
32 | mHandlerRef = new SoftReference(new Handler(){
33 | @Override
34 | public void handleMessage(Message msg) {
35 |
36 | @SuppressWarnings("unchecked")
37 | SoftReference ref = (SoftReference) msg.obj;
38 | IDListener realListener = ref.get();
39 |
40 | if(realListener == null)
41 | return;
42 |
43 | Bundle bundle = msg.getData();
44 | switch (msg.what){
45 | case OP_PREPARE:
46 | realListener.onPrepare();
47 | break;
48 | case OP_START:
49 | realListener.onStart(bundle.getString(EXTRA_ARGS_1),
50 | bundle.getString(EXTRA_ARGS_2),
51 | bundle.getInt(EXTRA_ARGS_3));
52 | break;
53 | case OP_STOP:
54 | realListener.onStop(bundle.getInt(EXTRA_ARGS_1));
55 | break;
56 | case OP_PROGRESS:
57 | realListener.onProgress(bundle.getInt(EXTRA_ARGS_1));
58 | break;
59 | case OP_FINISH:
60 | realListener.onFinish((File) bundle.getSerializable(EXTRA_ARGS_1));
61 | break;
62 | case OP_ERROR:
63 | realListener.onError(bundle.getInt(EXTRA_ARGS_1),
64 | bundle.getString(EXTRA_ARGS_3) );
65 | break;
66 |
67 | }
68 | }
69 | });
70 | }
71 |
72 | public AsyncDListener(IDListener listener){
73 | mRef = new SoftReference<>(listener);
74 | }
75 |
76 |
77 | @Override
78 | public void onPrepare() {
79 | invokeOnMainThread(OP_START, null);
80 | }
81 |
82 | @Override
83 | public void onStart(String fileName, String realUrl, int fileLength) {
84 | Bundle bundle = new Bundle();
85 | bundle.putString(EXTRA_ARGS_1, fileName);
86 | bundle.putString(EXTRA_ARGS_2, realUrl);
87 | bundle.putInt(EXTRA_ARGS_3, fileLength);
88 | invokeOnMainThread(OP_START, bundle);
89 | }
90 |
91 | @Override
92 | public void onProgress(int progress) {
93 | Bundle bundle = new Bundle();
94 | bundle.putInt(EXTRA_ARGS_1, progress);
95 | invokeOnMainThread(OP_PROGRESS, bundle);
96 | }
97 |
98 | @Override
99 | public void onStop(int progress) {
100 | Bundle bundle = new Bundle();
101 | bundle.putInt(EXTRA_ARGS_1, progress);
102 | invokeOnMainThread(OP_STOP, bundle);
103 | }
104 |
105 | @Override
106 | public void onFinish(File file) {
107 | Bundle bundle = new Bundle();
108 | bundle.putSerializable(EXTRA_ARGS_1, file);
109 | invokeOnMainThread(OP_FINISH, bundle);
110 | }
111 |
112 | @Override
113 | public void onError(int status, String error) {
114 | Bundle bundle = new Bundle();
115 | bundle.putInt(EXTRA_ARGS_1, status);
116 | bundle.putString(EXTRA_ARGS_2, error);
117 | invokeOnMainThread(OP_ERROR, bundle);
118 | }
119 |
120 |
121 | private void invokeOnMainThread(int what, Bundle data){
122 | if(mHandlerRef.get() != null) {
123 | Handler handler = mHandlerRef.get();
124 | Message message = Message.obtain(handler, what, mRef);
125 | message.setData(data);
126 | handler.sendMessage(message);
127 | }
128 | }
129 |
130 |
131 | public static IDListener wrap(IDListener listener){
132 | return listener != null ? new AsyncDListener(listener) : null;
133 | }
134 | }
135 |
--------------------------------------------------------------------------------
/Downloader/src/main/java/cn/aigestudio/downloader/interfaces/DLTaskListener.java:
--------------------------------------------------------------------------------
1 | package cn.aigestudio.downloader.interfaces;
2 |
3 | import java.io.File;
4 |
5 | /**
6 | * 下载监听器
7 | * Download listener.
8 | *
9 | * @author AigeStudio 2015-05-08
10 | */
11 | @Deprecated
12 | public class DLTaskListener implements IDListener {
13 | @Override
14 | public void onPrepare() {
15 |
16 | }
17 |
18 | /**
19 | * 下载开始时回调 暂未使用
20 | * Callback when download start. No use.
21 | *
22 | * @param fileName 文件名 File name.
23 | * @param url 文件下载地址 File length in byte.
24 | */
25 | @Deprecated
26 | public void onStart(String fileName, String url) {
27 | }
28 |
29 | @Override
30 | public void onStart(String fileName, String realUrl, int fileLength) {
31 | onStart(fileName, realUrl);
32 | }
33 |
34 | /**
35 | * 网络连接时回调
36 | * Callback when connect the network.
37 | *
38 | * @param msg 附加的连接信息 extra message of connect.
39 | * @return true表示连接正常 否则反之 true if connect success, otherwise is return false.
40 | */
41 | @Deprecated
42 | public boolean onConnect(int type, String msg) {
43 | return true;
44 | }
45 |
46 | /**
47 | * 下载进行时回调
48 | * Callback when download in progress.
49 | *
50 | * @param progress 当前的下载进度以100为最大单位 note:the max progress is 100.
51 | */
52 | public void onProgress(int progress) {
53 |
54 | }
55 |
56 | @Deprecated
57 | public void onStop() {
58 |
59 | }
60 |
61 | /**
62 | * 下载停止时回调 暂未使用
63 | * Callback when download stop. No use.
64 | */
65 | public void onStop(int progress) {
66 | onStop();
67 | }
68 |
69 | /**
70 | * 下载完成时回调
71 | * Callback when download finish.
72 | *
73 | * @param file 下载文件本地File对象 file downloaded.
74 | */
75 | public void onFinish(File file) {
76 |
77 | }
78 |
79 | /**
80 | * 下载出错时回调
81 | * Callback when download error.
82 | *
83 | * @param error 具体的错误信息 error message.
84 | */
85 | @Deprecated
86 | public void onError(String error) {
87 | }
88 |
89 | @Override
90 | public void onError(int status, String error) {
91 | onError(error);
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/Downloader/src/main/java/cn/aigestudio/downloader/interfaces/IDListener.java:
--------------------------------------------------------------------------------
1 | package cn.aigestudio.downloader.interfaces;
2 |
3 | import java.io.File;
4 |
5 | /**
6 | * @author AigeStudio 2015-10-18
7 | */
8 | public interface IDListener {
9 | void onPrepare();
10 |
11 | void onStart(String fileName, String realUrl, int fileLength);
12 |
13 | void onProgress(int progress);
14 |
15 | void onStop(int progress);
16 |
17 | void onFinish(File file);
18 |
19 | void onError(int status, String error);
20 | }
--------------------------------------------------------------------------------
/Downloader/src/main/java/cn/aigestudio/downloader/interfaces/SimpleDListener.java:
--------------------------------------------------------------------------------
1 | package cn.aigestudio.downloader.interfaces;
2 |
3 | import java.io.File;
4 |
5 | public class SimpleDListener implements IDListener {
6 |
7 | @Override
8 | public void onPrepare() {
9 |
10 | }
11 |
12 | @Override
13 | public void onStart(String fileName, String realUrl, int fileLength) {
14 |
15 | }
16 |
17 | @Override
18 | public void onProgress(int progress) {
19 |
20 | }
21 |
22 | @Override
23 | public void onStop(int progress) {
24 |
25 | }
26 |
27 | @Override
28 | public void onFinish(File file) {
29 |
30 | }
31 |
32 | @Override
33 | public void onError(int status, String error) {
34 |
35 | }
36 | }
--------------------------------------------------------------------------------
/MultiThreadDownloader.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](http://android-arsenal.com/details/1/1865)
2 |
3 | ***
4 |
5 | # Android Multi-Thread Downloader
6 | For more help please read [wiki](https://github.com/AigeStudio/MultiThreadDownloader/wiki).
7 |
8 | ## Android API Require
9 | **API 1**
10 |
11 | ## Versions
12 | ### 1.0.0 release
13 | * Multi-thread http download
14 |
15 | ### 1.2.1 release
16 | * Bugfix:download thread dispath
17 | * Support url redirection
18 | * DLManager will download with single thread if server does not support break-point, and it will not insert to database
19 |
20 | ### 1.3.7 release
21 | * Bugfix:can not start multi-threads to download file when we in url redirection.
22 | * Bugfix:can not stop a download task when we in url redirection.
23 |
24 | ### 1.4.0 release
25 | * Fix known bug.
26 |
27 | ### 1.4.1 release
28 | * BugFix:Can not resume download after stopped with large file.
29 | * Optimized code to enhancing the efficient implementation.
30 | * Optimized thread dispatch.
31 |
32 | ### 1.4.2 release
33 | * Add method getDLInfo(String url) to get the download info at the time.
34 | * Add method getDLDBManager() to get the datebase manager.
35 | * BugFix:Can not save progress when exception happened.
36 |
37 | ### 1.4.3 release
38 | * BugFix:Can not stop in non multi-thread download.
39 | * BugFix:Can not report result back in non multi-thread download.
40 |
41 | ## Preview
42 | **Download in activity**
43 |
44 | 
45 |
46 | **Download in statusbar**
47 |
48 | 
49 |
50 | ***
51 |
52 | # LICENSE
53 | Copyright 2014-2015 [AigeStudio](https://github.com/AigeStudio), [zhangchi](https://github.com/kxdd2002)
54 |
55 | Licensed under the Apache License, Version 2.0 (the "License");you may not use this file except in compliance with the License.
56 |
57 | You may obtain a copy of the License at
58 |
59 | http://www.apache.org/licenses/LICENSE-2.0
60 |
61 | Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
62 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | buildscript {
2 | repositories {
3 | jcenter()
4 | }
5 | dependencies {
6 | classpath 'com.android.tools.build:gradle:1.3.0'
7 | }
8 | }
9 |
10 | allprojects {
11 | repositories {
12 | jcenter()
13 | }
14 | }
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | ## Project-wide Gradle settings.
2 | #
3 | # For more details on how to configure your build environment visit
4 | # http://www.gradle.org/docs/current/userguide/build_environment.html
5 | #
6 | # Specifies the JVM arguments used for the daemon process.
7 | # The setting is particularly useful for tweaking memory settings.
8 | # Default value: -Xmx10248m -XX:MaxPermSize=256m
9 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
10 | #
11 | # When configured, Gradle will run in incubating parallel mode.
12 | # This option should only be used with decoupled projects. More details, visit
13 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
14 | # org.gradle.parallel=true
15 | #Fri Oct 23 15:59:06 CST 2015
16 | systemProp.http.proxyHost=127.0.0.1
17 | systemProp.http.proxyPort=1080
18 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devaige/MultiThreadDownloader/21bf649b47ffb6830a2d15a2b7438a41bf3bca70/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Wed Apr 10 15:27:10 PDT 2013
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.2.1-all.zip
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # For Cygwin, ensure paths are in UNIX format before anything is touched.
46 | if $cygwin ; then
47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
48 | fi
49 |
50 | # Attempt to set APP_HOME
51 | # Resolve links: $0 may be a link
52 | PRG="$0"
53 | # Need this for relative symlinks.
54 | while [ -h "$PRG" ] ; do
55 | ls=`ls -ld "$PRG"`
56 | link=`expr "$ls" : '.*-> \(.*\)$'`
57 | if expr "$link" : '/.*' > /dev/null; then
58 | PRG="$link"
59 | else
60 | PRG=`dirname "$PRG"`"/$link"
61 | fi
62 | done
63 | SAVED="`pwd`"
64 | cd "`dirname \"$PRG\"`/" >&-
65 | APP_HOME="`pwd -P`"
66 | cd "$SAVED" >&-
67 |
68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
69 |
70 | # Determine the Java command to use to start the JVM.
71 | if [ -n "$JAVA_HOME" ] ; then
72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
73 | # IBM's JDK on AIX uses strange locations for the executables
74 | JAVACMD="$JAVA_HOME/jre/sh/java"
75 | else
76 | JAVACMD="$JAVA_HOME/bin/java"
77 | fi
78 | if [ ! -x "$JAVACMD" ] ; then
79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
80 |
81 | Please set the JAVA_HOME variable in your environment to match the
82 | location of your Java installation."
83 | fi
84 | else
85 | JAVACMD="java"
86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
87 |
88 | Please set the JAVA_HOME variable in your environment to match the
89 | location of your Java installation."
90 | fi
91 |
92 | # Increase the maximum file descriptors if we can.
93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
94 | MAX_FD_LIMIT=`ulimit -H -n`
95 | if [ $? -eq 0 ] ; then
96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
97 | MAX_FD="$MAX_FD_LIMIT"
98 | fi
99 | ulimit -n $MAX_FD
100 | if [ $? -ne 0 ] ; then
101 | warn "Could not set maximum file descriptor limit: $MAX_FD"
102 | fi
103 | else
104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
105 | fi
106 | fi
107 |
108 | # For Darwin, add options to specify how the application appears in the dock
109 | if $darwin; then
110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
111 | fi
112 |
113 | # For Cygwin, switch paths to Windows format before running java
114 | if $cygwin ; then
115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
158 | function splitJvmOpts() {
159 | JVM_OPTS=("$@")
160 | }
161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
163 |
164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
165 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/preview1.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devaige/MultiThreadDownloader/21bf649b47ffb6830a2d15a2b7438a41bf3bca70/preview1.gif
--------------------------------------------------------------------------------
/preview2.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devaige/MultiThreadDownloader/21bf649b47ffb6830a2d15a2b7438a41bf3bca70/preview2.gif
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':Demo', ':Downloader'
2 |
--------------------------------------------------------------------------------