├── .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 | 9 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 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 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 |