├── .gitignore
├── .idea
├── caches
│ └── build_file_checksums.ser
├── encodings.xml
├── gradle.xml
├── misc.xml
├── runConfigurations.xml
└── vcs.xml
├── README.md
├── apk
└── DownloadUpdate.apk
├── app
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── com
│ │ └── winfo
│ │ └── update
│ │ └── ExampleInstrumentedTest.java
│ ├── main
│ ├── AndroidManifest.xml
│ ├── java
│ │ └── com
│ │ │ └── winfo
│ │ │ └── update
│ │ │ ├── MainActivity.java
│ │ │ ├── download
│ │ │ ├── exception
│ │ │ │ ├── HttpTimeException.java
│ │ │ │ └── RetryWhenNetworkException.java
│ │ │ ├── http
│ │ │ │ └── HttpService.java
│ │ │ ├── httpdownload
│ │ │ │ ├── DownInfo.java
│ │ │ │ ├── DownState.java
│ │ │ │ ├── HttpDownManager.java
│ │ │ │ ├── ProgressDownSubscriber.java
│ │ │ │ └── downloadlistener
│ │ │ │ │ ├── DownloadInterceptor.java
│ │ │ │ │ ├── DownloadProgressListener.java
│ │ │ │ │ └── DownloadResponseBody.java
│ │ │ └── listener
│ │ │ │ └── HttpProgressOnNextListener.java
│ │ │ └── version_update
│ │ │ ├── entity
│ │ │ └── VersionInfo.java
│ │ │ ├── request
│ │ │ ├── ApiService.java
│ │ │ ├── OkHttpUtils.java
│ │ │ └── RequestSubscriber.java
│ │ │ ├── service
│ │ │ └── DownloadService.java
│ │ │ └── utils
│ │ │ ├── ApkUtils.java
│ │ │ ├── AppUtils.java
│ │ │ ├── Constants.java
│ │ │ ├── NotificationBarUtil.java
│ │ │ ├── NotificationHelper.java
│ │ │ ├── StorageUtils.java
│ │ │ ├── UpdateChecker.java
│ │ │ └── UpdateDialog.java
│ └── res
│ │ ├── drawable-v24
│ │ └── ic_launcher_foreground.xml
│ │ ├── drawable
│ │ └── ic_launcher_background.xml
│ │ ├── layout
│ │ └── 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
│ │ └── xml
│ │ └── update_apk_paths.xml
│ └── test
│ └── java
│ └── com
│ └── winfo
│ └── update
│ └── ExampleUnitTest.java
├── build.gradle
├── gif
├── abc.gif
└── def.gif
├── 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/libraries
5 | /.idea/modules.xml
6 | /.idea/workspace.xml
7 | .DS_Store
8 | /build
9 | /captures
10 | .externalNativeBuild
11 |
--------------------------------------------------------------------------------
/.idea/caches/build_file_checksums.ser:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xuezhihuixzh/Downloadupdate/2ef3434d242e8e6c7b3ea781e242e6db9f0b0833/.idea/caches/build_file_checksums.ser
--------------------------------------------------------------------------------
/.idea/encodings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/.idea/gradle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
17 |
18 |
--------------------------------------------------------------------------------
/.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 | # Downloadupdate
2 | android版本更新功能。使用retrfit2 rxjava2 okhttp3实现多文件多线程下载(支持断点下载), android版本更新:通知栏更新,对话框更新 兼容8.0
3 |
--------------------------------------------------------------------------------
/apk/DownloadUpdate.apk:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xuezhihuixzh/Downloadupdate/2ef3434d242e8e6c7b3ea781e242e6db9f0b0833/apk/DownloadUpdate.apk
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion 27
5 | defaultConfig {
6 | applicationId "com.winfo.update"
7 | minSdkVersion 15
8 | targetSdkVersion 27
9 | versionCode 1
10 | versionName "1.0"
11 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
12 | }
13 | buildTypes {
14 | release {
15 | minifyEnabled false
16 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
17 | }
18 | }
19 | }
20 |
21 | dependencies {
22 | implementation fileTree(dir: 'libs', include: ['*.jar'])
23 | implementation 'com.android.support:appcompat-v7:27.1.1'
24 | implementation 'com.android.support.constraint:constraint-layout:1.1.2'
25 | testImplementation 'junit:junit:4.12'
26 | androidTestImplementation 'com.android.support.test:runner:1.0.2'
27 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
28 |
29 | //retrofit2 + rxjava2 + okhttp3
30 | implementation 'com.squareup.retrofit2:retrofit:2.4.0'
31 | implementation 'com.squareup.okhttp3:logging-interceptor:3.9.0'
32 | implementation 'io.reactivex.rxjava2:rxjava:2.1.12'
33 | implementation 'io.reactivex.rxjava2:rxandroid:2.0.2'
34 | implementation 'com.squareup.retrofit2:converter-gson:2.4.0'
35 | implementation 'com.squareup.retrofit2:adapter-rxjava2:2.4.0'
36 | implementation 'com.squareup.retrofit2:converter-scalars:2.4.0'
37 | }
38 |
--------------------------------------------------------------------------------
/app/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 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/winfo/update/ExampleInstrumentedTest.java:
--------------------------------------------------------------------------------
1 | package com.winfo.update;
2 |
3 | import android.content.Context;
4 | import android.support.test.InstrumentationRegistry;
5 | import android.support.test.runner.AndroidJUnit4;
6 |
7 | import org.junit.Test;
8 | import org.junit.runner.RunWith;
9 |
10 | import static org.junit.Assert.*;
11 |
12 | /**
13 | * Instrumented test, which will execute on an Android device.
14 | *
15 | * @see Testing documentation
16 | */
17 | @RunWith(AndroidJUnit4.class)
18 | public class ExampleInstrumentedTest {
19 | @Test
20 | public void useAppContext() {
21 | // Context of the app under test.
22 | Context appContext = InstrumentationRegistry.getTargetContext();
23 |
24 | assertEquals("com.winfo.update", appContext.getPackageName());
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
29 |
30 |
35 |
36 |
39 |
40 |
41 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/app/src/main/java/com/winfo/update/MainActivity.java:
--------------------------------------------------------------------------------
1 | package com.winfo.update;
2 |
3 | import android.annotation.SuppressLint;
4 | import android.app.ProgressDialog;
5 | import android.support.v7.app.AppCompatActivity;
6 | import android.os.Bundle;
7 | import android.view.View;
8 | import android.widget.Button;
9 | import android.widget.ProgressBar;
10 | import android.widget.TextView;
11 |
12 | import com.winfo.update.download.httpdownload.DownInfo;
13 | import com.winfo.update.download.httpdownload.DownState;
14 | import com.winfo.update.download.httpdownload.HttpDownManager;
15 | import com.winfo.update.download.listener.HttpProgressOnNextListener;
16 | import com.winfo.update.version_update.utils.UpdateChecker;
17 |
18 | import java.io.File;
19 |
20 | public class MainActivity extends AppCompatActivity implements View.OnClickListener {
21 |
22 | private ProgressBar qqProgressBar, alipayProgressBar, weixinProgressbar;
23 | private TextView tvQQMsg, tvAlipayMsg, tvWeixinMsg;
24 | private TextView tvQQProgress, tvAlipayProgress, tvWeixinProgress;
25 | private Button btnQQStart, btnQQPause, btnAlipayStart, btnAlipayPause, btnWeixinStart, btnWeixinPause;
26 |
27 | private DownInfo qqDownInfo, alipayDownInfo, weixinDownInfo;
28 |
29 | private ProgressDialog dialog;
30 |
31 |
32 | @Override
33 | protected void onCreate(Bundle savedInstanceState) {
34 | super.onCreate(savedInstanceState);
35 | setContentView(R.layout.activity_main);
36 |
37 | dialog = new ProgressDialog(this);
38 | dialog.setMessage(getString(R.string.android_auto_update_dialog_checking));
39 |
40 | qqProgressBar = findViewById(R.id.progressBar_qq);
41 | alipayProgressBar = findViewById(R.id.progressBar_alipay);
42 | weixinProgressbar = findViewById(R.id.progressBar_weixin);
43 |
44 | tvQQProgress = findViewById(R.id.tv_text_qq);
45 | tvAlipayProgress = findViewById(R.id.tv_text_alipay);
46 | tvWeixinProgress = findViewById(R.id.tv_text_weixin);
47 |
48 | tvQQMsg = findViewById(R.id.tv_msg_qq);
49 | tvAlipayMsg = findViewById(R.id.tv_msg_alipay);
50 | tvWeixinMsg = findViewById(R.id.tv_msg_weixin);
51 |
52 | btnQQStart = findViewById(R.id.btn_startDown_qq);
53 | btnQQPause = findViewById(R.id.btn_pauseDown_qq);
54 |
55 | btnAlipayStart = findViewById(R.id.btn_startDown_alipay);
56 | btnAlipayPause = findViewById(R.id.btn_pauseDown_alipay);
57 |
58 | btnWeixinStart = findViewById(R.id.btn_startDown_weixin);
59 | btnWeixinPause = findViewById(R.id.btn_pauseDown_weixin);
60 |
61 | btnQQStart.setOnClickListener(this);
62 | btnQQPause.setOnClickListener(this);
63 | btnAlipayStart.setOnClickListener(this);
64 | btnAlipayPause.setOnClickListener(this);
65 | btnWeixinStart.setOnClickListener(this);
66 | btnWeixinPause.setOnClickListener(this);
67 |
68 | String weixinDwonloadUrl = "http://dldir1.qq.com/weixin/android/weixin667android1320.apk";
69 | weixinDownInfo = new DownInfo(weixinDwonloadUrl);
70 | String weixinApkName = weixinDwonloadUrl.substring(weixinDwonloadUrl.lastIndexOf("/") + 1, weixinDwonloadUrl.length());
71 | File weixinApkFile = new File(getExternalCacheDir(), weixinApkName);
72 | weixinDownInfo.setSavePath(weixinApkFile.getAbsolutePath());
73 | weixinDownInfo.setState(DownState.START);
74 |
75 |
76 | String qqDwonloadUrl = "https://qd.myapp.com/myapp/qqteam/AndroidQQ/mobileqq_android.apk";
77 | qqDownInfo = new DownInfo(qqDwonloadUrl);
78 | String qqApkName = qqDwonloadUrl.substring(qqDwonloadUrl.lastIndexOf("/") + 1, qqDwonloadUrl.length());
79 | File qqApkFile = new File(getExternalCacheDir(), qqApkName);
80 | qqDownInfo.setSavePath(qqApkFile.getAbsolutePath());
81 | qqDownInfo.setState(DownState.START);
82 |
83 | String alipayDwonloadUrl = "http://gdown.baidu.com/data/wisegame/87b1af6e50012cb5/zhifubao_128.apk";
84 | alipayDownInfo = new DownInfo(alipayDwonloadUrl);
85 | String alipayApkName = alipayDwonloadUrl.substring(alipayDwonloadUrl.lastIndexOf("/") + 1, alipayDwonloadUrl.length());
86 | File alipayApkFile = new File(getExternalCacheDir(), alipayApkName);
87 | alipayDownInfo.setSavePath(alipayApkFile.getAbsolutePath());
88 | alipayDownInfo.setState(DownState.START);
89 |
90 | }
91 |
92 | @SuppressLint("SetTextI18n")
93 | @Override
94 | public void onClick(View v) {
95 | switch (v.getId()) {
96 | case R.id.btn_startDown_qq:
97 | HttpDownManager.getInstance().startDown(qqDownInfo, new HttpProgressOnNextListener() {
98 |
99 | @Override
100 | public void onNext(DownInfo downInfo) {
101 | tvQQMsg.setText("QQ下载完成" + downInfo.getSavePath());
102 | }
103 |
104 | @Override
105 | public void updateProgress(long readLength, long countLength) {
106 | qqProgressBar.setMax((int) countLength);
107 | qqProgressBar.setProgress((int) readLength);
108 | tvQQProgress.setText(readLength * 100 / countLength + "%");
109 | }
110 |
111 | @Override
112 | public void onError(Throwable e) {
113 | tvQQMsg.setText(e.getMessage());
114 | }
115 | });
116 | break;
117 | case R.id.btn_pauseDown_qq:
118 | String tag = (String) btnQQPause.getTag();
119 | if (tag.equals("true")) {
120 | btnQQPause.setText("继续下载");
121 | btnQQPause.setTag("false");
122 | HttpDownManager.getInstance().pause(qqDownInfo);
123 | } else {
124 | btnQQPause.setText("暂停下载");
125 | btnQQPause.setTag("true");
126 | HttpDownManager.getInstance().continueDownload(qqDownInfo);
127 | }
128 |
129 | break;
130 | case R.id.btn_startDown_alipay:
131 | HttpDownManager.getInstance().startDown(alipayDownInfo, new HttpProgressOnNextListener() {
132 |
133 | @Override
134 | public void onNext(DownInfo downInfo) {
135 | tvAlipayMsg.setText("下载完成" + downInfo.getSavePath());
136 | }
137 |
138 | @Override
139 | public void updateProgress(long readLength, long countLength) {
140 | alipayProgressBar.setMax((int) countLength);
141 | alipayProgressBar.setProgress((int) readLength);
142 | tvAlipayProgress.setText(readLength * 100 / countLength + "%");
143 | }
144 |
145 | @Override
146 | public void onError(Throwable e) {
147 | tvAlipayMsg.setText(e.getMessage());
148 | }
149 | });
150 | break;
151 | case R.id.btn_pauseDown_alipay:
152 | String tag1 = (String) btnAlipayPause.getTag();
153 | if (tag1.equals("true")) {
154 | btnAlipayPause.setText("继续下载");
155 | btnAlipayPause.setTag("false");
156 | HttpDownManager.getInstance().pause(alipayDownInfo);
157 | } else {
158 | btnAlipayPause.setText("暂停下载");
159 | btnAlipayPause.setTag("true");
160 | HttpDownManager.getInstance().continueDownload(alipayDownInfo);
161 | }
162 | break;
163 | case R.id.btn_startDown_weixin:
164 | HttpDownManager.getInstance().startDown(weixinDownInfo, new HttpProgressOnNextListener() {
165 |
166 | @Override
167 | public void onNext(DownInfo downInfo) {
168 | tvWeixinMsg.setText("下载完成" + downInfo.getSavePath());
169 | }
170 |
171 | @Override
172 | public void updateProgress(long readLength, long countLength) {
173 | weixinProgressbar.setMax((int) countLength);
174 | weixinProgressbar.setProgress((int) readLength);
175 | tvWeixinProgress.setText(readLength * 100 / countLength + "%");
176 | }
177 |
178 | @Override
179 | public void onError(Throwable e) {
180 | tvWeixinMsg.setText(e.getMessage());
181 | }
182 | });
183 | break;
184 | case R.id.btn_pauseDown_weixin:
185 | String tag2 = (String) btnWeixinPause.getTag();
186 | if (tag2.equals("true")) {
187 | btnWeixinPause.setText("继续下载");
188 | btnWeixinPause.setTag("false");
189 | HttpDownManager.getInstance().pause(weixinDownInfo);
190 | } else {
191 | btnWeixinPause.setText("暂停下载");
192 | btnWeixinPause.setTag("true");
193 | HttpDownManager.getInstance().continueDownload(weixinDownInfo);
194 | }
195 | break;
196 | }
197 | }
198 |
199 |
200 | public void updateDialog(View view) {
201 | UpdateChecker.checkForDialog(this, dialog);
202 | }
203 |
204 | public void updateNotification(View view) {
205 | UpdateChecker.checkForNotification(this, dialog);
206 | }
207 | }
208 |
--------------------------------------------------------------------------------
/app/src/main/java/com/winfo/update/download/exception/HttpTimeException.java:
--------------------------------------------------------------------------------
1 | package com.winfo.update.download.exception;
2 |
3 | /**
4 | * 自定义错误信息,统一处理返回处理
5 | */
6 | public class HttpTimeException extends RuntimeException {
7 |
8 | public static final int NO_DATA = 0x2;
9 |
10 | public HttpTimeException(int resultCode) {
11 | this(getApiExceptionMessage(resultCode));
12 | }
13 |
14 | public HttpTimeException(String detailMessage) {
15 | super(detailMessage);
16 | }
17 |
18 | /**
19 | * 转换错误数据
20 | *
21 | * @param code 错误吗
22 | * @return 错误信息
23 | */
24 | private static String getApiExceptionMessage(int code) {
25 | String message;
26 | switch (code) {
27 | case NO_DATA:
28 | message = "无数据";
29 | break;
30 | default:
31 | message = "error";
32 | break;
33 | }
34 | return message;
35 | }
36 | }
37 |
38 |
--------------------------------------------------------------------------------
/app/src/main/java/com/winfo/update/download/exception/RetryWhenNetworkException.java:
--------------------------------------------------------------------------------
1 | package com.winfo.update.download.exception;
2 |
3 | import android.util.Log;
4 |
5 | import java.net.ConnectException;
6 | import java.net.SocketTimeoutException;
7 | import java.util.concurrent.TimeUnit;
8 | import java.util.concurrent.TimeoutException;
9 |
10 | import io.reactivex.Observable;
11 | import io.reactivex.ObservableSource;
12 | import io.reactivex.functions.BiFunction;
13 | import io.reactivex.functions.Function;
14 |
15 |
16 | /**
17 | * retry条件
18 | */
19 | public class RetryWhenNetworkException implements Function, ObservableSource>> {
20 |
21 | // retry次数
22 | private int count = 3;
23 | // 延迟
24 | private long delay = 3000;
25 | // 叠加延迟
26 | private long increaseDelay = 3000;
27 |
28 | public RetryWhenNetworkException() {
29 | }
30 |
31 | public RetryWhenNetworkException(int count, long delay) {
32 | this.count = count;
33 | this.delay = delay;
34 | }
35 |
36 | public RetryWhenNetworkException(int count, long delay, long increaseDelay) {
37 | this.count = count;
38 | this.delay = delay;
39 | this.increaseDelay = increaseDelay;
40 | }
41 |
42 | @Override
43 | public Observable> apply(Observable extends Throwable> input) {
44 | return input.zipWith(Observable.range(1, count + 1), new BiFunction() {
45 | @Override
46 | public Wrapper apply(Throwable throwable, Integer integer) {
47 | return new Wrapper(throwable, integer);
48 | }
49 | }).flatMap(new io.reactivex.functions.Function>() {
50 | @Override
51 | public ObservableSource> apply(Wrapper wrapper) {
52 | if ((wrapper.throwable instanceof ConnectException
53 | || wrapper.throwable instanceof SocketTimeoutException
54 | || wrapper.throwable instanceof TimeoutException)
55 | && wrapper.index < count + 1) { //如果超出重试次数也抛出错误,否则默认是会进入onCompleted
56 | Log.e("tag", "retry---->" + wrapper.index);
57 | return Observable.timer(delay + (wrapper.index - 1) * increaseDelay, TimeUnit.MILLISECONDS);
58 | }
59 | return Observable.error(wrapper.throwable);
60 | }
61 | });
62 | }
63 |
64 | private class Wrapper {
65 | private int index;
66 | private Throwable throwable;
67 |
68 | Wrapper(Throwable throwable, int index) {
69 | this.index = index;
70 | this.throwable = throwable;
71 | }
72 | }
73 |
74 | }
75 |
--------------------------------------------------------------------------------
/app/src/main/java/com/winfo/update/download/http/HttpService.java:
--------------------------------------------------------------------------------
1 | package com.winfo.update.download.http;
2 |
3 |
4 | import io.reactivex.Observable;
5 | import okhttp3.ResponseBody;
6 | import retrofit2.http.GET;
7 | import retrofit2.http.Header;
8 | import retrofit2.http.Headers;
9 | import retrofit2.http.Streaming;
10 | import retrofit2.http.Url;
11 |
12 | /**
13 | * service统一接口数据
14 | */
15 | public interface HttpService {
16 |
17 | //断点续传下载接口
18 | @Streaming//大文件需要加入这个判断,防止下载过程中写入到内存中
19 | @Headers("Content-type:application/octet-stream")
20 | @GET
21 | Observable download(@Header("RANGE") String start, @Url String url);
22 |
23 | }
24 |
--------------------------------------------------------------------------------
/app/src/main/java/com/winfo/update/download/httpdownload/DownInfo.java:
--------------------------------------------------------------------------------
1 | package com.winfo.update.download.httpdownload;
2 |
3 | import com.winfo.update.download.http.HttpService;
4 |
5 | /**
6 | * Created by pc12 on 2017/2/5.
7 | */
8 |
9 | public class DownInfo {
10 | //存储位置
11 | private String savePath;
12 | //下载url
13 | private String url;
14 | //基础url
15 | private String baseUrl;
16 | //文件总长度
17 | private long countLength;
18 | //下载长度
19 | private long readLength;
20 | //下载唯一的HttpService
21 | private HttpService service;
22 | //回调监听
23 | // private HttpProgressOnNextListener listener;
24 | //超时设置
25 | private int DEFAULT_TIMEOUT = 6;
26 | //下载状态
27 | private DownState state;
28 |
29 |
30 | // public DownInfo(String url,HttpProgressOnNextListener listener) {
31 | // setUrl(url);
32 | // setBaseUrl(getBasUrl(url));
33 | // setListener(listener);
34 | // }
35 |
36 | public DownState getState() {
37 | return state;
38 | }
39 |
40 | public void setState(DownState state) {
41 | this.state = state;
42 | }
43 |
44 | public DownInfo(String url) {
45 | setUrl(url);
46 | setBaseUrl(getBasUrl(url));
47 | }
48 |
49 | public DownInfo() {
50 |
51 | }
52 |
53 | public int getConnectionTime() {
54 | return DEFAULT_TIMEOUT;
55 | }
56 |
57 | public void setConnectionTime(int DEFAULT_TIMEOUT) {
58 | this.DEFAULT_TIMEOUT = DEFAULT_TIMEOUT;
59 | }
60 |
61 | // public HttpProgressOnNextListener getListener() {
62 | // return listener;
63 | // }
64 | //
65 | // public void setListener(HttpProgressOnNextListener listener) {
66 | // this.listener = listener;
67 | // }
68 |
69 | public HttpService getService() {
70 | return service;
71 | }
72 |
73 | public void setService(HttpService service) {
74 | this.service = service;
75 | }
76 |
77 | public String getUrl() {
78 | return url;
79 | }
80 |
81 | public void setUrl(String url) {
82 | this.url = url;
83 | setBaseUrl(getBasUrl(url));
84 | }
85 |
86 | public String getSavePath() {
87 | return savePath;
88 | }
89 |
90 | public void setSavePath(String savePath) {
91 | this.savePath = savePath;
92 | }
93 |
94 | public String getBaseUrl() {
95 | return baseUrl;
96 | }
97 |
98 | public void setBaseUrl(String baseUrl) {
99 | this.baseUrl = baseUrl;
100 | }
101 |
102 | public long getCountLength() {
103 | return countLength;
104 | }
105 |
106 | public void setCountLength(long countLength) {
107 | this.countLength = countLength;
108 | }
109 |
110 |
111 | public long getReadLength() {
112 | return readLength;
113 | }
114 |
115 | public void setReadLength(long readLength) {
116 | this.readLength = readLength;
117 | }
118 |
119 | /**
120 | * 读取baseurl
121 | */
122 | private String getBasUrl(String url) {
123 | String head = "";
124 | int index = url.indexOf("://");
125 | if (index != -1) {
126 | head = url.substring(0, index + 3);
127 | url = url.substring(index + 3);
128 | }
129 | index = url.indexOf("/");
130 | if (index != -1) {
131 | url = url.substring(0, index + 1);
132 | }
133 | return head + url;
134 | }
135 | }
136 |
--------------------------------------------------------------------------------
/app/src/main/java/com/winfo/update/download/httpdownload/DownState.java:
--------------------------------------------------------------------------------
1 | package com.winfo.update.download.httpdownload;
2 |
3 | /**
4 | * 下载状态
5 | */
6 |
7 | public enum DownState {
8 | START,
9 | DOWN,
10 | PAUSE,
11 | STOP,
12 | ERROR,
13 | FINISH,
14 |
15 | }
16 |
--------------------------------------------------------------------------------
/app/src/main/java/com/winfo/update/download/httpdownload/HttpDownManager.java:
--------------------------------------------------------------------------------
1 | package com.winfo.update.download.httpdownload;
2 |
3 |
4 | import android.util.Log;
5 |
6 | import com.winfo.update.download.exception.HttpTimeException;
7 | import com.winfo.update.download.exception.RetryWhenNetworkException;
8 | import com.winfo.update.download.http.HttpService;
9 | import com.winfo.update.download.httpdownload.downloadlistener.DownloadInterceptor;
10 | import com.winfo.update.download.listener.HttpProgressOnNextListener;
11 |
12 | import java.io.File;
13 | import java.io.IOException;
14 | import java.io.RandomAccessFile;
15 | import java.nio.MappedByteBuffer;
16 | import java.nio.channels.FileChannel;
17 | import java.util.HashMap;
18 | import java.util.HashSet;
19 | import java.util.Set;
20 | import java.util.concurrent.TimeUnit;
21 |
22 | import io.reactivex.android.schedulers.AndroidSchedulers;
23 | import io.reactivex.functions.Function;
24 | import io.reactivex.schedulers.Schedulers;
25 | import okhttp3.OkHttpClient;
26 | import okhttp3.ResponseBody;
27 | import retrofit2.Retrofit;
28 | import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory;
29 | import retrofit2.converter.gson.GsonConverterFactory;
30 |
31 |
32 | /**
33 | * http下载处理类
34 | * Created by pc12 on 2017/2/5.
35 | */
36 |
37 | public class HttpDownManager {
38 | //记录下载数据
39 | private Set downInfos;
40 | //回调sub队列
41 | private HashMap subMap;
42 | //进度监听队列
43 | private HashMap> mProgressListenerHashMap;
44 |
45 | //单利对象
46 | private volatile static HttpDownManager INSTANCE;
47 |
48 | private HttpDownManager() {
49 | downInfos = new HashSet<>();
50 | subMap = new HashMap<>();
51 | mProgressListenerHashMap = new HashMap<>();
52 | }
53 |
54 | /**
55 | * 获取单例
56 | */
57 | public static HttpDownManager getInstance() {
58 | if (INSTANCE == null) {
59 | synchronized (HttpDownManager.class) {
60 | if (INSTANCE == null) {
61 | INSTANCE = new HttpDownManager();
62 | }
63 | }
64 | }
65 | return INSTANCE;
66 | }
67 |
68 | /**
69 | * 继续下载
70 | */
71 | public void continueDownload(DownInfo downInfo) {
72 | //根据下载的url获取到进度监听队列中的监听器
73 | HttpProgressOnNextListener httpProgressOnNextListener = mProgressListenerHashMap.get(downInfo.getUrl());
74 | if (httpProgressOnNextListener != null) {
75 | startDown(downInfo, httpProgressOnNextListener);
76 | }
77 | }
78 |
79 |
80 | /**
81 | * 开始下载
82 | */
83 | public void startDown(final DownInfo info, HttpProgressOnNextListener httpProgressOnNextListener) {
84 | // 正在下载返回异常信息
85 | if (info.getState() == DownState.DOWN) {
86 | httpProgressOnNextListener.onError(new Exception("正在下载中"));
87 | return;
88 | }
89 | //添加回调处理类
90 | ProgressDownSubscriber subscriber = new ProgressDownSubscriber<>(info, httpProgressOnNextListener);
91 | //将监听进度的器添加到队列中,以便于继续下载时获取
92 | mProgressListenerHashMap.put(info.getUrl(), httpProgressOnNextListener);
93 | //记录回调sub
94 | subMap.put(info.getUrl(), subscriber);
95 | //获取service,多次请求公用一个service
96 | HttpService httpService;
97 | //设置文件的状态为下载
98 | info.setState(DownState.START);
99 | if (downInfos.contains(info)) {
100 | httpService = info.getService();
101 | } else {
102 | DownloadInterceptor interceptor = new DownloadInterceptor(subscriber);
103 | OkHttpClient.Builder builder = new OkHttpClient.Builder();
104 | //手动创建一个OkHttpClient并设置超时时间
105 | builder.connectTimeout(info.getConnectionTime(), TimeUnit.SECONDS);
106 | builder.addInterceptor(interceptor);
107 |
108 | Retrofit retrofit = new Retrofit.Builder()
109 | .client(builder.build())
110 | .addConverterFactory(GsonConverterFactory.create())
111 | .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
112 | .baseUrl(info.getBaseUrl())
113 | .build();
114 | httpService = retrofit.create(HttpService.class);
115 | info.setService(httpService);
116 | downInfos.add(info);
117 | }
118 | //得到rx对象-上一次下载的位置开始下载
119 | httpService.download("bytes=" + info.getReadLength() + "-", info.getUrl())
120 | //指定线程
121 | .subscribeOn(Schedulers.io())
122 | .unsubscribeOn(Schedulers.io())
123 | //失败后的retry配置
124 | .retryWhen(new RetryWhenNetworkException())
125 | .map(new Function() {
126 | @Override
127 | public DownInfo apply(ResponseBody responseBody) {
128 | try {
129 | writeCache(responseBody, new File(info.getSavePath()), info);
130 | } catch (IOException e) {
131 | //失败抛出异常
132 | throw new HttpTimeException(e.getMessage());
133 | }
134 | return info;
135 | }
136 | })//回调线程
137 | .observeOn(AndroidSchedulers.mainThread())
138 | //数据回调
139 | .subscribe(subscriber);
140 | }
141 |
142 |
143 | /**
144 | * 停止下载
145 | */
146 | public void stopDown(DownInfo info) {
147 | if (info == null) return;
148 | info.setState(DownState.STOP);
149 | if (subMap.containsKey(info.getUrl())) {
150 | ProgressDownSubscriber subscriber = subMap.get(info.getUrl());
151 | subscriber.unSubscribe();
152 | subMap.remove(info.getUrl());
153 | }
154 | }
155 |
156 |
157 | /**
158 | * 删除
159 | */
160 | @SuppressWarnings("unused")
161 | public void deleteDown(DownInfo info) {
162 | stopDown(info);
163 | }
164 |
165 |
166 | /**
167 | * 暂停下载
168 | */
169 | public void pause(DownInfo info) {
170 | if (info == null) return;
171 | info.setState(DownState.PAUSE);
172 | if (subMap.containsKey(info.getUrl())) {
173 | ProgressDownSubscriber subscriber = subMap.get(info.getUrl());
174 | subscriber.unSubscribe();
175 | subMap.remove(info.getUrl());
176 | }
177 | }
178 |
179 | /**
180 | * 停止全部下载
181 | */
182 | @SuppressWarnings("unused")
183 | public void stopAllDown() {
184 | for (DownInfo downInfo : downInfos) {
185 | stopDown(downInfo);
186 | }
187 | subMap.clear();
188 | downInfos.clear();
189 | }
190 |
191 | /**
192 | * 暂停全部下载
193 | */
194 | @SuppressWarnings("unused")
195 | public void pauseAll() {
196 | for (DownInfo downInfo : downInfos) {
197 | pause(downInfo);
198 | }
199 | subMap.clear();
200 | downInfos.clear();
201 | }
202 |
203 |
204 | /**
205 | * 返回全部正在下载的数据
206 | */
207 | @SuppressWarnings("unused")
208 | public Set getDownInfos() {
209 | return downInfos;
210 | }
211 |
212 |
213 | /**
214 | * 写入文件
215 | */
216 | private void writeCache(ResponseBody responseBody, File file, DownInfo info) throws IOException {
217 | if (!file.getParentFile().exists()) {
218 | boolean bol = file.getParentFile().mkdirs();
219 | if (!bol) {
220 | Log.e("TAG", "文件创建失败");
221 | }
222 | }
223 | long allLength;
224 | if (info.getCountLength() == 0) {
225 | allLength = responseBody.contentLength();
226 | } else {
227 | allLength = info.getCountLength();
228 | }
229 | FileChannel channelOut;
230 | RandomAccessFile randomAccessFile;
231 | randomAccessFile = new RandomAccessFile(file, "rwd");
232 | channelOut = randomAccessFile.getChannel();
233 | MappedByteBuffer mappedBuffer = channelOut.map(FileChannel.MapMode.READ_WRITE,
234 | info.getReadLength(), allLength - info.getReadLength());
235 | byte[] buffer = new byte[1024 * 8];
236 | int len;
237 | while ((len = responseBody.byteStream().read(buffer)) != -1) {
238 | mappedBuffer.put(buffer, 0, len);
239 | }
240 | responseBody.byteStream().close();
241 | channelOut.close();
242 | randomAccessFile.close();
243 | }
244 |
245 | }
246 |
--------------------------------------------------------------------------------
/app/src/main/java/com/winfo/update/download/httpdownload/ProgressDownSubscriber.java:
--------------------------------------------------------------------------------
1 | package com.winfo.update.download.httpdownload;
2 |
3 |
4 | import android.annotation.SuppressLint;
5 |
6 | import com.winfo.update.download.httpdownload.downloadlistener.DownloadProgressListener;
7 | import com.winfo.update.download.listener.HttpProgressOnNextListener;
8 |
9 | import java.lang.ref.WeakReference;
10 |
11 | import io.reactivex.Observable;
12 | import io.reactivex.Observer;
13 | import io.reactivex.android.schedulers.AndroidSchedulers;
14 | import io.reactivex.disposables.Disposable;
15 | import io.reactivex.functions.Consumer;
16 |
17 |
18 | /**
19 | * 用于在Http请求开始时,自动显示一个ProgressDialog
20 | * 在Http请求结束是,关闭ProgressDialog
21 | * 调用者自己对请求数据进行处理
22 | */
23 | public class ProgressDownSubscriber implements Observer, DownloadProgressListener {
24 | //弱引用结果回调
25 | private WeakReference> mHttpProgressOnNextListener;
26 |
27 | /*下载数据*/
28 | private DownInfo downInfo;
29 |
30 | private Disposable mDisposable;
31 |
32 | public ProgressDownSubscriber(DownInfo downInfo, HttpProgressOnNextListener httpProgressOnNextListener) {
33 | this.mHttpProgressOnNextListener = new WeakReference<>(httpProgressOnNextListener);
34 | this.downInfo = downInfo;
35 | }
36 |
37 | public void unSubscribe() {
38 | if (mDisposable != null) {
39 | mDisposable.dispose();
40 | }
41 | }
42 |
43 |
44 | @Override
45 | public void onSubscribe(Disposable disposable) {
46 | mDisposable = disposable;
47 | }
48 |
49 | @Override
50 | public void onNext(T t) {
51 | if (mHttpProgressOnNextListener.get() != null) {
52 | mHttpProgressOnNextListener.get().onNext(t);
53 | }
54 | }
55 |
56 | /**
57 | * 对错误进行统一处理
58 | * 隐藏ProgressDialog
59 | *
60 | * @param e 异常
61 | */
62 | @Override
63 | public void onError(Throwable e) {
64 | mDisposable.dispose();
65 | /*停止下载*/
66 | HttpDownManager.getInstance().stopDown(downInfo);
67 | if (mHttpProgressOnNextListener.get() != null) {
68 | mHttpProgressOnNextListener.get().onError(e);
69 | }
70 | downInfo.setState(DownState.ERROR);
71 | }
72 |
73 | /**
74 | * 完成,隐藏ProgressDialog
75 | */
76 | @Override
77 | public void onComplete() {
78 | mDisposable.dispose();
79 | if (mHttpProgressOnNextListener.get() != null) {
80 | mHttpProgressOnNextListener.get().onComplete();
81 | }
82 | downInfo.setState(DownState.FINISH);
83 | }
84 |
85 | @SuppressLint("CheckResult")
86 | @Override
87 | public void update(long read, long count, boolean done) {
88 | if (downInfo.getCountLength() > count) {
89 | read = downInfo.getCountLength() - count + read;
90 | } else {
91 | downInfo.setCountLength(count);
92 | }
93 | downInfo.setReadLength(read);
94 | if (mHttpProgressOnNextListener.get() != null) {
95 | /*接受进度消息,造成UI阻塞,如果不需要显示进度可去掉实现逻辑,减少压力*/
96 | Observable.just(read).observeOn(AndroidSchedulers.mainThread())
97 | .subscribe(new Consumer() {
98 | @Override
99 | public void accept(Long aLong) {
100 | /*如果暂停或者停止状态延迟,不需要继续发送回调,影响显示*/
101 | if (downInfo.getState() == DownState.PAUSE || downInfo.getState() == DownState.STOP)
102 | return;
103 | downInfo.setState(DownState.DOWN);
104 | mHttpProgressOnNextListener.get().updateProgress(aLong, downInfo.getCountLength());
105 | }
106 | });
107 | }
108 | }
109 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/winfo/update/download/httpdownload/downloadlistener/DownloadInterceptor.java:
--------------------------------------------------------------------------------
1 | package com.winfo.update.download.httpdownload.downloadlistener;
2 |
3 | import android.support.annotation.NonNull;
4 |
5 | import java.io.IOException;
6 |
7 | import okhttp3.Interceptor;
8 | import okhttp3.Response;
9 |
10 | /**
11 | * 成功回调处理
12 | * Created by pc12 on 2017/2/5.
13 | */
14 |
15 | public class DownloadInterceptor implements Interceptor {
16 |
17 | private DownloadProgressListener listener;
18 |
19 | public DownloadInterceptor(DownloadProgressListener listener) {
20 | this.listener = listener;
21 | }
22 |
23 | @Override
24 | public Response intercept(@NonNull Chain chain) throws IOException {
25 | Response originalResponse = chain.proceed(chain.request());
26 | return originalResponse.newBuilder()
27 | .body(new DownloadResponseBody(originalResponse.body(), listener))
28 | .build();
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/app/src/main/java/com/winfo/update/download/httpdownload/downloadlistener/DownloadProgressListener.java:
--------------------------------------------------------------------------------
1 | package com.winfo.update.download.httpdownload.downloadlistener;
2 |
3 | /**
4 | * 成功回调处理
5 | * Created by pc12 on 2017/2/5.
6 | */
7 |
8 | public interface DownloadProgressListener {
9 | /**
10 | * 下载进度
11 | * @param read 进度
12 | * @param count 总长度
13 | */
14 | void update(long read, long count, boolean done);
15 | }
16 |
--------------------------------------------------------------------------------
/app/src/main/java/com/winfo/update/download/httpdownload/downloadlistener/DownloadResponseBody.java:
--------------------------------------------------------------------------------
1 | package com.winfo.update.download.httpdownload.downloadlistener;
2 |
3 | import android.support.annotation.NonNull;
4 |
5 | import java.io.IOException;
6 |
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 | * 自定义进度的body
17 | * Created by pc12 on 2017/2/5.
18 | */
19 |
20 | public class DownloadResponseBody extends ResponseBody {
21 |
22 | private ResponseBody responseBody;
23 | private DownloadProgressListener progressListener;
24 | private BufferedSource bufferedSource;
25 |
26 | DownloadResponseBody(ResponseBody responseBody, DownloadProgressListener progressListener) {
27 | this.responseBody = responseBody;
28 | this.progressListener = progressListener;
29 | }
30 |
31 | @Override
32 | public MediaType contentType() {
33 | return responseBody.contentType();
34 | }
35 |
36 | @Override
37 | public long contentLength() {
38 | return responseBody.contentLength();
39 | }
40 |
41 | @Override
42 | public BufferedSource source() {
43 | if (bufferedSource == null) {
44 | bufferedSource = Okio.buffer(source(responseBody.source()));
45 | }
46 | return bufferedSource;
47 | }
48 |
49 | private Source source(Source source) {
50 | return new ForwardingSource(source) {
51 | long totalBytesRead = 0L;
52 |
53 | @Override
54 | public long read(@NonNull Buffer sink, long byteCount) throws IOException {
55 | long bytesRead = super.read(sink, byteCount);
56 | // read() returns the number of bytes read, or -1 if this source is exhausted.
57 | totalBytesRead += bytesRead != -1 ? bytesRead : 0;
58 | if (null != progressListener) {
59 | progressListener.update(totalBytesRead, responseBody.contentLength(), bytesRead == -1);
60 | }
61 | return bytesRead;
62 | }
63 | };
64 |
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/app/src/main/java/com/winfo/update/download/listener/HttpProgressOnNextListener.java:
--------------------------------------------------------------------------------
1 | package com.winfo.update.download.listener;
2 |
3 | /**
4 | * 下载过程中的回调处理
5 | */
6 | public abstract class HttpProgressOnNextListener {
7 | /**
8 | * 成功后回调方法
9 | *
10 | * @param t 下载成功之后文件信息
11 | */
12 | public abstract void onNext(T t);
13 |
14 |
15 | /**
16 | * 完成下载
17 | * 主动调用,更加灵活
18 | */
19 | public void onComplete(){
20 |
21 | }
22 |
23 |
24 | /**
25 | * 下载进度
26 | *
27 | * @param readLength 当前下载进度
28 | * @param countLength 文件总长度
29 | */
30 | public abstract void updateProgress(long readLength, long countLength);
31 |
32 | /**
33 | * 失败或者错误方法
34 | * 主动调用,更加灵活
35 | *
36 | * @param e 下载失败异常
37 | */
38 | public void onError(Throwable e) {
39 |
40 | }
41 |
42 |
43 |
44 |
45 |
46 |
47 | // /**
48 | // * 开始下载
49 | // */
50 | // public abstract void onStart();
51 |
52 | // /**
53 | // * 暂停下载
54 | // */
55 | // public void onPuase() {
56 | //
57 | // }
58 | //
59 | // /**
60 | // * 停止下载销毁
61 | // */
62 | // public void onStop() {
63 | //
64 | // }
65 | }
66 |
--------------------------------------------------------------------------------
/app/src/main/java/com/winfo/update/version_update/entity/VersionInfo.java:
--------------------------------------------------------------------------------
1 | package com.winfo.update.version_update.entity;
2 |
3 | import java.io.Serializable;
4 |
5 | /**
6 | * 版本更新实体类
7 | */
8 | public class VersionInfo implements Serializable {
9 |
10 | private String downloadUrl;
11 | private Integer versionCode;
12 | private String versionName;
13 | private String updateMessage;
14 |
15 | public String getDownloadUrl() {
16 | return downloadUrl;
17 | }
18 |
19 | public void setDownloadUrl(String downloadUrl) {
20 | this.downloadUrl = downloadUrl;
21 | }
22 |
23 | public Integer getVersionCode() {
24 | return versionCode;
25 | }
26 |
27 | public void setVersionCode(Integer versionCode) {
28 | this.versionCode = versionCode;
29 | }
30 |
31 | public String getVersionName() {
32 | return versionName;
33 | }
34 |
35 | public void setVersionName(String versionName) {
36 | this.versionName = versionName;
37 | }
38 |
39 | public String getUpdateMessage() {
40 | return updateMessage;
41 | }
42 |
43 | public void setUpdateMessage(String updateMessage) {
44 | this.updateMessage = updateMessage;
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/app/src/main/java/com/winfo/update/version_update/request/ApiService.java:
--------------------------------------------------------------------------------
1 | package com.winfo.update.version_update.request;
2 |
3 | import com.winfo.update.version_update.entity.VersionInfo;
4 | import com.winfo.update.version_update.utils.Constants;
5 |
6 | import io.reactivex.Observable;
7 | import retrofit2.Response;
8 | import retrofit2.http.GET;
9 |
10 | public interface ApiService {
11 |
12 | /**
13 | * 版本检测
14 | */
15 | @GET(Constants.UPDATE_URL)
16 | Observable> checkVersion();
17 |
18 | }
19 |
--------------------------------------------------------------------------------
/app/src/main/java/com/winfo/update/version_update/request/OkHttpUtils.java:
--------------------------------------------------------------------------------
1 | package com.winfo.update.version_update.request;
2 |
3 | import java.util.concurrent.TimeUnit;
4 | import okhttp3.OkHttpClient;
5 | import retrofit2.Retrofit;
6 | import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory;
7 | import retrofit2.converter.gson.GsonConverterFactory;
8 |
9 | public class OkHttpUtils {
10 | /**
11 | * okhttp
12 | */
13 | private static OkHttpClient okHttpClient;
14 |
15 | /**
16 | * Retrofit
17 | */
18 | private static Retrofit retrofit;
19 |
20 | /**
21 | * 获取Retrofit的实例
22 | *
23 | * @return retrofit
24 | */
25 | public static Retrofit getRetrofit() {
26 | if (retrofit == null) {
27 | retrofit = new Retrofit.Builder()
28 | .baseUrl("https://raw.githubusercontent.com/wj576038874/mvp-rxjava-retrofit-okhttp/master/")
29 | .addConverterFactory(GsonConverterFactory.create())
30 | .client(getOkHttpClient())
31 | .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
32 | .build();
33 | }
34 | return retrofit;
35 | }
36 |
37 | private static OkHttpClient getOkHttpClient() {
38 | if (okHttpClient == null) {
39 | OkHttpClient.Builder builder = new OkHttpClient.Builder();
40 | builder.connectTimeout(15 * 1000, TimeUnit.SECONDS);
41 | builder.readTimeout(15 * 1000, TimeUnit.MILLISECONDS);//超时时间
42 | okHttpClient = builder.build();
43 | }
44 | return okHttpClient;
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/app/src/main/java/com/winfo/update/version_update/request/RequestSubscriber.java:
--------------------------------------------------------------------------------
1 | package com.winfo.update.version_update.request;
2 |
3 | import android.text.TextUtils;
4 |
5 | import java.net.ConnectException;
6 | import java.net.SocketTimeoutException;
7 |
8 | import io.reactivex.Observer;
9 | import io.reactivex.disposables.Disposable;
10 |
11 | /**
12 | * 请求被订阅者
13 | * @param
14 | */
15 | public abstract class RequestSubscriber implements Observer {
16 |
17 | /**
18 | * 定义一个请求成功的抽象方法 子类必须实现并在实现中进行处理服务器返回的数据
19 | *
20 | * @param t 服务器返回的数据
21 | */
22 | protected abstract void onSuccess(T t);
23 |
24 | /**
25 | * 定义一个请求失败的抽象方法 子类必须实现并在实现中进行服务器返回数据的处理
26 | *
27 | * @param msg 服务器返回的错误信息
28 | */
29 | protected abstract void onFailure(String msg);
30 |
31 | @Override
32 | public void onSubscribe(Disposable d) {
33 |
34 | }
35 |
36 | @Override
37 | public void onNext(T t) {
38 | /*
39 | * 请求成功将数据发出去
40 | */
41 | onSuccess(t);
42 | }
43 |
44 | @Override
45 | public void onError(Throwable e) {
46 | String msg;
47 | if (e instanceof SocketTimeoutException) {
48 | msg = "请求超时。请稍后重试!";
49 | } else if (e instanceof ConnectException) {
50 | msg = "请求超时。请稍后重试!";
51 | } else {
52 | msg = "请求未能成功,请稍后重试!";
53 | }
54 | if (!TextUtils.isEmpty(msg)) {
55 | onFailure(msg);
56 | }
57 | }
58 |
59 | @Override
60 | public void onComplete() {
61 |
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/app/src/main/java/com/winfo/update/version_update/service/DownloadService.java:
--------------------------------------------------------------------------------
1 | package com.winfo.update.version_update.service;
2 |
3 | import android.app.Service;
4 | import android.content.Intent;
5 | import android.os.IBinder;
6 | import android.support.annotation.Nullable;
7 | import android.widget.Toast;
8 |
9 | import com.winfo.update.version_update.utils.NotificationHelper;
10 | import com.winfo.update.version_update.utils.Constants;
11 | import com.winfo.update.version_update.utils.NotificationBarUtil;
12 | import com.winfo.update.download.httpdownload.DownInfo;
13 | import com.winfo.update.download.httpdownload.HttpDownManager;
14 | import com.winfo.update.download.listener.HttpProgressOnNextListener;
15 | import com.winfo.update.version_update.utils.ApkUtils;
16 | import com.winfo.update.version_update.utils.StorageUtils;
17 |
18 | import java.io.File;
19 |
20 | /**
21 | * 下载服务
22 | */
23 | public class DownloadService extends Service {
24 |
25 | private DownInfo downInfo;
26 | private int oldProgress = 0;
27 | private NotificationHelper notificationHelper;
28 |
29 | @Override
30 | public void onCreate() {
31 | super.onCreate();
32 | downInfo = new DownInfo();
33 | notificationHelper = new NotificationHelper(this);
34 | }
35 |
36 | @Override
37 | public int onStartCommand(@Nullable Intent intent, int flags, int startId) {
38 | assert intent != null;
39 | String urlStr = intent.getStringExtra(Constants.APK_DOWNLOAD_URL);
40 | downInfo.setUrl(urlStr);
41 | File dir = StorageUtils.getExternalCacheCustomDirectory(this);
42 | // File dir = StorageUtils.getExternalCacheDirectory(this);
43 | // File dir = StorageUtils.getCacheDirectory(this);
44 | String apkName = urlStr.substring(urlStr.lastIndexOf("/") + 1, urlStr.length());
45 | File apkFile = new File(dir, apkName);
46 | downInfo.setSavePath(apkFile.getAbsolutePath());
47 | downLoadFile();
48 | return super.onStartCommand(intent, flags, startId);
49 | }
50 |
51 |
52 | private void downLoadFile() {
53 | HttpDownManager.getInstance().startDown(downInfo, new HttpProgressOnNextListener() {
54 | @Override
55 | public void onNext(DownInfo downInfo) {
56 | //收起通知栏
57 | NotificationBarUtil.setNotificationBarVisibility(DownloadService.this, false);
58 | //安装
59 | ApkUtils.installAPk(DownloadService.this, new File(downInfo.getSavePath()));
60 | }
61 |
62 | @Override
63 | public void onComplete() {
64 | notificationHelper.cancel();
65 | stopSelf();
66 | }
67 |
68 | @Override
69 | public void updateProgress(long readLength, long countLength) {
70 | int progress = (int) (readLength * 100 / countLength);
71 | // 如果进度与之前进度相等,则不更新,如果更新太频繁,否则会造成界面卡顿
72 | if (progress != oldProgress) {
73 | notificationHelper.updateProgress(progress);
74 | }
75 | oldProgress = progress;
76 | }
77 |
78 | @Override
79 | public void onError(Throwable e) {
80 | Toast.makeText(DownloadService.this, e.getMessage(), Toast.LENGTH_SHORT).show();
81 | }
82 | });
83 | }
84 |
85 |
86 | @Override
87 | public void onDestroy() {
88 | super.onDestroy();
89 | }
90 |
91 | @Override
92 | public IBinder onBind(Intent intent) {
93 | return null;
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/app/src/main/java/com/winfo/update/version_update/utils/ApkUtils.java:
--------------------------------------------------------------------------------
1 | package com.winfo.update.version_update.utils;
2 |
3 | import android.content.Context;
4 | import android.content.Intent;
5 | import android.net.Uri;
6 | import android.os.Build;
7 | import android.support.v4.content.FileProvider;
8 | import java.io.File;
9 | import java.io.IOException;
10 |
11 | public class ApkUtils {
12 |
13 | /**
14 | * 安装apk
15 | * @param context context
16 | * @param apkFile 安装文件
17 | */
18 | public static void installAPk(Context context, File apkFile) {
19 | Intent installAPKIntent = getApkInStallIntent(context, apkFile);
20 | context.startActivity(installAPKIntent);
21 |
22 | }
23 |
24 | /**
25 | * 获取安装文件意图
26 | * @param context context
27 | * @param apkFile 安装文件
28 | * @return 安装意图
29 | */
30 | private static Intent getApkInStallIntent(Context context, File apkFile) {
31 | Intent intent = new Intent(Intent.ACTION_VIEW);
32 | intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
33 |
34 | if (Build.VERSION.SDK_INT > Build.VERSION_CODES.M) {
35 | Uri uri = FileProvider.getUriForFile(context, "com.winfo.update.provider", apkFile);
36 | intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
37 | intent.setDataAndType(uri, "application/vnd.android.package-archive");
38 | } else {
39 | Uri uri = getApkUri(apkFile);
40 | intent.setDataAndType(uri, "application/vnd.android.package-archive");
41 | }
42 | return intent;
43 | }
44 |
45 | /**
46 | * 获取安装文件的Uri
47 | * @param apkFile 安装文件
48 | * @return Uri
49 | */
50 | private static Uri getApkUri(File apkFile) {
51 | //如果没有设置 SDCard 写权限,或者没有 SDCard,apk 文件保存在内存中,需要授予权限才能安装
52 | try {
53 | String[] command = {"chmod", "777", apkFile.toString()};
54 | ProcessBuilder builder = new ProcessBuilder(command);
55 | builder.start();
56 | } catch (IOException ignored) {
57 | }
58 | return Uri.fromFile(apkFile);
59 | }
60 |
61 | }
62 |
--------------------------------------------------------------------------------
/app/src/main/java/com/winfo/update/version_update/utils/AppUtils.java:
--------------------------------------------------------------------------------
1 | package com.winfo.update.version_update.utils;
2 |
3 | import android.content.Context;
4 | import android.content.pm.PackageManager;
5 |
6 | public class AppUtils {
7 |
8 | /**
9 | * 获取当前应用的版本号
10 | * @param mContext context
11 | * @return 版本号
12 | */
13 | public static int getVersionCode(Context mContext) {
14 | if (mContext != null) {
15 | try {
16 | return mContext.getPackageManager().getPackageInfo(mContext.getPackageName(), 0).versionCode;
17 | } catch (PackageManager.NameNotFoundException ignored) {
18 | }
19 | }
20 | return 0;
21 | }
22 |
23 | /**
24 | * 获取当前应用的版本名称
25 | * @param mContext context
26 | * @return 版本名称
27 | */
28 | public static String getVersionName(Context mContext) {
29 | if (mContext != null) {
30 | try {
31 | return mContext.getPackageManager().getPackageInfo(mContext.getPackageName(), 0).versionName;
32 | } catch (PackageManager.NameNotFoundException ignored) {
33 | }
34 | }
35 |
36 | return "";
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/app/src/main/java/com/winfo/update/version_update/utils/Constants.java:
--------------------------------------------------------------------------------
1 | package com.winfo.update.version_update.utils;
2 |
3 | public class Constants {
4 |
5 | public static final String APK_DOWNLOAD_URL = "downloadUrl";
6 | public static final String UPDATE_URL = "version.json";
7 |
8 | }
9 |
--------------------------------------------------------------------------------
/app/src/main/java/com/winfo/update/version_update/utils/NotificationBarUtil.java:
--------------------------------------------------------------------------------
1 | package com.winfo.update.version_update.utils;
2 |
3 | import android.annotation.SuppressLint;
4 | import android.content.Context;
5 | import android.os.Build;
6 | import android.support.annotation.RequiresPermission;
7 |
8 | import java.lang.reflect.Method;
9 |
10 | import static android.Manifest.permission.EXPAND_STATUS_BAR;
11 |
12 | public class NotificationBarUtil {
13 |
14 | /**
15 | * 设置通知栏是否收起
16 | * @param context context
17 | * @param isVisible 是否收起
18 | */
19 | @RequiresPermission(EXPAND_STATUS_BAR)
20 | public static void setNotificationBarVisibility(Context context, boolean isVisible) {
21 | String methodName;
22 | if (isVisible) {
23 | methodName = (Build.VERSION.SDK_INT <= 16) ? "expand" : "expandNotificationsPanel";
24 | } else {
25 | methodName = (Build.VERSION.SDK_INT <= 16) ? "collapse" : "collapsePanels";
26 | }
27 | invokePanels(context, methodName);
28 | }
29 |
30 | private static void invokePanels(Context context, String methodName) {
31 | try {
32 | @SuppressLint("WrongConstant")
33 | Object service = context.getSystemService("statusbar");
34 | @SuppressLint("PrivateApi")
35 | Class> statusBarManager = Class.forName("android.app.StatusBarManager");
36 | Method expand = statusBarManager.getMethod(methodName);
37 | expand.invoke(service);
38 | } catch (Exception e) {
39 | e.printStackTrace();
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/app/src/main/java/com/winfo/update/version_update/utils/NotificationHelper.java:
--------------------------------------------------------------------------------
1 | package com.winfo.update.version_update.utils;
2 |
3 | import android.app.NotificationChannel;
4 | import android.app.NotificationManager;
5 | import android.app.PendingIntent;
6 | import android.content.Context;
7 | import android.content.Intent;
8 | import android.os.Build;
9 | import android.support.v4.app.NotificationCompat;
10 |
11 | import com.winfo.update.R;
12 | import com.winfo.update.version_update.service.DownloadService;
13 |
14 | public class NotificationHelper {
15 |
16 | private NotificationManager manager;
17 |
18 | private Context mContext;
19 |
20 | private static String CHANNEL_ID = "dxy_app_update";
21 |
22 | private static final int NOTIFICATION_ID = 0;
23 |
24 | public NotificationHelper(Context context) {
25 | this.mContext = context;
26 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
27 | NotificationChannel mChannel = new NotificationChannel(CHANNEL_ID, "应用更新", NotificationManager.IMPORTANCE_NONE);
28 | mChannel.setDescription("应用有新版本");
29 | mChannel.enableLights(true); //是否在桌面icon右上角展示小红点
30 | mChannel.setShowBadge(true);
31 | getManager().createNotificationChannel(mChannel);
32 | }
33 | }
34 |
35 | /**
36 | * 显示Notification
37 | */
38 | public void showNotification(String content, String apkUrl) {
39 |
40 | Intent myIntent = new Intent(mContext, DownloadService.class);
41 | myIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
42 | myIntent.putExtra(Constants.APK_DOWNLOAD_URL, apkUrl);
43 | PendingIntent pendingIntent = PendingIntent.getService(mContext, 0, myIntent, PendingIntent.FLAG_UPDATE_CURRENT);
44 |
45 | NotificationCompat.Builder builder = getNofity(content)
46 | .setContentIntent(pendingIntent);
47 |
48 | getManager().notify(NOTIFICATION_ID, builder.build());
49 | }
50 |
51 | /**
52 | * 不断调用次方法通知通知栏更新进度条
53 | */
54 | public void updateProgress(int progress) {
55 |
56 | String text = mContext.getString(R.string.android_auto_update_download_progress, progress);
57 |
58 | PendingIntent pendingintent = PendingIntent.getActivity(mContext, 0, new Intent(), PendingIntent.FLAG_UPDATE_CURRENT);
59 |
60 | NotificationCompat.Builder builder = getNofity(text)
61 | .setProgress(100, progress, false)
62 | .setContentIntent(pendingintent);
63 |
64 | getManager().notify(NOTIFICATION_ID, builder.build());
65 | }
66 |
67 | private NotificationCompat.Builder getNofity(String text) {
68 | return new NotificationCompat.Builder(mContext.getApplicationContext(), CHANNEL_ID)
69 | .setTicker(mContext.getString(R.string.android_auto_update_notify_ticker))
70 | .setContentTitle("版本更新")
71 | .setContentText(text)
72 | .setSmallIcon(R.mipmap.ic_launcher)
73 | .setAutoCancel(true)
74 | .setOnlyAlertOnce(true)
75 | .setWhen(System.currentTimeMillis())
76 | .setPriority(NotificationCompat.PRIORITY_HIGH);
77 |
78 | }
79 |
80 | public void cancel() {
81 | getManager().cancel(NOTIFICATION_ID);
82 | }
83 |
84 |
85 | private NotificationManager getManager() {
86 | if (manager == null) {
87 | manager = (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
88 | }
89 | return manager;
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/app/src/main/java/com/winfo/update/version_update/utils/StorageUtils.java:
--------------------------------------------------------------------------------
1 | package com.winfo.update.version_update.utils;
2 |
3 | import android.content.Context;
4 | import android.util.Log;
5 |
6 | import java.io.File;
7 |
8 | public class StorageUtils {
9 |
10 |
11 | /*
12 | * context.getCacheDir()和context.getExternalCacheDir()
13 | * 目录的路径不同。
14 | * 前者的目录存在外部SD卡上的。在手机里可以直接看到
15 | * 后者的目录存在app的内部存储上,需要root以后,用Root Explorer 文件管理器才能看到
16 | */
17 |
18 | /**
19 | * 获取应用的缓存目录
20 | * 路径需要root以后,用Root Explorer 文件管理器才能看到
21 | */
22 | public static File getCacheDirectory(Context context) {
23 | File appCacheDir = context.getCacheDir();
24 | if (appCacheDir == null) {
25 | Log.w("StorageUtils", "Can't define system cache directory! The app should be re-installed.");
26 | }
27 | return appCacheDir;
28 | }
29 |
30 | /**
31 | * 获取应用的缓存目录 路径在手机里可以直接看到
32 | * apk下载路径为:SDCard/Android/data/com.winfo.update/cache/
33 | */
34 | public static File getExternalCacheDirectory(Context context) {
35 | File appCacheDir = context.getExternalCacheDir();
36 | if (appCacheDir == null) {
37 | Log.w("StorageUtils", "Can't define system cache directory! The app should be re-installed.");
38 | }
39 | return appCacheDir;
40 | }
41 |
42 | /**
43 | * 在cache下新增自定义缓存路径
44 | * apk下载路径为:SDCard/Android/data/com.winfo.update/cache/update_file/
45 | */
46 | public static File getExternalCacheCustomDirectory(Context context) {
47 | //在SDCard/Android/data/com.winfo.update/cache/update_file创建文件夹
48 | File appCacheDir = new File(context.getExternalCacheDir(), "update");
49 | //如果不存在就创建
50 | if (!appCacheDir.exists()) {
51 | if (appCacheDir.mkdirs()) {//创建成功就返回SDCard/Android/data/com.winfo.update/cache/update_file/
52 | return appCacheDir;
53 | } else {
54 | //创建失败就返回默认的SDCard/Android/data/com.winfo.update/cache/
55 | return context.getExternalCacheDir();
56 | }
57 | } else {
58 | //存在直接返回
59 | return appCacheDir;
60 | }
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/app/src/main/java/com/winfo/update/version_update/utils/UpdateChecker.java:
--------------------------------------------------------------------------------
1 | package com.winfo.update.version_update.utils;
2 |
3 | import android.app.Dialog;
4 | import android.content.Context;
5 | import android.widget.Toast;
6 |
7 | import com.winfo.update.R;
8 | import com.winfo.update.version_update.entity.VersionInfo;
9 | import com.winfo.update.version_update.request.ApiService;
10 | import com.winfo.update.version_update.request.OkHttpUtils;
11 | import com.winfo.update.version_update.request.RequestSubscriber;
12 |
13 | import io.reactivex.Observable;
14 | import io.reactivex.Observer;
15 | import io.reactivex.android.schedulers.AndroidSchedulers;
16 | import io.reactivex.schedulers.Schedulers;
17 | import retrofit2.Response;
18 |
19 | public class UpdateChecker {
20 |
21 | /**
22 | * 对话框检测
23 | *
24 | * @param mContext context
25 | * @param dialog 加载框
26 | */
27 | public static void checkForDialog(final Context mContext, final Dialog dialog) {
28 | dialog.show();
29 | Observable> observable = OkHttpUtils.getRetrofit().create(ApiService.class).checkVersion();
30 | Observer> observer = new RequestSubscriber>() {
31 | @Override
32 | protected void onSuccess(Response versionInfoResponse) {
33 | dialog.dismiss();
34 | if (versionInfoResponse.isSuccessful()) {
35 | VersionInfo versionInfo = versionInfoResponse.body();
36 | if (versionInfo != null) {
37 | int apkCode = versionInfo.getVersionCode();
38 | int versionCode = AppUtils.getVersionCode(mContext);
39 | if (apkCode > versionCode) {
40 | UpdateDialog.show(mContext, versionInfo.getUpdateMessage(), versionInfo.getDownloadUrl());
41 | } else {
42 | Toast.makeText(mContext, mContext.getString(R.string.android_auto_update_toast_no_new_update), Toast.LENGTH_SHORT).show();
43 | }
44 | } else {
45 | Toast.makeText(mContext, "请求失败了", Toast.LENGTH_SHORT).show();
46 | }
47 | } else {
48 | Toast.makeText(mContext, "请求失败了", Toast.LENGTH_SHORT).show();
49 | }
50 | }
51 |
52 | @Override
53 | protected void onFailure(String msg) {
54 | dialog.dismiss();
55 | Toast.makeText(mContext, msg, Toast.LENGTH_SHORT).show();
56 | }
57 | };
58 | observable.subscribeOn(Schedulers.io())
59 | .unsubscribeOn(Schedulers.io())
60 | .observeOn(AndroidSchedulers.mainThread())
61 | .subscribe(observer);
62 | }
63 |
64 | /**
65 | * 通知栏通知
66 | *
67 | * @param mContext context
68 | * @param dialog 加载框
69 | */
70 | public static void checkForNotification(final Context mContext, final Dialog dialog) {
71 | dialog.show();
72 | Observable> observable = OkHttpUtils.getRetrofit().create(ApiService.class).checkVersion();
73 | Observer> observer = new RequestSubscriber>() {
74 | @Override
75 | protected void onSuccess(Response versionInfoResponse) {
76 | dialog.dismiss();
77 | if (versionInfoResponse.isSuccessful()) {
78 | VersionInfo versionInfo = versionInfoResponse.body();
79 | if (versionInfo != null) {
80 | int apkCode = versionInfo.getVersionCode();
81 | int versionCode = AppUtils.getVersionCode(mContext);
82 | if (apkCode > versionCode) {
83 | new NotificationHelper(mContext).showNotification(versionInfo.getUpdateMessage(), versionInfo.getDownloadUrl());
84 | } else {
85 | Toast.makeText(mContext, mContext.getString(R.string.android_auto_update_toast_no_new_update), Toast.LENGTH_SHORT).show();
86 | }
87 | } else {
88 | Toast.makeText(mContext, "请求没有成功", Toast.LENGTH_SHORT).show();
89 | }
90 | } else {
91 | Toast.makeText(mContext, "请求没有成功", Toast.LENGTH_SHORT).show();
92 | }
93 | }
94 |
95 | @Override
96 | protected void onFailure(String msg) {
97 | dialog.dismiss();
98 | Toast.makeText(mContext, msg, Toast.LENGTH_SHORT).show();
99 | }
100 | };
101 | observable.subscribeOn(Schedulers.io())
102 | .unsubscribeOn(Schedulers.io())
103 | .observeOn(AndroidSchedulers.mainThread())
104 | .subscribe(observer);
105 | }
106 | }
107 |
--------------------------------------------------------------------------------
/app/src/main/java/com/winfo/update/version_update/utils/UpdateDialog.java:
--------------------------------------------------------------------------------
1 | package com.winfo.update.version_update.utils;
2 |
3 | import android.app.Activity;
4 | import android.app.AlertDialog;
5 | import android.content.Context;
6 | import android.content.DialogInterface;
7 | import android.content.Intent;
8 |
9 | import com.winfo.update.R;
10 | import com.winfo.update.version_update.service.DownloadService;
11 |
12 | /**
13 | * 弹出对话框提示更新信息,可自定义
14 | */
15 | public class UpdateDialog {
16 |
17 | /**
18 | * 显示对话框
19 | *
20 | * @param context context
21 | * @param content 更新内容
22 | * @param downloadUrl apk下载地址
23 | */
24 | public static void show(final Context context, String content, final String downloadUrl) {
25 | if (isContextValid(context)) {
26 | new AlertDialog.Builder(context)
27 | .setTitle(R.string.android_auto_update_dialog_title)
28 | .setMessage(content)
29 | .setPositiveButton(R.string.android_auto_update_dialog_btn_download, new DialogInterface.OnClickListener() {
30 | public void onClick(DialogInterface dialog, int id) {
31 | goToDownload(context, downloadUrl);
32 | }
33 | })
34 | .setNegativeButton(R.string.android_auto_update_dialog_btn_cancel, new DialogInterface.OnClickListener() {
35 | public void onClick(DialogInterface dialog, int id) {
36 | dialog.dismiss();
37 | }
38 | })
39 | .setCancelable(false)
40 | .show();
41 | }
42 | }
43 |
44 | /**
45 | * 检测context是否是Activity
46 | *
47 | * @param context 上下文
48 | * @return 是否
49 | */
50 | private static boolean isContextValid(Context context) {
51 | return context instanceof Activity && !((Activity) context).isFinishing();
52 | }
53 |
54 | /**
55 | * 启动服务传递下载地址进行下载
56 | *
57 | * @param context activity
58 | * @param downloadUrl 下载地址
59 | */
60 | private static void goToDownload(Context context, String downloadUrl) {
61 | Intent intent = new Intent(context.getApplicationContext(), DownloadService.class);
62 | intent.putExtra(Constants.APK_DOWNLOAD_URL, downloadUrl);
63 | context.startService(intent);
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-v24/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
12 |
13 |
19 |
22 |
25 |
26 |
27 |
28 |
34 |
35 |
--------------------------------------------------------------------------------
/app/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 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
11 |
15 |
16 |
20 |
21 |
27 |
28 |
35 |
36 |
37 |
41 |
42 |
51 |
52 |
58 |
59 |
60 |
61 |
66 |
67 |
68 |
69 |
73 |
74 |
78 |
79 |
85 |
86 |
93 |
94 |
95 |
99 |
100 |
109 |
110 |
116 |
117 |
118 |
119 |
124 |
125 |
126 |
130 |
131 |
135 |
136 |
142 |
143 |
150 |
151 |
152 |
156 |
157 |
166 |
167 |
173 |
174 |
175 |
176 |
181 |
182 |
183 |
189 |
190 |
195 |
196 |
197 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xuezhihuixzh/Downloadupdate/2ef3434d242e8e6c7b3ea781e242e6db9f0b0833/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xuezhihuixzh/Downloadupdate/2ef3434d242e8e6c7b3ea781e242e6db9f0b0833/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xuezhihuixzh/Downloadupdate/2ef3434d242e8e6c7b3ea781e242e6db9f0b0833/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xuezhihuixzh/Downloadupdate/2ef3434d242e8e6c7b3ea781e242e6db9f0b0833/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xuezhihuixzh/Downloadupdate/2ef3434d242e8e6c7b3ea781e242e6db9f0b0833/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xuezhihuixzh/Downloadupdate/2ef3434d242e8e6c7b3ea781e242e6db9f0b0833/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xuezhihuixzh/Downloadupdate/2ef3434d242e8e6c7b3ea781e242e6db9f0b0833/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xuezhihuixzh/Downloadupdate/2ef3434d242e8e6c7b3ea781e242e6db9f0b0833/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xuezhihuixzh/Downloadupdate/2ef3434d242e8e6c7b3ea781e242e6db9f0b0833/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xuezhihuixzh/Downloadupdate/2ef3434d242e8e6c7b3ea781e242e6db9f0b0833/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #3F51B5
4 | #303F9F
5 | #FF4081
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | DownloadUpdateDemo
3 |
4 | 发现新版本
5 |
6 | 发现新版本,点击进行升级
7 | 发现新版本,点击进行升级
8 |
9 | 立即下载
10 | 以后再说
11 |
12 | 已经是最新版本
13 |
14 | 正在下载:%1$d%%
15 |
16 | 正在检查版本
17 |
18 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/update_apk_paths.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/test/java/com/winfo/update/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package com.winfo.update;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.junit.Assert.*;
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * @see Testing documentation
11 | */
12 | public class ExampleUnitTest {
13 | @Test
14 | public void addition_isCorrect() {
15 | assertEquals(4, 2 + 2);
16 | }
17 | }
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 |
3 | buildscript {
4 |
5 | repositories {
6 | google()
7 | jcenter()
8 | }
9 | dependencies {
10 | classpath 'com.android.tools.build:gradle:3.1.3'
11 |
12 |
13 | // NOTE: Do not place your application dependencies here; they belong
14 | // in the individual module build.gradle files
15 | }
16 | }
17 |
18 | allprojects {
19 | repositories {
20 | google()
21 | jcenter()
22 | }
23 | }
24 |
25 | task clean(type: Delete) {
26 | delete rootProject.buildDir
27 | }
28 |
--------------------------------------------------------------------------------
/gif/abc.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xuezhihuixzh/Downloadupdate/2ef3434d242e8e6c7b3ea781e242e6db9f0b0833/gif/abc.gif
--------------------------------------------------------------------------------
/gif/def.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xuezhihuixzh/Downloadupdate/2ef3434d242e8e6c7b3ea781e242e6db9f0b0833/gif/def.gif
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xuezhihuixzh/Downloadupdate/2ef3434d242e8e6c7b3ea781e242e6db9f0b0833/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Thu Jun 21 14:16:31 CST 2018
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-4.4-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 ':app'
2 |
--------------------------------------------------------------------------------