diff --git a/.idea/.name b/.idea/.name
new file mode 100644
index 0000000..60ccf44
--- /dev/null
+++ b/.idea/.name
@@ -0,0 +1 @@
+MultiThreadDownloader
\ No newline at end of file
diff --git a/.idea/compiler.xml b/.idea/compiler.xml
new file mode 100644
index 0000000..217af47
--- /dev/null
+++ b/.idea/compiler.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/.idea/copyright/Apache.xml b/.idea/copyright/Apache.xml
new file mode 100644
index 0000000..7c21b31
--- /dev/null
+++ b/.idea/copyright/Apache.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/copyright/profiles_settings.xml b/.idea/copyright/profiles_settings.xml
new file mode 100644
index 0000000..e7bedf3
--- /dev/null
+++ b/.idea/copyright/profiles_settings.xml
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/.idea/encodings.xml b/.idea/encodings.xml
new file mode 100644
index 0000000..e206d70
--- /dev/null
+++ b/.idea/encodings.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/.idea/gradle.xml b/.idea/gradle.xml
new file mode 100644
index 0000000..f1f8114
--- /dev/null
+++ b/.idea/gradle.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 0000000..cbaffd1
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,38 @@
+
+
+
+
+
+
+
+
+
+
+
+
+ Apache
+
+
+
+
+
+
+
+
+
+
+
+ 1.8
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/.idea/modules.xml b/.idea/modules.xml
new file mode 100644
index 0000000..823d9dd
--- /dev/null
+++ b/.idea/modules.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/.idea/scopes/scope_settings.xml b/.idea/scopes/scope_settings.xml
new file mode 100644
index 0000000..922003b
--- /dev/null
+++ b/.idea/scopes/scope_settings.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..9d32e50
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
diff --git a/Demo/Demo.iml b/Demo/Demo.iml
new file mode 100644
index 0000000..5449630
--- /dev/null
+++ b/Demo/Demo.iml
@@ -0,0 +1,91 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Demo/build.gradle b/Demo/build.gradle
new file mode 100644
index 0000000..6e30614
--- /dev/null
+++ b/Demo/build.gradle
@@ -0,0 +1,26 @@
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion 22
+ buildToolsVersion "22.0.1"
+
+ defaultConfig {
+ applicationId "cn.aigestudio.downloader.demo"
+ minSdkVersion 4
+ targetSdkVersion 22
+ versionCode 1
+ versionName "1.0"
+ }
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+ }
+ }
+}
+
+dependencies {
+ compile fileTree(dir: 'libs', include: ['*.jar'])
+ compile project(':Downloader')
+ compile 'com.android.support:support-v4:22.1.1'
+}
diff --git a/Demo/proguard-rules.pro b/Demo/proguard-rules.pro
new file mode 100644
index 0000000..723652e
--- /dev/null
+++ b/Demo/proguard-rules.pro
@@ -0,0 +1,17 @@
+# Add project specific ProGuard rules here.
+# By default, the flags in this dlLocalFile are appended to flags specified
+# in H:\Programming\Android\sdk/tools/proguard/proguard-android.txt
+# You can edit the include dirPath and order by changing the proguardFiles
+# directive in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
diff --git a/Demo/src/main/AndroidManifest.xml b/Demo/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..5f7f85c
--- /dev/null
+++ b/Demo/src/main/AndroidManifest.xml
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Demo/src/main/java/cn/aigestudio/downloader/demo/DLService.java b/Demo/src/main/java/cn/aigestudio/downloader/demo/DLService.java
new file mode 100644
index 0000000..c8bba8c
--- /dev/null
+++ b/Demo/src/main/java/cn/aigestudio/downloader/demo/DLService.java
@@ -0,0 +1,65 @@
+package cn.aigestudio.downloader.demo;
+
+import android.app.NotificationManager;
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.IBinder;
+import android.support.v4.app.NotificationCompat;
+
+import java.io.File;
+
+import cn.aigestudio.downloader.bizs.DLManager;
+import cn.aigestudio.downloader.interfaces.DLTaskListener;
+import cn.aigestudio.downloader.utils.FileUtil;
+import cn.aigestudio.downloader.utils.LogUtil;
+
+/**
+ * 执行下载的Service
+ *
+ * @author AigeStudio 2015-05-18
+ */
+public class DLService extends Service {
+
+ @Override
+ public int onStartCommand(Intent intent, int flags, int startId) {
+ String url = intent.getStringExtra("url");
+ String path = intent.getStringExtra("path");
+ final int id = intent.getIntExtra("id", -1);
+ final NotificationManager nm = (NotificationManager) getSystemService(Context
+ .NOTIFICATION_SERVICE);
+ final NotificationCompat.Builder builder = new NotificationCompat.Builder(this)
+ .setContentTitle(FileUtil.getFileNameFromUrl(url).replace("/", ""))
+ .setSmallIcon(R.drawable.ic_launcher);
+
+ DLManager.getInstance(this).dlStart(url, path, new DLTaskListener() {
+ @Override
+ public void onProgress(int progress) {
+ builder.setProgress(100, progress, false);
+ nm.notify(id, builder.build());
+ }
+
+ @Override
+ public void onFinish(File file) {
+ LogUtil.i("onFinish");
+ installApk(file);
+ nm.cancel(id);
+ }
+ });
+ return super.onStartCommand(intent, flags, startId);
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return null;
+ }
+
+ private void installApk(File file) {
+ LogUtil.i("installApk");
+ Intent intent = new Intent(Intent.ACTION_VIEW);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ intent.setDataAndType(Uri.fromFile(file), "application/vnd.android.package-archive");
+ startActivity(intent);
+ }
+}
diff --git a/Demo/src/main/java/cn/aigestudio/downloader/demo/MainActivity.java b/Demo/src/main/java/cn/aigestudio/downloader/demo/MainActivity.java
new file mode 100644
index 0000000..1178c08
--- /dev/null
+++ b/Demo/src/main/java/cn/aigestudio/downloader/demo/MainActivity.java
@@ -0,0 +1,168 @@
+package cn.aigestudio.downloader.demo;
+
+import android.app.Activity;
+import android.app.Notification;
+import android.os.Bundle;
+import android.os.Environment;
+import android.view.View;
+import android.widget.Button;
+import android.widget.ProgressBar;
+
+import cn.aigestudio.downloader.bizs.DLManager;
+import cn.aigestudio.downloader.interfaces.DLTaskListener;
+
+public class MainActivity extends Activity {
+ private static final String URL1 = "http://183.230.95.76:9999/bcs.91.com/pcsuite-dev/apk/c684f54c3f941cd71baef085e2ac8662.apk";
+ private static final String URL2 = "http://bcs.apk.r1.91.com/data/upload/apkres/2015/5_17/13/com.tencent.mobileqq_015020349.apk";
+ private static final String URL3 = "http://dlsw.baidu.com/sw-search-sp/soft/9e/12035/BaiduHi_V4.7.1.2_setup.1429175376.exe";
+ private static final String URL4 = "http://dlsw.baidu.com/sw-search-sp/soft/a2/12282/SinaUC_Release_8.3.4.22616.1396945592.exe";
+ private static final String URL5 = "http://dlsw.baidu.com/sw-search-sp/soft/4b/17170/Install_WLMessenger14.0.8117.416.1393467029.exe";
+ private static final String URL6 = "http://dlsw.baidu.com/sw-search-sp/soft/a2/25705/sinaSHOW-v1-1.1395901693.dmg";
+
+ private static final int[] RES_ID_BTN_START = {R.id.main_dl_start_btn1, R.id.main_dl_start_btn2,
+ R.id.main_dl_start_btn3, R.id.main_dl_start_btn4, R.id.main_dl_start_btn5,
+ R.id.main_dl_start_btn6};
+ private static final int[] RES_ID_BTN_STOP = {R.id.main_dl_stop_btn1, R.id.main_dl_stop_btn2,
+ R.id.main_dl_stop_btn3, R.id.main_dl_stop_btn4, R.id.main_dl_stop_btn5,
+ R.id.main_dl_stop_btn6};
+ private static final int[] RES_ID_PB = {R.id.main_dl_pb1, R.id.main_dl_pb2, R.id.main_dl_pb3,
+ R.id.main_dl_pb4, R.id.main_dl_pb5, R.id.main_dl_pb6};
+ private static final int[] RES_ID_NOTIFY = {R.id.main_notify_btn1, R.id.main_notify_btn2,
+ R.id.main_notify_btn3, R.id.main_notify_btn4, R.id.main_notify_btn5,
+ R.id.main_notify_btn6};
+
+ private String saveDir;
+
+ private ProgressBar[] pbDLs;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_main);
+
+ Button[] btnStarts = new Button[RES_ID_BTN_START.length];
+ for (int i = 0; i < btnStarts.length; i++) {
+ btnStarts[i] = (Button) findViewById(RES_ID_BTN_START[i]);
+ btnStarts[i].setOnClickListener(new MainClickListener());
+ }
+
+ Button[] btnStops = new Button[RES_ID_BTN_STOP.length];
+ for (int i = 0; i < btnStops.length; i++) {
+ btnStops[i] = (Button) findViewById(RES_ID_BTN_STOP[i]);
+ btnStops[i].setOnClickListener(new MainClickListener());
+ }
+
+ pbDLs = new ProgressBar[RES_ID_PB.length];
+ for (int i = 0; i < pbDLs.length; i++) {
+ pbDLs[i] = (ProgressBar) findViewById(RES_ID_PB[i]);
+ pbDLs[i].setMax(100);
+ pbDLs[i].setOnClickListener(new MainClickListener());
+ }
+
+ Button[] btnNotifys = new Button[RES_ID_NOTIFY.length];
+ for (int i = 0; i < btnNotifys.length; i++) {
+ btnNotifys[i] = (Button) findViewById(RES_ID_NOTIFY[i]);
+ btnNotifys[i].setOnClickListener(new MainClickListener());
+ }
+
+ saveDir = Environment.getExternalStorageDirectory() + "/AigeStudio/";
+ }
+
+ private class MainClickListener implements View.OnClickListener {
+ @Override
+ public void onClick(View v) {
+ switch (v.getId()) {
+ case R.id.main_dl_start_btn1:
+ DLManager.getInstance(MainActivity.this).dlStart(URL1, saveDir,
+ new DLTaskListener() {
+ @Override
+ public void onProgress(int progress) {
+ pbDLs[1].setProgress(progress);
+ }
+ });
+ break;
+ case R.id.main_dl_stop_btn1:
+ DLManager.getInstance(MainActivity.this).dlStop(URL1);
+ break;
+ case R.id.main_dl_start_btn2:
+ DLManager.getInstance(MainActivity.this).dlStart(URL2, saveDir,
+ new DLTaskListener() {
+ @Override
+ public void onProgress(int progress) {
+ pbDLs[2].setProgress(progress);
+ }
+ });
+ break;
+ case R.id.main_dl_stop_btn2:
+ DLManager.getInstance(MainActivity.this).dlStop(URL2);
+ break;
+ case R.id.main_dl_start_btn3:
+ DLManager.getInstance(MainActivity.this).dlStart(URL3, saveDir,
+ new DLTaskListener() {
+ @Override
+ public void onProgress(int progress) {
+ pbDLs[3].setProgress(progress);
+ }
+ });
+ break;
+ case R.id.main_dl_stop_btn3:
+ DLManager.getInstance(MainActivity.this).dlStop(URL3);
+ break;
+ case R.id.main_dl_start_btn4:
+ DLManager.getInstance(MainActivity.this).dlStart(URL4, saveDir,
+ new DLTaskListener() {
+ @Override
+ public void onProgress(int progress) {
+ pbDLs[4].setProgress(progress);
+ }
+ });
+ break;
+ case R.id.main_dl_stop_btn4:
+ DLManager.getInstance(MainActivity.this).dlStop(URL4);
+ break;
+ case R.id.main_dl_start_btn5:
+ DLManager.getInstance(MainActivity.this).dlStart(URL5, saveDir,
+ new DLTaskListener() {
+ @Override
+ public void onProgress(int progress) {
+ pbDLs[5].setProgress(progress);
+ }
+ });
+ break;
+ case R.id.main_dl_stop_btn5:
+ DLManager.getInstance(MainActivity.this).dlStop(URL5);
+ break;
+ case R.id.main_dl_start_btn6:
+ DLManager.getInstance(MainActivity.this).dlStart(URL6, saveDir,
+ new DLTaskListener() {
+ @Override
+ public void onProgress(int progress) {
+ pbDLs[6].setProgress(progress);
+ }
+ });
+ break;
+ case R.id.main_dl_stop_btn6:
+ DLManager.getInstance(MainActivity.this).dlStop(URL6);
+ break;
+ case R.id.main_notify_btn1:
+ NotificationUtil.notificationForDLAPK(MainActivity.this, URL1);
+ break;
+ case R.id.main_notify_btn2:
+ NotificationUtil.notificationForDLAPK(MainActivity.this, URL2);
+ break;
+ case R.id.main_notify_btn3:
+ NotificationUtil.notificationForDLAPK(MainActivity.this, URL3);
+ break;
+ case R.id.main_notify_btn4:
+ NotificationUtil.notificationForDLAPK(MainActivity.this, URL4);
+ break;
+ case R.id.main_notify_btn5:
+ NotificationUtil.notificationForDLAPK(MainActivity.this, URL5);
+ break;
+ case R.id.main_notify_btn6:
+ NotificationUtil.notificationForDLAPK(MainActivity.this, URL6);
+ break;
+ }
+ }
+ }
+}
diff --git a/Demo/src/main/java/cn/aigestudio/downloader/demo/NotificationUtil.java b/Demo/src/main/java/cn/aigestudio/downloader/demo/NotificationUtil.java
new file mode 100644
index 0000000..514d2f2
--- /dev/null
+++ b/Demo/src/main/java/cn/aigestudio/downloader/demo/NotificationUtil.java
@@ -0,0 +1,24 @@
+package cn.aigestudio.downloader.demo;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.Environment;
+
+/**
+ * 通知工具类
+ *
+ * @author AigeStudio 2015-05-18
+ */
+public final class NotificationUtil {
+ public static void notificationForDLAPK(Context context, String url) {
+ notificationForDLAPK(context, url, Environment.getExternalStorageDirectory() + "/AigeStudio/");
+ }
+
+ public static void notificationForDLAPK(Context context, String url, String path) {
+ Intent intent = new Intent(context, DLService.class);
+ intent.putExtra("url", url);
+ intent.putExtra("path", path);
+ intent.putExtra("id", (int) (Math.random() * 1024));
+ context.startService(intent);
+ }
+}
diff --git a/Demo/src/main/res/drawable-hdpi/ic_launcher.png b/Demo/src/main/res/drawable-hdpi/ic_launcher.png
new file mode 100644
index 0000000..cd9fe5f
Binary files /dev/null and b/Demo/src/main/res/drawable-hdpi/ic_launcher.png differ
diff --git a/Demo/src/main/res/drawable-mdpi/ic_launcher.png b/Demo/src/main/res/drawable-mdpi/ic_launcher.png
new file mode 100644
index 0000000..bc3beb8
Binary files /dev/null and b/Demo/src/main/res/drawable-mdpi/ic_launcher.png differ
diff --git a/Demo/src/main/res/drawable-xhdpi/ic_launcher.png b/Demo/src/main/res/drawable-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..234724a
Binary files /dev/null and b/Demo/src/main/res/drawable-xhdpi/ic_launcher.png differ
diff --git a/Demo/src/main/res/drawable-xxhdpi/ic_launcher.png b/Demo/src/main/res/drawable-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..6248217
Binary files /dev/null and b/Demo/src/main/res/drawable-xxhdpi/ic_launcher.png differ
diff --git a/Demo/src/main/res/drawable-xxxhdpi/ic_launcher.png b/Demo/src/main/res/drawable-xxxhdpi/ic_launcher.png
new file mode 100644
index 0000000..8cca713
Binary files /dev/null and b/Demo/src/main/res/drawable-xxxhdpi/ic_launcher.png differ
diff --git a/Demo/src/main/res/layout/activity_main.xml b/Demo/src/main/res/layout/activity_main.xml
new file mode 100644
index 0000000..9751885
--- /dev/null
+++ b/Demo/src/main/res/layout/activity_main.xml
@@ -0,0 +1,208 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Demo/src/main/res/values/strings.xml b/Demo/src/main/res/values/strings.xml
new file mode 100644
index 0000000..f3195fb
--- /dev/null
+++ b/Demo/src/main/res/values/strings.xml
@@ -0,0 +1,26 @@
+
+
+
+ MultiThreadDownloader
+
+ Start1
+ Stop1
+ Start2
+ Stop2
+ Start3
+ Stop3
+ Start4
+ Stop4
+ Start5
+ Stop5
+ Start6
+ Stop6
+
+ Notify1
+ Notify2
+ Notify3
+ Notify4
+ Notify5
+ Notify6
+
+
diff --git a/Downloader/Downloader.iml b/Downloader/Downloader.iml
new file mode 100644
index 0000000..3240a56
--- /dev/null
+++ b/Downloader/Downloader.iml
@@ -0,0 +1,89 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Downloader/build.gradle b/Downloader/build.gradle
new file mode 100644
index 0000000..cb1c44d
--- /dev/null
+++ b/Downloader/build.gradle
@@ -0,0 +1,23 @@
+apply plugin: 'com.android.library'
+
+android {
+ compileSdkVersion 22
+ buildToolsVersion "22.0.1"
+
+ defaultConfig {
+ minSdkVersion 4
+ targetSdkVersion 22
+ versionCode 1
+ versionName "1.0"
+ }
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+ }
+ }
+}
+
+dependencies {
+ compile fileTree(dir: 'libs', include: ['*.jar'])
+}
diff --git a/Downloader/proguard-rules.pro b/Downloader/proguard-rules.pro
new file mode 100644
index 0000000..86ebf4c
--- /dev/null
+++ b/Downloader/proguard-rules.pro
@@ -0,0 +1,17 @@
+# Add project specific ProGuard rules here.
+# By default, the flags in this dlLocalFile are appended to flags specified
+# in H:/Programming/Android/sdk/tools/proguard/proguard-android.txt
+# You can edit the include dirPath and order by changing the proguardFiles
+# directive in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
diff --git a/Downloader/src/main/AndroidManifest.xml b/Downloader/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..728d5cc
--- /dev/null
+++ b/Downloader/src/main/AndroidManifest.xml
@@ -0,0 +1 @@
+
diff --git a/Downloader/src/main/java/cn/aigestudio/downloader/bizs/DBManager.java b/Downloader/src/main/java/cn/aigestudio/downloader/bizs/DBManager.java
new file mode 100644
index 0000000..831d0ca
--- /dev/null
+++ b/Downloader/src/main/java/cn/aigestudio/downloader/bizs/DBManager.java
@@ -0,0 +1,142 @@
+package cn.aigestudio.downloader.bizs;
+
+import android.content.Context;
+
+import java.util.List;
+
+import cn.aigestudio.downloader.daos.TaskDAO;
+import cn.aigestudio.downloader.daos.ThreadDAO;
+import cn.aigestudio.downloader.entities.TaskInfo;
+import cn.aigestudio.downloader.entities.ThreadInfo;
+
+/**
+ * 数据库管理器
+ * 封装各种业务数据操作
+ *
+ * @author AigeStudio 2015-05-09
+ */
+public final class DBManager {
+ private static DBManager sManager = null;// 数据库管理静态引用
+
+ private TaskDAO daoTask;// 下载任务数据库操作对象
+ private ThreadDAO daoThread;// 线程任务数据库操作对象
+
+ private DBManager(Context context) {
+ // 初始化对象
+ daoTask = new TaskDAO(context);
+ daoThread = new ThreadDAO(context);
+ }
+
+ /**
+ * 获取数据库管理器单例对象
+ *
+ * @param context ...
+ * @return 数据库管理器单例对象
+ */
+ public static DBManager getInstance(Context context) {
+ if (null == sManager) {
+ sManager = new DBManager(context);
+ }
+ return sManager;
+ }
+
+ /**
+ * 插入一条下载任务数据信息
+ *
+ * @param info 下载任务对象
+ */
+ public synchronized void insertTaskInfo(TaskInfo info) {
+ daoTask.insertInfo(info);
+ }
+
+ /**
+ * 根据下载地址删除一条下载任务数据信息
+ *
+ * @param url 下载地址
+ */
+ public synchronized void deleteTaskInfo(String url) {
+ daoTask.deleteInfo(url);
+ }
+
+ /**
+ * 更新一条下载任务数据信息
+ *
+ * @param info 下载任务对象
+ */
+ public synchronized void updateTaskInfo(TaskInfo info) {
+ daoTask.updateInfo(info);
+ }
+
+ /**
+ * 根据下载地址查询一条下载任务数据信息
+ *
+ * @param url 下载地址
+ * @return 下载任务对象
+ */
+ public synchronized TaskInfo queryTaskInfoByUrl(String url) {
+ return (TaskInfo) daoTask.queryInfo(url);
+ }
+
+ /**
+ * 插入一条线程数据信息
+ *
+ * @param info 线程对象
+ */
+ public synchronized void insertThreadInfo(ThreadInfo info) {
+ daoThread.insertInfo(info);
+ }
+
+ /**
+ * 根据线程ID删除一条线程数据信息
+ *
+ * @param id 线程ID
+ */
+ public synchronized void deleteThreadInfoById(String id) {
+ daoThread.deleteInfo(id);
+ }
+
+ /**
+ * 根据下载地址删除所有线程数据信息
+ *
+ * @param url 下载地址
+ */
+ public synchronized void deleteThreadInfos(String url) {
+ daoThread.deleteInfo(url);
+ }
+
+ /**
+ * 更新一条线程数据信息
+ *
+ * @param info 线程对象
+ */
+ public synchronized void updateThreadInfo(ThreadInfo info) {
+ daoThread.updateInfo(info);
+ }
+
+ /**
+ * 根据线程ID查询一条线程数据信息
+ *
+ * @param id 线程ID
+ * @return 线程对象
+ */
+ public synchronized ThreadInfo queryThreadInfoById(String id) {
+ return (ThreadInfo) daoThread.queryInfo(id);
+ }
+
+ /**
+ * 根据下载地址查询所有线程数据信息
+ *
+ * @param url 下载地址
+ * @return 所有该地址下对应的线程信息
+ */
+ public synchronized List queryThreadInfos(String url) {
+ return daoThread.queryInfos(url);
+ }
+
+ /**
+ * 释放资源 暂无用
+ */
+ public void release() {
+ daoTask.close();
+ }
+}
diff --git a/Downloader/src/main/java/cn/aigestudio/downloader/bizs/DLManager.java b/Downloader/src/main/java/cn/aigestudio/downloader/bizs/DLManager.java
new file mode 100644
index 0000000..e0b8df7
--- /dev/null
+++ b/Downloader/src/main/java/cn/aigestudio/downloader/bizs/DLManager.java
@@ -0,0 +1,409 @@
+package cn.aigestudio.downloader.bizs;
+
+import android.content.Context;
+import android.widget.Toast;
+
+import org.apache.http.HttpStatus;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.RandomAccessFile;
+import java.net.HttpURLConnection;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.UUID;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+import cn.aigestudio.downloader.cons.PublicCons;
+import cn.aigestudio.downloader.entities.TaskInfo;
+import cn.aigestudio.downloader.entities.ThreadInfo;
+import cn.aigestudio.downloader.interfaces.DLTaskListener;
+import cn.aigestudio.downloader.interfaces.IDLThreadListener;
+import cn.aigestudio.downloader.utils.FileUtil;
+import cn.aigestudio.downloader.utils.LogUtil;
+import cn.aigestudio.downloader.utils.NetUtil;
+
+/**
+ * 下载管理器
+ * 执行具体的下载操作
+ * 开始一个下载任务只需调用{@link #dlStart}方法即可
+ * 停止某个下载任务需要调用{@link #dlStop}方法 停止下载任务仅仅会将对应下载任务移除下载队列而不删除相应数据 下次启动相同任务时会自动根据上一次停止时保存的数据重新开始下载
+ * 取消某个下载任务需要调用{@link #dlCancle}方法 取消下载任务会删除掉相应的本地数据库数据但文件不会被删除
+ * 相同url的下载任务视为相同任务
+ *
+ * @author AigeStudio 2015-05-09
+ */
+public final class DLManager {
+ private static final int THREAD_POOL_SIZE = 8;// 线程池大小
+
+ private static DLManager sManager;// 下载管理器的静态引用
+ private static Hashtable sTaskDLing; // 静态Hashtable用于存储正在下载的任务
+ private static DBManager sDBManager;// 数据库管理器的静态引用
+
+ private ExecutorService mExecutor;// 线程池业务引用
+ private Context context;// ...
+
+ public DLManager(Context context) {
+ // 初始化对象
+ this.context = context;
+ this.mExecutor = Executors.newFixedThreadPool(THREAD_POOL_SIZE);
+ sDBManager = DBManager.getInstance(context);
+ sTaskDLing = new Hashtable<>();
+ }
+
+ /**
+ * 获取下载管理器单例对象
+ *
+ * @param context ...
+ * @return 下载管理器单例对象
+ */
+ public static DLManager getInstance(Context context) {
+ if (null == sManager) {
+ sManager = new DLManager(context);
+ }
+ return sManager;
+ }
+
+ /**
+ * 开始一个下载任务
+ *
+ * @param url 下载地址
+ * @param dirPath 下载文件保存目录
+ * @param listener 下载监听对象
+ */
+ public void dlStart(String url, String dirPath, DLTaskListener listener) {
+ // 如果传入的url已存在于Map中则表示该文件正在下载
+ if (sTaskDLing.containsKey(url)) {
+ Toast.makeText(context, "文件正在下载", Toast.LENGTH_SHORT).show();
+ return;
+ }
+ // 尝试根据该url从数据库获取数据信息
+ TaskInfo info = sDBManager.queryTaskInfoByUrl(url);
+
+ // 根据url和文件保存目录路径生成文件File对象
+ String fileName = FileUtil.getFileNameFromUrl(url);
+ File file = new File(dirPath, fileName);
+
+ // 数据库没有对应信息或者对应文件不存在则表示新建下载任务
+ if (null == info || !file.exists()) {
+ LogUtil.i("New Task!");
+ info = new TaskInfo(FileUtil.createFile(dirPath, fileName), url, 0, 0);
+ }
+
+ // 构造下载任务对象
+ DLTask task = new DLTask(info, listener);
+
+ // 提交并执行任务
+ mExecutor.execute(task);
+ }
+
+ /**
+ * 根据下载地址停止下载任务
+ *
+ * @param url 下载地址
+ */
+ public void dlStop(String url) {
+ // 如果在下载在队列中包含该url则根据该url取出相应的下载任务对象并设置停止下载
+ if (sTaskDLing.containsKey(url)) {
+ DLTask task = sTaskDLing.get(url);
+ task.setStop(true);
+ }
+ }
+
+ /**
+ * 根据下载地址停取消下载任务
+ *
+ * @param url 下载地址
+ */
+ public void dlCancle(String url) {
+ // 取消前先停止
+ dlStop(url);
+
+ // 根据url从数据库查找相应数据并删除
+ if (null != sDBManager.queryTaskInfoByUrl(url)) {
+ sDBManager.deleteTaskInfo(url);
+ List infos = sDBManager.queryThreadInfos(url);
+ if (null != infos && infos.size() != 0) {
+ sDBManager.deleteThreadInfos(url);
+ }
+ }
+ }
+
+ /**
+ * 释放资源
+ */
+ public void release() {
+ mExecutor.shutdown();
+ }
+
+ /**
+ * 具体的下载任务Runnable类
+ * 该类主要任务为读取服务器文件大小并生成具体的下载线程分发下载
+ *
+ * @author AigeStudio 2015-05-09
+ */
+ private class DLTask implements Runnable, IDLThreadListener {
+ private static final int LENGTH_PER_THREAD = 5242880;// 单个线程下载的最大长度
+
+ private TaskInfo info;// 下载任务实体对象
+ private DLTaskListener mListener;// 下载任务监听器
+
+ private int totalProgress, fileLength;// 下载总进度和文件长度
+ private int totalProgressIn100;// 以100为最大值的进度表示
+ private boolean isResume;// 标识是否断点续传
+ private boolean isStop;// 标识下载任务是否暂停
+ private boolean isExists;// 标识文件是否存在
+ private boolean isConnect = true;// 标识网络是否连接 默认恒为连接
+
+ private List mThreadInfos;// 如果为断点续传则该引用不为空
+
+ private DLTask(TaskInfo info, DLTaskListener listener) {
+ // 初始化对象
+ this.info = info;
+ this.mListener = listener;
+ this.totalProgress = info.progress;
+ LogUtil.i("Last time we were download " + totalProgress + "byte.");
+ this.fileLength = info.length;
+
+ // 查询数据库信息判断是否存在该任务信息
+ if (null != sDBManager.queryTaskInfoByUrl(info.url)) {
+ // 如果数据库有信息但文件不存在则删除数据库信息
+ if (!info.dlLocalFile.exists()) {
+ sDBManager.deleteTaskInfo(info.url);
+ }
+ // 如果存在该信息那么继续判断是否拥有对应的线程
+ mThreadInfos = sDBManager.queryThreadInfos(info.url);
+ if (null != mThreadInfos && mThreadInfos.size() != 0) {
+ // 如果有对应的下载线程则表示可以断点续传
+ isResume = true;
+ } else {
+ // 否则视为异常情况删除该下载数据信息重建
+ sDBManager.deleteTaskInfo(info.url);
+ }
+ }
+ }
+
+ /**
+ * 对外接口停止下载任务
+ *
+ * @param isStop ...
+ */
+ public void setStop(boolean isStop) {
+ this.isStop = isStop;
+ }
+
+ @Override
+ public void run() {
+ // 判断网络连接状态
+ if (NetUtil.getNetWorkType(context) == PublicCons.NetType.INVALID) {
+ if (null != mListener)
+ mListener.onConnect(PublicCons.NetType.INVALID, "无网络连接");
+ isConnect = false;
+ } else if (NetUtil.getNetWorkType(context) == PublicCons.NetType.NO_WIFI) {
+ if (null != mListener)
+ isConnect = mListener.onConnect(PublicCons.NetType.NO_WIFI, "正在使用非WIFI网络下载");
+ }
+ // 如果网络可用
+ if (isConnect) {
+ // 将该任务添加至Map
+ sTaskDLing.put(info.url, this);
+
+ // 如果为断点续传则获取存储于数据库的下载线程实体对象构造下载线程执行
+ if (isResume) {
+ LogUtil.i("Resume download.");
+ for (ThreadInfo i : mThreadInfos) {
+ mExecutor.execute(new DLThread(i, this));
+ }
+ } else {
+ // 声明HttpURLConnection引用
+ HttpURLConnection conn = null;
+ try {
+ // 打开连接
+ conn = NetUtil.buildConnection(info.url);
+
+ // 如果网路连通
+ if (conn.getResponseCode() == HttpStatus.SC_OK) {
+ // 获取文件长度大小
+ fileLength = conn.getContentLength();
+
+ // 如果文件已存在并长度一致则直接返回
+ if (info.dlLocalFile.exists() && info.dlLocalFile.length() == fileLength) {
+ LogUtil.i("File exists!");
+ isExists = true;
+ sTaskDLing.remove(info.url);
+ if (null != mListener) mListener.onFinish(info.dlLocalFile);
+ }
+
+ // 如果文件不存在则下载
+ if (!isExists) {
+ info.length = fileLength;
+
+ // 将下载任务插入数据库
+ sDBManager.insertTaskInfo(info);
+
+ // 计算每个线程需要下载多少
+ int threadSize = fileLength / LENGTH_PER_THREAD;
+ LogUtil.i("We will start " + threadSize + " threads.");
+ int remainder = fileLength % LENGTH_PER_THREAD;
+ LogUtil.i("The last thread will download " + remainder + " more bytes.");
+ for (int i = 0; i < threadSize; i++) {
+ int start = i * LENGTH_PER_THREAD;
+ int end = start + LENGTH_PER_THREAD - 1;
+ if (i == threadSize - 1) {
+ end = start + LENGTH_PER_THREAD + remainder;
+ }
+ String id = UUID.randomUUID().toString();
+ LogUtil.i("The thread " + id + " will download from " + start + " to " +
+ "" + end + " byte.");
+ // 构造下载线程对象
+ ThreadInfo ti = new ThreadInfo(info.dlLocalFile, info.url, start,
+ end, id);
+
+ // 构造下载线程并执行
+ mExecutor.execute(new DLThread(ti, this));
+ }
+ }
+ }
+ } catch (Exception e) {
+ // 出现异常保存数据
+ if (null != sDBManager.queryTaskInfoByUrl(info.url)) {
+ info.progress = totalProgress;
+ sDBManager.updateTaskInfo(info);
+ sTaskDLing.remove(info.url);
+ }
+ if (null != mListener) mListener.onError(e.getMessage());
+ } finally {
+ // 释放资源
+ if (conn != null) {
+ conn.disconnect();
+ }
+ }
+ }
+ }
+ }
+
+ @Override
+ public void onThreadProgress(int progress) {
+ synchronized (this) {
+ totalProgress += progress;
+ int tmp = (int) (totalProgress * 1.0 / fileLength * 100);
+ if (null != mListener && tmp != totalProgressIn100) {
+ mListener.onProgress(tmp);
+ totalProgressIn100 = tmp;
+ }
+ // 下载完成删除数据
+ if (fileLength == totalProgress) {
+ LogUtil.i(info.url + " download finish!");
+ sDBManager.deleteTaskInfo(info.url);
+ sTaskDLing.remove(info.url);
+ if (null != mListener) mListener.onFinish(info.dlLocalFile);
+ }
+ // 停止下载存储数据
+ if (isStop) {
+ LogUtil.i("We were downloaded " + totalProgress + " bytes already.");
+ info.progress = totalProgress;
+ sDBManager.updateTaskInfo(info);
+ sTaskDLing.remove(info.url);
+ }
+ }
+ }
+
+ /**
+ * 真正执行下载的Runnable类
+ */
+ private class DLThread implements Runnable {
+ private ThreadInfo info;// 线程实体对象
+ private IDLThreadListener mListener;// 当前线程的下载监听器
+
+ private int progress;// 存储下载进度
+
+ public DLThread(ThreadInfo info, IDLThreadListener listener) {
+ this.info = info;
+ this.mListener = listener;
+ LogUtil.i("Thread " + info.id + " will start from " + info.start + " to " + info.end + " bytes.");
+ }
+
+ @Override
+ public void run() {
+ // 声明资源引用
+ HttpURLConnection conn = null;
+ RandomAccessFile raf = null;
+ InputStream is = null;
+ try {
+ // 打开连接设置请求头
+ conn = NetUtil.buildConnection(info.url);
+ conn.setRequestProperty("Range", "bytes=" + info.start + "-" + info.end);
+
+ // 构造RandomAccessFile对象准备读写文件
+ raf = new RandomAccessFile(info.dlLocalFile,
+ PublicCons.AccessModes.ACCESS_MODE_RWD);
+
+ // 如果服务器支持分段读取
+ if (conn.getResponseCode() == HttpStatus.SC_PARTIAL_CONTENT) {
+ // 网络连通且不为续传时开始写入数据库
+ if (!isResume) {
+ sDBManager.insertThreadInfo(info);
+ }
+
+ // 获取输入流
+ is = conn.getInputStream();
+
+ // 跳过一定字节数设置字节写入的开始位置
+ raf.seek(info.start);
+
+ // 计算当前线程应该下载的字节大小
+ int total = info.end - info.start;
+
+ // 读取&下载文件字节
+ byte[] b = new byte[1024];
+ int len;
+ while (!isStop && (len = is.read(b)) != -1) {
+ raf.write(b, 0, len);
+
+ // 累加当前进程下载进度
+ progress += len;
+
+ // 回调注册的监听器返回该线程该次下载长度
+ mListener.onThreadProgress(len);
+
+ // 如果progress == total则表示当前线程已下载完成
+ if (progress >= total) {
+ LogUtil.i("Thread " + info.id + " finish!");
+ // 此时可以将其从数据库删除
+ sDBManager.deleteThreadInfoById(info.id);
+ }
+ }
+ // 停止后存储数据
+ if (isStop && null != sDBManager.queryThreadInfoById(info.id)) {
+ info.start = info.start + progress;
+ sDBManager.updateThreadInfo(info);
+ }
+ }
+ } catch (Exception e) {
+ // 出现异常保存数据
+ if (null != sDBManager.queryThreadInfoById(info.id)) {
+ info.start = info.start + progress;
+ sDBManager.updateThreadInfo(info);
+ }
+ } finally {
+ // 释放资源
+ try {
+ if (null != is) {
+ is.close();
+ }
+ if (null != raf) {
+ raf.close();
+ }
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ if (null != conn) {
+ conn.disconnect();
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/Downloader/src/main/java/cn/aigestudio/downloader/cons/HttpConnPars.java b/Downloader/src/main/java/cn/aigestudio/downloader/cons/HttpConnPars.java
new file mode 100644
index 0000000..3d43693
--- /dev/null
+++ b/Downloader/src/main/java/cn/aigestudio/downloader/cons/HttpConnPars.java
@@ -0,0 +1,38 @@
+package cn.aigestudio.downloader.cons;
+
+/**
+ * HTTP参数枚举类
+ *
+ * @author AigeStudio 2015-05-08
+ */
+public enum HttpConnPars {
+ // 请求方式
+ POST("GET"),
+
+ // 请求格式
+ ACCECT("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, */*"),
+
+ // 请求语言
+ ACCECT_LANGAGE("Accept-Language", "zh-CN"),
+
+ // 请求的字符编码
+ CHARSET("Charset", "UTF-8"),
+
+ // 链接的超时数
+ CONNECTTIEMEDOUT("5000"),
+
+ // 保持链接
+ KEEPCONNECT("Connection", "Keep-Alive");
+
+ public String header;// 标题
+ public String content;// 内容
+
+ private HttpConnPars(String header, String content) {
+ this.header = header;
+ this.content = content;
+ }
+
+ private HttpConnPars(String content) {
+ this.content = content;
+ }
+}
diff --git a/Downloader/src/main/java/cn/aigestudio/downloader/cons/PublicCons.java b/Downloader/src/main/java/cn/aigestudio/downloader/cons/PublicCons.java
new file mode 100644
index 0000000..3fa70fe
--- /dev/null
+++ b/Downloader/src/main/java/cn/aigestudio/downloader/cons/PublicCons.java
@@ -0,0 +1,77 @@
+package cn.aigestudio.downloader.cons;
+
+import android.provider.BaseColumns;
+
+/**
+ * 公共常量
+ *
+ * @author AigeStudio 2015-05-08
+ */
+public final class PublicCons {
+ /**
+ * 文件访问模式
+ *
+ * @author AigeStudio 2015-05-08
+ */
+ public static final class AccessModes {
+ public static final String ACCESS_MODE_R = "r";
+ public static final String ACCESS_MODE_RW = "rw";
+ public static final String ACCESS_MODE_RWS = "rws";
+ public static final String ACCESS_MODE_RWD = "rwd";
+ }
+
+ /**
+ * 数据库常量
+ *
+ * @author AigeStudio 2015-05-08
+ */
+ public static final class DBCons {
+ public static final String TB_TASK = "task_info";// 下载任务表
+ public static final String TB_TASK_URL = "url";// 下载路径
+ public static final String TB_TASK_FILE_PATH = "file_path";// 下载文件本地保存路径
+ public static final String TB_TASK_PROGRESS = "onThreadProgress";// 下载进度
+ public static final String TB_TASK_FILE_LENGTH = "file_length";// 下载文件的长度
+
+ public static final String TB_THREAD = "thread_info";// 下载任务线程表
+ public static final String TB_THREAD_URL = "url";// 下载路径
+ public static final String TB_THREAD_FILE_PATH = "file_path";// 下载文件本地保存路径
+ public static final String TB_THREAD_START = "start";// 从哪个字节开始下载
+ public static final String TB_THREAD_END = "end";// 到哪个字节结束下载
+ public static final String TB_THREAD_ID = "id";// 下载线程的ID
+
+ public static final String TB_TASK_SQL_CREATE = "CREATE TABLE " +
+ PublicCons.DBCons.TB_TASK + "(" +
+ BaseColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " +
+ PublicCons.DBCons.TB_TASK_URL + " CHAR, " +
+ PublicCons.DBCons.TB_TASK_FILE_PATH + " CHAR, " +
+ PublicCons.DBCons.TB_TASK_PROGRESS + " INTEGER, " +
+ PublicCons.DBCons.TB_TASK_FILE_LENGTH + " INTEGER)";// 创建下载任务表的SQL语句
+ public static final String TB_THREAD_SQL_CREATE = "CREATE TABLE " +
+ PublicCons.DBCons.TB_THREAD + "(" +
+ BaseColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " +
+ PublicCons.DBCons.TB_THREAD_URL + " CHAR, " +
+ PublicCons.DBCons.TB_THREAD_FILE_PATH + " CHAR, " +
+ PublicCons.DBCons.TB_THREAD_START + " INTEGER, " +
+ PublicCons.DBCons.TB_THREAD_END + " INTEGER, " +
+ PublicCons.DBCons.TB_THREAD_ID + " CHAR)";// 创建下载任务线程表的SQL语句
+
+ public static final String TB_TASK_SQL_UPGRADE = "DROP TABLE IF EXISTS " +
+ PublicCons.DBCons.TB_TASK;// 删除下载任务表的SQL语句
+ public static final String TB_THREAD_SQL_UPGRADE = "DROP TABLE IF EXISTS " +
+ PublicCons.DBCons.TB_THREAD;// 删除下载任务表的SQL语句
+ }
+
+ /**
+ * 网络类型
+ *
+ * @author AigeStudio 2015-05-08
+ */
+ public static final class NetType {
+ public static final int INVALID = 0;// 没有网络
+ public static final int WAP = 1;// WAP网络
+ public static final int G2 = 2;// 2G网络
+ public static final int G3 = 3;// 3G+网络
+ public static final int WIFI = 4;// WIFI网络
+ public static final int NO_WIFI = 5;// 非WIFI网络
+ }
+}
diff --git a/Downloader/src/main/java/cn/aigestudio/downloader/daos/DBOpenHelper.java b/Downloader/src/main/java/cn/aigestudio/downloader/daos/DBOpenHelper.java
new file mode 100644
index 0000000..b925757
--- /dev/null
+++ b/Downloader/src/main/java/cn/aigestudio/downloader/daos/DBOpenHelper.java
@@ -0,0 +1,45 @@
+package cn.aigestudio.downloader.daos;
+
+import android.content.Context;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+
+import cn.aigestudio.downloader.cons.PublicCons;
+
+/**
+ * 打开数据库的帮助类
+ * 如果你想将表建立在自己的数据库直接将{@link #onCreate(android.database.sqlite.SQLiteDatabase)}和
+ * {@link #onUpgrade(android.database.sqlite.SQLiteDatabase, int, int)}方法中的逻辑Copy至在你自己
+ * SQLiteOpenHelper类的onCreate和onUpgrade方法中
+ *
+ * @author AigeStudio 2015-05-08
+ */
+public final class DBOpenHelper extends SQLiteOpenHelper {
+ private static final String DB_NAME = "dl.db";// 数据库名
+ private static final int DB_VERSION = 1;// 数据库版本号
+
+ public DBOpenHelper(Context context) {
+ super(context, DB_NAME, null, DB_VERSION);
+ }
+
+ @Override
+ public void onCreate(SQLiteDatabase db) {
+ // 创建下载任务表
+ db.execSQL(PublicCons.DBCons.TB_TASK_SQL_CREATE);
+
+ // 创建下载任务线程表
+ db.execSQL(PublicCons.DBCons.TB_THREAD_SQL_CREATE);
+ }
+
+ @Override
+ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+ // 删除下载任务表
+ db.execSQL(PublicCons.DBCons.TB_TASK_SQL_UPGRADE);
+
+ // 删除下载任务线程表
+ db.execSQL(PublicCons.DBCons.TB_THREAD_SQL_UPGRADE);
+
+ // 重建表信息
+ onCreate(db);
+ }
+}
diff --git a/Downloader/src/main/java/cn/aigestudio/downloader/daos/TaskDAO.java b/Downloader/src/main/java/cn/aigestudio/downloader/daos/TaskDAO.java
new file mode 100644
index 0000000..4dcadf4
--- /dev/null
+++ b/Downloader/src/main/java/cn/aigestudio/downloader/daos/TaskDAO.java
@@ -0,0 +1,72 @@
+package cn.aigestudio.downloader.daos;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+
+import java.io.File;
+
+import cn.aigestudio.downloader.cons.PublicCons;
+import cn.aigestudio.downloader.entities.DLInfo;
+import cn.aigestudio.downloader.entities.TaskInfo;
+import cn.aigestudio.downloader.interfaces.DAO;
+
+/**
+ * 下载任务的DAO实现
+ *
+ * @author AigeStudio 2015-05-09
+ */
+public class TaskDAO extends DAO {
+ public TaskDAO(Context context) {
+ super(context);
+ }
+
+ @Override
+ public void insertInfo(DLInfo info) {
+ TaskInfo i = (TaskInfo) info;
+ SQLiteDatabase db = dbHelper.getWritableDatabase();
+ db.execSQL("INSERT INTO " + PublicCons.DBCons.TB_TASK + "(" +
+ PublicCons.DBCons.TB_TASK_URL + ", " +
+ PublicCons.DBCons.TB_TASK_FILE_PATH + ", " +
+ PublicCons.DBCons.TB_TASK_PROGRESS + ", " +
+ PublicCons.DBCons.TB_TASK_FILE_LENGTH + ") values (?,?,?,?)", new Object[]{i.url,
+ i.dlLocalFile.getAbsolutePath(), i.progress, i.length});
+ db.close();
+ }
+
+ @Override
+ public void deleteInfo(String url) {
+ SQLiteDatabase db = dbHelper.getWritableDatabase();
+ db.execSQL("DELETE FROM " + PublicCons.DBCons.TB_TASK + " WHERE " +
+ PublicCons.DBCons.TB_TASK_URL + "=?", new String[]{url});
+ db.close();
+ }
+
+ @Override
+ public void updateInfo(DLInfo info) {
+ TaskInfo i = (TaskInfo) info;
+ SQLiteDatabase db = dbHelper.getWritableDatabase();
+ db.execSQL("UPDATE " + PublicCons.DBCons.TB_TASK + " SET " +
+ PublicCons.DBCons.TB_TASK_PROGRESS + "=? WHERE " +
+ PublicCons.DBCons.TB_TASK_URL + "=?", new Object[]{i.progress, i.url});
+ db.close();
+ }
+
+ @Override
+ public DLInfo queryInfo(String url) {
+ TaskInfo info = null;
+ SQLiteDatabase db = dbHelper.getWritableDatabase();
+ Cursor c = db.rawQuery("SELECT " + PublicCons.DBCons.TB_TASK_URL + ", " +
+ PublicCons.DBCons.TB_TASK_FILE_PATH + ", " +
+ PublicCons.DBCons.TB_TASK_PROGRESS + ", " +
+ PublicCons.DBCons.TB_TASK_FILE_LENGTH + " FROM " +
+ PublicCons.DBCons.TB_TASK + " WHERE " +
+ PublicCons.DBCons.TB_TASK_URL + "=?", new String[]{url});
+ if (c.moveToFirst()) {
+ info = new TaskInfo(new File(c.getString(1)), c.getString(0), c.getInt(2), c.getInt(3));
+ }
+ c.close();
+ db.close();
+ return info;
+ }
+}
diff --git a/Downloader/src/main/java/cn/aigestudio/downloader/daos/ThreadDAO.java b/Downloader/src/main/java/cn/aigestudio/downloader/daos/ThreadDAO.java
new file mode 100644
index 0000000..5013185
--- /dev/null
+++ b/Downloader/src/main/java/cn/aigestudio/downloader/daos/ThreadDAO.java
@@ -0,0 +1,103 @@
+package cn.aigestudio.downloader.daos;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+
+import cn.aigestudio.downloader.cons.PublicCons;
+import cn.aigestudio.downloader.entities.DLInfo;
+import cn.aigestudio.downloader.entities.ThreadInfo;
+import cn.aigestudio.downloader.interfaces.DAO;
+
+/**
+ * 线程的DAO实现
+ *
+ * @author AigeStudio 2015-05-16
+ */
+public class ThreadDAO extends DAO {
+ public ThreadDAO(Context context) {
+ super(context);
+ }
+
+ @Override
+ public void insertInfo(DLInfo info) {
+ ThreadInfo i = (ThreadInfo) info;
+ SQLiteDatabase db = dbHelper.getWritableDatabase();
+ db.execSQL("INSERT INTO " + PublicCons.DBCons.TB_THREAD + "(" +
+ PublicCons.DBCons.TB_THREAD_URL + ", " +
+ PublicCons.DBCons.TB_THREAD_FILE_PATH + ", " +
+ PublicCons.DBCons.TB_THREAD_START + ", " +
+ PublicCons.DBCons.TB_THREAD_END + ", " +
+ PublicCons.DBCons.TB_THREAD_ID + ") VALUES (?,?,?,?,?)", new Object[]{i.url,
+ i.dlLocalFile.getAbsolutePath(), i.start, i.end, i.id});
+ db.close();
+ }
+
+ @Override
+ public void deleteInfo(String id) {
+ SQLiteDatabase db = dbHelper.getWritableDatabase();
+ db.execSQL("DELETE FROM " + PublicCons.DBCons.TB_THREAD + " WHERE " +
+ PublicCons.DBCons.TB_THREAD_ID + "=?", new String[]{id});
+ db.close();
+ }
+
+ public void deleteInfos(String url) {
+ SQLiteDatabase db = dbHelper.getWritableDatabase();
+ db.execSQL("DELETE FROM " + PublicCons.DBCons.TB_THREAD + " WHERE " +
+ PublicCons.DBCons.TB_THREAD_URL + "=?", new String[]{url});
+ db.close();
+ }
+
+ @Override
+ public void updateInfo(DLInfo info) {
+ ThreadInfo i = (ThreadInfo) info;
+ SQLiteDatabase db = dbHelper.getWritableDatabase();
+ db.execSQL("UPDATE " + PublicCons.DBCons.TB_THREAD + " SET " +
+ PublicCons.DBCons.TB_THREAD_START + "=? WHERE " +
+ PublicCons.DBCons.TB_THREAD_URL + "=? AND " +
+ PublicCons.DBCons.TB_THREAD_ID + "=?", new Object[]{i.start, i.url, i.id});
+ db.close();
+ }
+
+ @Override
+ public DLInfo queryInfo(String id) {
+ ThreadInfo info = null;
+ SQLiteDatabase db = dbHelper.getWritableDatabase();
+ Cursor c = db.rawQuery("SELECT " + PublicCons.DBCons.TB_THREAD_URL + ", " +
+ PublicCons.DBCons.TB_THREAD_FILE_PATH + ", " +
+ PublicCons.DBCons.TB_THREAD_START + ", " +
+ PublicCons.DBCons.TB_THREAD_END + " FROM " +
+ PublicCons.DBCons.TB_THREAD + " WHERE " +
+ PublicCons.DBCons.TB_THREAD_ID + "=?", new String[]{id});
+ if (c.moveToFirst()) {
+ info = new ThreadInfo(new File(c.getString(1)), c.getString(0), c.getInt(2),
+ c.getInt(3), id);
+ }
+ c.close();
+ db.close();
+ return info;
+ }
+
+ public List queryInfos(String url) {
+ List infos = new ArrayList<>();
+ SQLiteDatabase db = dbHelper.getWritableDatabase();
+ Cursor c = db.rawQuery("SELECT " + PublicCons.DBCons.TB_THREAD_URL + ", " +
+ PublicCons.DBCons.TB_THREAD_FILE_PATH + ", " +
+ PublicCons.DBCons.TB_THREAD_START + ", " +
+ PublicCons.DBCons.TB_THREAD_END + ", " +
+ PublicCons.DBCons.TB_THREAD_ID + " FROM " +
+ PublicCons.DBCons.TB_THREAD + " WHERE " +
+ PublicCons.DBCons.TB_THREAD_URL + "=?", new String[]{url});
+ while (c.moveToNext()) {
+ infos.add(new ThreadInfo(new File(c.getString(1)), c.getString(0), c.getInt(2),
+ c.getInt(3), c.getString(4)));
+ }
+ c.close();
+ db.close();
+ return infos;
+ }
+}
diff --git a/Downloader/src/main/java/cn/aigestudio/downloader/entities/DLInfo.java b/Downloader/src/main/java/cn/aigestudio/downloader/entities/DLInfo.java
new file mode 100644
index 0000000..2b3e975
--- /dev/null
+++ b/Downloader/src/main/java/cn/aigestudio/downloader/entities/DLInfo.java
@@ -0,0 +1,19 @@
+package cn.aigestudio.downloader.entities;
+
+import java.io.File;
+import java.io.Serializable;
+
+/**
+ * 下载实体类
+ *
+ * @author AigeStudio 2015-05-16
+ */
+public class DLInfo implements Serializable {
+ public File dlLocalFile;
+ public String url;
+
+ public DLInfo(File dlLocalFile, String url) {
+ this.dlLocalFile = dlLocalFile;
+ this.url = url;
+ }
+}
diff --git a/Downloader/src/main/java/cn/aigestudio/downloader/entities/TaskInfo.java b/Downloader/src/main/java/cn/aigestudio/downloader/entities/TaskInfo.java
new file mode 100644
index 0000000..ad59024
--- /dev/null
+++ b/Downloader/src/main/java/cn/aigestudio/downloader/entities/TaskInfo.java
@@ -0,0 +1,19 @@
+package cn.aigestudio.downloader.entities;
+
+import java.io.File;
+import java.io.Serializable;
+
+/**
+ * 任务实体类
+ *
+ * @author AigeStudio 2015-05-16
+ */
+public class TaskInfo extends DLInfo implements Serializable {
+ public int progress, length;
+
+ public TaskInfo(File dlLocalFile, String url, int progress, int length) {
+ super(dlLocalFile, url);
+ this.progress = progress;
+ this.length = length;
+ }
+}
diff --git a/Downloader/src/main/java/cn/aigestudio/downloader/entities/ThreadInfo.java b/Downloader/src/main/java/cn/aigestudio/downloader/entities/ThreadInfo.java
new file mode 100644
index 0000000..faefeb2
--- /dev/null
+++ b/Downloader/src/main/java/cn/aigestudio/downloader/entities/ThreadInfo.java
@@ -0,0 +1,21 @@
+package cn.aigestudio.downloader.entities;
+
+import java.io.File;
+import java.io.Serializable;
+
+/**
+ * 线程实体类
+ *
+ * @author AigeStudio 2015-05-16
+ */
+public class ThreadInfo extends DLInfo implements Serializable {
+ public String id;
+ public int start, end;
+
+ public ThreadInfo(File dlLocalFile, String url, int start, int end, String id) {
+ super(dlLocalFile, url);
+ this.start = start;
+ this.end = end;
+ this.id = id;
+ }
+}
diff --git a/Downloader/src/main/java/cn/aigestudio/downloader/interfaces/DAO.java b/Downloader/src/main/java/cn/aigestudio/downloader/interfaces/DAO.java
new file mode 100644
index 0000000..591b189
--- /dev/null
+++ b/Downloader/src/main/java/cn/aigestudio/downloader/interfaces/DAO.java
@@ -0,0 +1,31 @@
+package cn.aigestudio.downloader.interfaces;
+
+import android.content.Context;
+
+import cn.aigestudio.downloader.daos.DBOpenHelper;
+import cn.aigestudio.downloader.entities.DLInfo;
+
+/**
+ * DAO抽象类
+ *
+ * @author AigeStudio 2015-05-16
+ */
+public abstract class DAO {
+ protected DBOpenHelper dbHelper;
+
+ public DAO(Context context) {
+ dbHelper = new DBOpenHelper(context);
+ }
+
+ public abstract void insertInfo(DLInfo info);
+
+ public abstract void deleteInfo(String url);
+
+ public abstract void updateInfo(DLInfo info);
+
+ public abstract DLInfo queryInfo(String str);
+
+ public void close() {
+ dbHelper.close();
+ }
+}
diff --git a/Downloader/src/main/java/cn/aigestudio/downloader/interfaces/DLTaskListener.java b/Downloader/src/main/java/cn/aigestudio/downloader/interfaces/DLTaskListener.java
new file mode 100644
index 0000000..bc96262
--- /dev/null
+++ b/Downloader/src/main/java/cn/aigestudio/downloader/interfaces/DLTaskListener.java
@@ -0,0 +1,63 @@
+package cn.aigestudio.downloader.interfaces;
+
+import java.io.File;
+
+/**
+ * 下载监听器
+ *
+ * @author AigeStudio 2015-05-08
+ */
+public class DLTaskListener {
+ /**
+ * 下载开始时回调
+ *
+ * @param length 文件字节长度
+ */
+ public void onStart(int length) {
+
+ }
+
+ /**
+ * 网络连接时回调
+ *
+ * @param type 具体的网络类型{@link cn.aigestudio.downloader.cons.PublicCons.NetType}
+ * @param msg 附加的连接信息
+ * @return true表示连接正常 否则反之
+ */
+ public boolean onConnect(int type, String msg) {
+ return true;
+ }
+
+ /**
+ * 下载进行时回调
+ *
+ * @param progress 当前的下载进度以100为最大单位
+ */
+ public void onProgress(int progress) {
+
+ }
+
+ /**
+ * 下载停止时回调
+ */
+ public void onStop() {
+
+ }
+
+ /**
+ * 下载完成时回调
+ *
+ * @param file 下载文件本地File对象
+ */
+ public void onFinish(File file) {
+
+ }
+
+ /**
+ * 下载出错时回调
+ *
+ * @param error 具体的错误信息
+ */
+ public void onError(String error) {
+ }
+}
diff --git a/Downloader/src/main/java/cn/aigestudio/downloader/interfaces/IDLThreadListener.java b/Downloader/src/main/java/cn/aigestudio/downloader/interfaces/IDLThreadListener.java
new file mode 100644
index 0000000..a2d6cbe
--- /dev/null
+++ b/Downloader/src/main/java/cn/aigestudio/downloader/interfaces/IDLThreadListener.java
@@ -0,0 +1,11 @@
+package cn.aigestudio.downloader.interfaces;
+
+/**
+ * 下载线程监听器
+ * 该监听仅供下载线程使用
+ *
+ * @author AigeStudio 2015-05-16
+ */
+public interface IDLThreadListener {
+ void onThreadProgress(int progress);
+}
diff --git a/Downloader/src/main/java/cn/aigestudio/downloader/utils/FileUtil.java b/Downloader/src/main/java/cn/aigestudio/downloader/utils/FileUtil.java
new file mode 100644
index 0000000..60bb4a7
--- /dev/null
+++ b/Downloader/src/main/java/cn/aigestudio/downloader/utils/FileUtil.java
@@ -0,0 +1,64 @@
+package cn.aigestudio.downloader.utils;
+
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * 文件操作工具类
+ *
+ * @author AigeStudio 2015-05-08
+ */
+public final class FileUtil {
+ /**
+ * 根据URL路径获取文件名
+ *
+ * @param url URL路径
+ * @return 文件名
+ */
+ public static String getFileNameFromUrl(String url) {
+ return url.substring(url.lastIndexOf("/"));
+ }
+
+ /**
+ * 创建文件夹
+ *
+ * @param path 文件夹路径
+ * @return 创建了的文件夹File对象
+ */
+ public static File makeDir(String path) {
+ File dir = new File(path);
+ if (!isExist(dir)) {
+ dir.mkdirs();
+ }
+ return dir;
+ }
+
+ /**
+ * 创建文件
+ *
+ * @param path 文件路径
+ * @param fileName 文件名
+ * @return 文件File对象
+ */
+ public static File createFile(String path, String fileName) {
+ File file = new File(makeDir(path), fileName);
+ if (!isExist(file)) {
+ try {
+ file.createNewFile();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ return file;
+ }
+
+ /**
+ * 判断File对象所指的目录或文件是否存在
+ *
+ * @param file File对象
+ * @return true表示存在 false反之
+ */
+ public static boolean isExist(File file) {
+ return file.exists();
+ }
+}
diff --git a/Downloader/src/main/java/cn/aigestudio/downloader/utils/LogUtil.java b/Downloader/src/main/java/cn/aigestudio/downloader/utils/LogUtil.java
new file mode 100644
index 0000000..b1c190d
--- /dev/null
+++ b/Downloader/src/main/java/cn/aigestudio/downloader/utils/LogUtil.java
@@ -0,0 +1,52 @@
+package cn.aigestudio.downloader.utils;
+
+import android.util.Log;
+
+/**
+ * Log输出工具类
+ *
+ * @author AigeStudio 2015-05-08
+ */
+public final class LogUtil {
+ public final static String TAG = "AigeStudio";
+ public final static String MATCH = "%s->%s->%d";
+ public final static String CONNECTOR = ":<--->:";
+
+ public final static boolean SWITCH = true;
+
+ public static String buildHeader() {
+ StackTraceElement stack = Thread.currentThread().getStackTrace()[4];
+ return String.format(MATCH, stack.getClassName(), stack.getMethodName(),
+ stack.getLineNumber()) + CONNECTOR;
+ }
+
+ public static void v(Object msg) {
+ if (SWITCH) {
+ Log.v(TAG, buildHeader() + msg.toString());
+ }
+ }
+
+ public static void d(Object msg) {
+ if (SWITCH) {
+ Log.d(TAG, buildHeader() + msg.toString());
+ }
+ }
+
+ public static void i(Object msg) {
+ if (SWITCH) {
+ Log.i(TAG, buildHeader() + msg.toString());
+ }
+ }
+
+ public static void w(Object msg) {
+ if (SWITCH) {
+ Log.w(TAG, buildHeader() + msg.toString());
+ }
+ }
+
+ public static void e(Object msg) {
+ if (SWITCH) {
+ Log.e(TAG, buildHeader() + msg.toString());
+ }
+ }
+}
diff --git a/Downloader/src/main/java/cn/aigestudio/downloader/utils/NetUtil.java b/Downloader/src/main/java/cn/aigestudio/downloader/utils/NetUtil.java
new file mode 100644
index 0000000..dbefc19
--- /dev/null
+++ b/Downloader/src/main/java/cn/aigestudio/downloader/utils/NetUtil.java
@@ -0,0 +1,123 @@
+package cn.aigestudio.downloader.utils;
+
+import android.content.Context;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
+import android.telephony.TelephonyManager;
+import android.text.TextUtils;
+
+import java.io.IOException;
+import java.net.HttpURLConnection;
+import java.net.URL;
+
+import cn.aigestudio.downloader.cons.HttpConnPars;
+import cn.aigestudio.downloader.cons.PublicCons;
+
+/**
+ * 网络操作工具类
+ *
+ * @author AigeStudio 2015-05-08
+ */
+public class NetUtil {
+ /**
+ * 根据url构建HTTP链接对象
+ *
+ * @param url url路径
+ * @return HTTP链接对象
+ * @throws IOException 链接异常时抛出
+ */
+ public static HttpURLConnection buildConnection(String url) throws IOException {
+ return buildConnection(url, false);
+ }
+
+ /**
+ * 根据url构建HTTP链接对象
+ *
+ * @param url url路径
+ * @param isAlive 是否保持长连接
+ * @return HTTP链接对象
+ * @throws IOException 链接异常时抛出
+ */
+ public static HttpURLConnection buildConnection(String url, boolean isAlive) throws IOException {
+ HttpURLConnection connection = (HttpURLConnection) new URL(url).openConnection();
+ connection.setRequestMethod(HttpConnPars.POST.content);
+ connection.setConnectTimeout(Integer.parseInt(HttpConnPars.CONNECTTIEMEDOUT.content));
+ connection.setRequestProperty(HttpConnPars.ACCECT.header, HttpConnPars.ACCECT.content);
+ connection.setRequestProperty(HttpConnPars.ACCECT_LANGAGE.header, HttpConnPars.ACCECT_LANGAGE.content);
+ connection.setRequestProperty(HttpConnPars.CHARSET.header, HttpConnPars.CHARSET.content);
+ if (isAlive) {
+ connection.setRequestProperty(HttpConnPars.KEEPCONNECT.header, HttpConnPars.KEEPCONNECT.content);
+ }
+ return connection;
+ }
+
+ /**
+ * 获取网络类型
+ *
+ * @param context ...
+ * @return 网络类型ID {@link PublicCons.NetType}
+ */
+ public static int getNetWorkType(Context context) {
+ int type = PublicCons.NetType.INVALID;
+ ConnectivityManager manager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
+ NetworkInfo networkInfo = manager.getActiveNetworkInfo();
+ if (networkInfo != null && networkInfo.isConnected()) {
+ String typeName = networkInfo.getTypeName();
+ if (typeName.equalsIgnoreCase("WIFI")) {
+ type = PublicCons.NetType.WIFI;
+ } else if (typeName.equalsIgnoreCase("MOBILE")) {
+ String proxyHost = android.net.Proxy.getDefaultHost();
+ type = TextUtils.isEmpty(proxyHost) ? (isFastMobileNetwork(context) ?
+ PublicCons.NetType.G3 : PublicCons.NetType.G2) :
+ PublicCons.NetType.WAP;
+ }
+ }
+ return type;
+ }
+
+ /**
+ * 判断是否是3G+的移动网络
+ *
+ * @param context ...
+ * @return ...
+ */
+ private static boolean isFastMobileNetwork(Context context) {
+ TelephonyManager telephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
+ switch (telephonyManager.getNetworkType()) {
+ case TelephonyManager.NETWORK_TYPE_1xRTT:
+ return false;
+ case TelephonyManager.NETWORK_TYPE_CDMA:
+ return false;
+ case TelephonyManager.NETWORK_TYPE_EDGE:
+ return false;
+ case TelephonyManager.NETWORK_TYPE_EVDO_0:
+ return true;
+ case TelephonyManager.NETWORK_TYPE_EVDO_A:
+ return true;
+ case TelephonyManager.NETWORK_TYPE_GPRS:
+ return false;
+ case TelephonyManager.NETWORK_TYPE_HSDPA:
+ return true;
+ case TelephonyManager.NETWORK_TYPE_HSPA:
+ return true;
+ case TelephonyManager.NETWORK_TYPE_HSUPA:
+ return true;
+ case TelephonyManager.NETWORK_TYPE_UMTS:
+ return true;
+ case TelephonyManager.NETWORK_TYPE_EHRPD:
+ return true;
+ case TelephonyManager.NETWORK_TYPE_EVDO_B:
+ return true;
+ case TelephonyManager.NETWORK_TYPE_HSPAP:
+ return true;
+ case TelephonyManager.NETWORK_TYPE_IDEN:
+ return false;
+ case TelephonyManager.NETWORK_TYPE_LTE:
+ return true;
+ case TelephonyManager.NETWORK_TYPE_UNKNOWN:
+ return false;
+ default:
+ return false;
+ }
+ }
+}
diff --git a/MultiThreadDownloader.iml b/MultiThreadDownloader.iml
new file mode 100644
index 0000000..2a02201
--- /dev/null
+++ b/MultiThreadDownloader.iml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/build.gradle b/build.gradle
new file mode 100644
index 0000000..7130c34
--- /dev/null
+++ b/build.gradle
@@ -0,0 +1,14 @@
+buildscript {
+ repositories {
+ jcenter()
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:1.0.0'
+ }
+}
+
+allprojects {
+ repositories {
+ jcenter()
+ }
+}
diff --git a/gradle.properties b/gradle.properties
new file mode 100644
index 0000000..1d3591c
--- /dev/null
+++ b/gradle.properties
@@ -0,0 +1,18 @@
+# Project-wide Gradle settings.
+
+# IDE (e.g. Android Studio) users:
+# Gradle settings configured through the IDE *will override*
+# any settings specified in this file.
+
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+
+# Specifies the JVM arguments used for the daemon process.
+# The setting is particularly useful for tweaking memory settings.
+# Default value: -Xmx10248m -XX:MaxPermSize=256m
+# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
+
+# When configured, Gradle will run in incubating parallel mode.
+# This option should only be used with decoupled projects. More details, visit
+# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
+# org.gradle.parallel=true
\ No newline at end of file
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..8c0fb64
Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..0c71e76
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Wed Apr 10 15:27:10 PDT 2013
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-2.2.1-all.zip
diff --git a/gradlew b/gradlew
new file mode 100644
index 0000000..91a7e26
--- /dev/null
+++ b/gradlew
@@ -0,0 +1,164 @@
+#!/usr/bin/env bash
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn ( ) {
+ echo "$*"
+}
+
+die ( ) {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+esac
+
+# For Cygwin, ensure paths are in UNIX format before anything is touched.
+if $cygwin ; then
+ [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
+fi
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >&-
+APP_HOME="`pwd -P`"
+cd "$SAVED" >&-
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=$((i+1))
+ done
+ case $i in
+ (0) set -- ;;
+ (1) set -- "$args0" ;;
+ (2) set -- "$args0" "$args1" ;;
+ (3) set -- "$args0" "$args1" "$args2" ;;
+ (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
+function splitJvmOpts() {
+ JVM_OPTS=("$@")
+}
+eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
+JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
+
+exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
diff --git a/gradlew.bat b/gradlew.bat
new file mode 100644
index 0000000..8a0b282
--- /dev/null
+++ b/gradlew.bat
@@ -0,0 +1,90 @@
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windowz variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+if "%@eval[2+2]" == "4" goto 4NT_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+goto execute
+
+:4NT_args
+@rem Get arguments from the 4NT Shell from JP Software
+set CMD_LINE_ARGS=%$
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/settings.gradle b/settings.gradle
new file mode 100644
index 0000000..a06b2ca
--- /dev/null
+++ b/settings.gradle
@@ -0,0 +1 @@
+include ':Demo', ':Downloader'