├── .gitignore
├── .idea
├── codeStyles
│ └── Project.xml
├── encodings.xml
├── gradle.xml
├── misc.xml
├── runConfigurations.xml
└── vcs.xml
├── README.md
├── build.gradle
├── demo
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── java
│ └── com
│ │ └── lwc
│ │ └── simpledownload
│ │ ├── FirstActivity.java
│ │ └── MainActivity.java
│ └── res
│ ├── drawable-v24
│ └── ic_launcher_foreground.xml
│ ├── drawable
│ └── ic_launcher_background.xml
│ ├── layout
│ ├── activity_first.xml
│ └── activity_main.xml
│ ├── mipmap-anydpi-v26
│ ├── ic_launcher.xml
│ └── ic_launcher_round.xml
│ ├── mipmap-hdpi
│ ├── ic_launcher.png
│ └── ic_launcher_round.png
│ ├── mipmap-mdpi
│ ├── ic_launcher.png
│ └── ic_launcher_round.png
│ ├── mipmap-xhdpi
│ ├── ic_launcher.png
│ └── ic_launcher_round.png
│ ├── mipmap-xxhdpi
│ ├── ic_launcher.png
│ └── ic_launcher_round.png
│ ├── mipmap-xxxhdpi
│ ├── ic_launcher.png
│ └── ic_launcher_round.png
│ └── values
│ ├── colors.xml
│ ├── strings.xml
│ └── styles.xml
├── download
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── java
│ └── com
│ │ └── lwc
│ │ └── download
│ │ ├── DownState.java
│ │ ├── DownloadInfo.java
│ │ ├── DownloadInterceptor.java
│ │ ├── DownloadListener.java
│ │ ├── DownloadManager.java
│ │ ├── DownloadProgressListener.java
│ │ ├── DownloadResponse.java
│ │ ├── DownloadService.java
│ │ └── FileUtils.java
│ └── res
│ └── values
│ └── strings.xml
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
└── settings.gradle
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/caches
5 | /.idea/libraries
6 | /.idea/modules.xml
7 | /.idea/workspace.xml
8 | /.idea/navEditor.xml
9 | /.idea/assetWizardSettings.xml
10 | .DS_Store
11 | /build
12 | /captures
13 | .externalNativeBuild
14 |
--------------------------------------------------------------------------------
/.idea/codeStyles/Project.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 | xmlns:android
14 |
15 | ^$
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 | xmlns:.*
25 |
26 | ^$
27 |
28 |
29 | BY_NAME
30 |
31 |
32 |
33 |
34 |
35 |
36 | .*:id
37 |
38 | http://schemas.android.com/apk/res/android
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 | .*:name
48 |
49 | http://schemas.android.com/apk/res/android
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 | name
59 |
60 | ^$
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 | style
70 |
71 | ^$
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 | .*
81 |
82 | ^$
83 |
84 |
85 | BY_NAME
86 |
87 |
88 |
89 |
90 |
91 |
92 | .*
93 |
94 | http://schemas.android.com/apk/res/android
95 |
96 |
97 | ANDROID_ATTRIBUTE_ORDER
98 |
99 |
100 |
101 |
102 |
103 |
104 | .*
105 |
106 | .*
107 |
108 |
109 | BY_NAME
110 |
111 |
112 |
113 |
114 |
115 |
116 |
--------------------------------------------------------------------------------
/.idea/encodings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/gradle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
21 |
22 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/.idea/runConfigurations.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # SimpleDownload
2 | 简介:Retrofit+OkHttp+RxJava实现的下载工具类
3 |
4 | ### 一、本框架实现的功能特性
5 | 本下载框架实现了这样的特性:
6 |
7 | 1. 支持断点续传。即下载断开后,支持从已完成的部分开始继续下载。
8 | 2. 支持多个任务同时下载。
9 | 3. 有各种情况的回调。如下载进度回调,任务取消、完成等回调。
10 | 4. 下载任务在子线程进行,回调在主线程。不需要用户自己切换线程。
11 | 5. 用户可以自定义文件保存路径和文件名。
12 |
13 |
14 | ### 二、最终效果图
15 |
16 |
17 | ### 三、使用方法
18 | 如果想在Android Studio项目中使用本框架,下面是使用步骤:
19 |
20 | 1.项目根目录下的Build.gradle文件中修改:
21 |
22 | ```
23 | allprojects {
24 | repositories {
25 | google()
26 | jcenter()
27 | maven { url 'https://www.jitpack.io' } // 添加这行
28 | }
29 | }
30 | ```
31 | 2.模块Build.gradle文件添加:
32 |
33 | ```
34 | dependencies {
35 | implementation fileTree(dir: 'libs', include: ['*.jar'])
36 | api 'com.github.liwuchen:SimpleDownload:1.4.0' // 添加这行
37 | }
38 | ```
39 | 请使用最新版本。
40 |
41 | 3.添加权限(**读写文件的权限还需动态申请**,这里就不贴出了):
42 |
43 | ```
44 |
45 |
46 |
47 | ```
48 |
49 | 4.代码中使用起来也很简单:
50 |
51 | ```java
52 | // 自定义文件夹名
53 | private final String FOLDER_NAME = "SimpleDownload";
54 | // 文件保存路径
55 | private final String SAVE_PATH = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + FOLDER_NAME;
56 | // 文件下载地址
57 | private final String url = "文件下载地址";
58 | // 保存文件名
59 | private final String fileName = "1.file";
60 | ```
61 |
62 | ```java
63 | // 定义下载回调
64 | DownloadListener listener = new DownloadListener() {
65 | @Override
66 | public void onStartDownload(TextView textView) {
67 | Log.d(TAG, "1 onStartDownload() called");
68 | }
69 |
70 | @Override
71 | public void onProgress(long downloaded, long total, TextView textView) {
72 | Log.d(TAG, "1 onProgress() called with: downloaded = [" + downloaded + "], total = [" + total + "]");
73 | textView.setText(getPercentString(downloaded, total));
74 | }
75 |
76 | @Override
77 | public void onPauseDownload(TextView textView) {
78 | Log.d(TAG, "1 onPauseDownload() called");
79 | textView.setText("暂停");
80 | }
81 |
82 | @Override
83 | public void onCancelDownload(TextView textView) {
84 | Log.d(TAG, "1 onCancelDownload() called");
85 | textView.setText("已取消");
86 | }
87 |
88 | @Override
89 | public void onFinishDownload(String file, TextView textView) {
90 | Log.d(TAG, "1 onFinishDownload() called:" + file);
91 | textView.setText("已完成");
92 | }
93 |
94 | @Override
95 | public void onFail(String errorInfo, TextView textView) {
96 | Log.d(TAG, "1 onFail() called with: errorInfo = [" + errorInfo + "]");
97 | textView.setText("下载出错");
98 | }
99 | };
100 | ```
101 |
102 | ```java
103 | DownloadManager downloadManager = DownloadManager.getInstance();
104 | // 开始下载
105 | TextView tvStatus = findViewById(R.id.tvStatus);
106 | downloadManager.download(url, SAVE_PATH, fileName, tvStatus, listener);
107 | // 暂停下载
108 | downloadManager.pauseDownload(url);
109 | // 继续下载
110 | downloadManager.continueDownload(url);
111 | // 取消下载,不删除文件
112 | downloadManager.cancelDownload(url, false);
113 | // 取消下载,并删除文件
114 | downloadManager.cancelDownload(url, true);
115 | ```
116 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 |
3 | buildscript {
4 | repositories {
5 | google()
6 | jcenter()
7 |
8 | }
9 | dependencies {
10 | classpath 'com.android.tools.build:gradle:3.4.0'
11 |
12 | // NOTE: Do not place your application dependencies here; they belong
13 | // in the individual module build.gradle files
14 | }
15 | }
16 |
17 | allprojects {
18 | repositories {
19 | google()
20 | jcenter()
21 | maven { url 'https://www.jitpack.io' }
22 | }
23 | }
24 |
25 | task clean(type: Delete) {
26 | delete rootProject.buildDir
27 | }
28 |
--------------------------------------------------------------------------------
/demo/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/demo/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion 29
5 | buildToolsVersion "29.0.0"
6 | defaultConfig {
7 | applicationId "com.lwc.simpledownload"
8 | minSdkVersion 19
9 | targetSdkVersion 29
10 | versionCode 1
11 | versionName "1.0"
12 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
13 | }
14 | buildTypes {
15 | release {
16 | minifyEnabled false
17 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
18 | }
19 | }
20 | }
21 |
22 | dependencies {
23 | implementation fileTree(dir: 'libs', include: ['*.jar'])
24 | implementation 'androidx.appcompat:appcompat:1.1.0'
25 | api 'com.github.liwuchen:SimpleDownload:1.4.1'
26 | // api project(":download")
27 | }
28 |
--------------------------------------------------------------------------------
/demo/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
22 |
--------------------------------------------------------------------------------
/demo/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
9 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/demo/src/main/java/com/lwc/simpledownload/FirstActivity.java:
--------------------------------------------------------------------------------
1 | package com.lwc.simpledownload;
2 |
3 | import android.app.Activity;
4 | import android.content.Intent;
5 | import android.os.Bundle;
6 | import android.view.View;
7 |
8 | import androidx.annotation.Nullable;
9 |
10 | public class FirstActivity extends Activity {
11 | @Override
12 | protected void onCreate(@Nullable Bundle savedInstanceState) {
13 | super.onCreate(savedInstanceState);
14 | setContentView(R.layout.activity_first);
15 | }
16 |
17 | public void goToMainPage(View view) {
18 | startActivity(new Intent(this, MainActivity.class));
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/demo/src/main/java/com/lwc/simpledownload/MainActivity.java:
--------------------------------------------------------------------------------
1 | package com.lwc.simpledownload;
2 |
3 | import androidx.annotation.NonNull;
4 | import androidx.appcompat.app.AppCompatActivity;
5 |
6 | import android.Manifest;
7 | import android.content.pm.PackageManager;
8 | import android.os.Build;
9 | import android.os.Bundle;
10 | import android.util.Log;
11 | import android.view.View;
12 | import android.widget.TextView;
13 | import android.widget.Toast;
14 |
15 | import com.lwc.download.DownState;
16 | import com.lwc.download.DownloadListener;
17 | import com.lwc.download.DownloadManager;
18 |
19 | import java.io.File;
20 |
21 | public class MainActivity extends AppCompatActivity {
22 | private static final String TAG = "Download";
23 | public static final int REQUEST_CODE = 100;
24 |
25 | private static TextView tvProgress1;
26 | private static TextView tvProgress2;
27 | private DownloadManager downloadManager = DownloadManager.getInstance();
28 | private String permissions[] = {Manifest.permission.WRITE_EXTERNAL_STORAGE};
29 | private final String FOLDER_NAME = "SimpleDownload";
30 | private String savePath;
31 |
32 | private final String url1 = "http://ccr.csslcloud.net/5D2636511DBBCADD/BBD5D1D6504FF2AD9C33DC5901307461/8DE588DAEE2FE914.ccr";
33 | private final String fileName1 = "1.file";
34 | private final String url2 = "http://ccr.csslcloud.net/5D2636511DBBCADD/BBD5D1D6504FF2AD9C33DC5901307461/D030B9064D6442EB.ccr";
35 | private final String fileName2 = "2.file";
36 |
37 | @Override
38 | protected void onCreate(Bundle savedInstanceState) {
39 | super.onCreate(savedInstanceState);
40 | setContentView(R.layout.activity_main);
41 | tvProgress1 = findViewById(R.id.tvProgress1);
42 | tvProgress2 = findViewById(R.id.tvProgress2);
43 |
44 | setOnClickListeners();
45 | requestPermissions();
46 | savePath = getApplication().getExternalCacheDir() + File.separator + FOLDER_NAME;
47 |
48 | restoreDownState(url1, tvProgress1);
49 | restoreDownState(url2, tvProgress2);
50 | }
51 |
52 | private void restoreDownState(String url, TextView textView) {
53 | if (DownloadManager.getInstance().getTaskState(url) == DownState.DOWNLOADING) {
54 | DownloadManager.getInstance().updateStatusTextView(url, textView);
55 | } else if (DownloadManager.getInstance().getTaskState(url) == DownState.PAUSE) {
56 | DownloadManager.getInstance().updateStatusTextView(url, textView);
57 | textView.setText("暂停");
58 | }
59 | }
60 |
61 | private void requestPermissions() {
62 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
63 | requestPermissions(permissions, REQUEST_CODE);
64 | }
65 | }
66 |
67 | @Override
68 | public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
69 | super.onRequestPermissionsResult(requestCode, permissions, grantResults);
70 | if (requestCode == REQUEST_CODE ) {
71 | if (grantResults[0] != PackageManager.PERMISSION_GRANTED) {
72 | Toast.makeText(this, "没有权限保存文件", Toast.LENGTH_LONG).show();
73 | finish();
74 | }
75 | }
76 | }
77 |
78 | private void setOnClickListeners() {
79 |
80 | // 第一组
81 | findViewById(R.id.btnStart1).setOnClickListener(new View.OnClickListener() {
82 | @Override
83 | public void onClick(View v) {
84 | downloadManager.download(MainActivity.this, url1, savePath, fileName1, tvProgress1, listener1);
85 | }
86 | });
87 |
88 | findViewById(R.id.btnStop1).setOnClickListener(new View.OnClickListener() {
89 | @Override
90 | public void onClick(View v) {
91 | downloadManager.pauseDownload(url1);
92 | }
93 | });
94 |
95 | findViewById(R.id.btnContinue1).setOnClickListener(new View.OnClickListener() {
96 | @Override
97 | public void onClick(View v) {
98 | downloadManager.continueDownload(url1);
99 | // downloadManager.continueDownload(url1, listener1);
100 | }
101 | });
102 |
103 | findViewById(R.id.btnRemove1).setOnClickListener(new View.OnClickListener() {
104 | @Override
105 | public void onClick(View v) {
106 | downloadManager.cancelDownload(url1, false);
107 | }
108 | });
109 |
110 |
111 | // 第二组
112 | findViewById(R.id.btnStart2).setOnClickListener(new View.OnClickListener() {
113 | @Override
114 | public void onClick(View v) {
115 | downloadManager.download(MainActivity.this, url2, savePath, fileName2, listener2);
116 | }
117 | });
118 |
119 | findViewById(R.id.btnStop2).setOnClickListener(new View.OnClickListener() {
120 | @Override
121 | public void onClick(View v) {
122 | downloadManager.pauseDownload(url2);
123 | }
124 | });
125 |
126 | findViewById(R.id.btnContinue2).setOnClickListener(new View.OnClickListener() {
127 | @Override
128 | public void onClick(View v) {
129 | downloadManager.continueDownload(url2, listener2);
130 | }
131 | });
132 |
133 | findViewById(R.id.btnRemove2).setOnClickListener(new View.OnClickListener() {
134 | @Override
135 | public void onClick(View v) {
136 | downloadManager.cancelDownload(url2, true);
137 | }
138 | });
139 | }
140 |
141 |
142 | static DownloadListener listener1 = new DownloadListener() {
143 | @Override
144 | public void onStartDownload(TextView textView) {
145 | Log.d(TAG, "1 onStartDownload() called");
146 | }
147 |
148 | @Override
149 | public void onProgress(long downloaded, long total, TextView textView) {
150 | Log.d(TAG, "1 onProgress() called with: downloaded = [" + downloaded + "], total = [" + total + "]");
151 | textView.setText(getPercentString(downloaded, total));
152 | }
153 |
154 | @Override
155 | public void onPauseDownload(TextView textView) {
156 | Log.d(TAG, "1 onPauseDownload() called");
157 | textView.setText("暂停");
158 | }
159 |
160 | @Override
161 | public void onCancelDownload(TextView textView) {
162 | Log.d(TAG, "1 onCancelDownload() called");
163 | textView.setText("已取消");
164 | }
165 |
166 | @Override
167 | public void onFinishDownload(String file, TextView textView) {
168 | Log.d(TAG, "1 onFinishDownload() called:" + file);
169 | textView.setText("已完成");
170 | }
171 |
172 | @Override
173 | public void onFail(String errorInfo, TextView textView) {
174 | Log.d(TAG, "1 onFail() called with: errorInfo = [" + errorInfo + "]");
175 | textView.setText("下载出错");
176 | }
177 | };
178 |
179 |
180 | static DownloadListener listener2 = new DownloadListener() {
181 | @Override
182 | public void onStartDownload(TextView textView) {
183 | Log.d(TAG, "2 onStartDownload() called");
184 | }
185 |
186 | @Override
187 | public void onProgress(long downloaded, long total, TextView textView) {
188 | Log.d(TAG, "2 onProgress() called with: downloaded = [" + downloaded + "], total = [" + total + "]");
189 | tvProgress2.setText(getPercentString(downloaded, total));
190 | }
191 |
192 | @Override
193 | public void onPauseDownload(TextView textView) {
194 | Log.d(TAG, "2 onPauseDownload() called");
195 | }
196 |
197 | @Override
198 | public void onCancelDownload(TextView textView) {
199 | Log.d(TAG, "2 onCancelDownload() called");
200 | }
201 |
202 | @Override
203 | public void onFinishDownload(String file, TextView textView) {
204 | Log.d(TAG, "2 onFinishDownload() called:" + file);
205 | }
206 |
207 | @Override
208 | public void onFail(String errorInfo, TextView textView) {
209 | Log.d(TAG, "2 onFail() called with: errorInfo = [" + errorInfo + "]");
210 | }
211 | };
212 |
213 | private static String getPercentString(long val1, long val2) {
214 | if (val2 > 0) {
215 | int val = (int)(val1*100/val2);
216 | return val+"%";
217 | } else {
218 | if (val1 < 1024) {
219 | return val1+"bytes";
220 | } else if (val1 < 1024 * 1024) {
221 | return (val1/1024)+"KB";
222 | } else if (val1 < 1024 * 1024 * 1024) {
223 | return (val1/1024/1024)+"MB";
224 | } else {
225 | return (val1/1024/1024/1024)+"GB";
226 | }
227 | }
228 | }
229 |
230 | @Override
231 | protected void onStop() {
232 | super.onStop();
233 | // downloadManager.pauseDownload(url1);
234 | // downloadManager.pauseDownload(url2);
235 | }
236 | }
237 |
--------------------------------------------------------------------------------
/demo/src/main/res/drawable-v24/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
12 |
13 |
19 |
22 |
25 |
26 |
27 |
28 |
34 |
35 |
--------------------------------------------------------------------------------
/demo/src/main/res/drawable/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
10 |
15 |
20 |
25 |
30 |
35 |
40 |
45 |
50 |
55 |
60 |
65 |
70 |
75 |
80 |
85 |
90 |
95 |
100 |
105 |
110 |
115 |
120 |
125 |
130 |
135 |
140 |
145 |
150 |
155 |
160 |
165 |
170 |
171 |
--------------------------------------------------------------------------------
/demo/src/main/res/layout/activity_first.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
16 |
--------------------------------------------------------------------------------
/demo/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
16 |
22 |
27 |
32 |
37 |
42 |
43 |
49 |
55 |
60 |
65 |
70 |
75 |
76 |
--------------------------------------------------------------------------------
/demo/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/demo/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/demo/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/liwuchen/SimpleDownload/242178b6048ca66f4fe000da78239e41fc7a6730/demo/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/demo/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/liwuchen/SimpleDownload/242178b6048ca66f4fe000da78239e41fc7a6730/demo/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/demo/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/liwuchen/SimpleDownload/242178b6048ca66f4fe000da78239e41fc7a6730/demo/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/demo/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/liwuchen/SimpleDownload/242178b6048ca66f4fe000da78239e41fc7a6730/demo/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/demo/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/liwuchen/SimpleDownload/242178b6048ca66f4fe000da78239e41fc7a6730/demo/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/demo/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/liwuchen/SimpleDownload/242178b6048ca66f4fe000da78239e41fc7a6730/demo/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/demo/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/liwuchen/SimpleDownload/242178b6048ca66f4fe000da78239e41fc7a6730/demo/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/demo/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/liwuchen/SimpleDownload/242178b6048ca66f4fe000da78239e41fc7a6730/demo/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/demo/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/liwuchen/SimpleDownload/242178b6048ca66f4fe000da78239e41fc7a6730/demo/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/demo/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/liwuchen/SimpleDownload/242178b6048ca66f4fe000da78239e41fc7a6730/demo/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/demo/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #008577
4 | #00574B
5 | #D81B60
6 |
7 |
--------------------------------------------------------------------------------
/demo/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | SimpleDownload
3 |
4 |
--------------------------------------------------------------------------------
/demo/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/download/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/download/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 |
3 | android {
4 | compileSdkVersion 29
5 | buildToolsVersion "29.0.0"
6 |
7 |
8 | defaultConfig {
9 | minSdkVersion 16
10 | targetSdkVersion 29
11 | versionCode 15
12 | versionName "1.4.1"
13 |
14 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
15 |
16 | }
17 |
18 | buildTypes {
19 | release {
20 | minifyEnabled false
21 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
22 | }
23 | }
24 |
25 | }
26 |
27 | dependencies {
28 | implementation fileTree(dir: 'libs', include: ['*.jar'])
29 | // 导入Retrofit
30 | implementation 'com.squareup.retrofit2:retrofit:2.1.0'
31 | implementation 'com.squareup.retrofit2:converter-gson:2.1.0'
32 | implementation 'com.squareup.retrofit2:adapter-rxjava2:2.2.0'
33 | implementation 'com.squareup.retrofit2:adapter-rxjava2:2.2.0'
34 | // 导入OkHttp
35 | implementation 'com.squareup.okhttp3:okhttp:3.4.1'
36 | implementation 'com.squareup.okio:okio:1.9.0'
37 | // 导入RxJava
38 | implementation 'com.jakewharton.rxbinding:rxbinding:0.4.0'
39 | implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'
40 | implementation 'io.reactivex.rxjava2:rxjava:2.2.13'
41 | implementation 'org.litepal.android:java:3.0.0'
42 | // 申请权限所用
43 | implementation 'androidx.appcompat:appcompat:1.1.0'
44 | }
45 |
--------------------------------------------------------------------------------
/download/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
22 |
--------------------------------------------------------------------------------
/download/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
--------------------------------------------------------------------------------
/download/src/main/java/com/lwc/download/DownState.java:
--------------------------------------------------------------------------------
1 | package com.lwc.download;
2 |
3 | /**
4 | * @Package: com.lwc.download
5 | * @ClassName: DownState
6 | * @Description: 下载状态枚举类
7 | * @Author: liwuchen
8 | * @CreateDate: 2019/10/15
9 | */
10 | public enum DownState {
11 | DEFAULT(0),
12 | DOWNLOADING(1),
13 | PAUSE(2),
14 | ERROR(3),
15 | FINISH(4);
16 |
17 | private int state;
18 |
19 | public int getState() {
20 | return state;
21 | }
22 |
23 | public void setState(int state) {
24 | this.state = state;
25 | }
26 |
27 | DownState(int state) {
28 | this.state = state;
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/download/src/main/java/com/lwc/download/DownloadInfo.java:
--------------------------------------------------------------------------------
1 | package com.lwc.download;
2 |
3 | import android.widget.TextView;
4 |
5 | import io.reactivex.disposables.Disposable;
6 |
7 | /**
8 | * @Package: com.lwc.download
9 | * @ClassName: DownloadInfo
10 | * @Description: 下载内容信息
11 | * @Author: liwuchen
12 | * @CreateDate: 2019/10/18
13 | */
14 | public class DownloadInfo {
15 | /* 文件存储名 */
16 | private String fileName;
17 | /* 存储位置 */
18 | private String savePath;
19 | /* 文件总长度 */
20 | private long contentLength;
21 | /* 已下载长度 */
22 | private long readLength;
23 | /* 下载该文件的url */
24 | private String url;
25 | /* 复用Retrofit对象 */
26 | private DownloadService service;
27 | /* 被观察对象,用于取消下载 */
28 | private Disposable disposable;
29 | /* 下载状态 */
30 | private DownState state;
31 | /* 下载回调 */
32 | private DownloadListener listener;
33 | /* 可显示下载状态的TextView */
34 | private TextView stateTextView;
35 |
36 | public TextView getStateTextView() {
37 | return stateTextView;
38 | }
39 |
40 | public void setStateTextView(TextView stateTextView) {
41 | this.stateTextView = stateTextView;
42 | }
43 |
44 | public String getFileName() {
45 | return fileName;
46 | }
47 |
48 | public void setFileName(String fileName) {
49 | this.fileName = fileName;
50 | }
51 |
52 | public DownloadService getService() {
53 | return service;
54 | }
55 |
56 | public void setService(DownloadService service) {
57 | this.service = service;
58 | }
59 |
60 | public String getUrl() {
61 | return url;
62 | }
63 |
64 | public void setUrl(String url) {
65 | this.url = url;
66 | }
67 |
68 | public String getSavePath() {
69 | return savePath;
70 | }
71 |
72 | public void setSavePath(String savePath) {
73 | this.savePath = savePath;
74 | }
75 |
76 | public long getContentLength() {
77 | return contentLength;
78 | }
79 |
80 | public void setContentLength(long contentLength) {
81 | this.contentLength = contentLength;
82 | }
83 |
84 | public long getReadLength() {
85 | return readLength;
86 | }
87 |
88 | public void setReadLength(long readLength) {
89 | this.readLength = readLength;
90 | }
91 |
92 | public Disposable getDisposable() {
93 | return disposable;
94 | }
95 |
96 | public void setDisposable(Disposable disposable) {
97 | this.disposable = disposable;
98 | }
99 |
100 | public DownState getState() {
101 | return state;
102 | }
103 |
104 | public void setState(DownState state) {
105 | this.state = state;
106 | }
107 |
108 | public DownloadListener getListener() {
109 | return listener;
110 | }
111 |
112 | public void setListener(DownloadListener listener) {
113 | this.listener = listener;
114 | }
115 |
116 | @Override
117 | public String toString() {
118 | return "DownloadInfo{" +
119 | "fileName='" + fileName + '\'' +
120 | ", savePath='" + savePath + '\'' +
121 | ", contentLength=" + contentLength +
122 | ", readLength=" + readLength +
123 | ", url='" + url + '\'' +
124 | ", service=" + service +
125 | ", disposable=" + disposable +
126 | ", state=" + state +
127 | ", listener=" + listener +
128 | ", stateTextView=" + stateTextView +
129 | '}';
130 | }
131 | }
132 |
--------------------------------------------------------------------------------
/download/src/main/java/com/lwc/download/DownloadInterceptor.java:
--------------------------------------------------------------------------------
1 | package com.lwc.download;
2 |
3 | import java.io.IOException;
4 |
5 | import io.reactivex.annotations.NonNull;
6 | import okhttp3.Interceptor;
7 | import okhttp3.Response;
8 |
9 | /**
10 | * @Package: com.lwc.download
11 | * @ClassName: DownloadInterceptor
12 | * @Description: 带进度 下载 拦截器
13 | * @Author: liwuchen
14 | * @CreateDate: 2019/10/12
15 | */
16 | public class DownloadInterceptor implements Interceptor {
17 |
18 | private DownloadProgressListener listener;
19 |
20 | public DownloadInterceptor(DownloadProgressListener listener) {
21 | this.listener = listener;
22 | }
23 |
24 | @NonNull
25 | @Override
26 | public Response intercept(@NonNull Chain chain) throws IOException {
27 | Response response = chain.proceed(chain.request());
28 | return response.newBuilder()
29 | .body(new DownloadResponse(response.body(), listener)).build();
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/download/src/main/java/com/lwc/download/DownloadListener.java:
--------------------------------------------------------------------------------
1 | package com.lwc.download;
2 |
3 | import android.widget.TextView;
4 |
5 | /**
6 | * @Package: com.lwc.download
7 | * @ClassName: DownloadListener
8 | * @Description: 下载进度回调
9 | * @Author: liwuchen
10 | * @CreateDate: 2019/10/12
11 | */
12 | public interface DownloadListener {
13 |
14 | void onStartDownload(TextView textView);
15 |
16 | void onProgress(long downloaded, long total, TextView textView);
17 |
18 | void onPauseDownload(TextView textView);
19 |
20 | void onCancelDownload(TextView textView);
21 |
22 | void onFinishDownload(String savedFile, TextView textView);
23 |
24 | void onFail(String errorInfo, TextView textView);
25 | }
26 |
--------------------------------------------------------------------------------
/download/src/main/java/com/lwc/download/DownloadManager.java:
--------------------------------------------------------------------------------
1 | package com.lwc.download;
2 |
3 | import android.Manifest;
4 | import android.app.Activity;
5 | import android.content.pm.PackageManager;
6 | import android.os.Handler;
7 | import android.text.TextUtils;
8 | import android.widget.TextView;
9 | import android.widget.Toast;
10 |
11 | import androidx.annotation.NonNull;
12 | import androidx.core.app.ActivityCompat;
13 | import androidx.core.content.ContextCompat;
14 |
15 | import java.io.File;
16 | import java.io.InputStream;
17 | import java.util.HashMap;
18 | import java.util.concurrent.ExecutorService;
19 | import java.util.concurrent.Executors;
20 | import java.util.concurrent.TimeUnit;
21 |
22 | import io.reactivex.Observer;
23 | import io.reactivex.android.schedulers.AndroidSchedulers;
24 | import io.reactivex.disposables.Disposable;
25 | import io.reactivex.functions.Consumer;
26 | import io.reactivex.functions.Function;
27 | import io.reactivex.schedulers.Schedulers;
28 | import okhttp3.OkHttpClient;
29 | import okhttp3.ResponseBody;
30 | import retrofit2.Retrofit;
31 | import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory;
32 |
33 | /**
34 | * @Package: com.lwc.download
35 | * @ClassName: DownloadManager
36 | * @Description: 下载管理
37 | * @Author: liwuchen
38 | * @CreateDate: 2019/10/12
39 | */
40 | public class DownloadManager {
41 |
42 | private static final int REQUEST_WRITE_EXTERNAL_STORAGE = 99;
43 | private static final String TAG = "DownloadManager";
44 | private static final int DEFAULT_TIMEOUT = 40;
45 | private static final String BASE_URL = "http://www.baidu.com/";
46 | /*正在下载的队列*/
47 | private static HashMap downloadMap;
48 |
49 | private volatile static DownloadManager INSTANCE;
50 |
51 | public static DownloadManager getInstance() {
52 | if (INSTANCE == null) {
53 | synchronized (DownloadManager.class) {
54 | if (INSTANCE == null) {
55 | INSTANCE = new DownloadManager();
56 | }
57 | }
58 | }
59 | return INSTANCE;
60 | }
61 |
62 | private DownloadManager() {
63 | downloadMap = new HashMap<>();
64 | }
65 |
66 | /**
67 | * 判断有无存储权限,没有的话就申请权限
68 | * @param context
69 | * @param url 文件下载地址。例:https://xtknowledgeoss.xuetian.cn/multimedia_knowledge/R16f4601f703000n_crm测试账号.xlsx
70 | * @param filePath 文件保存路径(不以“/”结尾)。例:/storage/emulated/0/xt_smart/课程资料
71 | * @param fileName 文件保存名称。例:资料测试单独1.xlsx
72 | * @param listener 下载监听
73 | */
74 | public void download(Activity context, @NonNull String url, final String filePath, final String fileName, final DownloadListener listener) {
75 | download(context, url, filePath, fileName, null, listener);
76 | }
77 |
78 |
79 | /**
80 | * 判断有无存储权限,没有的话就申请权限
81 | * @param context
82 | * @param url 文件下载地址。例:https://xtknowledgeoss.xuetian.cn/multimedia_knowledge/R16f4601f703000n_crm测试账号.xlsx
83 | * @param filePath 文件保存路径(不以“/”结尾)。例:/storage/emulated/0/xt_smart/课程资料
84 | * @param fileName 文件保存名称。例:资料测试单独1.xlsx
85 | * @param textView 可显示下载状态的TextView
86 | * @param listener 下载监听
87 | */
88 | public void download(Activity context, @NonNull String url, final String filePath, final String fileName, TextView textView, final DownloadListener listener) {
89 | if (ContextCompat.checkSelfPermission(context, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
90 | // Toast.makeText(context, "请先获取存储权限,然后点击下载", Toast.LENGTH_SHORT).show();
91 | ActivityCompat.requestPermissions(context, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, REQUEST_WRITE_EXTERNAL_STORAGE);
92 | } else {
93 | startDownload(url, filePath, fileName, textView, listener);
94 | }
95 | }
96 |
97 | /**
98 | * 下载文件
99 | * @param url 文件下载地址
100 | * @param filePath 文件保存路径(不以“/”结尾)
101 | * @param fileName 文件保存名称
102 | * @param textView 可显示下载状态的TextView
103 | * @param listener 下载监听
104 | */
105 | private void startDownload(@NonNull String url, final String filePath, final String fileName, TextView textView, final DownloadListener listener) {
106 | DownloadInfo tempInfo = downloadMap.get(url);
107 | long start;
108 | boolean newTask;
109 | DownloadService downloadService;
110 | if (tempInfo != null) {
111 | tempInfo.setStateTextView(textView);
112 | if (tempInfo.getState() == DownState.DOWNLOADING) {
113 | //正在下载则不处理
114 | return;
115 | } else if (tempInfo.getState() == DownState.PAUSE){
116 | //从暂停处继续下载
117 | start = tempInfo.getReadLength();
118 | downloadService = tempInfo.getService();
119 | newTask = false;
120 | } else {
121 | //出错,或者下载已完成
122 | start = 0;
123 | downloadService = tempInfo.getService();
124 | newTask = true;
125 | }
126 | } else {
127 | //新的下载任务
128 | tempInfo = new DownloadInfo();
129 | tempInfo.setUrl(url);
130 | tempInfo.setSavePath(filePath);
131 | tempInfo.setFileName(fileName);
132 | tempInfo.setListener(listener);
133 | tempInfo.setStateTextView(textView);
134 |
135 | DownloadInterceptor interceptor = new DownloadInterceptor(new DownloadProgressListener(tempInfo, listener));
136 | OkHttpClient httpClient = new OkHttpClient.Builder()
137 | .addInterceptor(interceptor)
138 | .retryOnConnectionFailure(true)
139 | .connectTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS)
140 | .build();
141 |
142 | ExecutorService executorService = Executors.newFixedThreadPool(1);
143 | Retrofit retrofit = new Retrofit.Builder()
144 | .baseUrl(BASE_URL)
145 | .client(httpClient)
146 | .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
147 | .callbackExecutor(executorService) //设置CallBack回调在子线程进行
148 | .build();
149 | downloadService = retrofit.create(DownloadService.class);
150 | tempInfo.setService(downloadService);
151 | start = 0;
152 | newTask = true;
153 | }
154 | final DownloadInfo downloadInfo = tempInfo;
155 | final boolean newFile = newTask;
156 | downloadService
157 | .rxDownload("bytes=" + start + "-", url)
158 | .subscribeOn(Schedulers.io())
159 | .observeOn(Schedulers.io())
160 | .map(new Function() {
161 | @Override
162 | public InputStream apply(ResponseBody responseBody) throws Exception {
163 | return responseBody.byteStream();
164 | }
165 | })
166 | .doOnNext(new Consumer() {
167 | @Override
168 | public void accept(InputStream inputStream) throws Exception {
169 | FileUtils.writeFileFromIS(filePath, fileName, inputStream, newFile);
170 | }
171 | })
172 | .observeOn(AndroidSchedulers.mainThread())
173 | .subscribe(new Observer() {
174 | @Override
175 | public void onSubscribe(Disposable d) {
176 | if (listener != null) {
177 | listener.onStartDownload(downloadInfo.getStateTextView());
178 | }
179 | downloadInfo.setDisposable(d);
180 | }
181 |
182 | @Override
183 | public void onNext(InputStream stream) {
184 | }
185 |
186 | @Override
187 | public void onError(Throwable e) {
188 | if (listener != null) {
189 | listener.onFail(e.getMessage(), downloadInfo.getStateTextView());
190 | downloadInfo.setState(DownState.ERROR);
191 | }
192 | }
193 |
194 | @Override
195 | public void onComplete() {
196 | downloadMap.remove(downloadInfo.getUrl());
197 | }
198 | });
199 | downloadInfo.setState(DownState.DOWNLOADING);
200 | downloadMap.put(url, downloadInfo);
201 | }
202 |
203 | public void pauseAllDownload(){
204 | if(downloadMap != null){
205 | for (HashMap.Entry entry : downloadMap.entrySet()) {
206 | pauseDownload(entry.getKey());
207 | }
208 | }
209 | }
210 |
211 | /**
212 | * 暂停下载
213 | * @param url 文件下载地址
214 | */
215 | public void pauseDownload(String url) {
216 | if (TextUtils.isEmpty(url)) {
217 | return;
218 | }
219 | if(downloadMap.containsKey(url)) {
220 | final DownloadInfo downloadInfo = downloadMap.get(url);
221 | Disposable disposable = null;
222 | if (downloadInfo != null) {
223 | disposable = downloadInfo.getDisposable();
224 | }
225 | if (disposable != null && !disposable.isDisposed()) {
226 | disposable.dispose();
227 | downloadInfo.setState(DownState.PAUSE);
228 |
229 | //延迟回调,解决disposable.dispose()调用后任务会继续执行若干时间的问题
230 | final DownloadListener listener = downloadInfo.getListener();
231 | new Handler().postDelayed(new Runnable() {
232 | @Override
233 | public void run() {
234 | listener.onPauseDownload(downloadInfo.getStateTextView());
235 | }
236 | }, 1000);
237 | }
238 | }
239 | }
240 |
241 | /**
242 | * 继续下载
243 | * @param url 文件下载地址
244 | */
245 | public void continueDownload(String url) {
246 | if (TextUtils.isEmpty(url)) {
247 | return;
248 | }
249 | if(downloadMap.containsKey(url)) {
250 | DownloadInfo downloadInfo = downloadMap.get(url);
251 | if (downloadInfo != null && downloadInfo.getState() == DownState.PAUSE) {
252 | startDownload(url, downloadInfo.getSavePath(), downloadInfo.getFileName(), downloadInfo.getStateTextView(), downloadInfo.getListener());
253 | }
254 | } else {
255 | // 没有此任务
256 | }
257 | }
258 |
259 | /**
260 | * 继续下载
261 | * @param url 文件下载地址
262 | */
263 | public void continueDownload(final String url, final DownloadListener listener) {
264 | if (TextUtils.isEmpty(url)) {
265 | return;
266 | }
267 | if(downloadMap.containsKey(url)) {
268 | DownloadInfo downloadInfo = downloadMap.get(url);
269 | if (downloadInfo != null && downloadInfo.getState() == DownState.PAUSE) {
270 | startDownload(url, downloadInfo.getSavePath(), downloadInfo.getFileName(), downloadInfo.getStateTextView(), listener);
271 | }
272 | } else {
273 | // 没有此任务
274 | }
275 | }
276 |
277 | /**
278 | * 继续下载
279 | * @param url 文件下载地址
280 | * @param textView
281 | * @param listener
282 | */
283 | public void continueDownload(final String url, final TextView textView, final DownloadListener listener) {
284 | if (TextUtils.isEmpty(url)) {
285 | return;
286 | }
287 | if(downloadMap.containsKey(url)) {
288 | DownloadInfo downloadInfo = downloadMap.get(url);
289 | if (downloadInfo != null && downloadInfo.getState() == DownState.PAUSE) {
290 | startDownload(url, downloadInfo.getSavePath(), downloadInfo.getFileName(), textView, listener);
291 | }
292 | } else {
293 | // 没有此任务
294 | }
295 | }
296 |
297 | /**
298 | * 取消下载
299 | * @param url 文件下载地址
300 | * @param deleteFile 是否删除已下载的文件
301 | */
302 | public void cancelDownload(String url, boolean deleteFile) {
303 | if (TextUtils.isEmpty(url)) {
304 | return;
305 | }
306 | if(downloadMap.containsKey(url)) {
307 | final DownloadInfo downloadInfo = downloadMap.get(url);
308 | Disposable disposable = null;
309 | if (downloadInfo != null) {
310 | disposable = downloadInfo.getDisposable();
311 | }
312 | if (disposable != null && !disposable.isDisposed()) {
313 | disposable.dispose();
314 |
315 | //延迟回调,解决disposable.dispose()调用后任务会继续执行若干时间的问题
316 | final DownloadListener listener = downloadInfo.getListener();
317 | new Handler().postDelayed(new Runnable() {
318 | @Override
319 | public void run() {
320 | listener.onCancelDownload(downloadInfo.getStateTextView());
321 | }
322 | }, 1000);
323 | }
324 | downloadMap.remove(url);
325 |
326 | if (deleteFile && downloadInfo != null) {
327 | FileUtils.deleteFile(downloadInfo.getSavePath() + File.separator + downloadInfo.getFileName());
328 | }
329 | } else {
330 | // 没有此任务
331 | }
332 | }
333 |
334 | /**
335 | * 获取下载任务的状态
336 | * @param url
337 | * @return
338 | */
339 | public DownState getTaskState(String url) {
340 | if (TextUtils.isEmpty(url)) {
341 | return DownState.DEFAULT;
342 | }
343 | if(downloadMap.containsKey(url)) {
344 | DownloadInfo downloadInfo = downloadMap.get(url);
345 | if (downloadInfo != null) {
346 | return downloadInfo.getState();
347 | }
348 | }
349 | return DownState.DEFAULT;
350 | }
351 |
352 |
353 | /**
354 | * 更新显示状态的TextView
355 | * @param url
356 | * @return
357 | */
358 | public void updateStatusTextView(String url, TextView textView) {
359 | if (TextUtils.isEmpty(url)) {
360 | return;
361 | }
362 | if(downloadMap.containsKey(url)) {
363 | DownloadInfo downloadInfo = downloadMap.get(url);
364 | if (downloadInfo != null) {
365 | downloadInfo.setStateTextView(textView);
366 | }
367 | }
368 | }
369 |
370 |
371 | }
--------------------------------------------------------------------------------
/download/src/main/java/com/lwc/download/DownloadProgressListener.java:
--------------------------------------------------------------------------------
1 | package com.lwc.download;
2 |
3 | import java.io.File;
4 |
5 | import io.reactivex.Observable;
6 | import io.reactivex.android.schedulers.AndroidSchedulers;
7 | import io.reactivex.disposables.Disposable;
8 | import io.reactivex.functions.Consumer;
9 |
10 | /**
11 | * @Package: com.lwc.download
12 | * @ClassName: DownloadProgressListener
13 | * @Description: 下载进度处理
14 | * @Author: liwuchen
15 | * @CreateDate: 2019/10/15
16 | */
17 | public class DownloadProgressListener {
18 |
19 | private DownloadInfo info;
20 | private DownloadListener listener;
21 |
22 | public DownloadProgressListener(DownloadInfo downloadInfo, DownloadListener listener) {
23 | this.info = downloadInfo;
24 | this.listener = listener;
25 | }
26 | /**
27 | * @param read 已下载长度
28 | * @param contentLength 总长度
29 | * @param done 是否下载完毕
30 | */
31 | public void progress(long read, long contentLength, final boolean done){
32 | //更新已下载的文件大小
33 | if (info.getContentLength() > contentLength) {
34 | read = read + (info.getContentLength() - contentLength);
35 | } else {
36 | info.setContentLength(contentLength);
37 | }
38 | info.setReadLength(read);
39 | //切换到主线程
40 | Disposable d = Observable.just(1)
41 | .observeOn(AndroidSchedulers.mainThread())
42 | .subscribe(new Consumer() {
43 | @Override
44 | public void accept(Integer integer) {
45 | if (done) {
46 | listener.onFinishDownload(info.getSavePath() + File.separator + info.getFileName(), info.getStateTextView());
47 | info.setState(DownState.FINISH);
48 | } else {
49 | if(info.getState() == DownState.DOWNLOADING) {
50 | if (info.getContentLength() > 0) {
51 | listener.onProgress(info.getReadLength(), info.getContentLength(), info.getStateTextView());
52 | } else {
53 | // 获取不到文件总大小(contentLength==0)
54 | listener.onProgress(info.getReadLength(), -1, info.getStateTextView());
55 | }
56 | }
57 | }
58 | }
59 | });
60 | }
61 |
62 | }
--------------------------------------------------------------------------------
/download/src/main/java/com/lwc/download/DownloadResponse.java:
--------------------------------------------------------------------------------
1 | package com.lwc.download;
2 |
3 | import java.io.IOException;
4 |
5 | import io.reactivex.annotations.NonNull;
6 | import io.reactivex.annotations.Nullable;
7 | import okhttp3.MediaType;
8 | import okhttp3.ResponseBody;
9 | import okio.Buffer;
10 | import okio.BufferedSource;
11 | import okio.ForwardingSource;
12 | import okio.Okio;
13 | import okio.Source;
14 |
15 | /**
16 | * @Package: com.lwc.download
17 | * @ClassName: DownloadResponse
18 | * @Description: 下载请求体,带进度
19 | * @Author: liwuchen
20 | * @CreateDate: 2019/10/12
21 | */
22 | public class DownloadResponse extends ResponseBody {
23 |
24 | private ResponseBody responseBody;
25 |
26 | private DownloadProgressListener listener;
27 |
28 | // BufferedSource 是okio库中的输入流,这里就当作inputStream来使用。
29 | private BufferedSource bufferedSource;
30 |
31 | public DownloadResponse(ResponseBody responseBody, DownloadProgressListener listener) {
32 | this.responseBody = responseBody;
33 | this.listener = listener;
34 | }
35 |
36 | @Nullable
37 | @Override
38 | public MediaType contentType() {
39 | return responseBody.contentType();
40 | }
41 |
42 | @Override
43 | public long contentLength() {
44 | return responseBody.contentLength();
45 | }
46 |
47 | @NonNull
48 | @Override
49 | public BufferedSource source() {
50 | if (bufferedSource == null) {
51 | bufferedSource = Okio.buffer(source(responseBody.source()));
52 | }
53 | return bufferedSource;
54 | }
55 |
56 | private Source source(Source source) {
57 | return new ForwardingSource(source) {
58 | long totalBytesRead = 0L;
59 |
60 | @Override
61 | public long read(@NonNull Buffer sink, long byteCount) throws IOException {
62 | final long bytesRead = super.read(sink, byteCount);
63 | // read() returns the number of bytes read, or -1 if this source is exhausted.
64 | totalBytesRead += bytesRead != -1 ? bytesRead : 0;
65 | if (null != listener) {
66 | listener.progress(totalBytesRead, responseBody.contentLength(), bytesRead == -1);
67 | }
68 | return bytesRead;
69 | }
70 | };
71 |
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/download/src/main/java/com/lwc/download/DownloadService.java:
--------------------------------------------------------------------------------
1 | package com.lwc.download;
2 |
3 | import io.reactivex.Observable;
4 | import okhttp3.ResponseBody;
5 | import retrofit2.http.GET;
6 | import retrofit2.http.Header;
7 | import retrofit2.http.Streaming;
8 | import retrofit2.http.Url;
9 |
10 | /**
11 | * @Package: com.lwc.download
12 | * @ClassName: DownloadService
13 | * @Description: 下载请求
14 | * @Author: liwuchen
15 | * @CreateDate: 2019/10/12
16 | */
17 | public interface DownloadService {
18 | /**
19 | * @param start 从某个字节开始下载数据
20 | */
21 | @Streaming
22 | @GET
23 | Observable rxDownload(@Header("RANGE") String start, @Url String url);
24 |
25 | @Streaming
26 | @GET
27 | Observable rxDownload(@Url String url);
28 |
29 | }
30 |
--------------------------------------------------------------------------------
/download/src/main/java/com/lwc/download/FileUtils.java:
--------------------------------------------------------------------------------
1 | package com.lwc.download;
2 |
3 | import android.os.Environment;
4 | import android.text.TextUtils;
5 |
6 | import java.io.BufferedOutputStream;
7 | import java.io.File;
8 | import java.io.FileFilter;
9 | import java.io.FileInputStream;
10 | import java.io.FileNotFoundException;
11 | import java.io.FileOutputStream;
12 | import java.io.IOException;
13 | import java.io.InputStream;
14 | import java.io.OutputStream;
15 |
16 |
17 | /**
18 | * @Package: com.lwc.download;
19 | * @ClassName: FileUtils
20 | * @Description: 文件操作工具
21 | * @Author: liwuchen
22 | * @CreateDate: 2019/10/12
23 | */
24 | public class FileUtils {
25 |
26 | private FileUtils() {
27 | throw new UnsupportedOperationException("u can't instantiate me...");
28 | }
29 |
30 | /**
31 | * 检查外部存储是否可用
32 | * @return
33 | */
34 | private static boolean checkExternalStorageState(){
35 | return Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED);
36 | }
37 |
38 | /**
39 | * 通过路径获取文件
40 | * @param filePath
41 | * @return
42 | */
43 | public static File getFileByPath(final String filePath) {
44 | return isSpace(filePath) ? null : new File(filePath);
45 | }
46 |
47 | /**
48 | * 文件是否存在
49 | * @param filePath 必须是完整路径
50 | * @return
51 | */
52 | public static boolean isFileExists(final String filePath) {
53 | return isFileExists(getFileByPath(filePath));
54 | }
55 |
56 | /**
57 | * 文件是否存在
58 | * @param file
59 | * @return
60 | */
61 | public static boolean isFileExists(final File file) {
62 | return file != null && file.exists();
63 | }
64 |
65 | /**
66 | * 是否是目录
67 | * @param dirPath
68 | * @return
69 | */
70 | public static boolean isDir(final String dirPath) {
71 | return isDir(getFileByPath(dirPath));
72 | }
73 |
74 | /**
75 | * 是否是目录
76 | * @param file
77 | * @return
78 | */
79 | public static boolean isDir(final File file) {
80 | return file != null && file.exists() && file.isDirectory();
81 | }
82 |
83 | /**
84 | * 是否是文件
85 | * @param filePath
86 | * @return
87 | */
88 | public static boolean isFile(final String filePath) {
89 | return isFile(getFileByPath(filePath));
90 | }
91 |
92 | /**
93 | * 是否是文件
94 | * @param file
95 | * @return
96 | */
97 | public static boolean isFile(final File file) {
98 | return file != null && file.exists() && file.isFile();
99 | }
100 |
101 | /**
102 | * 创建目录
103 | * @param dirPath
104 | * @return
105 | */
106 | public static boolean createDir(final String dirPath) {
107 | return createDir(getFileByPath(dirPath));
108 | }
109 |
110 | /**
111 | * 创建目录
112 | * @param file
113 | * @return
114 | */
115 | public static boolean createDir(final File file) {
116 | return file != null && (file.exists() ? file.isDirectory() : file.mkdirs());
117 | }
118 |
119 | /**
120 | * 创建文件
121 | * @param filePath
122 | * @return
123 | */
124 | public static boolean createFile(final String filePath) {
125 | return createFile(getFileByPath(filePath));
126 | }
127 |
128 | /**
129 | * 创建文件
130 | * @param file
131 | * @return
132 | */
133 | public static boolean createFile(final File file) {
134 | if (file == null) {
135 | return false;
136 | }
137 | if (file.exists()) {
138 | return file.isFile();
139 | }
140 | if (!createFile(file.getParentFile())) {
141 | return false;
142 | }
143 | try {
144 | return file.createNewFile();
145 | } catch (IOException e) {
146 | e.printStackTrace();
147 | return false;
148 | }
149 | }
150 |
151 | /**
152 | * 创建文件
153 | * @param filePath
154 | * @return
155 | */
156 | public static boolean createFileByDeleteOldFile(final String filePath) {
157 | return createFileByDeleteOldFile(getFileByPath(filePath));
158 | }
159 |
160 | /**
161 | * 创建文件
162 | * @param file
163 | * @return
164 | */
165 | public static boolean createFileByDeleteOldFile(final File file) {
166 | if (file == null) {
167 | return false;
168 | }
169 | if (file.exists() && !file.delete()) {
170 | return false;
171 | }
172 | if (!createDir(file.getParentFile())) {
173 | return false;
174 | }
175 | try {
176 | return file.createNewFile();
177 | } catch (IOException e) {
178 | e.printStackTrace();
179 | return false;
180 | }
181 | }
182 |
183 | /**
184 | * 拷贝目录
185 | * @param srcDirPath
186 | * @param destDirPath
187 | * @return
188 | */
189 | public static boolean copyDir(String srcDirPath, String destDirPath) {
190 | return copyDir(getFileByPath(srcDirPath), getFileByPath(destDirPath));
191 | }
192 |
193 | /**
194 | * 拷贝目录
195 | * @param srcDir
196 | * @param destDir
197 | * @return
198 | */
199 | public static boolean copyDir(File srcDir, File destDir) {
200 | return copyOrMoveDir(srcDir, destDir, false);
201 | }
202 |
203 | /**
204 | * 拷贝目录
205 | * @param srcDirPath
206 | * @param destDirPath
207 | * @param listener
208 | * @return
209 | */
210 | public static boolean copyDir(String srcDirPath, String destDirPath, OnReplaceListener listener) {
211 | return copyDir(getFileByPath(srcDirPath), getFileByPath(destDirPath), listener);
212 | }
213 |
214 | /**
215 | * 拷贝目录
216 | * @param srcDir
217 | * @param destDir
218 | * @param listener
219 | * @return
220 | */
221 | public static boolean copyDir(File srcDir, File destDir, OnReplaceListener listener) {
222 | return copyOrMoveDir(srcDir, destDir, listener, false);
223 | }
224 |
225 | /**
226 | * 拷贝文件
227 | * @param srcFilePath
228 | * @param destFilePath
229 | * @return
230 | */
231 | public static boolean copyFile(String srcFilePath, String destFilePath) {
232 | return copyFile(getFileByPath(srcFilePath), getFileByPath(destFilePath));
233 | }
234 |
235 | /**
236 | * 拷贝文件
237 | * @param srcFile
238 | * @param destFile
239 | * @return
240 | */
241 | public static boolean copyFile(File srcFile, File destFile) {
242 | return copyOrMoveFile(srcFile, destFile, false);
243 | }
244 |
245 | /**
246 | * 拷贝文件
247 | * @param srcFilePath
248 | * @param destFilePath
249 | * @param listener
250 | * @return
251 | */
252 | public static boolean copyFile(String srcFilePath, String destFilePath, OnReplaceListener listener) {
253 | return copyFile(getFileByPath(srcFilePath), getFileByPath(destFilePath), listener);
254 | }
255 |
256 | /**
257 | * 拷贝文件
258 | * @param srcFile
259 | * @param destFile
260 | * @param listener
261 | * @return
262 | */
263 | public static boolean copyFile(final File srcFile, File destFile, OnReplaceListener listener) {
264 | return copyOrMoveFile(srcFile, destFile, listener, false);
265 | }
266 |
267 | private static boolean copyOrMoveDir(File srcDir, File destDir, boolean isMove) {
268 | return copyOrMoveDir(srcDir, destDir, new OnReplaceListener() {
269 | @Override
270 | public boolean onReplace() {
271 | return true;
272 | }
273 | }, isMove);
274 | }
275 |
276 | private static boolean copyOrMoveDir(File srcDir, File destDir, OnReplaceListener listener, boolean isMove) {
277 | if (srcDir == null || destDir == null) {
278 | return false;
279 | }
280 | String srcPath = srcDir.getPath() + File.separator;
281 | String destPath = destDir.getPath() + File.separator;
282 | if (destPath.contains(srcPath)) {
283 | return false;
284 | }
285 | if (!srcDir.exists() || !srcDir.isDirectory()) {
286 | return false;
287 | }
288 | if (destDir.exists()) {
289 | if (listener == null || listener.onReplace()) {
290 | if (!deleteAllInDir(destDir)) {
291 | return false;
292 | }
293 | } else {
294 | return true;
295 | }
296 | }
297 | if (!createDir(destDir)) {
298 | return false;
299 | }
300 | File[] files = srcDir.listFiles();
301 | for (File file : files) {
302 | File oneDestFile = new File(destPath + file.getName());
303 | if (file.isFile()) {
304 | if (!copyOrMoveFile(file, oneDestFile, listener, isMove)) return false;
305 | } else if (file.isDirectory()) {
306 | if (!copyOrMoveDir(file, oneDestFile, listener, isMove)) return false;
307 | }
308 | }
309 | return !isMove || deleteDir(srcDir);
310 | }
311 |
312 | private static boolean copyOrMoveFile(File srcFile, File destFile, boolean isMove) {
313 | return copyOrMoveFile(srcFile, destFile, new OnReplaceListener() {
314 | @Override
315 | public boolean onReplace() {
316 | return true;
317 | }
318 | }, isMove);
319 | }
320 |
321 | private static boolean copyOrMoveFile(File srcFile, File destFile, OnReplaceListener listener, boolean isMove) {
322 | if (srcFile == null || destFile == null) {
323 | return false;
324 | }
325 | if (srcFile.equals(destFile)) {
326 | return false;
327 | }
328 | if (!srcFile.exists() || !srcFile.isFile()) {
329 | return false;
330 | }
331 | if (destFile.exists()) {
332 | if (listener == null || listener.onReplace()) {
333 | if (!destFile.delete()) {
334 | return false;
335 | }
336 | } else {
337 | return true;
338 | }
339 | }
340 | if (!createDir(destFile.getParentFile())) {
341 | return false;
342 | }
343 | try {
344 | return writeFileFromIS(destFile, new FileInputStream(srcFile))
345 | && !(isMove && !deleteFile(srcFile));
346 | } catch (FileNotFoundException e) {
347 | e.printStackTrace();
348 | return false;
349 | }
350 | }
351 |
352 | /**
353 | *
354 | * @param dirPath
355 | * @return
356 | */
357 | public static boolean deleteAllInDir(final String dirPath) {
358 | return deleteAllInDir(getFileByPath(dirPath));
359 | }
360 |
361 | /**
362 | * 删除目录(含过滤器)
363 | * @param dir
364 | * @return
365 | */
366 | public static boolean deleteAllInDir(File dir) {
367 | return deleteFilesInDirWithFilter(dir, new FileFilter() {
368 | @Override
369 | public boolean accept(File pathname) {
370 | return true;
371 | }
372 | });
373 | }
374 |
375 | /**
376 | * 删除目录(过滤器之内的文件)
377 | * @param dirPath
378 | * @param filter
379 | * @return
380 | */
381 | public static boolean deleteFilesInDirWithFilter(String dirPath, FileFilter filter) {
382 | return deleteFilesInDirWithFilter(getFileByPath(dirPath), filter);
383 | }
384 |
385 | /**
386 | * 删除目录(过滤器之内的文件)
387 | * @param dir
388 | * @param filter
389 | * @return
390 | */
391 | public static boolean deleteFilesInDirWithFilter(File dir, FileFilter filter) {
392 | if (dir == null) {
393 | return false;
394 | }
395 | if (!dir.exists()) {
396 | return true;
397 | }
398 | if (!dir.isDirectory()) {
399 | return false;
400 | }
401 | File[] files = dir.listFiles();
402 | if (files != null && files.length != 0) {
403 | for (File file : files) {
404 | if (filter.accept(file)) {
405 | if (file.isFile()) {
406 | if (!file.delete()) return false;
407 | } else if (file.isDirectory()) {
408 | if (!deleteDir(file)) return false;
409 | }
410 | }
411 | }
412 | }
413 | return true;
414 | }
415 |
416 | /**
417 | * 删除目录
418 | * @param dir
419 | * @return
420 | */
421 | public static boolean deleteDir(File dir) {
422 | if (dir == null) {
423 | return false;
424 | }
425 | if (!dir.exists()) {
426 | return true;
427 | }
428 | if (!dir.isDirectory()) {
429 | return false;
430 | }
431 | File[] files = dir.listFiles();
432 | if (files != null && files.length != 0) {
433 | for (File file : files) {
434 | if (file.isFile()) {
435 | if (!file.delete()) {
436 | return false;
437 | }
438 | } else if (file.isDirectory()) {
439 | if (!deleteDir(file)) {
440 | return false;
441 | }
442 | }
443 | }
444 | }
445 | return dir.delete();
446 | }
447 |
448 | /**
449 | * 写文件(把流写进文件)
450 | * @param file
451 | * @param is
452 | * @return
453 | */
454 | public static boolean writeFileFromIS(File file, InputStream is) {
455 | OutputStream os = null;
456 | try {
457 | FileOutputStream fos = new FileOutputStream(file);
458 | os = new BufferedOutputStream(fos);
459 | byte data[] = new byte[8192];
460 | int len;
461 | while ((len = is.read(data, 0, 8192)) != -1) {
462 | os.write(data, 0, len);
463 | }
464 | return true;
465 | } catch (IOException e) {
466 | e.printStackTrace();
467 | return false;
468 | } finally {
469 | try {
470 | is.close();
471 | } catch (IOException e) {
472 | e.printStackTrace();
473 | }
474 | try {
475 | if (os != null) {
476 | os.close();
477 | }
478 | } catch (IOException e) {
479 | e.printStackTrace();
480 | }
481 | }
482 | }
483 |
484 | /**
485 | * 写文件(把流写进文件)
486 | * @param filePath
487 | * @param fileName
488 | * @param is
489 | * @return
490 | */
491 | public static boolean writeFileFromIS(String filePath, String fileName, InputStream is, boolean newFile) {
492 | File dirFolder = new File(filePath);
493 | if (!dirFolder.exists()) {
494 | dirFolder.mkdirs();
495 | }
496 | File file = new File(filePath + File.separator + fileName);
497 | OutputStream os = null;
498 | try {
499 | if (newFile) {
500 | file.createNewFile();
501 | os = new BufferedOutputStream(new FileOutputStream(file));
502 | } else {
503 | if (!file.exists()) {
504 | file.createNewFile();
505 | }
506 | os = new BufferedOutputStream(new FileOutputStream(file, true));
507 | }
508 | byte data[] = new byte[8192];
509 | int len;
510 | while ((len = is.read(data, 0, 8192)) != -1) {
511 | os.write(data, 0, len);
512 | }
513 | return true;
514 | } catch (IOException e) {
515 | e.printStackTrace();
516 | return false;
517 | } finally {
518 | try {
519 | is.close();
520 | } catch (IOException e) {
521 | e.printStackTrace();
522 | }
523 | try {
524 | if (os != null) {
525 | os.close();
526 | }
527 | } catch (IOException e) {
528 | e.printStackTrace();
529 | }
530 | }
531 | }
532 |
533 | /**
534 | * 删除文件
535 | * @param srcFilePath
536 | * @param fileName
537 | * @return
538 | */
539 | public static boolean deleteFile(String srcFilePath, String fileName) {
540 | if (TextUtils.isEmpty(srcFilePath) || TextUtils.isEmpty(fileName)) {
541 | return false;
542 | }
543 | File file = new File(srcFilePath + File.separator + fileName);
544 | return deleteFile(file.getPath());
545 | }
546 |
547 | /**
548 | * 删除文件
549 | * @param srcFilePath
550 | * @return
551 | */
552 | public static boolean deleteFile(String srcFilePath) {
553 | return deleteFile(getFileByPath(srcFilePath));
554 | }
555 |
556 | /**
557 | * 删除文件
558 | * @param file
559 | * @return
560 | */
561 | public static boolean deleteFile(File file) {
562 | return file != null && (!file.exists() || file.isFile() && file.delete());
563 | }
564 |
565 | private static boolean isSpace(final String s) {
566 | if (s == null) return true;
567 | for (int i = 0, len = s.length(); i < len; ++i) {
568 | if (!Character.isWhitespace(s.charAt(i))) {
569 | return false;
570 | }
571 | }
572 | return true;
573 | }
574 |
575 |
576 | public interface OnReplaceListener {
577 | boolean onReplace();
578 | }
579 |
580 |
581 |
582 | }
583 |
--------------------------------------------------------------------------------
/download/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | download
3 |
4 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | org.gradle.jvmargs=-Xmx1536m
10 | # When configured, Gradle will run in incubating parallel mode.
11 | # This option should only be used with decoupled projects. More details, visit
12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
13 | # org.gradle.parallel=true
14 | # AndroidX package structure to make it clearer which packages are bundled with the
15 | # Android operating system, and which are packaged with your app's APK
16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn
17 | android.useAndroidX=true
18 | # Automatically convert third-party libraries to use AndroidX
19 | android.enableJetifier=true
20 |
21 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/liwuchen/SimpleDownload/242178b6048ca66f4fe000da78239e41fc7a6730/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Sat Dec 14 17:59:43 CST 2019
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-5.1.1-all.zip
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Attempt to set APP_HOME
10 | # Resolve links: $0 may be a link
11 | PRG="$0"
12 | # Need this for relative symlinks.
13 | while [ -h "$PRG" ] ; do
14 | ls=`ls -ld "$PRG"`
15 | link=`expr "$ls" : '.*-> \(.*\)$'`
16 | if expr "$link" : '/.*' > /dev/null; then
17 | PRG="$link"
18 | else
19 | PRG=`dirname "$PRG"`"/$link"
20 | fi
21 | done
22 | SAVED="`pwd`"
23 | cd "`dirname \"$PRG\"`/" >/dev/null
24 | APP_HOME="`pwd -P`"
25 | cd "$SAVED" >/dev/null
26 |
27 | APP_NAME="Gradle"
28 | APP_BASE_NAME=`basename "$0"`
29 |
30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
31 | DEFAULT_JVM_OPTS=""
32 |
33 | # Use the maximum available, or set MAX_FD != -1 to use that value.
34 | MAX_FD="maximum"
35 |
36 | warn () {
37 | echo "$*"
38 | }
39 |
40 | die () {
41 | echo
42 | echo "$*"
43 | echo
44 | exit 1
45 | }
46 |
47 | # OS specific support (must be 'true' or 'false').
48 | cygwin=false
49 | msys=false
50 | darwin=false
51 | nonstop=false
52 | case "`uname`" in
53 | CYGWIN* )
54 | cygwin=true
55 | ;;
56 | Darwin* )
57 | darwin=true
58 | ;;
59 | MINGW* )
60 | msys=true
61 | ;;
62 | NONSTOP* )
63 | nonstop=true
64 | ;;
65 | esac
66 |
67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
68 |
69 | # Determine the Java command to use to start the JVM.
70 | if [ -n "$JAVA_HOME" ] ; then
71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
72 | # IBM's JDK on AIX uses strange locations for the executables
73 | JAVACMD="$JAVA_HOME/jre/sh/java"
74 | else
75 | JAVACMD="$JAVA_HOME/bin/java"
76 | fi
77 | if [ ! -x "$JAVACMD" ] ; then
78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
79 |
80 | Please set the JAVA_HOME variable in your environment to match the
81 | location of your Java installation."
82 | fi
83 | else
84 | JAVACMD="java"
85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
86 |
87 | Please set the JAVA_HOME variable in your environment to match the
88 | location of your Java installation."
89 | fi
90 |
91 | # Increase the maximum file descriptors if we can.
92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
93 | MAX_FD_LIMIT=`ulimit -H -n`
94 | if [ $? -eq 0 ] ; then
95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
96 | MAX_FD="$MAX_FD_LIMIT"
97 | fi
98 | ulimit -n $MAX_FD
99 | if [ $? -ne 0 ] ; then
100 | warn "Could not set maximum file descriptor limit: $MAX_FD"
101 | fi
102 | else
103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
104 | fi
105 | fi
106 |
107 | # For Darwin, add options to specify how the application appears in the dock
108 | if $darwin; then
109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
110 | fi
111 |
112 | # For Cygwin, switch paths to Windows format before running java
113 | if $cygwin ; then
114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
116 | JAVACMD=`cygpath --unix "$JAVACMD"`
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 | # Escape application args
158 | save () {
159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
160 | echo " "
161 | }
162 | APP_ARGS=$(save "$@")
163 |
164 | # Collect all arguments for the java command, following the shell quoting and substitution rules
165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
166 |
167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
169 | cd "$(dirname "$0")"
170 | fi
171 |
172 | exec "$JAVACMD" "$@"
173 |
--------------------------------------------------------------------------------
/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 | set DIRNAME=%~dp0
12 | if "%DIRNAME%" == "" set DIRNAME=.
13 | set APP_BASE_NAME=%~n0
14 | set APP_HOME=%DIRNAME%
15 |
16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
17 | set DEFAULT_JVM_OPTS=
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 Windows variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 |
53 | :win9xME_args
54 | @rem Slurp the command line arguments.
55 | set CMD_LINE_ARGS=
56 | set _SKIP=2
57 |
58 | :win9xME_args_slurp
59 | if "x%~1" == "x" goto execute
60 |
61 | set CMD_LINE_ARGS=%*
62 |
63 | :execute
64 | @rem Setup the command line
65 |
66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
67 |
68 | @rem Execute Gradle
69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
70 |
71 | :end
72 | @rem End local scope for the variables with windows NT shell
73 | if "%ERRORLEVEL%"=="0" goto mainEnd
74 |
75 | :fail
76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
77 | rem the _cmd.exe /c_ return code!
78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
79 | exit /b 1
80 |
81 | :mainEnd
82 | if "%OS%"=="Windows_NT" endlocal
83 |
84 | :omega
85 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':demo', ':download'
2 |
--------------------------------------------------------------------------------