├── .gitignore
├── README.md
├── _config.yml
├── apk
└── sample-debug.apk
├── build.gradle
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── image
├── 1498810770.png
├── example_01.png
├── example_02.png
├── example_03.png
├── example_04.png
├── example_05.png
└── example_06.png
├── java.md
├── json
├── json.txt
└── json1.txt
├── kotlin.md
├── sample
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── com
│ │ └── vector
│ │ └── appupdatedemo
│ │ └── ExampleInstrumentedTest.java
│ ├── main
│ ├── AndroidManifest.xml
│ ├── java
│ │ └── com
│ │ │ └── vector
│ │ │ └── appupdatedemo
│ │ │ ├── App.java
│ │ │ ├── ext
│ │ │ ├── ActivityExt.kt
│ │ │ ├── CprogressExt.kt
│ │ │ └── DialogExt.kt
│ │ │ ├── http
│ │ │ ├── OkGoUpdateHttpUtil.java
│ │ │ └── UpdateAppHttpUtil.java
│ │ │ ├── ui
│ │ │ ├── JavaActivity.java
│ │ │ ├── KotlinActivity.kt
│ │ │ └── MainActivity.java
│ │ │ └── util
│ │ │ ├── CProgressDialogUtils.java
│ │ │ └── HProgressDialogUtils.java
│ └── res
│ │ ├── layout
│ │ ├── activity_java.xml
│ │ ├── activity_kotlin.xml
│ │ └── activity_main.xml
│ │ ├── mipmap-hdpi
│ │ ├── top_2.png
│ │ ├── top_3.png
│ │ ├── top_4.png
│ │ ├── top_5.png
│ │ ├── top_6.png
│ │ └── top_7.png
│ │ ├── mipmap-xxhdpi
│ │ ├── app_update_logo.png
│ │ └── top_8.png
│ │ └── values
│ │ ├── colors.xml
│ │ ├── strings.xml
│ │ └── styles.xml
│ └── test
│ └── java
│ └── com
│ └── vector
│ └── appupdatedemo
│ └── ExampleUnitTest.java
├── settings.gradle
├── update-app-kotlin
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── com
│ │ └── vector
│ │ └── update_app_kotlin
│ │ └── ExampleInstrumentedTest.java
│ ├── main
│ ├── AndroidManifest.xml
│ └── kotlin
│ │ └── com
│ │ └── vector
│ │ └── update_app_kotlin
│ │ ├── TextViewExt.kt
│ │ └── UpdateAppExt.kt
│ └── test
│ └── java
│ └── com
│ └── vector
│ └── update_app_kotlin
│ └── ExampleUnitTest.java
├── update-app
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── java
│ └── com
│ │ └── vector
│ │ └── update_app
│ │ ├── HttpManager.java
│ │ ├── SilenceUpdateCallback.java
│ │ ├── UpdateAppBean.java
│ │ ├── UpdateAppManager.java
│ │ ├── UpdateCallback.java
│ │ ├── UpdateDialogFragment.java
│ │ ├── UpdateFileProvider.java
│ │ ├── listener
│ │ ├── ExceptionHandler.java
│ │ ├── ExceptionHandlerHelper.java
│ │ └── IUpdateDialogFragmentListener.java
│ │ ├── service
│ │ └── DownloadService.java
│ │ ├── utils
│ │ ├── AppUpdateUtils.java
│ │ ├── ColorUtil.java
│ │ ├── DrawableUtil.java
│ │ └── Md5Util.java
│ │ └── view
│ │ └── NumberProgressBar.java
│ └── res
│ ├── anim
│ ├── update_app_window_in.xml
│ └── update_app_window_out.xml
│ ├── drawable
│ └── lib_update_app_info_bg.xml
│ ├── layout
│ └── lib_update_app_dialog.xml
│ ├── mipmap-hdpi
│ └── lib_update_app_top_bg.png
│ ├── mipmap-xhdpi
│ ├── lib_update_app_close.png
│ └── lib_update_app_update_icon.png
│ ├── values
│ ├── attrs.xml
│ └── style.xml
│ └── xml
│ └── new_app_file_paths.xml
└── web
└── AppVersionManger.rar
/.gitignore:
--------------------------------------------------------------------------------
1 | # Built application files
2 | #*.apk
3 | #*.ap_
4 |
5 | # Files for the ART/Dalvik VM
6 | *.dex
7 |
8 | # Java class files
9 | *.class
10 |
11 | # Generated files
12 | bin/
13 | gen/
14 | out/
15 |
16 | # Gradle files
17 | .gradle/
18 | build/
19 |
20 | # Local configuration file (sdk path, etc)
21 | local.properties
22 |
23 | # Proguard folder generated by Eclipse
24 | proguard/
25 |
26 | # Log Files
27 | *.log
28 |
29 | # Android Studio Navigation editor temp files
30 | .navigation/
31 |
32 | # Android Studio captures folder
33 | captures/
34 |
35 | # Intellij
36 | *.iml
37 | .idea/
38 |
39 | # Keystore files
40 | *.jks
41 |
42 |
43 | *.iml
44 | .gradle
45 | /local.properties
46 | /.idea/workspace.xml
47 | /.idea/libraries
48 | .DS_Store
49 | /build
50 | /captures
51 | .externalNativeBuild
52 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 | ## Android 版本更新
3 |
4 | ## 目录
5 |
6 | * [功能介绍](#功能介绍)
7 | * [效果图与示例 apk](#效果图与示例-apk)
8 | * [Gradle 依赖](#Gradle依赖)
9 | * [简单使用](#简单使用)
10 | * [详细说明](#详细说明)
11 | * [更新日志](#更新日志)
12 | * [License](#license)
13 |
14 | ## 功能介绍
15 |
16 | - [x] 实现android版本更新
17 | - [x] 对kotlin适配,调用更简单
18 | - [x] 自定义接口协议,可以不改变现有项目的协议就能使用
19 | - [x] 支持get,post请求
20 | - [x] 支持进度显示,对话框进度条,和通知栏进度条展示
21 | - [x] 支持后台下载
22 | - [x] 支持强制更新
23 | - [x] 支持简单主题色配置(可以自动从顶部图片提取主色)
24 | - [x] 支持自定义对话框(可以监听下载进度)
25 | - [x] 支持静默下载(可以设置wifi状态下)
26 | - [x] 支持android7.0
27 |
28 | ## 效果图与示例 apk
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 | [点击下载 Demo.apk](https://raw.githubusercontent.com/WVector/AppUpdateDemo/master/apk/sample-debug.apk) 或扫描下面的二维码安装
42 |
43 | 
44 |
45 |
46 |
47 | ## Gradle 依赖
48 |
49 | **java方式引用**
50 |
51 | ```gradle
52 | dependencies {
53 | compile 'com.qianwen:update-app:3.5.2'
54 | }
55 | ```
56 |
57 | [ ](https://bintray.com/qianwen/maven/update-app/_latestVersion) [](https://android-arsenal.com/api?level=14) [](https://opensource.org/licenses/Apache-2.0) [ ](https://github.com/WVector/AppUpdate)
58 |
59 |
60 | **kotlin方式引用**
61 |
62 | ```gradle
63 | dependencies {
64 | compile 'com.qianwen:update-app-kotlin:1.2.3'
65 | }
66 | ```
67 |
68 | [ ](https://bintray.com/qianwen/maven/update-app/_latestVersion) [](https://android-arsenal.com/api?level=14) [](https://opensource.org/licenses/Apache-2.0) [ ](https://github.com/WVector/AppUpdate)
69 |
70 |
71 | ## 简单使用
72 |
73 |
74 |
75 | 1,java方式
76 |
77 | ```java
78 | new UpdateAppManager
79 | .Builder()
80 | //当前Activity
81 | .setActivity(this)
82 | //更新地址
83 | .setUpdateUrl(mUpdateUrl)
84 | //实现httpManager接口的对象
85 | .setHttpManager(new UpdateAppHttpUtil())
86 | .build()
87 | .update();
88 | ```
89 | 2,kotlin方式
90 |
91 | ```kotlin
92 | updateApp(mUpdateUrl, UpdateAppHttpUtil()).update()
93 | ```
94 |
95 | ## 详细说明
96 |
97 | - [java方式](java.md)
98 | - [kotlin方式](kotlin.md)
99 |
100 | #### 进度条使用的是代码家的「[NumberProgressBar](https://github.com/daimajia/NumberProgressBar)」
101 |
102 | ## 更新日志
103 |
104 | kotlin版本是依赖java版本的,所以java版本的问题kotlin自然修复
105 |
106 |
107 | v3.5.2
108 |
109 | 1,修复下载过程中,关闭对话框不能自动安装问题。
110 |
111 | v3.5.1
112 |
113 | 1,修复bug
114 |
115 | v3.5.0
116 |
117 | 1,优化强制更新
118 |
119 | v3.4.8
120 |
121 | 1,修复bug
122 |
123 | v3.4.7
124 |
125 | 1,优化 APP 安装的问题
126 |
127 | v3.4.6
128 |
129 | 1,优化 APP 安装的问题
130 |
131 | v3.4.5
132 |
133 | 1,增加全局异常捕获方法
134 |
135 | .handleException(new ExceptionHandler() {
136 | @Override
137 | public void onException(Exception e) {
138 |
139 | }
140 | })
141 |
142 | v3.4.4
143 |
144 | 1,修复bug
145 | [bug](https://github.com/WVector/AppUpdate/pull/68)
146 |
147 | v3.4.3
148 |
149 | 1,修复bug
150 | [bug](https://github.com/WVector/AppUpdate/pull/67)
151 |
152 | v3.4.2
153 |
154 | 1,修复bug
155 | [bug](https://github.com/WVector/AppUpdate/pull/66)
156 |
157 | v3.4.1
158 |
159 | 1,给插件使用者更多的配置和开启一些钩子方便适配不同的业务需求
160 | 2,适配android8.0
161 |
162 | 感谢[Jiiiiiin](https://github.com/Jiiiiiin)对项目的维护
163 |
164 | v3.4.0
165 |
166 | 1,修复
167 | [issues#59](https://github.com/WVector/AppUpdate/issues/59)
168 |
169 |
170 |
171 | v3.3.9
172 |
173 | 1,适配android8.0的通知和安装未知来源的app
174 |
175 | 感谢[typ0520](https://github.com/typ0520)对项目的维护
176 |
177 | v3.3.8
178 |
179 | 1,增加存储空间权限申请
180 |
181 | V3.3.7
182 |
183 | 1,修改默认安装包下载路径为download/packageName
184 |
185 | 感谢[bean-liu](https://github.com/bean-liu)对项目的维护
186 |
187 | V3.3.6
188 |
189 | 1,去掉对下载路径前缀的校验。
190 | [https://github.com/WVector/AppUpdate/issues/26](https://github.com/WVector/AppUpdate/issues/26)
191 |
192 | V3.3.5
193 |
194 | 1,修复升级对话框布局中的问题。
195 | 2,修复静默下载,关闭更新弹窗 再点击更新 一直显示的问题。
196 | [https://github.com/WVector/AppUpdate/issues/21](https://github.com/WVector/AppUpdate/issues/21)
197 |
198 | V3.3.4
199 |
200 | 1,修复对话框更新内容过多,升级按钮被挤压的问题。
201 | 2,去掉自动从图片提取颜色的功能, 通过.setThemeColor()设置按钮和精度条颜色,
202 | 3,兼容compileSdkVersion <25
203 |
204 | V3.3.3
205 |
206 | 1,修复下载路径是重定向路径不能下载的问题
207 |
208 | V3.3.2
209 |
210 | 1,修复正在下载时,返回桌面报错的问题
211 | [https://github.com/WVector/AppUpdate/issues/14](https://github.com/WVector/AppUpdate/issues/14)
212 |
213 | V3.3.1
214 |
215 | 1,修复对话框外可以点击的问题
216 |
217 | V3.3.0
218 |
219 | 1,可以设置不显示通知栏进度条。
220 | 2,可以设置忽略版本。
221 | 3,优化下载时页面卡的问题(由于下载进度回调调用频繁,造成ui线程阻塞)。
222 | 4,可以静默下载,类似网易云音乐,并且设置wifi状态下。
223 |
224 | V3.2.9
225 |
226 | 1,新增自定义对话框。
227 | 2,适配kotlin,写法更简单。
228 |
229 |
230 | ## License
231 |
232 | Copyright 2017 千匍
233 |
234 | Licensed under the Apache License, Version 2.0 (the "License");
235 | you may not use this file except in compliance with the License.
236 | You may obtain a copy of the License at
237 |
238 | http://www.apache.org/licenses/LICENSE-2.0
239 |
240 | Unless required by applicable law or agreed to in writing, software
241 | distributed under the License is distributed on an "AS IS" BASIS,
242 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
243 | See the License for the specific language governing permissions and
244 | limitations under the License.
--------------------------------------------------------------------------------
/_config.yml:
--------------------------------------------------------------------------------
1 | theme: jekyll-theme-cayman
--------------------------------------------------------------------------------
/apk/sample-debug.apk:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WVector/AppUpdate/3041a62adeec3006f74a0b50728fe3b8c57d1a29/apk/sample-debug.apk
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 |
3 | buildscript {
4 | ext.kotlin_version = '1.1.4'
5 | ext.novoda_version = '0.4.0'
6 | repositories {
7 | jcenter()
8 |
9 | maven {
10 | url 'https://maven.google.com/'
11 | name 'Google'
12 | }
13 | }
14 | dependencies {
15 | classpath 'com.android.tools.build:gradle:2.3.3'
16 | classpath "com.novoda:bintray-release:$novoda_version"
17 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
18 | // NOTE: Do not place your application dependencies here; they belong
19 | // in the individual module build.gradle files
20 | }
21 | }
22 | allprojects {
23 | repositories {
24 | jcenter()
25 | mavenCentral()
26 |
27 | // maven{url 'https://dl.bintray.com/qianwen/maven/'}
28 | maven { url "https://www.jitpack.io" }
29 | maven {
30 | url 'https://maven.google.com/'
31 | name 'Google'
32 | }
33 | }
34 | tasks.withType(Javadoc) {
35 | options {
36 | encoding "UTF-8"
37 | charSet 'UTF-8'
38 | links "http://docs.oracle.com/javase/7/docs/api"
39 | }
40 | }
41 | }
42 |
43 | ext {
44 | update_app_version = '3.5.2'
45 | update_app_kotlin_version = '1.2.3'
46 | }
47 |
48 |
49 | //防止上传kotlin是报错,
50 | tasks.getByPath(":update-app-kotlin:releaseAndroidJavadocs").enabled = false
51 | task clean(type: Delete) {
52 | delete rootProject.buildDir
53 | }
54 |
55 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Gradle settings configured through the IDE *will override*
5 | # any settings specified in this file.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
10 | # Specifies the JVM arguments used for the daemon process.
11 | # The setting is particularly useful for tweaking memory settings.
12 | org.gradle.jvmargs=-Xmx1536m
13 |
14 | # When configured, Gradle will run in incubating parallel mode.
15 | # This option should only be used with decoupled projects. More details, visit
16 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
17 | # org.gradle.parallel=true
18 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WVector/AppUpdate/3041a62adeec3006f74a0b50728fe3b8c57d1a29/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Mon Jun 19 11:15:33 CST 2017
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-3.3-all.zip
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # Attempt to set APP_HOME
46 | # Resolve links: $0 may be a link
47 | PRG="$0"
48 | # Need this for relative symlinks.
49 | while [ -h "$PRG" ] ; do
50 | ls=`ls -ld "$PRG"`
51 | link=`expr "$ls" : '.*-> \(.*\)$'`
52 | if expr "$link" : '/.*' > /dev/null; then
53 | PRG="$link"
54 | else
55 | PRG=`dirname "$PRG"`"/$link"
56 | fi
57 | done
58 | SAVED="`pwd`"
59 | cd "`dirname \"$PRG\"`/" >/dev/null
60 | APP_HOME="`pwd -P`"
61 | cd "$SAVED" >/dev/null
62 |
63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
64 |
65 | # Determine the Java command to use to start the JVM.
66 | if [ -n "$JAVA_HOME" ] ; then
67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
68 | # IBM's JDK on AIX uses strange locations for the executables
69 | JAVACMD="$JAVA_HOME/jre/sh/java"
70 | else
71 | JAVACMD="$JAVA_HOME/bin/java"
72 | fi
73 | if [ ! -x "$JAVACMD" ] ; then
74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
75 |
76 | Please set the JAVA_HOME variable in your environment to match the
77 | location of your Java installation."
78 | fi
79 | else
80 | JAVACMD="java"
81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
82 |
83 | Please set the JAVA_HOME variable in your environment to match the
84 | location of your Java installation."
85 | fi
86 |
87 | # Increase the maximum file descriptors if we can.
88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
89 | MAX_FD_LIMIT=`ulimit -H -n`
90 | if [ $? -eq 0 ] ; then
91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
92 | MAX_FD="$MAX_FD_LIMIT"
93 | fi
94 | ulimit -n $MAX_FD
95 | if [ $? -ne 0 ] ; then
96 | warn "Could not set maximum file descriptor limit: $MAX_FD"
97 | fi
98 | else
99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
100 | fi
101 | fi
102 |
103 | # For Darwin, add options to specify how the application appears in the dock
104 | if $darwin; then
105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
106 | fi
107 |
108 | # For Cygwin, switch paths to Windows format before running java
109 | if $cygwin ; then
110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
112 | JAVACMD=`cygpath --unix "$JAVACMD"`
113 |
114 | # We build the pattern for arguments to be converted via cygpath
115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
116 | SEP=""
117 | for dir in $ROOTDIRSRAW ; do
118 | ROOTDIRS="$ROOTDIRS$SEP$dir"
119 | SEP="|"
120 | done
121 | OURCYGPATTERN="(^($ROOTDIRS))"
122 | # Add a user-defined pattern to the cygpath arguments
123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
125 | fi
126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
127 | i=0
128 | for arg in "$@" ; do
129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
131 |
132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
134 | else
135 | eval `echo args$i`="\"$arg\""
136 | fi
137 | i=$((i+1))
138 | done
139 | case $i in
140 | (0) set -- ;;
141 | (1) set -- "$args0" ;;
142 | (2) set -- "$args0" "$args1" ;;
143 | (3) set -- "$args0" "$args1" "$args2" ;;
144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
150 | esac
151 | fi
152 |
153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
154 | function splitJvmOpts() {
155 | JVM_OPTS=("$@")
156 | }
157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
159 |
160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
161 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/image/1498810770.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WVector/AppUpdate/3041a62adeec3006f74a0b50728fe3b8c57d1a29/image/1498810770.png
--------------------------------------------------------------------------------
/image/example_01.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WVector/AppUpdate/3041a62adeec3006f74a0b50728fe3b8c57d1a29/image/example_01.png
--------------------------------------------------------------------------------
/image/example_02.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WVector/AppUpdate/3041a62adeec3006f74a0b50728fe3b8c57d1a29/image/example_02.png
--------------------------------------------------------------------------------
/image/example_03.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WVector/AppUpdate/3041a62adeec3006f74a0b50728fe3b8c57d1a29/image/example_03.png
--------------------------------------------------------------------------------
/image/example_04.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WVector/AppUpdate/3041a62adeec3006f74a0b50728fe3b8c57d1a29/image/example_04.png
--------------------------------------------------------------------------------
/image/example_05.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WVector/AppUpdate/3041a62adeec3006f74a0b50728fe3b8c57d1a29/image/example_05.png
--------------------------------------------------------------------------------
/image/example_06.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WVector/AppUpdate/3041a62adeec3006f74a0b50728fe3b8c57d1a29/image/example_06.png
--------------------------------------------------------------------------------
/json/json.txt:
--------------------------------------------------------------------------------
1 | {
2 | "update": "Yes",
3 | "new_version": "0.8.3",
4 | "apk_file_url": "https://raw.githubusercontent.com/WVector/AppUpdateDemo/master/apk/sample-debug.apk",
5 | "update_log": "1,添加删除信用卡接口。\r\n2,添加vip认证。\r\n3,区分自定义消费,一个小时不限制。\r\n4,添加放弃任务接口,小时内不生成。\r\n5,消费任务手动生成。",
6 | "target_size": "5M",
7 | "new_md5":"b97bea014531123f94c3ba7b7afbaad2",
8 | "constraint": false
9 | }
--------------------------------------------------------------------------------
/json/json1.txt:
--------------------------------------------------------------------------------
1 | {
2 | "update": "Yes",
3 | "new_version": "1.1",
4 | "apk_file_url": "https://raw.githubusercontent.com/WVector/AppUpdateDemo/master/apk/sample-debug.apk",
5 | "update_log": "1,添加删除信用卡接口。\r\n2,添加vip认证。\r\n3,区分自定义消费,一个小时不限制。\r\n4,添加放弃任务接口,小时内不生成。\r\n5,消费任务手动生成。",
6 | "target_size": "5M",
7 | "new_md5":"b97bea014531123f94c3ba7b7afbaad2",
8 | "constraint": true
9 | }
--------------------------------------------------------------------------------
/kotlin.md:
--------------------------------------------------------------------------------
1 |
2 | ### 目录
3 |
4 | * [默认接口协议](#默认接口协议)
5 | * [自定义接口协议](#自定义接口协议)
6 | * [自定义接口协议+自定义对话框](#自定义接口协议+自定义对话框)
7 | * [自定义接口协议+自定义对话框+显示进度对话框](#自定义接口协议+自定义对话框+显示进度对话框)
8 | * [静默下载](#静默下载)
9 | * [静默下载+自定义对话框](#静默下载+自定义对话框)
10 | * [实现HttpManager接口](#实现HttpManager接口)
11 |
12 | ### 默认接口协议
13 |
14 | #### 1,Request 请求参数
15 |
16 | GET:[https://raw.githubusercontent.com/WVector/AppUpdateDemo/master/json/json.txt?appKey=ab55ce55Ac4bcP408cPb8c1Aaeac179c5f6f&version=0.1.0](https://raw.githubusercontent.com/WVector/AppUpdateDemo/master/json/json.txt?appKey=ab55ce55Ac4bcP408cPb8c1Aaeac179c5f6f&version=0.1.0)
17 |
18 |
19 |
20 | 1,参数 appkey
21 | app的唯一标志
22 | appkey可以在manifest文件中配置,也可以在代码中添加
23 | xml配置如下:
24 |
25 | ```xml
26 |
27 |
30 |
31 | ```
32 |
33 | 2,参数 version
34 | 版本号,工具自动添加(服务器判断客户端传过来的version和服务器存的最新的version,决定是否更新)
35 |
36 | ```json
37 |
38 | ```
39 |
40 | 3, 服务器app后台管理界面
41 |
42 | [点击下载后台代码](https://raw.githubusercontent.com/WVector/AppUpdateDemo/master/web/AppVersionManger.rar)
43 |
44 |
45 |
46 | #### 2, Response 服务器的返回json格式
47 | 1,有新版本
48 |
49 | ```json
50 |
51 | {
52 | "update": "Yes",//有新版本
53 | "new_version": "0.8.3",//新版本号
54 | "apk_file_url": "https://raw.githubusercontent.com/WVector/AppUpdateDemo/master/apk/app-debug.apk", //apk下载地址
55 | "update_log": "1,添加删除信用卡接口\r\n2,添加vip认证\r\n3,区分自定义消费,一个小时不限制。\r\n4,添加放弃任务接口,小时内不生成。\r\n5,消费任务手动生成。",//更新内容
56 | "target_size": "5M",//apk大小
57 | "new_md5":"A818AD325EACC199BC62C552A32C35F2",
58 | "constraint": false//是否强制更新
59 | }
60 |
61 | ```
62 |
63 | 2,没有新版本
64 |
65 | ```json
66 |
67 | {
68 | "update": "No",//没有新版本
69 | }
70 |
71 | ```
72 |
73 |
74 | #### 3,客户端检测是否有新版本,并且更新下载
75 |
76 | 和自定义相比,不需要传自定义参数,和实现parseJson方法,其他都一样。
77 |
78 |
79 | ```java
80 |
81 | updateApp(mUpdateUrl, UpdateAppHttpUtil()).update()
82 |
83 | ```
84 |
85 | ### 自定义接口协议
86 |
87 | 根据自己项目的接口,自己传参数给服务器,实现parseJson方法,解析json,设置新版本app信息。
88 |
89 | 同时可以设置以下功能
90 |
91 | - 请求方式,get,post
92 | - 请求参数
93 | - 是否显示下载进度对话框
94 | - 对话框顶部图片(设置图片后自动识别主色调,然后为按钮,进度条设置颜色)
95 | - 按钮,进度条颜色
96 | - apk的下载路径
97 | - 是否忽略版本
98 | - 是否显示通知栏进度条
99 |
100 | 如果以下的例子出错,请看项目中详细的使用案例
101 |
102 | ```java
103 |
104 | //下载路径
105 | val path = Environment.getExternalStorageDirectory().absolutePath
106 | //自定义参数
107 | val params = HashMap()
108 | params.put("appKey", "ab55ce55Ac4bcP408cPb8c1Aaeac179c5f6f")
109 | params.put("appVersion", AppUpdateUtils.getVersionName(this))
110 | params.put("key1", "value2")
111 | params.put("key2", "value3")
112 |
113 | updateApp(mUpdateUrl, UpdateAppHttpUtil())
114 | //自定义配置
115 | {
116 | //以下设置,都是可选
117 | //设置请求方式,默认get
118 | isPost = false
119 | //添加自定义参数,默认version=1.0.0(app的versionName);apkKey=唯一表示(在AndroidManifest.xml配置)
120 | setParams(params)
121 | //设置点击升级后,消失对话框,默认点击升级后,对话框显示下载进度
122 | hideDialogOnDownloading(true)
123 | //设置头部,不设置显示默认的图片,设置图片后自动识别主色调,然后为按钮,进度条设置颜色
124 | topPic = R.mipmap.top_8
125 | //为按钮,进度条设置颜色,默认从顶部图片自动识别。
126 | //setThemeColor(ColorUtil.getRandomColor())
127 | //设置apk下砸路径,默认是在下载到sd卡下/Download/1.0.0/test.apk
128 | targetPath = path
129 | //设置appKey,默认从AndroidManifest.xml获取,如果,使用自定义参数,则此项无效
130 | //setAppKey("ab55ce55Ac4bcP408cPb8c1Aaeac179c5f6f")
131 |
132 | }
133 | .check {
134 | onBefore { showProgressDialog() }
135 | //自定义解析
136 | parseJson {
137 | val jsonObject = JSONObject(it)
138 | UpdateAppBean()
139 | //(必须)是否更新Yes,No
140 | .setUpdate(jsonObject.optString("update"))
141 | //(必须)新版本号,
142 | .setNewVersion(jsonObject.optString("new_version"))
143 | //(必须)下载地址
144 | .setApkFileUrl(jsonObject.optString("apk_file_url"))
145 | //(必须)更新内容
146 | .setUpdateLog(jsonObject.optString("update_log"))
147 | //大小,不设置不显示大小,可以不设置
148 | .setTargetSize(jsonObject.optString("target_size"))
149 | //是否强制更新,可以不设置
150 | .setConstraint(false)
151 | //设置md5,可以不设置
152 | .setNewMd5(jsonObject.optString("new_md5"))
153 |
154 | }
155 | noNewApp { toast("没有新版本") }
156 | onAfter { cancelProgressDialog() }
157 | }
158 |
159 |
160 | ```
161 |
162 | ### 自定义接口协议+自定义对话框
163 |
164 | 其他代码和上面一样,只需重写UpdateCallback 的 hasNewApp方法,然后调用自己的对话框
165 |
166 | ```java
167 |
168 | hasNewApp { updateApp, updateAppManager ->
169 | showDiyDialog(updateApp, updateAppManager)
170 | }
171 |
172 | ```
173 |
174 |
175 |
176 | 下面是简单的对话框,新版本信息从 updateApp 对象获取,updateAppManager 可以控制后台开始下载,下载完自动安装
177 |
178 | 直接调用 'updateAppManager.download();' ,进行下载。
179 |
180 | ```java
181 |
182 | dialog("是否升级到${updateApp.newVersion}版本?"
183 | , "新版本大小:${updateApp.targetSize}\n\n${updateApp.updateLog}")
184 | {
185 | positiveButton("升级") {
186 | updateAppManager.download()
187 | dismiss()
188 |
189 | }
190 | negativeButton("暂不升级") {
191 | dismiss()
192 | }
193 | show()
194 | }
195 |
196 | ```
197 |
198 | ### 自定义接口协议+自定义对话框+显示进度对话框
199 |
200 | 和上面的例子只有在控制下载有区别,传个回调,监听到下载进度。
201 |
202 | onFinish() 当返回 true :下载完自动跳到安装界面,false:则不进行安装
203 |
204 | ```java
205 |
206 | updateAppManager.download {
207 | onStart { HProgressDialogUtils.showHorizontalProgressDialog(this@KotlinActivity, "下载进度", false) }
208 | onProgress { progress, _ -> HProgressDialogUtils.setProgress(Math.round(progress * 100)) }
209 | onFinish {
210 | HProgressDialogUtils.cancel()
211 | true
212 | }
213 | onError {
214 | toast(it)
215 | HProgressDialogUtils.cancel()
216 | }
217 | }
218 | ```
219 |
220 | ### 静默下载
221 | 以下是使用默认协议的例子,
222 | ```java
223 |
224 |
225 | updateApp(mUpdateUrl, UpdateAppHttpUtil())
226 | {
227 | setOnlyWifi()
228 | }.silenceUpdate()
229 |
230 |
231 |
232 | ```
233 |
234 | ### 静默下载+自定义对话框
235 |
236 | 以下是使用默认协议的例子,也可以使用自定义协议(请参考自定义协议例子)
237 |
238 | ```java
239 |
240 |
241 | updateApp(mUpdateUrl, UpdateAppHttpUtil())
242 | {
243 | setOnlyWifi()
244 | }.checkNewApp(object : SilenceUpdateCallback() {
245 | override fun showDialog(updateApp: UpdateAppBean, updateAppManager: UpdateAppManager?, appFile: File) {
246 | showSilenceDiyDialog(updateApp, appFile)
247 | }
248 | })
249 |
250 |
251 | /**
252 | * 自定义对话框
253 | */
254 | private fun showSilenceDiyDialog(updateApp: UpdateAppBean, appFile: File?) {
255 | dialog("是否升级到${updateApp.newVersion}版本?"
256 | , "新版本大小:${updateApp.targetSize}\n\n${updateApp.updateLog}")
257 | {
258 | positiveButton("升级") {
259 | AppUpdateUtils.installApp(this@KotlinActivity, appFile)
260 | dismiss()
261 |
262 | }
263 | negativeButton("暂不升级") {
264 | dismiss()
265 | }
266 | show()
267 | }
268 | }
269 |
270 |
271 | ```
272 |
273 | ### 实现HttpManager接口
274 |
275 | 根据自己项目使用的网络框架,自己实现HttpManager接口
276 |
277 | ```java
278 |
279 | class UpdateAppHttpUtil implements HttpManager {
280 | /**
281 | * 异步get
282 | *
283 | * @param url get请求地址
284 | * @param params get参数
285 | * @param callBack 回调
286 | */
287 | @Override
288 | public void asyncGet(@NonNull String url, @NonNull Map params, @NonNull final Callback callBack) {
289 | OkHttpUtils.get()
290 | .url(url)
291 | .params(params)
292 | .build()
293 | .execute(new StringCallback() {
294 | @Override
295 | public void onError(Call call, Response response, Exception e, int id) {
296 | callBack.onError(validateError(e, response));
297 | }
298 |
299 | @Override
300 | public void onResponse(String response, int id) {
301 | callBack.onResponse(response);
302 | }
303 | });
304 | }
305 |
306 | /**
307 | * 异步post
308 | *
309 | * @param url post请求地址
310 | * @param params post请求参数
311 | * @param callBack 回调
312 | */
313 | @Override
314 | public void asyncPost(@NonNull String url, @NonNull Map params, @NonNull final Callback callBack) {
315 | OkHttpUtils.post()
316 | .url(url)
317 | .params(params)
318 | .build()
319 | .execute(new StringCallback() {
320 | @Override
321 | public void onError(Call call, Response response, Exception e, int id) {
322 | callBack.onError(validateError(e, response));
323 | }
324 |
325 | @Override
326 | public void onResponse(String response, int id) {
327 | callBack.onResponse(response);
328 | }
329 | });
330 |
331 | }
332 |
333 | /**
334 | * 下载
335 | *
336 | * @param url 下载地址
337 | * @param path 文件保存路径
338 | * @param fileName 文件名称
339 | * @param callback 回调
340 | */
341 | @Override
342 | public void download(@NonNull String url, @NonNull String path, @NonNull String fileName, @NonNull final FileCallback callback) {
343 | OkHttpUtils.get()
344 | .url(url)
345 | .build()
346 | .execute(new FileCallBack(path, fileName) {
347 | @Override
348 | public void inProgress(float progress, long total, int id) {
349 | super.inProgress(progress, total, id);
350 | callback.onProgress(progress, total);
351 | }
352 |
353 | @Override
354 | public void onError(Call call, Response response, Exception e, int id) {
355 | callback.onError(validateError(e, response));
356 | }
357 |
358 | @Override
359 | public void onResponse(File response, int id) {
360 | callback.onResponse(response);
361 |
362 | }
363 |
364 | @Override
365 | public void onBefore(Request request, int id) {
366 | super.onBefore(request, id);
367 | callback.onBefore();
368 | }
369 | });
370 |
371 | }
372 | }
373 |
374 | ```
375 |
376 |
--------------------------------------------------------------------------------
/sample/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/sample/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 | apply plugin: 'kotlin-android-extensions'
3 | apply plugin: 'kotlin-android'
4 | android {
5 | compileSdkVersion 26
6 | buildToolsVersion "25.0.3"
7 | defaultConfig {
8 | applicationId "com.vector.appupdatedemo"
9 | minSdkVersion 14
10 | targetSdkVersion 26
11 | versionCode 1
12 | versionName "0.1.0"
13 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
14 | }
15 | buildTypes {
16 | release {
17 | minifyEnabled false
18 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
19 | }
20 | }
21 | lintOptions {
22 | abortOnError false
23 | }
24 | }
25 |
26 | dependencies {
27 |
28 | compile fileTree(include: ['*.jar'], dir: 'libs')
29 | androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
30 | exclude group: 'com.android.support', module: 'support-annotations'
31 | })
32 | compile "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version"
33 |
34 | // compile project(':update-app')
35 | compile project(':update-app-kotlin')
36 |
37 | //compile 'com.qianwen:update-app-kotlin:1.1.0'
38 | //okgo
39 | //rxjava 1
40 | compile 'com.android.support:palette-v7:26.1.0'
41 | //权限引导
42 | compile 'com.android.support:appcompat-v7:26.1.0'
43 |
44 | compile 'com.android.support.constraint:constraint-layout:1.0.2'
45 |
46 | compile 'com.qianwen:okhttp-utils:3.8.0'
47 |
48 | compile 'com.lzy.net:okgo:3.0.4'
49 |
50 | compile 'io.reactivex:rxjava:1.2.9'
51 |
52 | compile 'io.reactivex:rxandroid:1.2.0'
53 | compile 'com.tbruyelle.rxpermissions:rxpermissions:0.9.4@aar'
54 | testCompile 'junit:junit:4.12'
55 | }
56 |
--------------------------------------------------------------------------------
/sample/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in D:\code\AndroidStudio\sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
19 | # Uncomment this to preserve the line number information for
20 | # debugging stack traces.
21 | #-keepattributes SourceFile,LineNumberTable
22 |
23 | # If you keep the line number information, uncomment this to
24 | # hide the original source file name.
25 | #-renamesourcefileattribute SourceFile
26 |
--------------------------------------------------------------------------------
/sample/src/androidTest/java/com/vector/appupdatedemo/ExampleInstrumentedTest.java:
--------------------------------------------------------------------------------
1 | package com.vector.appupdatedemo;
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.assertEquals;
11 |
12 | /**
13 | * Instrumentation 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() throws Exception {
21 | // Context of the app under test.
22 | Context appContext = InstrumentationRegistry.getTargetContext();
23 |
24 | assertEquals("com.vector.appupdatedemo", appContext.getPackageName());
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/sample/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
18 |
19 |
22 |
23 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/sample/src/main/java/com/vector/appupdatedemo/App.java:
--------------------------------------------------------------------------------
1 | package com.vector.appupdatedemo;
2 |
3 | import android.app.Application;
4 |
5 | import com.lzy.okgo.OkGo;
6 | import com.zhy.http.okhttp.OkHttpUtils;
7 |
8 | /**
9 | * Created by Vector
10 | * on 2017/7/17 0017.
11 | */
12 |
13 | public class App extends Application {
14 | @Override
15 | public void onCreate() {
16 | super.onCreate();
17 |
18 |
19 | OkHttpUtils.getInstance()
20 | .init(this)
21 | .debug(true, "okHttp")
22 | .timeout(20 * 1000);
23 |
24 |
25 | OkGo.getInstance().init(this);
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/sample/src/main/java/com/vector/appupdatedemo/ext/ActivityExt.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015-2016 Paweł Gajda
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | @file:Suppress("NOTHING_TO_INLINE")
18 |
19 | package com.vector.appupdatedemo.ext
20 |
21 | import android.app.Activity
22 | import android.support.annotation.IdRes
23 | import android.support.annotation.StringRes
24 | import android.view.View
25 | import android.widget.Toast
26 |
27 | inline fun Activity.find(@IdRes id: Int): T = findViewById(id)
28 |
29 | inline fun Activity.toast(text: CharSequence): Unit = Toast.makeText(this, text, Toast.LENGTH_SHORT).show()
30 |
31 | inline fun Activity.longToast(text: CharSequence): Unit = Toast.makeText(this, text, Toast.LENGTH_LONG).show()
32 |
33 | inline fun Activity.toast(@StringRes resId: Int): Unit = Toast.makeText(this, resId, Toast.LENGTH_SHORT).show()
34 |
35 | inline fun Activity.longToast(@StringRes resId: Int): Unit = Toast.makeText(this, resId, Toast.LENGTH_LONG).show()
36 |
--------------------------------------------------------------------------------
/sample/src/main/java/com/vector/appupdatedemo/ext/CprogressExt.kt:
--------------------------------------------------------------------------------
1 | @file:Suppress("NOTHING_TO_INLINE")
2 |
3 | package com.vector.appupdatedemo.ext
4 |
5 | import android.app.Activity
6 | import com.vector.appupdatedemo.util.CProgressDialogUtils
7 |
8 | /**
9 | * Created by Vector
10 | * on 2017/7/18 0018.
11 | */
12 | inline fun Activity.showProgressDialog() {
13 | CProgressDialogUtils.showProgressDialog(this)
14 | }
15 |
16 | inline fun Activity.cancelProgressDialog() {
17 | CProgressDialogUtils.cancelProgressDialog(this)
18 | }
--------------------------------------------------------------------------------
/sample/src/main/java/com/vector/appupdatedemo/ext/DialogExt.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015-2016 Paweł Gajda
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.vector.appupdatedemo.ext
18 |
19 | import android.app.ProgressDialog
20 | import android.content.Context
21 | import android.content.DialogInterface
22 | import android.database.Cursor
23 | import android.graphics.drawable.Drawable
24 | import android.support.annotation.ArrayRes
25 | import android.support.annotation.DrawableRes
26 | import android.support.annotation.StringRes
27 | import android.support.v4.app.Fragment
28 | import android.support.v7.app.AlertDialog
29 | import android.view.KeyEvent
30 | import android.view.View
31 | import android.widget.ListAdapter
32 |
33 |
34 | fun Fragment.dialog(
35 | title: String? = null,
36 | message: String? = null,
37 | init: (KAlertDialogBuilder.() -> Unit)? = null
38 | ) = activity.dialog(title, message, init)
39 |
40 | fun Context.dialog(
41 | title: String? = null,
42 | message: String? = null,
43 | init: (KAlertDialogBuilder.() -> Unit)? = null
44 | ) = KAlertDialogBuilder(this).apply {
45 | if (title != null) title(title)
46 | if (message != null) message(message)
47 | if (init != null) init()
48 | }
49 |
50 | fun Fragment.dialog(
51 | @StringRes message: Int,
52 | @StringRes title: Int? = null,
53 | init: (KAlertDialogBuilder.() -> Unit)? = null
54 | ) = activity.dialog(message, title, init)
55 |
56 | fun Context.dialog(
57 | @StringRes message: Int,
58 | @StringRes title: Int? = null,
59 | init: (KAlertDialogBuilder.() -> Unit)? = null
60 | ) = KAlertDialogBuilder(this).apply {
61 | if (title != null) title(title)
62 | message(message)
63 | if (init != null) init()
64 | }
65 |
66 | fun Fragment.dialog(init: KAlertDialogBuilder.() -> Unit): KAlertDialogBuilder = activity.dialog(init)
67 |
68 | fun Context.dialog(init: KAlertDialogBuilder.() -> Unit) = KAlertDialogBuilder(this).apply { init() }
69 |
70 | fun Fragment.progressDialog(
71 | @StringRes message: Int? = null,
72 | @StringRes title: Int? = null,
73 | init: (ProgressDialog.() -> Unit)? = null
74 | ) = activity.progressDialog(message, title, init)
75 |
76 | fun Context.progressDialog(
77 | @StringRes message: Int? = null,
78 | @StringRes title: Int? = null,
79 | init: (ProgressDialog.() -> Unit)? = null
80 | ) = progressDialog(false, message?.let { getString(it) }, title?.let { getString(it) }, init)
81 |
82 | fun Fragment.indeterminateProgressDialog(
83 | @StringRes message: Int? = null,
84 | @StringRes title: Int? = null,
85 | init: (ProgressDialog.() -> Unit)? = null
86 | ) = activity.progressDialog(message, title, init)
87 |
88 | fun Context.indeterminateProgressDialog(
89 | @StringRes message: Int? = null,
90 | @StringRes title: Int? = null,
91 | init: (ProgressDialog.() -> Unit)? = null
92 | ) = progressDialog(true, message?.let { getString(it) }, title?.let { getString(it) }, init)
93 |
94 | fun Fragment.progressDialog(
95 | message: String? = null,
96 | title: String? = null,
97 | init: (ProgressDialog.() -> Unit)? = null
98 | ) = activity.progressDialog(message, title, init)
99 |
100 | fun Context.progressDialog(
101 | message: String? = null,
102 | title: String? = null,
103 | init: (ProgressDialog.() -> Unit)? = null
104 | ) = progressDialog(false, message, title, init)
105 |
106 | fun Fragment.indeterminateProgressDialog(
107 | message: String? = null,
108 | title: String? = null,
109 | init: (ProgressDialog.() -> Unit)? = null
110 | ) = activity.indeterminateProgressDialog(message, title, init)
111 |
112 | fun Context.indeterminateProgressDialog(
113 | message: String? = null,
114 | title: String? = null,
115 | init: (ProgressDialog.() -> Unit)? = null
116 | ) = progressDialog(true, message, title, init)
117 |
118 | private fun Context.progressDialog(
119 | indeterminate: Boolean,
120 | message: String? = null,
121 | title: String? = null,
122 | init: (ProgressDialog.() -> Unit)? = null
123 | ) = ProgressDialog(this).apply {
124 | isIndeterminate = indeterminate
125 | if (!indeterminate) setProgressStyle(ProgressDialog.STYLE_HORIZONTAL)
126 | if (message != null) setMessage(message)
127 | if (title != null) setTitle(title)
128 | if (init != null) init()
129 | show()
130 | }
131 |
132 | fun Fragment.sheet(
133 | title: CharSequence? = null,
134 | items: List,
135 | onClick: (Int) -> Unit
136 | ) = activity.sheet(title, items, onClick)
137 |
138 | fun Context.sheet(
139 | title: CharSequence? = null,
140 | items: List,
141 | onClick: (Int) -> Unit
142 | ) {
143 | with(KAlertDialogBuilder(this)) {
144 | if (title != null) title(title)
145 | items(items, onClick)
146 | show()
147 | }
148 | }
149 |
150 | class KAlertDialogBuilder(val ctx: Context) {
151 |
152 | val builder: AlertDialog.Builder = AlertDialog.Builder(ctx)
153 | var dialog: AlertDialog? = null
154 |
155 | fun dismiss() = dialog?.dismiss()
156 |
157 | fun show(): KAlertDialogBuilder {
158 | dialog = builder.create()
159 | dialog!!.show()
160 | return this
161 | }
162 |
163 | fun title(title: CharSequence) {
164 | builder.setTitle(title)
165 | }
166 |
167 | fun title(@StringRes resource: Int) {
168 | builder.setTitle(resource)
169 | }
170 |
171 | fun message(title: CharSequence) {
172 | builder.setMessage(title)
173 | }
174 |
175 | fun message(@StringRes resource: Int) {
176 | builder.setMessage(resource)
177 | }
178 |
179 | fun icon(@DrawableRes icon: Int) {
180 | builder.setIcon(icon)
181 | }
182 |
183 | fun icon(icon: Drawable) {
184 | builder.setIcon(icon)
185 | }
186 |
187 | fun customTitle(title: View) {
188 | builder.setCustomTitle(title)
189 | }
190 |
191 | fun customView(view: View) {
192 | builder.setView(view)
193 | }
194 |
195 | fun cancellable(value: Boolean = true) {
196 | builder.setCancelable(value)
197 | }
198 |
199 | fun onCancel(f: () -> Unit) {
200 | builder.setOnCancelListener { f() }
201 | }
202 |
203 | fun onKey(f: (keyCode: Int, e: KeyEvent) -> Boolean) {
204 | builder.setOnKeyListener({ _, keyCode, event -> f(keyCode, event) })
205 | }
206 |
207 | fun neutralButton(@StringRes textResource: Int = android.R.string.ok, f: DialogInterface.() -> Unit = { dismiss() }) {
208 | neutralButton(ctx.getString(textResource), f)
209 | }
210 |
211 | fun neutralButton(title: String, f: DialogInterface.() -> Unit = { dismiss() }) {
212 | builder.setNeutralButton(title, { dialog, _ -> dialog.f() })
213 | }
214 |
215 | fun positiveButton(@StringRes textResource: Int = android.R.string.ok, f: DialogInterface.() -> Unit) {
216 | positiveButton(ctx.getString(textResource), f)
217 | }
218 |
219 | fun positiveButton(title: String, f: DialogInterface.() -> Unit) {
220 | builder.setPositiveButton(title, { dialog, _ -> dialog.f() })
221 | }
222 |
223 | fun negativeButton(@StringRes textResource: Int = android.R.string.cancel, f: DialogInterface.() -> Unit = { dismiss() }) {
224 | negativeButton(ctx.getString(textResource), f)
225 | }
226 |
227 | fun negativeButton(title: String, f: DialogInterface.() -> Unit = { dismiss() }) {
228 | builder.setNegativeButton(title, { dialog, _ -> dialog.f() })
229 | }
230 |
231 | fun items(@ArrayRes itemsId: Int, f: (which: Int) -> Unit) {
232 | items(ctx.resources!!.getTextArray(itemsId), f)
233 | }
234 |
235 | fun items(items: List, f: (which: Int) -> Unit) {
236 | items(items.toTypedArray(), f)
237 | }
238 |
239 | fun items(items: Array, f: (which: Int) -> Unit) {
240 | builder.setItems(items, { _, which -> f(which) })
241 | }
242 |
243 | fun adapter(adapter: ListAdapter, f: (which: Int) -> Unit) {
244 | builder.setAdapter(adapter, { _, which -> f(which) })
245 | }
246 |
247 | fun adapter(cursor: Cursor, labelColumn: String, f: (which: Int) -> Unit) {
248 | builder.setCursor(cursor, { _, which -> f(which) }, labelColumn)
249 | }
250 | }
--------------------------------------------------------------------------------
/sample/src/main/java/com/vector/appupdatedemo/http/OkGoUpdateHttpUtil.java:
--------------------------------------------------------------------------------
1 | package com.vector.appupdatedemo.http;
2 |
3 | import android.support.annotation.NonNull;
4 |
5 | import com.lzy.okgo.OkGo;
6 | import com.lzy.okgo.model.Progress;
7 | import com.vector.update_app.HttpManager;
8 |
9 | import java.io.File;
10 | import java.util.Map;
11 |
12 | /**
13 | * 使用OkGo实现接口
14 | */
15 |
16 | public class OkGoUpdateHttpUtil implements HttpManager {
17 | /**
18 | * 异步get
19 | *
20 | * @param url get请求地址
21 | * @param params get参数
22 | * @param callBack 回调
23 | */
24 | @Override
25 | public void asyncGet(@NonNull String url, @NonNull Map params, @NonNull final Callback callBack) {
26 | OkGo.get(url).params(params).execute(new com.lzy.okgo.callback.StringCallback() {
27 | @Override
28 | public void onSuccess(com.lzy.okgo.model.Response response) {
29 | callBack.onResponse(response.body());
30 | }
31 |
32 | @Override
33 | public void onError(com.lzy.okgo.model.Response response) {
34 | super.onError(response);
35 | callBack.onError("异常");
36 | }
37 | });
38 | }
39 |
40 | /**
41 | * 异步post
42 | *
43 | * @param url post请求地址
44 | * @param params post请求参数
45 | * @param callBack 回调
46 | */
47 | @Override
48 | public void asyncPost(@NonNull String url, @NonNull Map params, @NonNull final Callback callBack) {
49 | OkGo.post(url).params(params).execute(new com.lzy.okgo.callback.StringCallback() {
50 | @Override
51 | public void onSuccess(com.lzy.okgo.model.Response response) {
52 | callBack.onResponse(response.body());
53 | }
54 |
55 | @Override
56 | public void onError(com.lzy.okgo.model.Response response) {
57 | super.onError(response);
58 | callBack.onError("异常");
59 | }
60 | });
61 | }
62 |
63 | /**
64 | * 下载
65 | *
66 | * @param url 下载地址
67 | * @param path 文件保存路径
68 | * @param fileName 文件名称
69 | * @param callback 回调
70 | */
71 | @Override
72 | public void download(@NonNull String url, @NonNull String path, @NonNull String fileName, @NonNull final FileCallback callback) {
73 | OkGo.get(url).execute(new com.lzy.okgo.callback.FileCallback(path, fileName) {
74 | @Override
75 | public void onSuccess(com.lzy.okgo.model.Response response) {
76 | callback.onResponse(response.body());
77 | }
78 |
79 | @Override
80 | public void onStart(com.lzy.okgo.request.base.Request request) {
81 | super.onStart(request);
82 | callback.onBefore();
83 | }
84 |
85 | @Override
86 | public void onError(com.lzy.okgo.model.Response response) {
87 | super.onError(response);
88 | callback.onError("异常");
89 | }
90 |
91 | @Override
92 | public void downloadProgress(Progress progress) {
93 | super.downloadProgress(progress);
94 |
95 | callback.onProgress(progress.fraction, progress.totalSize);
96 | }
97 | });
98 | }
99 | }
--------------------------------------------------------------------------------
/sample/src/main/java/com/vector/appupdatedemo/http/UpdateAppHttpUtil.java:
--------------------------------------------------------------------------------
1 | package com.vector.appupdatedemo.http;
2 |
3 | import android.support.annotation.NonNull;
4 |
5 | import com.vector.update_app.HttpManager;
6 | import com.zhy.http.okhttp.OkHttpUtils;
7 | import com.zhy.http.okhttp.callback.FileCallBack;
8 | import com.zhy.http.okhttp.callback.StringCallback;
9 |
10 | import java.io.File;
11 | import java.util.Map;
12 |
13 | import okhttp3.Call;
14 | import okhttp3.Request;
15 | import okhttp3.Response;
16 |
17 | /**
18 | * Created by Vector
19 | * on 2017/6/19 0019.
20 | */
21 |
22 | public class UpdateAppHttpUtil implements HttpManager {
23 | /**
24 | * 异步get
25 | *
26 | * @param url get请求地址
27 | * @param params get参数
28 | * @param callBack 回调
29 | */
30 | @Override
31 | public void asyncGet(@NonNull String url, @NonNull Map params, @NonNull final Callback callBack) {
32 | OkHttpUtils.get()
33 | .url(url)
34 | .params(params)
35 | .build()
36 | .execute(new StringCallback() {
37 | @Override
38 | public void onError(Call call, Response response, Exception e, int id) {
39 | callBack.onError(validateError(e, response));
40 | }
41 |
42 | @Override
43 | public void onResponse(String response, int id) {
44 | callBack.onResponse(response);
45 | }
46 | });
47 | }
48 |
49 | /**
50 | * 异步post
51 | *
52 | * @param url post请求地址
53 | * @param params post请求参数
54 | * @param callBack 回调
55 | */
56 | @Override
57 | public void asyncPost(@NonNull String url, @NonNull Map params, @NonNull final Callback callBack) {
58 | OkHttpUtils.post()
59 | .url(url)
60 | .params(params)
61 | .build()
62 | .execute(new StringCallback() {
63 | @Override
64 | public void onError(Call call, Response response, Exception e, int id) {
65 | callBack.onError(validateError(e, response));
66 | }
67 |
68 | @Override
69 | public void onResponse(String response, int id) {
70 | callBack.onResponse(response);
71 | }
72 | });
73 |
74 | }
75 |
76 | /**
77 | * 下载
78 | *
79 | * @param url 下载地址
80 | * @param path 文件保存路径
81 | * @param fileName 文件名称
82 | * @param callback 回调
83 | */
84 | @Override
85 | public void download(@NonNull String url, @NonNull String path, @NonNull String fileName, @NonNull final FileCallback callback) {
86 | OkHttpUtils.get()
87 | .url(url)
88 | .build()
89 | .execute(new FileCallBack(path, fileName) {
90 | @Override
91 | public void inProgress(float progress, long total, int id) {
92 | callback.onProgress(progress, total);
93 | }
94 |
95 | @Override
96 | public void onError(Call call, Response response, Exception e, int id) {
97 | callback.onError(validateError(e, response));
98 | }
99 |
100 | @Override
101 | public void onResponse(File response, int id) {
102 | callback.onResponse(response);
103 |
104 | }
105 |
106 | @Override
107 | public void onBefore(Request request, int id) {
108 | super.onBefore(request, id);
109 | callback.onBefore();
110 | }
111 | });
112 |
113 | }
114 | }
--------------------------------------------------------------------------------
/sample/src/main/java/com/vector/appupdatedemo/ui/KotlinActivity.kt:
--------------------------------------------------------------------------------
1 | package com.vector.appupdatedemo.ui
2 |
3 | import android.os.Bundle
4 | import android.os.Environment
5 | import android.support.v7.app.AppCompatActivity
6 | import com.vector.appupdatedemo.R
7 | import com.vector.appupdatedemo.ext.cancelProgressDialog
8 | import com.vector.appupdatedemo.ext.dialog
9 | import com.vector.appupdatedemo.ext.showProgressDialog
10 | import com.vector.appupdatedemo.ext.toast
11 | import com.vector.appupdatedemo.http.UpdateAppHttpUtil
12 | import com.vector.appupdatedemo.util.HProgressDialogUtils
13 | import com.vector.update_app.SilenceUpdateCallback
14 | import com.vector.update_app.UpdateAppBean
15 | import com.vector.update_app.UpdateAppManager
16 | import com.vector.update_app.utils.AppUpdateUtils
17 | import com.vector.update_app_kotlin.*
18 | import kotlinx.android.synthetic.main.activity_kotlin.*
19 | import org.json.JSONObject
20 | import java.io.File
21 | import java.util.*
22 |
23 | class KotlinActivity : AppCompatActivity() {
24 | private val mUpdateUrl = "https://raw.githubusercontent.com/WVector/AppUpdateDemo/master/json/json.txt"
25 | override fun onCreate(savedInstanceState: Bundle?) {
26 | super.onCreate(savedInstanceState)
27 | setContentView(R.layout.activity_kotlin)
28 | btn_default.setSolidTheme()
29 | btn_diy_1.setStrokeTheme()
30 | btn_diy_2.setStrokeTheme()
31 | btn_diy_3.setStrokeTheme()
32 | btn_diy_4.setStrokeTheme()
33 | btn_diy_5.setStrokeTheme()
34 |
35 | btn_default.setOnClickListener {
36 | defaultUpdate()
37 | }
38 | btn_diy_1.setOnClickListener {
39 | updateDiy1()
40 | }
41 |
42 | btn_diy_2.setOnClickListener {
43 | updateDiy2()
44 | }
45 | btn_diy_3.setOnClickListener {
46 | updateDiy3()
47 | }
48 | btn_diy_4.setOnClickListener {
49 | updateDiy4()
50 | }
51 | btn_diy_5.setOnClickListener {
52 | updateDiy5()
53 | }
54 | }
55 |
56 |
57 | private var isShowDownloadProgress: Boolean = false
58 | /**
59 | * 最简方式
60 | */
61 | private fun defaultUpdate() {
62 | updateApp(mUpdateUrl, UpdateAppHttpUtil()).update()
63 | }
64 |
65 | /**
66 | * 自定义接口协议+自定义对话框
67 | */
68 | private fun updateDiy2() {
69 | isShowDownloadProgress = false
70 | diy()
71 | }
72 |
73 |
74 | /**
75 | * 自定义接口协议+自定义对话框+显示进度对话框
76 | */
77 | private fun updateDiy3() {
78 | isShowDownloadProgress = true
79 | diy()
80 | }
81 |
82 | private fun diy() {
83 | //下载路径
84 | val path = Environment.getExternalStorageDirectory().absolutePath
85 | //自定义参数
86 | val params = HashMap()
87 | params.put("appKey", "ab55ce55Ac4bcP408cPb8c1Aaeac179c5f6f")
88 | params.put("appVersion", AppUpdateUtils.getVersionName(this))
89 | params.put("key1", "value2")
90 | params.put("key2", "value3")
91 |
92 | updateApp(mUpdateUrl, UpdateAppHttpUtil())
93 | //自定义配置
94 | {
95 | //以下设置,都是可选
96 | //设置请求方式,默认get
97 | isPost = false
98 | //添加自定义参数,默认version=1.0.0(app的versionName);apkKey=唯一表示(在AndroidManifest.xml配置)
99 | setParams(params)
100 | //设置apk下砸路径,默认是在下载到sd卡下/Download/1.0.0/test.apk
101 | targetPath = path
102 | //设置appKey,默认从AndroidManifest.xml获取,如果,使用自定义参数,则此项无效
103 | // setAppKey("ab55ce55Ac4bcP408cPb8c1Aaeac179c5f6f")
104 |
105 | }
106 | .check {
107 | onBefore { showProgressDialog() }
108 | //自定义解析
109 | parseJson {
110 | val jsonObject = JSONObject(it)
111 | UpdateAppBean()
112 | //(必须)是否更新Yes,No
113 | .setUpdate(jsonObject.optString("update"))
114 | //(必须)新版本号,
115 | .setNewVersion(jsonObject.optString("new_version"))
116 | //(必须)下载地址
117 | .setApkFileUrl(jsonObject.optString("apk_file_url"))
118 | //(必须)更新内容
119 | .setUpdateLog(jsonObject.optString("update_log"))
120 | //大小,不设置不显示大小,可以不设置
121 | .setTargetSize(jsonObject.optString("target_size"))
122 | //是否强制更新,可以不设置
123 | .setConstraint(false)
124 | //设置md5,可以不设置
125 | .setNewMd5(jsonObject.optString("new_md5"))
126 |
127 | }
128 |
129 | hasNewApp { updateApp, updateAppManager ->
130 | showDiyDialog(updateApp, updateAppManager)
131 | }
132 | noNewApp { toast("没有新版本") }
133 | onAfter { cancelProgressDialog() }
134 | }
135 | }
136 |
137 | /**
138 | * 自定义对话框
139 | */
140 | private fun showDiyDialog(updateApp: UpdateAppBean, updateAppManager: UpdateAppManager) {
141 | dialog("是否升级到${updateApp.newVersion}版本?"
142 | , "新版本大小:${updateApp.targetSize}\n\n${updateApp.updateLog}")
143 | {
144 | positiveButton("升级") {
145 | if (isShowDownloadProgress) {
146 | updateAppManager.download {
147 | onStart { HProgressDialogUtils.showHorizontalProgressDialog(this@KotlinActivity, "下载进度", false) }
148 | onProgress { progress, _ -> HProgressDialogUtils.setProgress(Math.round(progress * 100)) }
149 | onFinish {
150 | HProgressDialogUtils.cancel()
151 | true
152 | }
153 | onError {
154 | toast(it)
155 | HProgressDialogUtils.cancel()
156 | }
157 | }
158 |
159 | } else {
160 | updateAppManager.download()
161 | }
162 |
163 | dismiss()
164 |
165 | }
166 | negativeButton("暂不升级") {
167 | dismiss()
168 | }
169 | show()
170 | }
171 |
172 | }
173 |
174 |
175 | /**
176 | * 自定义接口协议
177 | */
178 | private fun updateDiy1() {
179 | //下载路径
180 | val path = Environment.getExternalStorageDirectory().absolutePath
181 | //自定义参数
182 | val params = HashMap()
183 | params.put("appKey", "ab55ce55Ac4bcP408cPb8c1Aaeac179c5f6f")
184 | params.put("appVersion", AppUpdateUtils.getVersionName(this))
185 | params.put("key1", "value2")
186 | params.put("key2", "value3")
187 |
188 | updateApp(mUpdateUrl, UpdateAppHttpUtil())
189 | //自定义配置
190 | {
191 | //以下设置,都是可选
192 | //设置请求方式,默认get
193 | isPost = false
194 | //添加自定义参数,默认version=1.0.0(app的versionName);apkKey=唯一表示(在AndroidManifest.xml配置)
195 | setParams(params)
196 | //设置点击升级后,消失对话框,默认点击升级后,对话框显示下载进度
197 | hideDialogOnDownloading()
198 | //设置头部,不设置显示默认的图片,设置图片后自动识别主色调,然后为按钮,进度条设置颜色
199 | topPic = R.mipmap.top_8
200 |
201 | //为按钮,进度条设置颜色。
202 | themeColor = 0xffffac5d.toInt()
203 | //设置apk下砸路径,默认是在下载到sd卡下/Download/1.0.0/test.apk
204 | targetPath = path
205 | //设置appKey,默认从AndroidManifest.xml获取,如果,使用自定义参数,则此项无效
206 | // setAppKey("ab55ce55Ac4bcP408cPb8c1Aaeac179c5f6f")
207 |
208 | }
209 | .check {
210 | onBefore { showProgressDialog() }
211 | //自定义解析
212 | parseJson {
213 | val jsonObject = JSONObject(it)
214 | UpdateAppBean()
215 | //(必须)是否更新Yes,No
216 | .setUpdate(jsonObject.optString("update"))
217 | //(必须)新版本号,
218 | .setNewVersion(jsonObject.optString("new_version"))
219 | //(必须)下载地址
220 | .setApkFileUrl(jsonObject.optString("apk_file_url"))
221 | //(必须)更新内容
222 | .setUpdateLog(jsonObject.optString("update_log"))
223 | //大小,不设置不显示大小,可以不设置
224 | .setTargetSize(jsonObject.optString("target_size"))
225 | //是否强制更新,可以不设置
226 | .setConstraint(false)
227 | //设置md5,可以不设置
228 | .setNewMd5(jsonObject.optString("new_md5"))
229 |
230 | }
231 | noNewApp { toast("没有新版本") }
232 | onAfter { cancelProgressDialog() }
233 | }
234 |
235 | }
236 |
237 | /**
238 | * 静默下载
239 | */
240 | private fun updateDiy4() {
241 | updateApp(mUpdateUrl, UpdateAppHttpUtil())
242 | {
243 | setOnlyWifi()
244 | }.silenceUpdate()
245 | }
246 |
247 | /**
248 | * 静默下载+自定义对话框
249 | */
250 | private fun updateDiy5() {
251 | updateApp(mUpdateUrl, UpdateAppHttpUtil())
252 | {
253 | setOnlyWifi()
254 | }.checkNewApp(object : SilenceUpdateCallback() {
255 | override fun showDialog(updateApp: UpdateAppBean, updateAppManager: UpdateAppManager?, appFile: File) {
256 | showSilenceDiyDialog(updateApp, appFile)
257 | }
258 | })
259 | }
260 |
261 | /**
262 | * 自定义对话框
263 | */
264 | private fun showSilenceDiyDialog(updateApp: UpdateAppBean, appFile: File?) {
265 | dialog("是否升级到${updateApp.newVersion}版本?"
266 | , "新版本大小:${updateApp.targetSize}\n\n${updateApp.updateLog}")
267 | {
268 | positiveButton("升级") {
269 | AppUpdateUtils.installApp(this@KotlinActivity, appFile)
270 | dismiss()
271 |
272 | }
273 | negativeButton("暂不升级") {
274 | dismiss()
275 | }
276 | show()
277 | }
278 | }
279 |
280 |
281 | }
282 |
--------------------------------------------------------------------------------
/sample/src/main/java/com/vector/appupdatedemo/ui/MainActivity.java:
--------------------------------------------------------------------------------
1 | package com.vector.appupdatedemo.ui;
2 |
3 | import android.content.Intent;
4 | import android.os.Bundle;
5 | import android.support.v7.app.AppCompatActivity;
6 | import android.view.View;
7 | import android.widget.Button;
8 | import android.widget.ImageView;
9 | import android.widget.Toast;
10 |
11 | import com.tbruyelle.rxpermissions.RxPermissions;
12 | import com.vector.appupdatedemo.R;
13 | import com.vector.update_app.utils.AppUpdateUtils;
14 | import com.vector.update_app.utils.DrawableUtil;
15 |
16 | import rx.functions.Action1;
17 |
18 | import static android.Manifest.permission.WRITE_EXTERNAL_STORAGE;
19 |
20 | public class MainActivity extends AppCompatActivity {
21 |
22 | @Override
23 | protected void onCreate(Bundle savedInstanceState) {
24 | super.onCreate(savedInstanceState);
25 | setContentView(R.layout.activity_main);
26 | DrawableUtil.setTextSolidTheme((Button) findViewById(R.id.btn_java), 2, 60, 0xffedd0be);
27 | DrawableUtil.setTextSolidTheme((Button) findViewById(R.id.btn_kotlin), 2, 60, 0xffff534d);
28 |
29 | ImageView im = findViewById(R.id.iv);
30 |
31 | im.setImageBitmap(AppUpdateUtils.drawableToBitmap(AppUpdateUtils.getAppIcon(this)));
32 |
33 | getPermission();
34 |
35 | }
36 |
37 |
38 | public void getPermission() {
39 | RxPermissions rxPermissions = new RxPermissions(this);
40 | rxPermissions.request(WRITE_EXTERNAL_STORAGE)
41 | .subscribe(new Action1() {
42 | @Override
43 | public void call(Boolean aBoolean) {
44 | if (aBoolean) {
45 | Toast.makeText(MainActivity.this, "已授权", Toast.LENGTH_SHORT).show();
46 | } else {
47 | Toast.makeText(MainActivity.this, "未授权", Toast.LENGTH_SHORT).show();
48 | }
49 | }
50 | });
51 |
52 | }
53 |
54 | public void updateJava(View view) {
55 | startActivity(new Intent(this, JavaActivity.class));
56 | }
57 |
58 | public void updateKotlin(View view) {
59 | startActivity(new Intent(this, KotlinActivity.class));
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/sample/src/main/java/com/vector/appupdatedemo/util/CProgressDialogUtils.java:
--------------------------------------------------------------------------------
1 | package com.vector.appupdatedemo.util;
2 |
3 | import android.app.Activity;
4 | import android.app.ProgressDialog;
5 | import android.content.DialogInterface;
6 |
7 | /**
8 | * Created by Vector on 2016/8/12 0012.
9 | */
10 | public class CProgressDialogUtils {
11 | private static final String TAG = CProgressDialogUtils.class.getSimpleName();
12 | private static ProgressDialog sCircleProgressDialog;
13 |
14 | private CProgressDialogUtils() {
15 | throw new UnsupportedOperationException("cannot be instantiated");
16 | }
17 |
18 | public static void showProgressDialog(Activity activity) {
19 | showProgressDialog(activity, "加载中", false, null);
20 | }
21 |
22 | public static void showProgressDialog(Activity activity, DialogInterface.OnCancelListener listener) {
23 | showProgressDialog(activity, "加载中", true, listener);
24 | }
25 |
26 | public static void showProgressDialog(Activity activity, String msg) {
27 | showProgressDialog(activity, msg, false, null);
28 | }
29 |
30 | public static void showProgressDialog(Activity activity, String msg, DialogInterface.OnCancelListener listener) {
31 | showProgressDialog(activity, msg, true, listener);
32 | }
33 |
34 | public static void showProgressDialog(final Activity activity, String msg, boolean cancelable, DialogInterface.OnCancelListener listener) {
35 | if (activity == null || activity.isFinishing()) {
36 | return;
37 | }
38 |
39 |
40 | if (sCircleProgressDialog == null) {
41 | sCircleProgressDialog = new ProgressDialog(activity);
42 | sCircleProgressDialog.setMessage(msg);
43 | sCircleProgressDialog.setOwnerActivity(activity);
44 | sCircleProgressDialog.setOnCancelListener(listener);
45 | sCircleProgressDialog.setCancelable(cancelable);
46 | } else {
47 | if (activity.equals(sCircleProgressDialog.getOwnerActivity())) {
48 | sCircleProgressDialog.setMessage(msg);
49 | sCircleProgressDialog.setCancelable(cancelable);
50 | sCircleProgressDialog.setOnCancelListener(listener);
51 | } else {
52 | //不相等,所以取消任何ProgressDialog
53 | cancelProgressDialog();
54 | sCircleProgressDialog = new ProgressDialog(activity);
55 | sCircleProgressDialog.setMessage(msg);
56 | sCircleProgressDialog.setCancelable(cancelable);
57 | sCircleProgressDialog.setOwnerActivity(activity);
58 | sCircleProgressDialog.setOnCancelListener(listener);
59 | }
60 | }
61 |
62 | if (!sCircleProgressDialog.isShowing()) {
63 | sCircleProgressDialog.show();
64 | }
65 |
66 | }
67 |
68 |
69 | public static void cancelProgressDialog(Activity activity) {
70 | if (sCircleProgressDialog != null && sCircleProgressDialog.isShowing()) {
71 | if (sCircleProgressDialog.getOwnerActivity() == activity) {
72 | sCircleProgressDialog.cancel();
73 | sCircleProgressDialog = null;
74 | }
75 | }
76 | }
77 |
78 | public static void cancelProgressDialog() {
79 | if (sCircleProgressDialog != null && sCircleProgressDialog.isShowing()) {
80 | sCircleProgressDialog.cancel();
81 | sCircleProgressDialog = null;
82 | }
83 | }
84 |
85 | }
86 |
--------------------------------------------------------------------------------
/sample/src/main/java/com/vector/appupdatedemo/util/HProgressDialogUtils.java:
--------------------------------------------------------------------------------
1 | package com.vector.appupdatedemo.util;
2 |
3 | import android.annotation.SuppressLint;
4 | import android.app.Activity;
5 | import android.app.ProgressDialog;
6 | import android.text.TextUtils;
7 |
8 | /**
9 | * Created by Vector on 2016/8/12 0012.
10 | */
11 | public class HProgressDialogUtils {
12 | private static ProgressDialog sHorizontalProgressDialog;
13 |
14 | private HProgressDialogUtils() {
15 | throw new UnsupportedOperationException("cannot be instantiated");
16 | }
17 |
18 | @SuppressLint("NewApi")
19 | public static void showHorizontalProgressDialog(Activity context, String msg, boolean isShowSize) {
20 | cancel();
21 |
22 | if (sHorizontalProgressDialog == null) {
23 | sHorizontalProgressDialog = new ProgressDialog(context);
24 | sHorizontalProgressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
25 | sHorizontalProgressDialog.setCanceledOnTouchOutside(false);
26 | if (isShowSize)
27 | sHorizontalProgressDialog.setProgressNumberFormat("%2dMB/%1dMB");
28 |
29 | }
30 | if (!TextUtils.isEmpty(msg)) {
31 | sHorizontalProgressDialog.setMessage(msg);
32 | }
33 | sHorizontalProgressDialog.show();
34 |
35 | }
36 |
37 | public static void setMax(long total) {
38 | if (sHorizontalProgressDialog != null) {
39 | sHorizontalProgressDialog.setMax(((int) total) / (1024 * 1024));
40 | }
41 | }
42 |
43 | public static void cancel() {
44 | if (sHorizontalProgressDialog != null) {
45 | sHorizontalProgressDialog.dismiss();
46 | sHorizontalProgressDialog = null;
47 | }
48 | }
49 |
50 | public static void setProgress(int current) {
51 | if (sHorizontalProgressDialog == null) {
52 | return;
53 | }
54 | sHorizontalProgressDialog.setProgress(current);
55 | if (sHorizontalProgressDialog.getProgress() >= sHorizontalProgressDialog.getMax()) {
56 | sHorizontalProgressDialog.dismiss();
57 | sHorizontalProgressDialog = null;
58 | }
59 | }
60 |
61 | public static void setProgress(long current) {
62 | if (sHorizontalProgressDialog == null) {
63 | return;
64 | }
65 | sHorizontalProgressDialog.setProgress(((int) current) / (1024 * 1024));
66 | if (sHorizontalProgressDialog.getProgress() >= sHorizontalProgressDialog.getMax()) {
67 | sHorizontalProgressDialog.dismiss();
68 | sHorizontalProgressDialog = null;
69 | }
70 | }
71 |
72 | public static void onLoading(long total, long current) {
73 | if (sHorizontalProgressDialog == null) {
74 | return;
75 | }
76 | if (current == 0) {
77 | sHorizontalProgressDialog.setMax(((int) total) / (1024 * 1024));
78 | }
79 | sHorizontalProgressDialog.setProgress(((int) current) / (1024 * 1024));
80 | if (sHorizontalProgressDialog.getProgress() >= sHorizontalProgressDialog.getMax()) {
81 | sHorizontalProgressDialog.dismiss();
82 | sHorizontalProgressDialog = null;
83 | }
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/sample/src/main/res/layout/activity_java.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 |
10 |
18 |
19 |
27 |
28 |
29 |
37 |
38 |
46 |
47 |
55 |
56 |
64 |
65 |
73 |
74 |
82 |
83 |
91 |
92 |
93 |
--------------------------------------------------------------------------------
/sample/src/main/res/layout/activity_kotlin.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
9 |
15 |
16 |
24 |
25 |
32 |
33 |
40 |
41 |
48 |
49 |
56 |
57 |
64 |
65 |
--------------------------------------------------------------------------------
/sample/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
11 |
12 |
13 |
21 |
22 |
30 |
31 |
35 |
43 |
44 |
52 |
53 |
54 |
--------------------------------------------------------------------------------
/sample/src/main/res/mipmap-hdpi/top_2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WVector/AppUpdate/3041a62adeec3006f74a0b50728fe3b8c57d1a29/sample/src/main/res/mipmap-hdpi/top_2.png
--------------------------------------------------------------------------------
/sample/src/main/res/mipmap-hdpi/top_3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WVector/AppUpdate/3041a62adeec3006f74a0b50728fe3b8c57d1a29/sample/src/main/res/mipmap-hdpi/top_3.png
--------------------------------------------------------------------------------
/sample/src/main/res/mipmap-hdpi/top_4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WVector/AppUpdate/3041a62adeec3006f74a0b50728fe3b8c57d1a29/sample/src/main/res/mipmap-hdpi/top_4.png
--------------------------------------------------------------------------------
/sample/src/main/res/mipmap-hdpi/top_5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WVector/AppUpdate/3041a62adeec3006f74a0b50728fe3b8c57d1a29/sample/src/main/res/mipmap-hdpi/top_5.png
--------------------------------------------------------------------------------
/sample/src/main/res/mipmap-hdpi/top_6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WVector/AppUpdate/3041a62adeec3006f74a0b50728fe3b8c57d1a29/sample/src/main/res/mipmap-hdpi/top_6.png
--------------------------------------------------------------------------------
/sample/src/main/res/mipmap-hdpi/top_7.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WVector/AppUpdate/3041a62adeec3006f74a0b50728fe3b8c57d1a29/sample/src/main/res/mipmap-hdpi/top_7.png
--------------------------------------------------------------------------------
/sample/src/main/res/mipmap-xxhdpi/app_update_logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WVector/AppUpdate/3041a62adeec3006f74a0b50728fe3b8c57d1a29/sample/src/main/res/mipmap-xxhdpi/app_update_logo.png
--------------------------------------------------------------------------------
/sample/src/main/res/mipmap-xxhdpi/top_8.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WVector/AppUpdate/3041a62adeec3006f74a0b50728fe3b8c57d1a29/sample/src/main/res/mipmap-xxhdpi/top_8.png
--------------------------------------------------------------------------------
/sample/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #1db0b8
4 | #1db0b8
5 | #e94339
6 |
7 |
--------------------------------------------------------------------------------
/sample/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | App更新
3 |
4 |
--------------------------------------------------------------------------------
/sample/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/sample/src/test/java/com/vector/appupdatedemo/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package com.vector.appupdatedemo;
2 |
3 |
4 | import org.junit.Test;
5 |
6 | import static org.junit.Assert.assertEquals;
7 |
8 | /**
9 | * Example local unit test, which will execute on the development machine (host).
10 | *
11 | * @see Testing documentation
12 | */
13 | public class ExampleUnitTest {
14 | @Test
15 | public void addition_isCorrect() throws Exception {
16 |
17 | assertEquals(4, 2 + 2);
18 | }
19 | }
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':sample'
2 | include ':update-app'
3 | include ':update-app-kotlin'
--------------------------------------------------------------------------------
/update-app-kotlin/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/update-app-kotlin/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 | apply plugin: 'kotlin-android'
3 | apply plugin: 'com.novoda.bintray-release'//添加
4 | android {
5 | compileSdkVersion 26
6 | buildToolsVersion "26.0.0"
7 |
8 | defaultConfig {
9 | minSdkVersion 14
10 | targetSdkVersion 26
11 | versionCode 1
12 | versionName "1.0"
13 |
14 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
15 |
16 | }
17 | buildTypes {
18 | release {
19 | minifyEnabled false
20 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
21 | }
22 | }
23 |
24 | sourceSets {
25 | main.java.srcDirs += 'src/main/kotlin'
26 | }
27 | }
28 |
29 | dependencies {
30 | compile fileTree(dir: 'libs', include: ['*.jar'])
31 | androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
32 | exclude group: 'com.android.support', module: 'support-annotations'
33 | })
34 | testCompile 'junit:junit:4.12'
35 |
36 | compile "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version"
37 |
38 | compile 'com.qianwen:update-app:'+rootProject.ext.update_app_version
39 | // compile project(':update-app')
40 | }
41 | //添加
42 | publish {
43 | userOrg = 'qianwen'//bintray.com用户名
44 | groupId = 'com.qianwen'//jcenter上的路径
45 | artifactId = 'update-app-kotlin'//项目名称
46 | publishVersion = rootProject.ext.update_app_kotlin_version//版本号
47 | desc = 'a library for android version update'
48 | website = 'https://github.com/WVector/AppUpdate'
49 | }
50 | repositories {
51 | mavenCentral()
52 | }
53 |
--------------------------------------------------------------------------------
/update-app-kotlin/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in D:\code\AndroidStudio\sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
19 | # Uncomment this to preserve the line number information for
20 | # debugging stack traces.
21 | #-keepattributes SourceFile,LineNumberTable
22 |
23 | # If you keep the line number information, uncomment this to
24 | # hide the original source file name.
25 | #-renamesourcefileattribute SourceFile
26 |
--------------------------------------------------------------------------------
/update-app-kotlin/src/androidTest/java/com/vector/update_app_kotlin/ExampleInstrumentedTest.java:
--------------------------------------------------------------------------------
1 | package com.vector.update_app_kotlin;
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.assertEquals;
11 |
12 | /**
13 | * Instrumentation 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() throws Exception {
21 | // Context of the app under test.
22 | Context appContext = InstrumentationRegistry.getTargetContext();
23 |
24 | assertEquals("com.vector.update_app_kotlin.test", appContext.getPackageName());
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/update-app-kotlin/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/update-app-kotlin/src/main/kotlin/com/vector/update_app_kotlin/TextViewExt.kt:
--------------------------------------------------------------------------------
1 | package com.vector.update_app_kotlin
2 |
3 | import android.widget.TextView
4 | import com.vector.update_app.utils.ColorUtil
5 | import com.vector.update_app.utils.DrawableUtil
6 |
7 | /**
8 | * Created by Vector
9 | * on 2017/7/18 0018.
10 | */
11 |
12 | fun TextView.setSolidTheme(color: Int = ColorUtil.getRandomColor(), strokeWidth: Int = 6, cornerRadius: Int = 10) {
13 | DrawableUtil.setTextSolidTheme(this, strokeWidth, cornerRadius, color)
14 | }
15 |
16 | fun TextView.setStrokeTheme(color: Int = ColorUtil.getRandomColor(), strokeWidth: Int = 6, cornerRadius: Int = 10) {
17 | DrawableUtil.setTextStrokeTheme(this, strokeWidth, cornerRadius, color)
18 | }
19 |
--------------------------------------------------------------------------------
/update-app-kotlin/src/main/kotlin/com/vector/update_app_kotlin/UpdateAppExt.kt:
--------------------------------------------------------------------------------
1 | package com.vector.update_app_kotlin
2 |
3 | import android.app.Activity
4 | import com.vector.update_app.HttpManager
5 | import com.vector.update_app.UpdateAppBean
6 | import com.vector.update_app.UpdateAppManager
7 | import com.vector.update_app.UpdateCallback
8 | import com.vector.update_app.service.DownloadService
9 | import java.io.File
10 |
11 | /**
12 | * Created by Vector
13 | * on 2017/7/18 0018.
14 | */
15 | fun Activity.updateApp(
16 | updateUrl_: String,
17 | httpManager_: HttpManager,
18 | init: (UpdateAppManager.Builder.() -> Unit)? = null): UpdateAppManager {
19 | val act = this
20 | return UpdateAppManager.Builder().apply {
21 | activity = act
22 | httpManager = httpManager_
23 | updateUrl = updateUrl_
24 | if (init != null) init()
25 | }.build()
26 | }
27 |
28 | inline fun UpdateAppManager.check(init: Callback.() -> Unit) {
29 | checkNewApp(Callback().apply(init))
30 | }
31 |
32 | class Callback : UpdateCallback() {
33 | private var _onBefore: (() -> Unit)? = null
34 | private var _noNewApp: (() -> Unit)? = null
35 | private var _onAfter: (() -> Unit)? = null
36 | private var _parseJson: ((json: String?) -> UpdateAppBean)? = null
37 | private var _hasNewApp: ((updateApp: UpdateAppBean, updateAppManager: UpdateAppManager) -> Unit)? = null
38 |
39 | override fun parseJson(json: String?): UpdateAppBean {
40 | if (_parseJson != null) {
41 | return _parseJson!!.invoke(json)
42 | }
43 | return super.parseJson(json)
44 | }
45 |
46 | override fun onBefore() {
47 | _onBefore?.invoke()
48 | }
49 |
50 | override fun hasNewApp(updateApp: UpdateAppBean, updateAppManager: UpdateAppManager) {
51 | if (_hasNewApp != null) {
52 | _hasNewApp?.invoke(updateApp, updateAppManager)
53 | } else {
54 | super.hasNewApp(updateApp, updateAppManager)
55 | }
56 | }
57 |
58 | override fun noNewApp(error: String) {
59 | _noNewApp?.invoke()
60 | }
61 |
62 | override fun onAfter() {
63 | _onAfter?.invoke()
64 | }
65 |
66 |
67 | fun onBefore(listener: () -> Unit) {
68 | _onBefore = listener
69 | }
70 |
71 | fun onAfter(listener: () -> Unit) {
72 | _onAfter = listener
73 | }
74 |
75 | fun noNewApp(listener: () -> Unit) {
76 | _noNewApp = listener
77 | }
78 |
79 | fun hasNewApp(listener: (updateApp: UpdateAppBean, updateAppManager: UpdateAppManager) -> Unit) {
80 | _hasNewApp = listener
81 | }
82 |
83 | fun parseJson(listener: (json: String?) -> UpdateAppBean) {
84 | _parseJson = listener
85 | }
86 | }
87 |
88 | inline fun UpdateAppManager.download(init: DownloadCallback.() -> Unit) {
89 | download(DownloadCallback().apply(init))
90 | }
91 |
92 | class DownloadCallback : DownloadService.DownloadCallback {
93 |
94 |
95 | private var _onStart: (() -> Unit)? = null
96 | private var _onFinish: (() -> Boolean)? = null
97 | private var _onError: ((msg: String) -> Unit)? = null
98 | private var _setMax: ((totalSize: Long) -> Unit)? = null
99 | private var _onInstallAppAndAppOnForeground: ((file: File) -> Boolean)? = null
100 | private var _onProgress: ((progress: Float, totalSize: Long) -> Unit)? = null
101 |
102 | override fun onStart() {
103 | _onStart?.invoke()
104 | }
105 |
106 | override fun onProgress(progress: Float, totalSize: Long) {
107 | _onProgress?.invoke(progress, totalSize)
108 | }
109 |
110 | override fun setMax(totalSize: Long) {
111 | _setMax?.invoke(totalSize)
112 | }
113 |
114 |
115 | override fun onFinish(file: File?): Boolean {
116 | if (_onFinish != null) {
117 | return _onFinish!!.invoke()
118 | } else {
119 | return true
120 | }
121 | }
122 |
123 | override fun onError(msg: String) {
124 | _onError?.invoke(msg)
125 | }
126 |
127 | override fun onInstallAppAndAppOnForeground(file: File?): Boolean {
128 |
129 | return _onInstallAppAndAppOnForeground?.invoke(file!!)!!
130 | }
131 |
132 | fun onStart(listener: () -> Unit) {
133 | _onStart = listener
134 | }
135 |
136 | fun onFinish(listener: () -> Boolean) {
137 | _onFinish = listener
138 | }
139 |
140 | fun onError(listener: (msg: String) -> Unit) {
141 | _onError = listener
142 | }
143 |
144 | fun setMax(listener: (totalSize: Long) -> Unit) {
145 | _setMax = listener
146 | }
147 |
148 | fun onProgress(listener: (progress: Float, totalSize: Long) -> Unit) {
149 | _onProgress = listener
150 | }
151 |
152 | fun onInstallAppAndAppOnForeground(listener: (file: File) -> Boolean) {
153 | _onInstallAppAndAppOnForeground = listener
154 | }
155 | }
--------------------------------------------------------------------------------
/update-app-kotlin/src/test/java/com/vector/update_app_kotlin/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package com.vector.update_app_kotlin;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.junit.Assert.assertEquals;
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() throws Exception {
15 | assertEquals(4, 2 + 2);
16 | }
17 | }
--------------------------------------------------------------------------------
/update-app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/update-app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 | apply plugin: 'com.novoda.bintray-release'//添加
3 | android {
4 | compileSdkVersion 26
5 | buildToolsVersion "25.0.3"
6 | defaultConfig {
7 | minSdkVersion 14
8 | targetSdkVersion 26
9 | versionCode 1
10 | versionName "1.0"
11 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
12 | vectorDrawables.useSupportLibrary = true
13 |
14 | }
15 | buildTypes {
16 | release {
17 | minifyEnabled false
18 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
19 | }
20 | }
21 | lintOptions {
22 | abortOnError false
23 | warning 'InvalidPackage'
24 | }
25 |
26 |
27 | }
28 |
29 | dependencies {
30 | compile fileTree(include: ['*.jar'], dir: 'libs')
31 | androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
32 | exclude group: 'com.android.support', module: 'support-annotations'
33 | })
34 | provided 'com.android.support:appcompat-v7:26.1.0'
35 | testCompile 'junit:junit:4.12'
36 | }
37 |
38 | //添加
39 | publish {
40 | userOrg = 'qianwen'//bintray.com用户名
41 | groupId = 'com.qianwen'//jcenter上的路径
42 | artifactId = 'update-app'//项目名称
43 | publishVersion = rootProject.ext.update_app_version//版本号
44 | desc = 'a library for android version update'
45 | website = 'https://github.com/WVector/AppUpdate'
46 | }
47 | repositories {
48 | mavenCentral()
49 | }
50 |
51 |
--------------------------------------------------------------------------------
/update-app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in D:\code\AndroidStudio\sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
19 | # Uncomment this to preserve the line number information for
20 | # debugging stack traces.
21 | #-keepattributes SourceFile,LineNumberTable
22 |
23 | # If you keep the line number information, uncomment this to
24 | # hide the original source file name.
25 | #-renamesourcefileattribute SourceFile
26 |
--------------------------------------------------------------------------------
/update-app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
18 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/update-app/src/main/java/com/vector/update_app/HttpManager.java:
--------------------------------------------------------------------------------
1 | package com.vector.update_app;
2 |
3 | import android.support.annotation.NonNull;
4 |
5 | import java.io.File;
6 | import java.io.Serializable;
7 | import java.util.Map;
8 |
9 | /**
10 | * app版本更新接口
11 | */
12 | public interface HttpManager extends Serializable {
13 | /**
14 | * 异步get
15 | *
16 | * @param url get请求地址
17 | * @param params get参数
18 | * @param callBack 回调
19 | */
20 | void asyncGet(@NonNull String url, @NonNull Map params, @NonNull Callback callBack);
21 |
22 |
23 | /**
24 | * 异步post
25 | *
26 | * @param url post请求地址
27 | * @param params post请求参数
28 | * @param callBack 回调
29 | */
30 | void asyncPost(@NonNull String url, @NonNull Map params, @NonNull Callback callBack);
31 |
32 | /**
33 | * 下载
34 | *
35 | * @param url 下载地址
36 | * @param path 文件保存路径
37 | * @param fileName 文件名称
38 | * @param callback 回调
39 | */
40 | void download(@NonNull String url, @NonNull String path, @NonNull String fileName, @NonNull FileCallback callback);
41 |
42 | /**
43 | * 下载回调
44 | */
45 | interface FileCallback {
46 | /**
47 | * 进度
48 | *
49 | * @param progress 进度0.00 - 0.50 - 1.00
50 | * @param total 文件总大小 单位字节
51 | */
52 | void onProgress(float progress, long total);
53 |
54 | /**
55 | * 错误回调
56 | *
57 | * @param error 错误提示
58 | */
59 | void onError(String error);
60 |
61 | /**
62 | * 结果回调
63 | *
64 | * @param file 下载好的文件
65 | */
66 | void onResponse(File file);
67 |
68 | /**
69 | * 请求之前
70 | */
71 | void onBefore();
72 | }
73 |
74 | /**
75 | * 网络请求回调
76 | */
77 | interface Callback {
78 | /**
79 | * 结果回调
80 | *
81 | * @param result 结果
82 | */
83 | void onResponse(String result);
84 |
85 | /**
86 | * 错误回调
87 | *
88 | * @param error 错误提示
89 | */
90 | void onError(String error);
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/update-app/src/main/java/com/vector/update_app/SilenceUpdateCallback.java:
--------------------------------------------------------------------------------
1 | package com.vector.update_app;
2 |
3 | import com.vector.update_app.service.DownloadService;
4 | import com.vector.update_app.utils.AppUpdateUtils;
5 |
6 | import java.io.File;
7 |
8 | /**
9 | * Created by Vector
10 | * on 2017/7/20 0020.
11 | */
12 |
13 | public class SilenceUpdateCallback extends UpdateCallback {
14 | @Override
15 | protected final void hasNewApp(final UpdateAppBean updateApp, final UpdateAppManager updateAppManager) {
16 | //添加信息
17 | UpdateAppBean updateAppBean = updateAppManager.fillUpdateAppData();
18 | //设置不显示通知栏下载进度
19 | updateAppBean.dismissNotificationProgress(true);
20 |
21 | if (AppUpdateUtils.appIsDownloaded(updateApp)) {
22 | showDialog(updateApp, updateAppManager, AppUpdateUtils.getAppFile(updateApp));
23 | } else {
24 | //假如是onlyWifi,则进行判断网络环境
25 | if (updateApp.isOnlyWifi() && !AppUpdateUtils.isWifi(updateAppManager.getContext())) {
26 | //要求是wifi下,且当前不是wifi环境
27 | return;
28 | }
29 | updateAppManager.download(new DownloadService.DownloadCallback() {
30 | @Override
31 | public void onStart() {
32 |
33 | }
34 |
35 | @Override
36 | public void onProgress(float progress, long totalSize) {
37 |
38 | }
39 |
40 | @Override
41 | public void setMax(long totalSize) {
42 |
43 | }
44 |
45 | @Override
46 | public boolean onFinish(File file) {
47 | showDialog(updateApp, updateAppManager, file);
48 | return false;
49 | }
50 |
51 |
52 | @Override
53 | public void onError(String msg) {
54 |
55 | }
56 |
57 | @Override
58 | public boolean onInstallAppAndAppOnForeground(File file) {
59 | return false;
60 | }
61 | });
62 | }
63 | }
64 |
65 | /**
66 | * 使用默认对话框,
67 | *
68 | * @param updateApp 新app信息
69 | * @param updateAppManager 网路接口
70 | * @param appFile 下载好的app文件
71 | */
72 | protected void showDialog(UpdateAppBean updateApp, UpdateAppManager updateAppManager, File appFile) {
73 | updateAppManager.showDialogFragment();
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/update-app/src/main/java/com/vector/update_app/UpdateAppBean.java:
--------------------------------------------------------------------------------
1 | package com.vector.update_app;
2 |
3 | import android.text.TextUtils;
4 |
5 | import java.io.Serializable;
6 |
7 | /**
8 | * 版本信息
9 | */
10 | public class UpdateAppBean implements Serializable {
11 | private static final long serialVersionUID = 1L;
12 |
13 | /**
14 | * update : Yes
15 | * new_version : xxxxx
16 | * apk_url : http://cdn.the.url.of.apk/or/patch
17 | * update_log : xxxx
18 | * delta : false
19 | * new_md5 : xxxxxxxxxxxxxx
20 | * target_size : 601132
21 | */
22 | //是否有新版本
23 | private String update;
24 | //新版本号
25 | private String new_version;
26 | //新app下载地址
27 | private String apk_file_url;
28 | //更新日志
29 | private String update_log;
30 | //配置默认更新dialog 的title
31 | private String update_def_dialog_title;
32 | //新app大小
33 | private String target_size;
34 | //是否强制更新
35 | private boolean constraint;
36 | //md5
37 | private String new_md5;
38 | //是否增量 暂时不用
39 | private boolean delta;
40 | //服务器端的原生返回数据(json),方便使用者在hasNewApp自定义渲染dialog的时候可以有别的控制,比如:#issues/59
41 | private String origin_res;
42 | /**********以下是内部使用的数据**********/
43 |
44 | //网络工具,内部使用
45 | private HttpManager httpManager;
46 | private String targetPath;
47 | private boolean mHideDialog;
48 | private boolean mShowIgnoreVersion;
49 | private boolean mDismissNotificationProgress;
50 | private boolean mOnlyWifi;
51 |
52 | //是否隐藏对话框下载进度条,内部使用
53 | public boolean isHideDialog() {
54 | return mHideDialog;
55 | }
56 |
57 | public void setHideDialog(boolean hideDialog) {
58 | mHideDialog = hideDialog;
59 | }
60 |
61 | public boolean isUpdate() {
62 | return !TextUtils.isEmpty(this.update) && "Yes".equals(this.update);
63 | }
64 |
65 | public HttpManager getHttpManager() {
66 | return httpManager;
67 | }
68 |
69 | public void setHttpManager(HttpManager httpManager) {
70 | this.httpManager = httpManager;
71 | }
72 |
73 | public String getTargetPath() {
74 | return targetPath;
75 | }
76 |
77 | public void setTargetPath(String targetPath) {
78 | this.targetPath = targetPath;
79 | }
80 |
81 | public boolean isConstraint() {
82 | return constraint;
83 | }
84 |
85 | public UpdateAppBean setConstraint(boolean constraint) {
86 | this.constraint = constraint;
87 | return this;
88 | }
89 |
90 | public String getUpdate() {
91 | return update;
92 | }
93 |
94 | public UpdateAppBean setUpdate(String update) {
95 | this.update = update;
96 | return this;
97 | }
98 |
99 | public String getNewVersion() {
100 | return new_version;
101 | }
102 |
103 | public UpdateAppBean setNewVersion(String new_version) {
104 | this.new_version = new_version;
105 | return this;
106 | }
107 |
108 | public String getApkFileUrl() {
109 | return apk_file_url;
110 | }
111 |
112 |
113 | public UpdateAppBean setApkFileUrl(String apk_file_url) {
114 | this.apk_file_url = apk_file_url;
115 | return this;
116 | }
117 |
118 | public String getUpdateLog() {
119 | return update_log;
120 | }
121 |
122 | public UpdateAppBean setUpdateLog(String update_log) {
123 | this.update_log = update_log;
124 | return this;
125 | }
126 |
127 | public String getUpdateDefDialogTitle() {
128 | return update_def_dialog_title;
129 | }
130 |
131 | public UpdateAppBean setUpdateDefDialogTitle(String updateDefDialogTitle) {
132 | this.update_def_dialog_title = updateDefDialogTitle;
133 | return this;
134 | }
135 |
136 | public boolean isDelta() {
137 | return delta;
138 | }
139 |
140 | public void setDelta(boolean delta) {
141 | this.delta = delta;
142 | }
143 |
144 | public String getNewMd5() {
145 | return new_md5;
146 | }
147 |
148 | public UpdateAppBean setNewMd5(String new_md5) {
149 | this.new_md5 = new_md5;
150 | return this;
151 | }
152 |
153 | public String getTargetSize() {
154 | return target_size;
155 | }
156 |
157 | public UpdateAppBean setTargetSize(String target_size) {
158 | this.target_size = target_size;
159 | return this;
160 | }
161 |
162 | public boolean isShowIgnoreVersion() {
163 | return mShowIgnoreVersion;
164 | }
165 |
166 | public void showIgnoreVersion(boolean showIgnoreVersion) {
167 | mShowIgnoreVersion = showIgnoreVersion;
168 | }
169 |
170 | public void dismissNotificationProgress(boolean dismissNotificationProgress) {
171 | mDismissNotificationProgress = dismissNotificationProgress;
172 | }
173 |
174 | public boolean isDismissNotificationProgress() {
175 | return mDismissNotificationProgress;
176 | }
177 |
178 | public boolean isOnlyWifi() {
179 | return mOnlyWifi;
180 | }
181 |
182 | public void setOnlyWifi(boolean onlyWifi) {
183 | mOnlyWifi = onlyWifi;
184 | }
185 |
186 | public String getOriginRes() {
187 | return origin_res;
188 | }
189 |
190 | public UpdateAppBean setOriginRes(String originRes) {
191 | this.origin_res = originRes;
192 | return this;
193 | }
194 |
195 | }
196 |
--------------------------------------------------------------------------------
/update-app/src/main/java/com/vector/update_app/UpdateAppManager.java:
--------------------------------------------------------------------------------
1 | package com.vector.update_app;
2 |
3 | import android.app.Activity;
4 | import android.content.ComponentName;
5 | import android.content.Context;
6 | import android.content.ServiceConnection;
7 | import android.os.Bundle;
8 | import android.os.Environment;
9 | import android.os.IBinder;
10 | import android.support.annotation.DrawableRes;
11 | import android.support.annotation.NonNull;
12 | import android.support.annotation.Nullable;
13 | import android.support.v4.app.FragmentActivity;
14 | import android.text.TextUtils;
15 | import android.util.Log;
16 | import android.widget.Toast;
17 |
18 | import com.vector.update_app.listener.ExceptionHandler;
19 | import com.vector.update_app.listener.ExceptionHandlerHelper;
20 | import com.vector.update_app.listener.IUpdateDialogFragmentListener;
21 | import com.vector.update_app.service.DownloadService;
22 | import com.vector.update_app.utils.AppUpdateUtils;
23 |
24 | import java.util.HashMap;
25 | import java.util.Map;
26 |
27 | /**
28 | * 版本更新管理器
29 | */
30 | public class UpdateAppManager {
31 | final static String INTENT_KEY = "update_dialog_values";
32 | final static String THEME_KEY = "theme_color";
33 | final static String TOP_IMAGE_KEY = "top_resId";
34 | private final static String UPDATE_APP_KEY = "UPDATE_APP_KEY";
35 | private static final String TAG = UpdateAppManager.class.getSimpleName();
36 | private Map mParams;
37 | // 是否忽略默认参数,解决
38 | private boolean mIgnoreDefParams = false;
39 | private Activity mActivity;
40 | private HttpManager mHttpManager;
41 | private String mUpdateUrl;
42 | private int mThemeColor;
43 | private
44 | @DrawableRes
45 | int mTopPic;
46 | private String mAppKey;
47 | private UpdateAppBean mUpdateApp;
48 | private String mTargetPath;
49 | private boolean isPost;
50 | private boolean mHideDialog;
51 | private boolean mShowIgnoreVersion;
52 | private boolean mDismissNotificationProgress;
53 | private boolean mOnlyWifi;
54 | //自定义参数
55 | private IUpdateDialogFragmentListener mUpdateDialogFragmentListener;
56 |
57 | private UpdateAppManager(Builder builder) {
58 | mActivity = builder.getActivity();
59 | mHttpManager = builder.getHttpManager();
60 | mUpdateUrl = builder.getUpdateUrl();
61 |
62 | mThemeColor = builder.getThemeColor();
63 | mTopPic = builder.getTopPic();
64 |
65 | mIgnoreDefParams = builder.isIgnoreDefParams();
66 | if(!mIgnoreDefParams) {
67 | mAppKey = builder.getAppKey();
68 | }
69 | mTargetPath = builder.getTargetPath();
70 | isPost = builder.isPost();
71 | mParams = builder.getParams();
72 | mHideDialog = builder.isHideDialog();
73 | mShowIgnoreVersion = builder.isShowIgnoreVersion();
74 | mDismissNotificationProgress = builder.isDismissNotificationProgress();
75 | mOnlyWifi = builder.isOnlyWifi();
76 | mUpdateDialogFragmentListener = builder.getUpdateDialogFragmentListener();
77 | }
78 |
79 | /**
80 | * 可以直接利用下载功能,
81 | *
82 | * @param context 上下文
83 | * @param updateAppBean 下载信息配置
84 | * @param downloadCallback 下载回调
85 | */
86 | public static void download(final Context context, @NonNull final UpdateAppBean updateAppBean, @Nullable final DownloadService.DownloadCallback downloadCallback) {
87 |
88 | if (updateAppBean == null) {
89 | throw new NullPointerException("updateApp 不能为空");
90 | }
91 |
92 | DownloadService.bindService(context.getApplicationContext(), new ServiceConnection() {
93 | @Override
94 | public void onServiceConnected(ComponentName name, IBinder service) {
95 | ((DownloadService.DownloadBinder) service).start(updateAppBean, downloadCallback);
96 | }
97 |
98 | @Override
99 | public void onServiceDisconnected(ComponentName name) {
100 |
101 | }
102 | });
103 | }
104 |
105 | public Context getContext() {
106 | return mActivity;
107 | }
108 |
109 | /**
110 | * 跳转到更新页面
111 | */
112 | // public void showDialog() {
113 | // if (verify()) return;
114 | //
115 | //
116 | // if (mActivity != null && !mActivity.isFinishing()) {
117 | // Intent updateIntent = new Intent(mActivity, DialogActivity.class);
118 | // fillUpdateAppData();
119 | // updateIntent.putExtra(INTENT_KEY, mUpdateApp);
120 | // if (mThemeColor != 0) {
121 | // updateIntent.putExtra(THEME_KEY, mThemeColor);
122 | // }
123 | //
124 | // if (mTopPic != 0) {
125 | // updateIntent.putExtra(TOP_IMAGE_KEY, mTopPic);
126 | // }
127 | // mActivity.startActivity(updateIntent);
128 | // }
129 | //
130 | // }
131 |
132 | /**
133 | * @return 新版本信息
134 | */
135 | public UpdateAppBean fillUpdateAppData() {
136 | if (mUpdateApp != null) {
137 | mUpdateApp.setTargetPath(mTargetPath);
138 | mUpdateApp.setHttpManager(mHttpManager);
139 | mUpdateApp.setHideDialog(mHideDialog);
140 | mUpdateApp.showIgnoreVersion(mShowIgnoreVersion);
141 | mUpdateApp.dismissNotificationProgress(mDismissNotificationProgress);
142 | mUpdateApp.setOnlyWifi(mOnlyWifi);
143 | return mUpdateApp;
144 | }
145 |
146 | return null;
147 | }
148 |
149 |
150 | private boolean verify() {
151 | //版本忽略
152 | if (mShowIgnoreVersion && AppUpdateUtils.isNeedIgnore(mActivity, mUpdateApp.getNewVersion())) {
153 | return true;
154 | }
155 |
156 | // String preSuffix = "/storage/emulated";
157 |
158 | if (TextUtils.isEmpty(mTargetPath)
159 | // || !mTargetPath.startsWith(preSuffix)
160 | ) {
161 | Log.e(TAG, "下载路径错误:" + mTargetPath);
162 | return true;
163 | }
164 | return mUpdateApp == null;
165 | }
166 |
167 | /**
168 | * 跳转到更新页面
169 | */
170 | public void showDialogFragment() {
171 |
172 | //校验
173 | if (verify()) return;
174 |
175 | if (mActivity != null && !mActivity.isFinishing()) {
176 | Bundle bundle = new Bundle();
177 | //添加信息,
178 | fillUpdateAppData();
179 | bundle.putSerializable(INTENT_KEY, mUpdateApp);
180 | if (mThemeColor != 0) {
181 | bundle.putInt(THEME_KEY, mThemeColor);
182 | }
183 |
184 | if (mTopPic != 0) {
185 | bundle.putInt(TOP_IMAGE_KEY, mTopPic);
186 | }
187 |
188 | UpdateDialogFragment
189 | .newInstance(bundle)
190 | .setUpdateDialogFragmentListener(mUpdateDialogFragmentListener)
191 | .show(((FragmentActivity) mActivity).getSupportFragmentManager(), "dialog");
192 |
193 | }
194 |
195 | }
196 |
197 | /**
198 | * 静默更新
199 | */
200 | public void silenceUpdate() {
201 | checkNewApp(new SilenceUpdateCallback());
202 | }
203 |
204 | /**
205 | * 最简方式
206 | */
207 |
208 | public void update() {
209 | checkNewApp(new UpdateCallback());
210 | }
211 |
212 | /**
213 | * 检测是否有新版本
214 | *
215 | * @param callback 更新回调
216 | */
217 | public void checkNewApp(final UpdateCallback callback) {
218 | if (callback == null) {
219 | return;
220 | }
221 | callback.onBefore();
222 |
223 | if (DownloadService.isRunning || UpdateDialogFragment.isShow) {
224 | callback.onAfter();
225 | Toast.makeText(mActivity, "app正在更新", Toast.LENGTH_SHORT).show();
226 | return;
227 | }
228 |
229 | //拼接参数
230 | Map params = new HashMap();
231 | if(!mIgnoreDefParams) {
232 | if (!TextUtils.isEmpty(mAppKey)) {
233 | params.put("appKey", mAppKey);
234 | }
235 | String versionName = AppUpdateUtils.getVersionName(mActivity);
236 | //过滤掉,debug 这情况
237 | if (versionName.endsWith("-debug")) {
238 | versionName = versionName.substring(0, versionName.lastIndexOf('-'));
239 | }
240 | if (!TextUtils.isEmpty(versionName)) {
241 | params.put("version", versionName);
242 | }
243 | }
244 |
245 | //添加自定义参数,其实可以实现HttManager中添加
246 | if (mParams != null && !mParams.isEmpty()) {
247 | //清空,那就使用自定参数
248 | params.clear();
249 | params.putAll(mParams);
250 | }
251 |
252 | //网络请求
253 | if (isPost) {
254 | mHttpManager.asyncPost(mUpdateUrl, params, new HttpManager.Callback() {
255 | @Override
256 | public void onResponse(String result) {
257 | callback.onAfter();
258 | if (result != null) {
259 | processData(result, callback);
260 | }
261 | }
262 |
263 | @Override
264 | public void onError(String error) {
265 | callback.onAfter();
266 | callback.noNewApp(error);
267 | }
268 | });
269 | } else {
270 | mHttpManager.asyncGet(mUpdateUrl, params, new HttpManager.Callback() {
271 | @Override
272 | public void onResponse(String result) {
273 | callback.onAfter();
274 | if (result != null) {
275 | processData(result, callback);
276 | }
277 | }
278 |
279 | @Override
280 | public void onError(String error) {
281 | callback.onAfter();
282 | callback.noNewApp(error);
283 | }
284 | });
285 | }
286 | }
287 |
288 | /**
289 | * 后台下载
290 | *
291 | * @param downloadCallback 后台下载回调
292 | */
293 | public void download(@Nullable final DownloadService.DownloadCallback downloadCallback) {
294 | if (mUpdateApp == null) {
295 | throw new NullPointerException("updateApp 不能为空");
296 | }
297 | mUpdateApp.setTargetPath(mTargetPath);
298 | mUpdateApp.setHttpManager(mHttpManager);
299 | DownloadService.bindService(mActivity.getApplicationContext(), new ServiceConnection() {
300 | @Override
301 | public void onServiceConnected(ComponentName name, IBinder service) {
302 | ((DownloadService.DownloadBinder) service).start(mUpdateApp, downloadCallback);
303 | }
304 |
305 | @Override
306 | public void onServiceDisconnected(ComponentName name) {
307 |
308 | }
309 | });
310 | }
311 |
312 | /**
313 | * 后台下载
314 | */
315 | public void download() {
316 | download(null);
317 | }
318 |
319 | /**
320 | * 解析
321 | *
322 | * @param result
323 | * @param callback
324 | */
325 | private void processData(String result, @NonNull UpdateCallback callback) {
326 | try {
327 | mUpdateApp = callback.parseJson(result);
328 | if (mUpdateApp.isUpdate()) {
329 | callback.hasNewApp(mUpdateApp, this);
330 | //假如是静默下载,可能需要判断,
331 | //是否wifi,
332 | //是否已经下载,如果已经下载直接提示安装
333 | //没有则进行下载,监听下载完成,弹出安装对话框
334 |
335 | } else {
336 | callback.noNewApp("没有新版本");
337 | }
338 | } catch (Exception ignored) {
339 | ignored.printStackTrace();
340 | callback.noNewApp(String.format("解析自定义更新配置消息出错[%s]", ignored.getMessage()));
341 | }
342 | }
343 |
344 | public static class Builder {
345 | //必须有
346 | private Activity mActivity;
347 | //必须有
348 | private HttpManager mHttpManager;
349 | //必须有
350 | private String mUpdateUrl;
351 |
352 | //1,设置按钮,进度条的颜色
353 | private int mThemeColor = 0;
354 | //2,顶部的图片
355 | private
356 | @DrawableRes
357 | int mTopPic = 0;
358 | //3,唯一的appkey
359 | private String mAppKey;
360 | //4,apk的下载路径
361 | private String mTargetPath;
362 | //5,是否是post请求,默认是get
363 | private boolean isPost;
364 | //6,自定义参数
365 | private Map params;
366 | // 是否忽略默认参数,解决
367 | private boolean mIgnoreDefParams = false;
368 | //7,是否隐藏对话框下载进度条
369 | private boolean mHideDialog = false;
370 | private boolean mShowIgnoreVersion;
371 | private boolean dismissNotificationProgress;
372 | private boolean mOnlyWifi;
373 | private IUpdateDialogFragmentListener mUpdateDialogFragmentListener;
374 |
375 | public Map getParams() {
376 | return params;
377 | }
378 |
379 | /**
380 | * 自定义请求参数
381 | *
382 | * @param params 自定义请求参数
383 | * @return Builder
384 | */
385 | public Builder setParams(Map params) {
386 | this.params = params;
387 | return this;
388 | }
389 |
390 | public boolean isIgnoreDefParams() {
391 | return mIgnoreDefParams;
392 | }
393 |
394 | /**
395 | * @param ignoreDefParams 是否忽略默认的参数注入 appKey version
396 | * @return Builder
397 | */
398 | public Builder setIgnoreDefParams(boolean ignoreDefParams) {
399 | this.mIgnoreDefParams = ignoreDefParams;
400 | return this;
401 | }
402 |
403 | public boolean isPost() {
404 | return isPost;
405 | }
406 |
407 | /**
408 | * 是否是post请求,默认是get
409 | *
410 | * @param post 是否是post请求,默认是get
411 | * @return Builder
412 | */
413 | public Builder setPost(boolean post) {
414 | isPost = post;
415 | return this;
416 | }
417 |
418 | public String getTargetPath() {
419 | return mTargetPath;
420 | }
421 |
422 | /**
423 | * apk的下载路径,
424 | *
425 | * @param targetPath apk的下载路径,
426 | * @return Builder
427 | */
428 | public Builder setTargetPath(String targetPath) {
429 | mTargetPath = targetPath;
430 | return this;
431 | }
432 |
433 | public String getAppKey() {
434 | return mAppKey;
435 | }
436 |
437 | /**
438 | * 唯一的appkey
439 | *
440 | * @param appKey 唯一的appkey
441 | * @return Builder
442 | */
443 | public Builder setAppKey(String appKey) {
444 | mAppKey = appKey;
445 | return this;
446 | }
447 |
448 | public Activity getActivity() {
449 | return mActivity;
450 | }
451 |
452 | /**
453 | * 是否是post请求,默认是get
454 | *
455 | * @param activity 当前提示的Activity
456 | * @return Builder
457 | */
458 | public Builder setActivity(Activity activity) {
459 | mActivity = activity;
460 | return this;
461 | }
462 |
463 | public HttpManager getHttpManager() {
464 | return mHttpManager;
465 | }
466 |
467 | /**
468 | * 设置网络工具
469 | *
470 | * @param httpManager 自己实现的网络对象
471 | * @return Builder
472 | */
473 | public Builder setHttpManager(HttpManager httpManager) {
474 | mHttpManager = httpManager;
475 | return this;
476 | }
477 |
478 | public String getUpdateUrl() {
479 | return mUpdateUrl;
480 | }
481 |
482 | /**
483 | * 更新地址
484 | *
485 | * @param updateUrl 更新地址
486 | * @return Builder
487 | */
488 | public Builder setUpdateUrl(String updateUrl) {
489 | mUpdateUrl = updateUrl;
490 | return this;
491 | }
492 |
493 | public int getThemeColor() {
494 | return mThemeColor;
495 | }
496 |
497 | /**
498 | * 设置按钮,进度条的颜色
499 | *
500 | * @param themeColor 设置按钮,进度条的颜色
501 | * @return Builder
502 | */
503 | public Builder setThemeColor(int themeColor) {
504 | mThemeColor = themeColor;
505 | return this;
506 | }
507 |
508 | public int getTopPic() {
509 | return mTopPic;
510 | }
511 |
512 | /**
513 | * 顶部的图片
514 | *
515 | * @param topPic 顶部的图片
516 | * @return Builder
517 | */
518 | public Builder setTopPic(int topPic) {
519 | mTopPic = topPic;
520 | return this;
521 | }
522 |
523 | public IUpdateDialogFragmentListener getUpdateDialogFragmentListener() {
524 | return mUpdateDialogFragmentListener;
525 | }
526 |
527 | /**
528 | * 设置默认的UpdateDialogFragment监听器
529 | * @param updateDialogFragmentListener updateDialogFragmentListener 更新对话框关闭监听
530 | * @return Builder
531 | */
532 | public Builder setUpdateDialogFragmentListener(IUpdateDialogFragmentListener updateDialogFragmentListener) {
533 | this.mUpdateDialogFragmentListener = updateDialogFragmentListener;
534 | return this;
535 | }
536 |
537 | /**
538 | * @return 生成app管理器
539 | */
540 | public UpdateAppManager build() {
541 | //校验
542 | if (getActivity() == null || getHttpManager() == null || TextUtils.isEmpty(getUpdateUrl())) {
543 | throw new NullPointerException("必要参数不能为空");
544 | }
545 | if (TextUtils.isEmpty(getTargetPath())) {
546 | //sd卡是否存在
547 | String path = "";
548 | if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED) || !Environment.isExternalStorageRemovable()) {
549 | try {
550 | path = getActivity().getExternalCacheDir().getAbsolutePath();
551 | } catch (Exception e) {
552 | e.printStackTrace();
553 | }
554 | if (TextUtils.isEmpty(path)) {
555 | path = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getAbsolutePath();
556 | }
557 | } else {
558 | path = getActivity().getCacheDir().getAbsolutePath();
559 | }
560 | setTargetPath(path);
561 | }
562 | if (TextUtils.isEmpty(getAppKey())) {
563 | String appKey = AppUpdateUtils.getManifestString(getActivity(), UPDATE_APP_KEY);
564 | if (!TextUtils.isEmpty(appKey)) {
565 | setAppKey(appKey);
566 | }
567 | }
568 | return new UpdateAppManager(this);
569 | }
570 |
571 | /**
572 | * 是否隐藏对话框下载进度条
573 | *
574 | *
575 | * @return Builder
576 | */
577 | public Builder hideDialogOnDownloading() {
578 | mHideDialog = true;
579 | return this;
580 | }
581 |
582 | /**
583 | * @return 是否影藏对话框
584 | */
585 | public boolean isHideDialog() {
586 | return mHideDialog;
587 | }
588 |
589 | /**
590 | * 显示忽略版本
591 | *
592 | * @return 是否忽略版本
593 | */
594 | public Builder showIgnoreVersion() {
595 | mShowIgnoreVersion = true;
596 | return this;
597 | }
598 |
599 | public boolean isShowIgnoreVersion() {
600 | return mShowIgnoreVersion;
601 | }
602 |
603 | /**
604 | * 不显示通知栏进度条
605 | *
606 | * @return 是否显示进度条
607 | */
608 | public Builder dismissNotificationProgress() {
609 | dismissNotificationProgress = true;
610 | return this;
611 | }
612 |
613 | public boolean isDismissNotificationProgress() {
614 | return dismissNotificationProgress;
615 | }
616 |
617 | public Builder setOnlyWifi() {
618 | mOnlyWifi = true;
619 | return this;
620 | }
621 |
622 | public boolean isOnlyWifi() {
623 | return mOnlyWifi;
624 | }
625 |
626 | public Builder handleException(ExceptionHandler exceptionHandler) {
627 | ExceptionHandlerHelper.init(exceptionHandler);
628 | return this;
629 | }
630 |
631 | }
632 |
633 | }
634 |
635 |
--------------------------------------------------------------------------------
/update-app/src/main/java/com/vector/update_app/UpdateCallback.java:
--------------------------------------------------------------------------------
1 | package com.vector.update_app;
2 |
3 | import org.json.JSONObject;
4 |
5 | /**
6 | * 新版本版本检测回调
7 | */
8 | public class UpdateCallback {
9 |
10 | /**
11 | * 解析json,自定义协议
12 | *
13 | * @param json 服务器返回的json
14 | * @return UpdateAppBean
15 | */
16 | protected UpdateAppBean parseJson(String json) {
17 | UpdateAppBean updateAppBean = new UpdateAppBean();
18 | try {
19 | JSONObject jsonObject = new JSONObject(json);
20 | updateAppBean.setUpdate(jsonObject.optString("update"))
21 | //存放json,方便自定义解析
22 | .setOriginRes(json)
23 | .setNewVersion(jsonObject.optString("new_version"))
24 | .setApkFileUrl(jsonObject.optString("apk_file_url"))
25 | .setTargetSize(jsonObject.optString("target_size"))
26 | .setUpdateLog(jsonObject.optString("update_log"))
27 | .setConstraint(jsonObject.optBoolean("constraint"))
28 | .setNewMd5(jsonObject.optString("new_md5"));
29 | } catch (Exception e) {
30 | e.printStackTrace();
31 | }
32 | return updateAppBean;
33 | }
34 |
35 | /**
36 | * 有新版本
37 | *
38 | * @param updateApp 新版本信息
39 | * @param updateAppManager app更新管理器
40 | */
41 | protected void hasNewApp(UpdateAppBean updateApp, UpdateAppManager updateAppManager) {
42 | updateAppManager.showDialogFragment();
43 | }
44 |
45 | /**
46 | * 网路请求之后
47 | */
48 | protected void onAfter() {
49 | }
50 |
51 |
52 | /**
53 | * 没有新版本
54 | * @param error HttpManager实现类请求出错返回的错误消息,交给使用者自己返回,有可能不同的应用错误内容需要提示给客户
55 | */
56 | protected void noNewApp(String error) {
57 | }
58 |
59 | /**
60 | * 网络请求之前
61 | */
62 | protected void onBefore() {
63 | }
64 |
65 | }
66 |
--------------------------------------------------------------------------------
/update-app/src/main/java/com/vector/update_app/UpdateDialogFragment.java:
--------------------------------------------------------------------------------
1 | package com.vector.update_app;
2 |
3 | import android.Manifest;
4 | import android.app.Activity;
5 | import android.content.ComponentName;
6 | import android.content.DialogInterface;
7 | import android.content.Intent;
8 | import android.content.ServiceConnection;
9 | import android.content.pm.PackageManager;
10 | import android.graphics.Color;
11 | import android.os.Build;
12 | import android.os.Bundle;
13 | import android.os.IBinder;
14 | import android.support.annotation.Nullable;
15 | import android.support.v4.app.ActivityCompat;
16 | import android.support.v4.app.DialogFragment;
17 | import android.support.v4.app.FragmentManager;
18 | import android.text.TextUtils;
19 | import android.util.DisplayMetrics;
20 | import android.view.Gravity;
21 | import android.view.KeyEvent;
22 | import android.view.LayoutInflater;
23 | import android.view.View;
24 | import android.view.ViewGroup;
25 | import android.view.Window;
26 | import android.view.WindowManager;
27 | import android.widget.Button;
28 | import android.widget.ImageView;
29 | import android.widget.LinearLayout;
30 | import android.widget.TextView;
31 | import android.widget.Toast;
32 |
33 | import com.vector.update_app.listener.ExceptionHandler;
34 | import com.vector.update_app.listener.ExceptionHandlerHelper;
35 | import com.vector.update_app.listener.IUpdateDialogFragmentListener;
36 | import com.vector.update_app.service.DownloadService;
37 | import com.vector.update_app.utils.AppUpdateUtils;
38 | import com.vector.update_app.utils.ColorUtil;
39 | import com.vector.update_app.utils.DrawableUtil;
40 | import com.vector.update_app.view.NumberProgressBar;
41 |
42 | import java.io.File;
43 |
44 | /**
45 | * Created by Vector
46 | * on 2017/7/19 0019.
47 | */
48 |
49 | public class UpdateDialogFragment extends DialogFragment implements View.OnClickListener {
50 | public static final String TIPS = "请授权访问存储空间权限,否则App无法更新";
51 | public static boolean isShow = false;
52 | private TextView mContentTextView;
53 | private Button mUpdateOkButton;
54 | private UpdateAppBean mUpdateApp;
55 | private NumberProgressBar mNumberProgressBar;
56 | private ImageView mIvClose;
57 | private TextView mTitleTextView;
58 | /**
59 | * 回调
60 | */
61 | private ServiceConnection conn = new ServiceConnection() {
62 |
63 | @Override
64 | public void onServiceConnected(ComponentName name, IBinder service) {
65 | startDownloadApp((DownloadService.DownloadBinder) service);
66 | }
67 |
68 | @Override
69 | public void onServiceDisconnected(ComponentName name) {
70 | }
71 | };
72 | private LinearLayout mLlClose;
73 | //默认色
74 | private int mDefaultColor = 0xffe94339;
75 | private int mDefaultPicResId = R.mipmap.lib_update_app_top_bg;
76 | private ImageView mTopIv;
77 | private TextView mIgnore;
78 | private IUpdateDialogFragmentListener mUpdateDialogFragmentListener;
79 | private DownloadService.DownloadBinder mDownloadBinder;
80 | private Activity mActivity;
81 |
82 | public UpdateDialogFragment setUpdateDialogFragmentListener(IUpdateDialogFragmentListener updateDialogFragmentListener) {
83 | this.mUpdateDialogFragmentListener = updateDialogFragmentListener;
84 | return this;
85 | }
86 |
87 |
88 | public static UpdateDialogFragment newInstance(Bundle args) {
89 | UpdateDialogFragment fragment = new UpdateDialogFragment();
90 | if (args != null) {
91 | fragment.setArguments(args);
92 | }
93 | return fragment;
94 | }
95 |
96 | @Override
97 | public void onCreate(@Nullable Bundle savedInstanceState) {
98 | super.onCreate(savedInstanceState);
99 | isShow = true;
100 | // setStyle(DialogFragment.STYLE_NO_TITLE | DialogFragment.STYLE_NO_FRAME, 0);
101 | setStyle(DialogFragment.STYLE_NO_TITLE, R.style.UpdateAppDialog);
102 |
103 |
104 | mActivity = getActivity();
105 |
106 |
107 |
108 | }
109 |
110 | @Override
111 | public void onStart() {
112 | super.onStart();
113 | //点击window外的区域 是否消失
114 | getDialog().setCanceledOnTouchOutside(false);
115 | //是否可以取消,会影响上面那条属性
116 | // setCancelable(false);
117 | // //window外可以点击,不拦截窗口外的事件
118 | // getDialog().getWindow().addFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL);
119 |
120 | getDialog().setOnKeyListener(new DialogInterface.OnKeyListener() {
121 | @Override
122 | public boolean onKey(DialogInterface dialog, int keyCode, KeyEvent event) {
123 | if (keyCode == KeyEvent.KEYCODE_BACK) {
124 | //禁用
125 | if (mUpdateApp != null && mUpdateApp.isConstraint()) {
126 | //返回桌面
127 | startActivity(new Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_HOME));
128 | return true;
129 | } else {
130 | return false;
131 | }
132 | }
133 | return false;
134 | }
135 | });
136 |
137 | Window dialogWindow = getDialog().getWindow();
138 | dialogWindow.setGravity(Gravity.CENTER);
139 | WindowManager.LayoutParams lp = dialogWindow.getAttributes();
140 | DisplayMetrics displayMetrics = getContext().getResources().getDisplayMetrics();
141 | lp.height = (int) (displayMetrics.heightPixels * 0.8f);
142 | dialogWindow.setAttributes(lp);
143 | }
144 |
145 | @Nullable
146 | @Override
147 | public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
148 | return inflater.inflate(R.layout.lib_update_app_dialog, container);
149 | }
150 |
151 | @Override
152 | public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
153 | super.onViewCreated(view, savedInstanceState);
154 | initView(view);
155 | }
156 |
157 | private void initView(View view) {
158 | //提示内容
159 | mContentTextView = view.findViewById(R.id.tv_update_info);
160 | //标题
161 | mTitleTextView = view.findViewById(R.id.tv_title);
162 | //更新按钮
163 | mUpdateOkButton = view.findViewById(R.id.btn_ok);
164 | //进度条
165 | mNumberProgressBar = view.findViewById(R.id.npb);
166 | //关闭按钮
167 | mIvClose = view.findViewById(R.id.iv_close);
168 | //关闭按钮+线 的整个布局
169 | mLlClose = view.findViewById(R.id.ll_close);
170 | //顶部图片
171 | mTopIv = view.findViewById(R.id.iv_top);
172 | //忽略
173 | mIgnore = view.findViewById(R.id.tv_ignore);
174 |
175 | }
176 |
177 | @Override
178 | public void onActivityCreated(Bundle savedInstanceState) {
179 | super.onActivityCreated(savedInstanceState);
180 | initData();
181 | }
182 |
183 | private void initData() {
184 | mUpdateApp = (UpdateAppBean) getArguments().getSerializable(UpdateAppManager.INTENT_KEY);
185 | //设置主题色
186 | initTheme();
187 |
188 |
189 | if (mUpdateApp != null) {
190 | //弹出对话框
191 | final String dialogTitle = mUpdateApp.getUpdateDefDialogTitle();
192 | final String newVersion = mUpdateApp.getNewVersion();
193 | final String targetSize = mUpdateApp.getTargetSize();
194 | final String updateLog = mUpdateApp.getUpdateLog();
195 |
196 | String msg = "";
197 |
198 | if (!TextUtils.isEmpty(targetSize)) {
199 | msg = "新版本大小:" + targetSize + "\n\n";
200 | }
201 |
202 | if (!TextUtils.isEmpty(updateLog)) {
203 | msg += updateLog;
204 | }
205 |
206 | //更新内容
207 | mContentTextView.setText(msg);
208 | //标题
209 | mTitleTextView.setText(TextUtils.isEmpty(dialogTitle) ? String.format("是否升级到%s版本?", newVersion) : dialogTitle);
210 | //强制更新
211 | if (mUpdateApp.isConstraint()) {
212 | mLlClose.setVisibility(View.GONE);
213 | } else {
214 | //不是强制更新时,才生效
215 | if (mUpdateApp.isShowIgnoreVersion()) {
216 | mIgnore.setVisibility(View.VISIBLE);
217 | }
218 | }
219 |
220 | initEvents();
221 | }
222 | }
223 |
224 | /**
225 | * 初始化主题色
226 | */
227 | private void initTheme() {
228 |
229 |
230 | final int color = getArguments().getInt(UpdateAppManager.THEME_KEY, -1);
231 |
232 | final int topResId = getArguments().getInt(UpdateAppManager.TOP_IMAGE_KEY, -1);
233 |
234 |
235 | if (-1 == topResId) {
236 | if (-1 == color) {
237 | //默认红色
238 | setDialogTheme(mDefaultColor, mDefaultPicResId);
239 | } else {
240 | setDialogTheme(color, mDefaultPicResId);
241 | }
242 |
243 | } else {
244 | if (-1 == color) {
245 | //自动提色
246 | // Palette.from(AppUpdateUtils.drawableToBitmap(this.getResources().getDrawable(topResId))).generate(new Palette.PaletteAsyncListener() {
247 | // @Override
248 | // public void onGenerated(Palette palette) {
249 | // int mDominantColor = palette.getDominantColor(mDefaultColor);
250 | // setDialogTheme(mDominantColor, topResId);
251 | // }
252 | // });
253 | setDialogTheme(mDefaultColor, topResId);
254 | } else {
255 | //更加指定的上色
256 | setDialogTheme(color, topResId);
257 | }
258 | }
259 |
260 |
261 | }
262 |
263 | /**
264 | * 设置
265 | *
266 | * @param color 主色
267 | * @param topResId 图片
268 | */
269 | private void setDialogTheme(int color, int topResId) {
270 | mTopIv.setImageResource(topResId);
271 | mUpdateOkButton.setBackgroundDrawable(DrawableUtil.getDrawable(AppUpdateUtils.dip2px(4, getActivity()), color));
272 | mNumberProgressBar.setProgressTextColor(color);
273 | mNumberProgressBar.setReachedBarColor(color);
274 | //随背景颜色变化
275 | mUpdateOkButton.setTextColor(ColorUtil.isTextColorDark(color) ? Color.BLACK : Color.WHITE);
276 | }
277 |
278 | private void initEvents() {
279 | mUpdateOkButton.setOnClickListener(this);
280 | mIvClose.setOnClickListener(this);
281 | mIgnore.setOnClickListener(this);
282 | }
283 |
284 | @Override
285 | public void onClick(View view) {
286 | int i = view.getId();
287 | if (i == R.id.btn_ok) {
288 |
289 | //权限判断是否有访问外部存储空间权限
290 | int flag = ActivityCompat.checkSelfPermission(getActivity(), Manifest.permission.WRITE_EXTERNAL_STORAGE);
291 | if (flag != PackageManager.PERMISSION_GRANTED) {
292 | if (ActivityCompat.shouldShowRequestPermissionRationale(getActivity(), Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
293 | // 用户拒绝过这个权限了,应该提示用户,为什么需要这个权限。
294 | Toast.makeText(getActivity(), TIPS, Toast.LENGTH_LONG).show();
295 | } else {
296 | // 申请授权。
297 | requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 1);
298 | }
299 |
300 | } else {
301 | installApp();
302 |
303 | }
304 |
305 | } else if (i == R.id.iv_close) {
306 | // TODO @WVector 这里是否要对UpdateAppBean的强制更新做处理?不会重合,当强制更新时,就不会显示这个按钮,也不会调这个方法。
307 | // if (mNumberProgressBar.getVisibility() == View.VISIBLE) {
308 | // Toast.makeText(getApplicationContext(), "后台更新app", Toast.LENGTH_LONG).show();
309 | // }
310 | cancelDownloadService();
311 | if (mUpdateDialogFragmentListener != null) {
312 | // 通知用户
313 | mUpdateDialogFragmentListener.onUpdateNotifyDialogCancel(mUpdateApp);
314 | }
315 | dismiss();
316 | } else if (i == R.id.tv_ignore) {
317 | AppUpdateUtils.saveIgnoreVersion(getActivity(), mUpdateApp.getNewVersion());
318 | dismiss();
319 | }
320 | }
321 |
322 | public void cancelDownloadService() {
323 | if (mDownloadBinder != null) {
324 | // 标识用户已经点击了更新,之后点击取消
325 | mDownloadBinder.stop("取消下载");
326 | }
327 | }
328 |
329 | private void installApp() {
330 | if (AppUpdateUtils.appIsDownloaded(mUpdateApp)) {
331 | AppUpdateUtils.installApp(UpdateDialogFragment.this, AppUpdateUtils.getAppFile(mUpdateApp));
332 | //安装完自杀
333 | //如果上次是强制更新,但是用户在下载完,强制杀掉后台,重新启动app后,则会走到这一步,所以要进行强制更新的判断。
334 | if (!mUpdateApp.isConstraint()) {
335 | dismiss();
336 | } else {
337 | showInstallBtn(AppUpdateUtils.getAppFile(mUpdateApp));
338 | }
339 | } else {
340 | downloadApp();
341 | //这里的隐藏对话框会和强制更新冲突,导致强制更新失效,所以当强制更新时,不隐藏对话框。
342 | if (mUpdateApp.isHideDialog() && !mUpdateApp.isConstraint()) {
343 | dismiss();
344 | }
345 |
346 | }
347 | }
348 |
349 | @Override
350 | public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
351 | super.onRequestPermissionsResult(requestCode, permissions, grantResults);
352 | if (requestCode == 1) {
353 | if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
354 | //升级
355 | installApp();
356 | } else {
357 | //提示,并且关闭
358 | Toast.makeText(getActivity(), TIPS, Toast.LENGTH_LONG).show();
359 | dismiss();
360 |
361 | }
362 | }
363 |
364 | }
365 |
366 | /**
367 | * 开启后台服务下载
368 | */
369 | private void downloadApp() {
370 | //使用ApplicationContext延长他的生命周期
371 | DownloadService.bindService(getActivity().getApplicationContext(), conn);
372 | }
373 |
374 | /**
375 | * 回调监听下载
376 | */
377 | private void startDownloadApp(DownloadService.DownloadBinder binder) {
378 | // 开始下载,监听下载进度,可以用对话框显示
379 | if (mUpdateApp != null) {
380 |
381 | this.mDownloadBinder = binder;
382 |
383 | binder.start(mUpdateApp, new DownloadService.DownloadCallback() {
384 | @Override
385 | public void onStart() {
386 | if (!UpdateDialogFragment.this.isRemoving()) {
387 | mNumberProgressBar.setVisibility(View.VISIBLE);
388 | mUpdateOkButton.setVisibility(View.GONE);
389 | }
390 | }
391 |
392 | @Override
393 | public void onProgress(float progress, long totalSize) {
394 | if (!UpdateDialogFragment.this.isRemoving()) {
395 | mNumberProgressBar.setProgress(Math.round(progress * 100));
396 | mNumberProgressBar.setMax(100);
397 | }
398 | }
399 |
400 | @Override
401 | public void setMax(long total) {
402 |
403 | }
404 |
405 | //TODO 这里的 onFinish 和 onInstallAppAndAppOnForeground 会有功能上的重合,后期考虑合并优化。
406 | @Override
407 | public boolean onFinish(final File file) {
408 | if (!UpdateDialogFragment.this.isRemoving()) {
409 | if (mUpdateApp.isConstraint()) {
410 | showInstallBtn(file);
411 | } else {
412 | dismissAllowingStateLoss();
413 | }
414 | }
415 | //一般返回 true ,当返回 false 时,则下载,不安装,为静默安装使用。
416 | return true;
417 | }
418 |
419 | @Override
420 | public void onError(String msg) {
421 | if (!UpdateDialogFragment.this.isRemoving()) {
422 | dismissAllowingStateLoss();
423 | }
424 | }
425 |
426 | @Override
427 | public boolean onInstallAppAndAppOnForeground(File file) {
428 | //这样做的目的是在跳转安装界面,可以监听到用户取消安装的动作;
429 | //activity.startActivityForResult(intent, REQ_CODE_INSTALL_APP);
430 | //但是如果 由DownloadService 跳转到安装界面,则监听失效。
431 | if (!mUpdateApp.isConstraint()) {
432 | dismiss();
433 | }
434 | if (mActivity != null) {
435 | AppUpdateUtils.installApp(mActivity, file);
436 | //返回 true ,自己处理。
437 | return true;
438 | } else {
439 | //返回 flase ,则由 DownloadService 跳转到安装界面。
440 | return false;
441 | }
442 | }
443 | });
444 | }
445 | }
446 |
447 | private void showInstallBtn(final File file) {
448 | mNumberProgressBar.setVisibility(View.GONE);
449 | mUpdateOkButton.setText("安装");
450 | mUpdateOkButton.setVisibility(View.VISIBLE);
451 | mUpdateOkButton.setOnClickListener(new View.OnClickListener() {
452 | @Override
453 | public void onClick(View v) {
454 | AppUpdateUtils.installApp(UpdateDialogFragment.this, file);
455 | }
456 | });
457 | }
458 |
459 |
460 | // @Override
461 | // public void onActivityResult(int requestCode, int resultCode, Intent data) {
462 | // Log.e("", "对话框 requestCode = [" + requestCode + "], resultCode = [" + resultCode + "], data = [" + data + "]");
463 | // switch (resultCode) {
464 | // case Activity.RESULT_CANCELED:
465 | // switch (requestCode){
466 | // // 得到通过UpdateDialogFragment默认dialog方式安装,用户取消安装的回调通知,以便用户自己去判断,比如这个更新如果是强制的,但是用户下载之后取消了,在这里发起相应的操作
467 | // case AppUpdateUtils.REQ_CODE_INSTALL_APP:
468 | // if (mUpdateApp.isConstraint()) {
469 | // if (AppUpdateUtils.appIsDownloaded(mUpdateApp)) {
470 | // AppUpdateUtils.installApp(UpdateDialogFragment.this, AppUpdateUtils.getAppFile(mUpdateApp));
471 | // }
472 | // }
473 | // break;
474 | // }
475 | // break;
476 | //
477 | // default:
478 | // }
479 | // }
480 |
481 | @Override
482 | public void show(FragmentManager manager, String tag) {
483 |
484 | if (Build.VERSION.SDK_INT > Build.VERSION_CODES.JELLY_BEAN) {
485 | if (manager.isDestroyed()) {
486 | return;
487 | }
488 | }
489 |
490 | try {
491 | super.show(manager, tag);
492 | } catch (Exception e) {
493 | ExceptionHandler exceptionHandler = ExceptionHandlerHelper.getInstance();
494 | if (exceptionHandler != null) {
495 | exceptionHandler.onException(e);
496 | }
497 | }
498 | }
499 |
500 | @Override
501 | public void onDestroyView() {
502 | isShow = false;
503 | super.onDestroyView();
504 | }
505 | }
506 |
507 |
--------------------------------------------------------------------------------
/update-app/src/main/java/com/vector/update_app/UpdateFileProvider.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016 czy1121
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.vector.update_app;
18 |
19 |
20 | import android.support.v4.content.FileProvider;
21 |
22 | public class UpdateFileProvider extends FileProvider {
23 | }
24 |
--------------------------------------------------------------------------------
/update-app/src/main/java/com/vector/update_app/listener/ExceptionHandler.java:
--------------------------------------------------------------------------------
1 | package com.vector.update_app.listener;
2 |
3 | /**
4 | * Created by Vector
5 | * on 2018/4/9.
6 | */
7 | public interface ExceptionHandler {
8 | void onException(Exception e);
9 | }
10 |
--------------------------------------------------------------------------------
/update-app/src/main/java/com/vector/update_app/listener/ExceptionHandlerHelper.java:
--------------------------------------------------------------------------------
1 | package com.vector.update_app.listener;
2 |
3 | /**
4 | * Created by Vector
5 | * on 2018/4/9.
6 | */
7 | public class ExceptionHandlerHelper {
8 | private static ExceptionHandler instance;
9 | public static void init(ExceptionHandler exceptionHandler) {
10 | ExceptionHandler temp = instance;
11 | if (temp == null) {
12 | synchronized (ExceptionHandlerHelper.class) {
13 | temp = instance;
14 | if (temp == null) {
15 | temp = exceptionHandler;
16 | instance = temp;
17 | }
18 | }
19 | }
20 | }
21 | public static ExceptionHandler getInstance() {
22 | return instance;
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/update-app/src/main/java/com/vector/update_app/listener/IUpdateDialogFragmentListener.java:
--------------------------------------------------------------------------------
1 | package com.vector.update_app.listener;
2 |
3 | import com.vector.update_app.UpdateAppBean;
4 |
5 | /**
6 | * version 1.0
7 | * Created by jiiiiiin on 2018/4/1.
8 | */
9 |
10 | public interface IUpdateDialogFragmentListener {
11 | /**
12 | * 当默认的更新提示框被用户点击取消的时候调用
13 | * @param updateApp updateApp
14 | */
15 | void onUpdateNotifyDialogCancel(UpdateAppBean updateApp);
16 | }
17 |
--------------------------------------------------------------------------------
/update-app/src/main/java/com/vector/update_app/service/DownloadService.java:
--------------------------------------------------------------------------------
1 | package com.vector.update_app.service;
2 |
3 | import android.app.Notification;
4 | import android.app.NotificationChannel;
5 | import android.app.NotificationManager;
6 | import android.app.PendingIntent;
7 | import android.app.Service;
8 | import android.content.Context;
9 | import android.content.Intent;
10 | import android.content.ServiceConnection;
11 | import android.os.Binder;
12 | import android.os.IBinder;
13 | import android.support.annotation.Nullable;
14 | import android.support.v4.app.NotificationCompat;
15 | import android.text.TextUtils;
16 | import android.widget.Toast;
17 |
18 | import com.vector.update_app.HttpManager;
19 | import com.vector.update_app.R;
20 | import com.vector.update_app.UpdateAppBean;
21 | import com.vector.update_app.utils.AppUpdateUtils;
22 |
23 | import java.io.File;
24 |
25 |
26 | /**
27 | * 后台下载
28 | */
29 | public class DownloadService extends Service {
30 |
31 | private static final int NOTIFY_ID = 0;
32 | private static final String TAG = DownloadService.class.getSimpleName();
33 | private static final String CHANNEL_ID = "app_update_id";
34 | private static final CharSequence CHANNEL_NAME = "app_update_channel";
35 |
36 | public static boolean isRunning = false;
37 | private NotificationManager mNotificationManager;
38 | private DownloadBinder binder = new DownloadBinder();
39 | private NotificationCompat.Builder mBuilder;
40 | // /**
41 | // * 开启服务方法
42 | // *
43 | // * @param context
44 | // */
45 | // public static void startService(Context context) {
46 | // Intent intent = new Intent(context, DownloadService.class);
47 | // context.startService(intent);
48 | // }
49 | private boolean mDismissNotificationProgress = false;
50 |
51 | public static void bindService(Context context, ServiceConnection connection) {
52 | Intent intent = new Intent(context, DownloadService.class);
53 | context.startService(intent);
54 | context.bindService(intent, connection, Context.BIND_AUTO_CREATE);
55 | isRunning = true;
56 | }
57 |
58 | @Override
59 | public boolean onUnbind(Intent intent) {
60 | isRunning = false;
61 | return super.onUnbind(intent);
62 | }
63 |
64 | @Override
65 | public void onCreate() {
66 | super.onCreate();
67 | mNotificationManager = (NotificationManager) getSystemService(android.content.Context.NOTIFICATION_SERVICE);
68 | }
69 |
70 | @Override
71 | public IBinder onBind(Intent intent) {
72 | // 返回自定义的DownloadBinder实例
73 | return binder;
74 | }
75 |
76 | @Override
77 | public void onDestroy() {
78 | mNotificationManager = null;
79 | super.onDestroy();
80 | }
81 |
82 | /**
83 | * 创建通知
84 | */
85 | private void setUpNotification() {
86 | if (mDismissNotificationProgress) {
87 | return;
88 | }
89 |
90 |
91 | if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
92 | NotificationChannel channel = new NotificationChannel(CHANNEL_ID, CHANNEL_NAME, NotificationManager.IMPORTANCE_HIGH);
93 | //设置绕过免打扰模式
94 | // channel.setBypassDnd(false);
95 | // //检测是否绕过免打扰模式
96 | // channel.canBypassDnd();
97 | // //设置在锁屏界面上显示这条通知
98 | // channel.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
99 | // channel.setLightColor(Color.GREEN);
100 | // channel.setShowBadge(true);
101 | // channel.setVibrationPattern(new long[]{100, 200, 300, 400, 500, 400, 300, 200, 400});
102 | channel.enableVibration(false);
103 | channel.enableLights(false);
104 |
105 | mNotificationManager.createNotificationChannel(channel);
106 | }
107 |
108 |
109 | mBuilder = new NotificationCompat.Builder(this, CHANNEL_ID);
110 | mBuilder.setContentTitle("开始下载")
111 | .setContentText("正在连接服务器")
112 | .setSmallIcon(R.mipmap.lib_update_app_update_icon)
113 | .setLargeIcon(AppUpdateUtils.drawableToBitmap(AppUpdateUtils.getAppIcon(DownloadService.this)))
114 | .setOngoing(true)
115 | .setAutoCancel(true)
116 | .setWhen(System.currentTimeMillis());
117 | mNotificationManager.notify(NOTIFY_ID, mBuilder.build());
118 | }
119 |
120 | /**
121 | * 下载模块
122 | */
123 | private void startDownload(UpdateAppBean updateApp, final DownloadCallback callback) {
124 |
125 | mDismissNotificationProgress = updateApp.isDismissNotificationProgress();
126 |
127 | String apkUrl = updateApp.getApkFileUrl();
128 | if (TextUtils.isEmpty(apkUrl)) {
129 | String contentText = "新版本下载路径错误";
130 | stop(contentText);
131 | return;
132 | }
133 | String appName = AppUpdateUtils.getApkName(updateApp);
134 |
135 | File appDir = new File(updateApp.getTargetPath());
136 | if (!appDir.exists()) {
137 | appDir.mkdirs();
138 | }
139 |
140 | String target = appDir + File.separator + updateApp.getNewVersion();
141 |
142 | updateApp.getHttpManager().download(apkUrl, target, appName, new FileDownloadCallBack(callback));
143 | }
144 |
145 | private void stop(String contentText) {
146 | if (mBuilder != null) {
147 | mBuilder.setContentTitle(AppUpdateUtils.getAppName(DownloadService.this))
148 | .setContentText(contentText);
149 | Notification notification = mBuilder.build();
150 | notification.flags = Notification.FLAG_AUTO_CANCEL;
151 | mNotificationManager.notify(NOTIFY_ID, notification);
152 | }
153 | close();
154 | }
155 |
156 | private void close() {
157 | stopSelf();
158 | isRunning = false;
159 | }
160 |
161 | /**
162 | * 进度条回调接口
163 | */
164 | public interface DownloadCallback {
165 | /**
166 | * 开始
167 | */
168 | void onStart();
169 |
170 | /**
171 | * 进度
172 | *
173 | * @param progress 进度 0.00 -1.00 ,总大小
174 | * @param totalSize 总大小 单位B
175 | */
176 | void onProgress(float progress, long totalSize);
177 |
178 | /**
179 | * 总大小
180 | *
181 | * @param totalSize 单位B
182 | */
183 | void setMax(long totalSize);
184 |
185 | /**
186 | * 下载完了
187 | *
188 | * @param file 下载的app
189 | * @return true :下载完自动跳到安装界面,false:则不进行安装
190 | */
191 | boolean onFinish(File file);
192 |
193 | /**
194 | * 下载异常
195 | *
196 | * @param msg 异常信息
197 | */
198 | void onError(String msg);
199 |
200 | /**
201 | * 当应用处于前台,准备执行安装程序时候的回调,
202 | *
203 | * @param file 当前安装包
204 | * @return false 默认 false ,当返回时 true 时,需要自己处理 ,前提条件是 onFinish 返回 false 。
205 | */
206 | boolean onInstallAppAndAppOnForeground(File file);
207 | }
208 |
209 | /**
210 | * DownloadBinder中定义了一些实用的方法
211 | *
212 | * @author user
213 | */
214 | public class DownloadBinder extends Binder {
215 | /**
216 | * 开始下载
217 | *
218 | * @param updateApp 新app信息
219 | * @param callback 下载回调
220 | */
221 | public void start(UpdateAppBean updateApp, DownloadCallback callback) {
222 | //下载
223 | startDownload(updateApp, callback);
224 | }
225 |
226 | public void stop(String msg) {
227 | DownloadService.this.stop(msg);
228 | }
229 | }
230 |
231 | class FileDownloadCallBack implements HttpManager.FileCallback {
232 | private final DownloadCallback mCallBack;
233 | int oldRate = 0;
234 |
235 | public FileDownloadCallBack(@Nullable DownloadCallback callback) {
236 | super();
237 | this.mCallBack = callback;
238 | }
239 |
240 | @Override
241 | public void onBefore() {
242 | //初始化通知栏
243 | setUpNotification();
244 | if (mCallBack != null) {
245 | mCallBack.onStart();
246 | }
247 | }
248 |
249 | @Override
250 | public void onProgress(float progress, long total) {
251 | //做一下判断,防止自回调过于频繁,造成更新通知栏进度过于频繁,而出现卡顿的问题。
252 | int rate = Math.round(progress * 100);
253 | if (oldRate != rate) {
254 | if (mCallBack != null) {
255 | mCallBack.setMax(total);
256 | mCallBack.onProgress(progress, total);
257 | }
258 |
259 | if (mBuilder != null) {
260 | mBuilder.setContentTitle("正在下载:" + AppUpdateUtils.getAppName(DownloadService.this))
261 | .setContentText(rate + "%")
262 | .setProgress(100, rate, false)
263 | .setWhen(System.currentTimeMillis());
264 | Notification notification = mBuilder.build();
265 | notification.flags = Notification.FLAG_AUTO_CANCEL | Notification.FLAG_ONLY_ALERT_ONCE;
266 | mNotificationManager.notify(NOTIFY_ID, notification);
267 | }
268 |
269 | //重新赋值
270 | oldRate = rate;
271 | }
272 |
273 |
274 | }
275 |
276 | @Override
277 | public void onError(String error) {
278 | Toast.makeText(DownloadService.this, "更新新版本出错," + error, Toast.LENGTH_SHORT).show();
279 | //App前台运行
280 | if (mCallBack != null) {
281 | mCallBack.onError(error);
282 | }
283 | try {
284 | mNotificationManager.cancel(NOTIFY_ID);
285 | close();
286 | } catch (Exception e1) {
287 | e1.printStackTrace();
288 | }
289 | }
290 |
291 | @Override
292 | public void onResponse(File file) {
293 | if (mCallBack != null) {
294 | if (!mCallBack.onFinish(file)) {
295 | close();
296 | return;
297 | }
298 | }
299 |
300 | try {
301 |
302 | if (AppUpdateUtils.isAppOnForeground(DownloadService.this) || mBuilder == null) {
303 | //App前台运行
304 | mNotificationManager.cancel(NOTIFY_ID);
305 |
306 | if (mCallBack != null) {
307 | boolean temp = mCallBack.onInstallAppAndAppOnForeground(file);
308 | if (!temp) {
309 | AppUpdateUtils.installApp(DownloadService.this, file);
310 | }
311 | } else {
312 | AppUpdateUtils.installApp(DownloadService.this, file);
313 | }
314 |
315 |
316 | } else {
317 | //App后台运行
318 | //更新参数,注意flags要使用FLAG_UPDATE_CURRENT
319 | Intent installAppIntent = AppUpdateUtils.getInstallAppIntent(DownloadService.this, file);
320 | PendingIntent contentIntent = PendingIntent.getActivity(DownloadService.this, 0, installAppIntent, PendingIntent.FLAG_UPDATE_CURRENT);
321 | mBuilder.setContentIntent(contentIntent)
322 | .setContentTitle(AppUpdateUtils.getAppName(DownloadService.this))
323 | .setContentText("下载完成,请点击安装")
324 | .setProgress(0, 0, false)
325 | // .setAutoCancel(true)
326 | .setDefaults((Notification.DEFAULT_ALL));
327 | Notification notification = mBuilder.build();
328 | notification.flags = Notification.FLAG_AUTO_CANCEL;
329 | mNotificationManager.notify(NOTIFY_ID, notification);
330 | }
331 | //下载完自杀
332 | close();
333 | } catch (Exception e) {
334 | e.printStackTrace();
335 | } finally {
336 | close();
337 | }
338 | }
339 | }
340 | }
341 |
--------------------------------------------------------------------------------
/update-app/src/main/java/com/vector/update_app/utils/AppUpdateUtils.java:
--------------------------------------------------------------------------------
1 | package com.vector.update_app.utils;
2 |
3 | import android.app.Activity;
4 | import android.app.ActivityManager;
5 | import android.content.Context;
6 | import android.content.Intent;
7 | import android.content.SharedPreferences;
8 | import android.content.pm.ApplicationInfo;
9 | import android.content.pm.PackageInfo;
10 | import android.content.pm.PackageManager;
11 | import android.graphics.Bitmap;
12 | import android.graphics.Canvas;
13 | import android.graphics.PixelFormat;
14 | import android.graphics.drawable.Drawable;
15 | import android.net.ConnectivityManager;
16 | import android.net.NetworkInfo;
17 | import android.net.Uri;
18 | import android.os.Build;
19 | import android.support.annotation.NonNull;
20 | import android.support.v4.app.Fragment;
21 | import android.support.v4.content.FileProvider;
22 | import android.text.TextUtils;
23 | import android.util.DisplayMetrics;
24 |
25 | import com.vector.update_app.UpdateAppBean;
26 | import com.vector.update_app.listener.ExceptionHandler;
27 | import com.vector.update_app.listener.ExceptionHandlerHelper;
28 |
29 | import java.io.File;
30 | import java.util.List;
31 |
32 | /**
33 | * Created by Vector
34 | * on 2017/6/6 0006.
35 | */
36 |
37 | public class AppUpdateUtils {
38 |
39 |
40 | public static final String IGNORE_VERSION = "ignore_version";
41 | private static final String PREFS_FILE = "update_app_config.xml";
42 | public static final int REQ_CODE_INSTALL_APP = 99;
43 |
44 | public static boolean isWifi(Context context) {
45 | ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
46 | NetworkInfo info = connectivityManager.getActiveNetworkInfo();
47 | return info != null && info.getType() == ConnectivityManager.TYPE_WIFI;
48 | }
49 |
50 |
51 | public static File getAppFile(UpdateAppBean updateAppBean) {
52 | String appName = getApkName(updateAppBean);
53 | return new File(updateAppBean.getTargetPath()
54 | .concat(File.separator + updateAppBean.getNewVersion())
55 | .concat(File.separator + appName));
56 | }
57 |
58 | @NonNull
59 | public static String getApkName(UpdateAppBean updateAppBean) {
60 | String apkUrl = updateAppBean.getApkFileUrl();
61 | String appName = apkUrl.substring(apkUrl.lastIndexOf("/") + 1, apkUrl.length());
62 | if (!appName.endsWith(".apk")) {
63 | appName = "temp.apk";
64 | }
65 | return appName;
66 | }
67 |
68 | public static boolean appIsDownloaded(UpdateAppBean updateAppBean) {
69 | //md5不为空
70 | //文件存在
71 | //md5只一样
72 | File appFile = getAppFile(updateAppBean);
73 | return !TextUtils.isEmpty(updateAppBean.getNewMd5())
74 | && appFile.exists()
75 | && Md5Util.getFileMD5(appFile).equalsIgnoreCase(updateAppBean.getNewMd5());
76 | }
77 |
78 |
79 | public static boolean installApp(Context context, File appFile) {
80 | try {
81 | Intent intent = getInstallAppIntent(context, appFile);
82 | if (context.getPackageManager().queryIntentActivities(intent, 0).size() > 0) {
83 | context.startActivity(intent);
84 |
85 | }
86 | return true;
87 | } catch (Exception e) {
88 | ExceptionHandler exceptionHandler = ExceptionHandlerHelper.getInstance();
89 | if (exceptionHandler != null) {
90 | exceptionHandler.onException(e);
91 | }
92 | }
93 | return false;
94 | }
95 |
96 | public static boolean installApp(Activity activity, File appFile) {
97 | try {
98 | Intent intent = getInstallAppIntent(activity, appFile);
99 | if (activity.getPackageManager().queryIntentActivities(intent, 0).size() > 0) {
100 | activity.startActivityForResult(intent, REQ_CODE_INSTALL_APP);
101 | }
102 | return true;
103 | } catch (Exception e) {
104 | ExceptionHandler exceptionHandler = ExceptionHandlerHelper.getInstance();
105 | if (exceptionHandler != null) {
106 | exceptionHandler.onException(e);
107 | }
108 | }
109 | return false;
110 | }
111 |
112 | public static boolean installApp(Fragment fragment, File appFile) {
113 | return installApp(fragment.getActivity(), appFile);
114 | }
115 |
116 | public static Intent getInstallAppIntent(Context context, File appFile) {
117 | try {
118 | Intent intent = new Intent(Intent.ACTION_VIEW);
119 | intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
120 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
121 | //区别于 FLAG_GRANT_READ_URI_PERMISSION 跟 FLAG_GRANT_WRITE_URI_PERMISSION, URI权限会持久存在即使重启,直到明确的用 revokeUriPermission(Uri, int) 撤销。 这个flag只提供可能持久授权。但是接收的应用必须调用ContentResolver的takePersistableUriPermission(Uri, int)方法实现
122 | intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION);
123 | Uri fileUri = FileProvider.getUriForFile(context, context.getApplicationContext().getPackageName() + ".fileProvider", appFile);
124 | intent.setDataAndType(fileUri, "application/vnd.android.package-archive");
125 | } else {
126 | intent.setDataAndType(Uri.fromFile(appFile), "application/vnd.android.package-archive");
127 | }
128 | return intent;
129 | } catch (Exception e) {
130 | ExceptionHandler exceptionHandler = ExceptionHandlerHelper.getInstance();
131 | if (exceptionHandler != null) {
132 | exceptionHandler.onException(e);
133 | }
134 | }
135 | return null;
136 | }
137 |
138 | public static String getVersionName(Context context) {
139 | PackageInfo packageInfo = getPackageInfo(context);
140 | if (packageInfo != null) {
141 | return packageInfo.versionName;
142 | }
143 | return "";
144 | }
145 |
146 | public static int getVersionCode(Context context) {
147 | PackageInfo packageInfo = getPackageInfo(context);
148 | if (packageInfo != null) {
149 | return packageInfo.versionCode;
150 | }
151 | return 0;
152 | }
153 |
154 | public static PackageInfo getPackageInfo(Context context) {
155 | try {
156 | return context.getPackageManager().getPackageInfo(context.getPackageName(), 0);
157 | } catch (PackageManager.NameNotFoundException e) {
158 | e.printStackTrace();
159 | }
160 | return null;
161 | }
162 |
163 | public static boolean isAppOnForeground(Context context) {
164 |
165 | ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
166 | String packageName = context.getPackageName();
167 |
168 | List appProcesses = activityManager.getRunningAppProcesses();
169 | if (appProcesses == null)
170 | return false;
171 |
172 | for (ActivityManager.RunningAppProcessInfo appProcess : appProcesses) {
173 |
174 | if (appProcess.processName.equals(packageName) && appProcess.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND) {
175 | return true;
176 | }
177 | }
178 |
179 | return false;
180 | }
181 |
182 | public static String getAppName(Context context) {
183 | PackageInfo packageInfo = getPackageInfo(context);
184 | if (packageInfo != null) {
185 | return packageInfo.applicationInfo.loadLabel(context.getPackageManager()).toString();
186 | }
187 | return "";
188 | }
189 |
190 | public static Drawable getAppIcon(Context context) {
191 | try {
192 | return context.getPackageManager().getApplicationIcon(context.getPackageName());
193 | } catch (PackageManager.NameNotFoundException e) {
194 | e.printStackTrace();
195 | }
196 | return null;
197 | }
198 |
199 | public static Bitmap drawableToBitmap(Drawable drawable) {
200 |
201 |
202 | Bitmap bitmap = Bitmap.createBitmap(
203 |
204 | drawable.getIntrinsicWidth(),
205 |
206 | drawable.getIntrinsicHeight(),
207 |
208 | drawable.getOpacity() != PixelFormat.OPAQUE ? Bitmap.Config.ARGB_8888
209 |
210 | : Bitmap.Config.RGB_565);
211 |
212 | Canvas canvas = new Canvas(bitmap);
213 |
214 | //canvas.setBitmap(bitmap);
215 |
216 | drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());
217 |
218 | drawable.draw(canvas);
219 |
220 | return bitmap;
221 |
222 | }
223 |
224 | public static int dip2px(int dip, Context context) {
225 | return (int) (dip * getDensity(context) + 0.5f);
226 | }
227 |
228 | public static float getDensity(Context context) {
229 | return getDisplayMetrics(context).density;
230 | }
231 |
232 | public static DisplayMetrics getDisplayMetrics(Context context) {
233 | return context.getResources().getDisplayMetrics();
234 | }
235 |
236 | public static String getManifestString(Context context, String name) {
237 | try {
238 | ApplicationInfo appInfo = context.getPackageManager().getApplicationInfo(context.getPackageName(), PackageManager.GET_META_DATA);
239 | return appInfo.metaData.getString(name);
240 | } catch (Exception e) {
241 | e.printStackTrace();
242 | }
243 | return null;
244 | }
245 |
246 | private static SharedPreferences getSP(Context context) {
247 | return context.getSharedPreferences(PREFS_FILE, Context.MODE_PRIVATE);
248 | }
249 |
250 | public static void saveIgnoreVersion(Context context, String newVersion) {
251 | getSP(context).edit().putString(IGNORE_VERSION, newVersion).apply();
252 | }
253 |
254 | public static boolean isNeedIgnore(Context context, String newVersion) {
255 | return getSP(context).getString(IGNORE_VERSION, "").equals(newVersion);
256 | }
257 | }
258 |
--------------------------------------------------------------------------------
/update-app/src/main/java/com/vector/update_app/utils/ColorUtil.java:
--------------------------------------------------------------------------------
1 | package com.vector.update_app.utils;
2 |
3 | import android.content.res.ColorStateList;
4 | import android.graphics.Color;
5 |
6 | import java.util.Random;
7 |
8 | /**
9 | * Created by Vector
10 | * on 2017/6/29 0029.
11 | */
12 |
13 | public class ColorUtil {
14 |
15 |
16 | /**
17 | * 颜色选择器
18 | *
19 | * @param pressedColor 按下的颜色
20 | * @param normalColor 正常的颜色
21 | * @return 颜色选择器
22 | */
23 | public static ColorStateList getColorStateList(int pressedColor, int normalColor) {
24 | //其他状态默认为白色
25 | return new ColorStateList(
26 | new int[][]{{android.R.attr.state_enabled, android.R.attr.state_pressed}, {android.R.attr.state_enabled}, {}},
27 | new int[]{pressedColor, normalColor, Color.WHITE});
28 | }
29 |
30 | /**
31 | * 加深颜色
32 | *
33 | * @param color 原色
34 | * @return 加深后的
35 | */
36 | public static int colorDeep(int color) {
37 |
38 | int alpha = Color.alpha(color);
39 | int red = Color.red(color);
40 | int green = Color.green(color);
41 | int blue = Color.blue(color);
42 |
43 | float ratio = 0.8F;
44 |
45 | red = (int) (red * ratio);
46 | green = (int) (green * ratio);
47 | blue = (int) (blue * ratio);
48 |
49 | return Color.argb(alpha, red, green, blue);
50 | }
51 |
52 | /**
53 | * @param color 背景颜色
54 | * @return 前景色是否深色
55 | */
56 | public static boolean isTextColorDark(int color) {
57 | float a = (Color.red(color) * 0.299f + Color.green(color) * 0.587f + Color.blue(color) * 0.114f);
58 | return a > 180;
59 | }
60 |
61 | /**
62 | * 按条件的到随机颜色
63 | *
64 | * @param alpha 透明
65 | * @param lower 下边界
66 | * @param upper 上边界
67 | * @return 颜色值
68 | */
69 |
70 | public static int getRandomColor(int alpha, int lower, int upper) {
71 | return new RandomColor(alpha, lower, upper).getColor();
72 | }
73 |
74 | /**
75 | * @return 获取随机色
76 | */
77 | public static int getRandomColor() {
78 | return new RandomColor(255, 80, 200).getColor();
79 | }
80 |
81 | /**
82 | * 随机颜色
83 | */
84 | public static class RandomColor {
85 | int alpha;
86 | int lower;
87 | int upper;
88 |
89 | public RandomColor(int alpha, int lower, int upper) {
90 | if (upper <= lower) {
91 | throw new IllegalArgumentException("must be lower < upper");
92 | }
93 | setAlpha(alpha);
94 | setLower(lower);
95 | setUpper(upper);
96 | }
97 |
98 | public int getColor() {
99 |
100 | //随机数是前闭 后开
101 |
102 | int red = getLower() + new Random().nextInt(getUpper() - getLower() + 1);
103 | int green = getLower() + new Random().nextInt(getUpper() - getLower() + 1);
104 | int blue = getLower() + new Random().nextInt(getUpper() - getLower() + 1);
105 |
106 |
107 | return Color.argb(getAlpha(), red, green, blue);
108 | }
109 |
110 | public int getAlpha() {
111 | return alpha;
112 | }
113 |
114 | public void setAlpha(int alpha) {
115 | if (alpha > 255) alpha = 255;
116 | if (alpha < 0) alpha = 0;
117 | this.alpha = alpha;
118 | }
119 |
120 | public int getLower() {
121 | return lower;
122 | }
123 |
124 | public void setLower(int lower) {
125 | if (lower < 0) lower = 0;
126 | this.lower = lower;
127 | }
128 |
129 | public int getUpper() {
130 | return upper;
131 | }
132 |
133 | public void setUpper(int upper) {
134 | if (upper > 255) upper = 255;
135 | this.upper = upper;
136 | }
137 | }
138 | }
139 |
--------------------------------------------------------------------------------
/update-app/src/main/java/com/vector/update_app/utils/DrawableUtil.java:
--------------------------------------------------------------------------------
1 | package com.vector.update_app.utils;
2 |
3 | import android.graphics.Color;
4 | import android.graphics.Paint;
5 | import android.graphics.drawable.Drawable;
6 | import android.graphics.drawable.GradientDrawable;
7 | import android.graphics.drawable.StateListDrawable;
8 | import android.widget.TextView;
9 |
10 | import static com.vector.update_app.utils.ColorUtil.getColorStateList;
11 | import static com.vector.update_app.utils.ColorUtil.getRandomColor;
12 |
13 |
14 | /**
15 | * Created by Vector
16 | * on 2017/6/26 0026.
17 | */
18 |
19 | public class DrawableUtil {
20 | private DrawableUtil() {
21 | throw new UnsupportedOperationException("cannot be instantiated");
22 | }
23 |
24 | /**
25 | * 得到实心的drawable, 一般作为选中,点中的效果
26 | *
27 | * @param cornerRadius 圆角半径
28 | * @param solidColor 实心颜色
29 | * @return 得到实心效果
30 | */
31 | public static GradientDrawable getSolidRectDrawable(int cornerRadius, int solidColor) {
32 | GradientDrawable gradientDrawable = new GradientDrawable();
33 | // 设置矩形的圆角半径
34 | gradientDrawable.setCornerRadius(cornerRadius);
35 | // 设置绘画图片色值
36 | gradientDrawable.setColor(solidColor);
37 | // 绘画的是矩形
38 | gradientDrawable.setGradientType(GradientDrawable.RADIAL_GRADIENT);
39 | return gradientDrawable;
40 | }
41 |
42 | /**
43 | * 得到空心的效果,一般作为默认的效果
44 | *
45 | * @param cornerRadius 圆角半径
46 | * @param solidColor 实心颜色
47 | * @param strokeColor 边框颜色
48 | * @param strokeWidth 边框宽度
49 | * @return 得到空心效果
50 | */
51 | public static GradientDrawable getStrokeRectDrawable(int cornerRadius, int solidColor, int strokeColor, int strokeWidth) {
52 | GradientDrawable gradientDrawable = new GradientDrawable();
53 | gradientDrawable.setStroke(strokeWidth, strokeColor);
54 | gradientDrawable.setColor(solidColor);
55 | gradientDrawable.setCornerRadius(cornerRadius);
56 | gradientDrawable.setGradientType(GradientDrawable.RADIAL_GRADIENT);
57 | return gradientDrawable;
58 |
59 | }
60 |
61 | /**
62 | * 背景选择器
63 | *
64 | * @param pressedDrawable 按下状态的Drawable
65 | * @param normalDrawable 正常状态的Drawable
66 | * @return 状态选择器
67 | */
68 | public static StateListDrawable getStateListDrawable(Drawable pressedDrawable, Drawable normalDrawable) {
69 | StateListDrawable stateListDrawable = new StateListDrawable();
70 | stateListDrawable.addState(new int[]{android.R.attr.state_enabled, android.R.attr.state_pressed}, pressedDrawable);
71 | stateListDrawable.addState(new int[]{android.R.attr.state_enabled}, normalDrawable);
72 | //设置不能用的状态
73 | //默认其他状态背景
74 | GradientDrawable gray = getSolidRectDrawable(10, Color.GRAY);
75 | stateListDrawable.addState(new int[]{}, gray);
76 | return stateListDrawable;
77 | }
78 |
79 | /**
80 | * 实体 状态选择器
81 | *
82 | * @param cornerRadius 圆角半径
83 | * @param pressedColor 按下颜色
84 | * @param normalColor 正常的颜色
85 | * @return 状态选择器
86 | */
87 | public static StateListDrawable getDrawable(int cornerRadius, int pressedColor, int normalColor) {
88 | return getStateListDrawable(getSolidRectDrawable(cornerRadius, pressedColor), getSolidRectDrawable(cornerRadius, normalColor));
89 | }
90 |
91 | /**
92 | * 得到 正常空心, 按下实体的状态选择器
93 | *
94 | * @param cornerRadiusPX 圆角半径
95 | * @param strokeWidthPX 边框宽度
96 | * @param subColor 表框颜色
97 | * @param mainColor 实心颜色
98 | * @return 状态选择器
99 | */
100 | public static StateListDrawable getStrokeSolidDrawable(int cornerRadiusPX, int strokeWidthPX, int subColor, int mainColor) {
101 | //一般solidColor 为透明
102 | return getStateListDrawable(
103 | //实心
104 | getSolidRectDrawable(cornerRadiusPX, subColor),
105 | //空心
106 | getStrokeRectDrawable(cornerRadiusPX, mainColor, subColor, strokeWidthPX));
107 | }
108 |
109 | /**
110 | * 得到 正常空心, 按下实体的状态选择器
111 | *
112 | * @param cornerRadiusPX 圆角半径
113 | * @param strokeWidthPX 边框宽度
114 | * @param subColor 表框颜色
115 | * @param mainColor 实心颜色
116 | * @return 状态选择器
117 | */
118 | public static StateListDrawable getSolidStrokeDrawable(int cornerRadiusPX, int strokeWidthPX, int subColor, int mainColor) {
119 | //一般solidColor 为透明
120 | return getStateListDrawable(
121 | //空心
122 | getStrokeRectDrawable(cornerRadiusPX, subColor, mainColor, strokeWidthPX),
123 | //实心
124 | getSolidRectDrawable(cornerRadiusPX, mainColor));
125 | }
126 |
127 | /**
128 | * 实体 按下的颜色加深
129 | *
130 | * @param cornerRadius 圆角半径
131 | * @param normalColor 正常的颜色
132 | * @return 状态选择器
133 | */
134 |
135 | public static StateListDrawable getDrawable(int cornerRadius, int normalColor) {
136 | return getDrawable(cornerRadius, ColorUtil.colorDeep(normalColor), normalColor);
137 | }
138 |
139 | /**
140 | * 实体 得到随机色 状态选择器
141 | *
142 | * @param cornerRadius 圆角半径
143 | * @return 状态选择器
144 | */
145 |
146 | public static StateListDrawable getDrawable(int cornerRadius) {
147 | return getDrawable(cornerRadius, getRandomColor());
148 | }
149 |
150 | /**
151 | * 实体 得到随机色 状态选择器 默认10px
152 | *
153 | * @return 状态选择器
154 | */
155 |
156 | public static StateListDrawable getDrawable() {
157 | return getDrawable(10);
158 | }
159 |
160 |
161 | /**
162 | * 实心 得到 随机背景色并且带选择器, 并且可以设置圆角
163 | *
164 | * @param cornerRadius 圆角
165 | * @return 状态选择器
166 | */
167 | public static StateListDrawable getRandomColorDrawable(int cornerRadius) {
168 | return getDrawable(cornerRadius, getRandomColor(), getRandomColor());
169 |
170 | }
171 |
172 | /**
173 | * 实心 得到随机背景色并且带选择器, 并且可以设置圆角
174 | *
175 | * @return 状态选择器
176 | */
177 | public static StateListDrawable getRandomColorDrawable() {
178 | return getRandomColorDrawable(10);
179 |
180 | }
181 |
182 | /**
183 | * 空心,按下实心 得到随机背景色并且带选择器, 并且可以设置圆角
184 | *
185 | * @return 状态选择器
186 | */
187 | public static StateListDrawable getStrokeRandomColorDrawable() {
188 | return getStrokeSolidDrawable(10, 4, getRandomColor(), Color.TRANSPARENT);
189 | }
190 |
191 | /**
192 | * 默认空心 设置TextView 主题,
193 | *
194 | * @param textView textView
195 | * @param strokeWidth 边框宽度 px
196 | * @param cornerRadius 圆角
197 | * @param color 颜色
198 | */
199 | public static void setTextStrokeTheme(TextView textView, int strokeWidth, int cornerRadius, int color) {
200 | textView.setBackgroundDrawable(getStrokeSolidDrawable(cornerRadius, strokeWidth, color, Color.WHITE));
201 | textView.setTextColor(getColorStateList(Color.WHITE, color));
202 | textView.getPaint().setFlags(Paint.FAKE_BOLD_TEXT_FLAG);
203 | }
204 |
205 | /**
206 | * 默认空心 设置TextView 主题,随机颜色
207 | *
208 | * @param textView textView
209 | * @param strokeWidth 边框宽度 px
210 | * @param cornerRadius 圆角
211 | */
212 | public static void setTextStrokeTheme(TextView textView, int strokeWidth, int cornerRadius) {
213 | setTextStrokeTheme(textView, strokeWidth, cornerRadius, ColorUtil.getRandomColor());
214 | }
215 |
216 | /**
217 | * 默认空心 设置TextView 主题,随机颜色
218 | *
219 | * @param textView textView
220 | */
221 | public static void setTextStrokeTheme(TextView textView) {
222 | setTextStrokeTheme(textView, 6, 10);
223 | }
224 |
225 | /**
226 | * 默认空心 设置TextView 主题,随机颜色
227 | * @param textView 文本控件
228 | * @param color 颜色
229 | */
230 | public static void setTextStrokeTheme(TextView textView, int color) {
231 | setTextStrokeTheme(textView, 6, 10, color);
232 | }
233 |
234 | /**
235 | * 默认实心 设置TextView 主题,
236 | *
237 | * @param textView textView
238 | * @param strokeWidth 边框宽度 px
239 | * @param cornerRadius 圆角
240 | * @param color 颜色
241 | */
242 | public static void setTextSolidTheme(TextView textView, int strokeWidth, int cornerRadius, int color) {
243 | textView.setBackgroundDrawable(getSolidStrokeDrawable(cornerRadius, strokeWidth, Color.WHITE, color));
244 | textView.setTextColor(getColorStateList(color, Color.WHITE));
245 | textView.getPaint().setFlags(Paint.FAKE_BOLD_TEXT_FLAG);
246 | }
247 |
248 | /**
249 | * 默认实心 设置TextView 主题,随机颜色
250 | *
251 | * @param textView textView
252 | * @param strokeWidth 边框宽度 px
253 | * @param cornerRadius 圆角
254 | */
255 | public static void setTextSolidTheme(TextView textView, int strokeWidth, int cornerRadius) {
256 | setTextSolidTheme(textView, strokeWidth, cornerRadius, ColorUtil.getRandomColor());
257 | }
258 |
259 | /**
260 | * 默认实心 设置TextView 主题,随机颜色
261 | *
262 | * @param textView textView
263 | */
264 | public static void setTextSolidTheme(TextView textView) {
265 | setTextSolidTheme(textView, 6, 10);
266 | }
267 |
268 | }
269 |
--------------------------------------------------------------------------------
/update-app/src/main/java/com/vector/update_app/utils/Md5Util.java:
--------------------------------------------------------------------------------
1 | package com.vector.update_app.utils;
2 |
3 | import java.io.File;
4 | import java.io.FileInputStream;
5 | import java.io.IOException;
6 | import java.nio.MappedByteBuffer;
7 | import java.nio.channels.FileChannel;
8 | import java.security.MessageDigest;
9 | import java.security.NoSuchAlgorithmException;
10 |
11 | /**
12 | * Created by Vector
13 | * on 2017/7/11 0011.
14 | */
15 |
16 | public class Md5Util {
17 | public static String getFileMD5(File file) {
18 | if (!file.exists()) {
19 | return "";
20 | }
21 | FileInputStream in = null;
22 | try {
23 | in = new FileInputStream(file);
24 | FileChannel channel = in.getChannel();
25 | MappedByteBuffer buffer = channel.map(FileChannel.MapMode.READ_ONLY, 0, file.length());
26 | MessageDigest md = MessageDigest.getInstance("MD5");
27 | md.update(buffer);
28 | return bytes2Hex(md.digest());
29 | } catch (NoSuchAlgorithmException | IOException e) {
30 | e.printStackTrace();
31 | } finally {
32 | if (in != null) {
33 | try {
34 | in.close();
35 | } catch (IOException ignored) {
36 | }
37 | }
38 | }
39 | return "";
40 | }
41 |
42 | /**
43 | * 一个byte转为2个hex字符
44 | *
45 | * @param src byte数组
46 | * @return 16进制大写字符串
47 | */
48 | public static String bytes2Hex(byte[] src) {
49 | char[] res = new char[src.length << 1];
50 | final char hexDigits[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
51 | for (int i = 0, j = 0; i < src.length; i++) {
52 | res[j++] = hexDigits[src[i] >>> 4 & 0x0f];
53 | res[j++] = hexDigits[src[i] & 0x0f];
54 | }
55 | return new String(res);
56 | }
57 |
58 | }
59 |
--------------------------------------------------------------------------------
/update-app/src/main/java/com/vector/update_app/view/NumberProgressBar.java:
--------------------------------------------------------------------------------
1 | package com.vector.update_app.view;
2 |
3 | import android.content.Context;
4 | import android.content.res.TypedArray;
5 | import android.graphics.Canvas;
6 | import android.graphics.Color;
7 | import android.graphics.Paint;
8 | import android.graphics.RectF;
9 | import android.os.Bundle;
10 | import android.os.Parcelable;
11 | import android.util.AttributeSet;
12 | import android.view.View;
13 |
14 | import com.vector.update_app.R;
15 |
16 |
17 | /**
18 | * Created by daimajia on 14-4-30.
19 | */
20 | public class NumberProgressBar extends View {
21 |
22 | /**
23 | * For save and restore instance of progressbar.
24 | */
25 | private static final String INSTANCE_STATE = "saved_instance";
26 | private static final String INSTANCE_TEXT_COLOR = "text_color";
27 | private static final String INSTANCE_TEXT_SIZE = "text_size";
28 | private static final String INSTANCE_REACHED_BAR_HEIGHT = "reached_bar_height";
29 | private static final String INSTANCE_REACHED_BAR_COLOR = "reached_bar_color";
30 | private static final String INSTANCE_UNREACHED_BAR_HEIGHT = "unreached_bar_height";
31 | private static final String INSTANCE_UNREACHED_BAR_COLOR = "unreached_bar_color";
32 | private static final String INSTANCE_MAX = "max";
33 | private static final String INSTANCE_PROGRESS = "progress";
34 | private static final String INSTANCE_SUFFIX = "suffix";
35 | private static final String INSTANCE_PREFIX = "prefix";
36 | private static final String INSTANCE_TEXT_VISIBILITY = "text_visibility";
37 | private static final int PROGRESS_TEXT_VISIBLE = 0;
38 | private final int default_text_color = Color.rgb(66, 145, 241);
39 | private final int default_reached_color = Color.rgb(66, 145, 241);
40 | private final int default_unreached_color = Color.rgb(204, 204, 204);
41 | private final float default_progress_text_offset;
42 | private final float default_text_size;
43 | private final float default_reached_bar_height;
44 | private final float default_unreached_bar_height;
45 | private int mMaxProgress = 100;
46 | /**
47 | * Current progress, can not exceed the max progress.
48 | */
49 | private int mCurrentProgress = 0;
50 | /**
51 | * The progress area bar color.
52 | */
53 | private int mReachedBarColor;
54 | /**
55 | * The bar unreached area color.
56 | */
57 | private int mUnreachedBarColor;
58 | /**
59 | * The progress text color.
60 | */
61 | private int mTextColor;
62 | /**
63 | * The progress text size.
64 | */
65 | private float mTextSize;
66 | /**
67 | * The height of the reached area.
68 | */
69 | private float mReachedBarHeight;
70 | /**
71 | * The height of the unreached area.
72 | */
73 | private float mUnreachedBarHeight;
74 | /**
75 | * The suffix of the number.
76 | */
77 | private String mSuffix = "%";
78 | /**
79 | * The prefix.
80 | */
81 | private String mPrefix = "";
82 | /**
83 | * The width of the text that to be drawn.
84 | */
85 | private float mDrawTextWidth;
86 |
87 | /**
88 | * The drawn text start.
89 | */
90 | private float mDrawTextStart;
91 |
92 | /**
93 | * The drawn text end.
94 | */
95 | private float mDrawTextEnd;
96 |
97 | /**
98 | * The text that to be drawn in onDraw().
99 | */
100 | private String mCurrentDrawText;
101 |
102 | /**
103 | * The Paint of the reached area.
104 | */
105 | private Paint mReachedBarPaint;
106 | /**
107 | * The Paint of the unreached area.
108 | */
109 | private Paint mUnreachedBarPaint;
110 | /**
111 | * The Paint of the progress text.
112 | */
113 | private Paint mTextPaint;
114 |
115 | /**
116 | * Unreached bar area to draw rect.
117 | */
118 | private RectF mUnreachedRectF = new RectF(0, 0, 0, 0);
119 | /**
120 | * Reached bar area rect.
121 | */
122 | private RectF mReachedRectF = new RectF(0, 0, 0, 0);
123 |
124 | /**
125 | * The progress text offset.
126 | */
127 | private float mOffset;
128 |
129 | /**
130 | * Determine if need to draw unreached area.
131 | */
132 | private boolean mDrawUnreachedBar = true;
133 |
134 | private boolean mDrawReachedBar = true;
135 |
136 | private boolean mIfDrawText = true;
137 |
138 | /**
139 | * Listener
140 | */
141 | private OnProgressBarListener mListener;
142 | private Paint mCicrlePaint;
143 |
144 | public NumberProgressBar(Context context) {
145 | this(context, null);
146 | }
147 |
148 | public NumberProgressBar(Context context, AttributeSet attrs) {
149 | this(context, attrs, 0);
150 | }
151 |
152 | public NumberProgressBar(Context context, AttributeSet attrs, int defStyleAttr) {
153 | super(context, attrs, defStyleAttr);
154 |
155 | default_reached_bar_height = dp2px(1.5f);
156 | default_unreached_bar_height = dp2px(1.0f);
157 | default_text_size = sp2px(10);
158 | default_progress_text_offset = dp2px(3.0f);
159 |
160 | //load styled attributes.
161 | final TypedArray attributes = context.getTheme().obtainStyledAttributes(attrs, R.styleable.UpdateAppNumberProgressBar, defStyleAttr, 0);
162 |
163 | mReachedBarColor = attributes.getColor(R.styleable.UpdateAppNumberProgressBar_progress_reached_color, default_reached_color);
164 | mUnreachedBarColor = attributes.getColor(R.styleable.UpdateAppNumberProgressBar_progress_unreached_color, default_unreached_color);
165 | mTextColor = attributes.getColor(R.styleable.UpdateAppNumberProgressBar_progress_text_color, default_text_color);
166 | mTextSize = attributes.getDimension(R.styleable.UpdateAppNumberProgressBar_progress_text_size, default_text_size);
167 |
168 | mReachedBarHeight = attributes.getDimension(R.styleable.UpdateAppNumberProgressBar_progress_reached_bar_height, default_reached_bar_height);
169 | mUnreachedBarHeight = attributes.getDimension(R.styleable.UpdateAppNumberProgressBar_progress_unreached_bar_height, default_unreached_bar_height);
170 | mOffset = attributes.getDimension(R.styleable.UpdateAppNumberProgressBar_progress_text_offset, default_progress_text_offset);
171 |
172 | int textVisible = attributes.getInt(R.styleable.UpdateAppNumberProgressBar_progress_text_visibility, PROGRESS_TEXT_VISIBLE);
173 | if (textVisible != PROGRESS_TEXT_VISIBLE) {
174 | mIfDrawText = false;
175 | }
176 |
177 | setProgress(attributes.getInt(R.styleable.UpdateAppNumberProgressBar_progress_current, 0));
178 | setMax(attributes.getInt(R.styleable.UpdateAppNumberProgressBar_progress_max, 100));
179 |
180 | attributes.recycle();
181 | initializePainters();
182 | }
183 |
184 | @Override
185 | protected int getSuggestedMinimumWidth() {
186 | return (int) mTextSize;
187 | }
188 |
189 | @Override
190 | protected int getSuggestedMinimumHeight() {
191 | return Math.max((int) mTextSize, Math.max((int) mReachedBarHeight, (int) mUnreachedBarHeight));
192 | }
193 |
194 | @Override
195 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
196 | setMeasuredDimension(measure(widthMeasureSpec, true), measure(heightMeasureSpec, false));
197 | }
198 |
199 | private int measure(int measureSpec, boolean isWidth) {
200 | int result;
201 | int mode = MeasureSpec.getMode(measureSpec);
202 | int size = MeasureSpec.getSize(measureSpec);
203 | int padding = isWidth ? getPaddingLeft() + getPaddingRight() : getPaddingTop() + getPaddingBottom();
204 | if (mode == MeasureSpec.EXACTLY) {
205 | result = size;
206 | } else {
207 | result = isWidth ? getSuggestedMinimumWidth() : getSuggestedMinimumHeight();
208 | result += padding;
209 | if (mode == MeasureSpec.AT_MOST) {
210 | if (isWidth) {
211 | result = Math.max(result, size);
212 | } else {
213 | result = Math.min(result, size);
214 | }
215 | }
216 | }
217 | return result;
218 | }
219 |
220 | @Override
221 | protected void onDraw(Canvas canvas) {
222 | if (mIfDrawText) {
223 | calculateDrawRectF();
224 | } else {
225 | calculateDrawRectFWithoutProgressText();
226 | }
227 |
228 | if (mDrawReachedBar) {
229 | canvas.drawRect(mReachedRectF, mReachedBarPaint);
230 | }
231 |
232 | if (mDrawUnreachedBar) {
233 | canvas.drawRect(mUnreachedRectF, mUnreachedBarPaint);
234 | }
235 |
236 | if (mIfDrawText) {
237 | // float start = (mUnreachedRectF.left + mReachedRectF.right) / 2.0f;
238 | // float radius = (mUnreachedRectF.left - mReachedRectF.right) / 2.0f;
239 | // canvas.drawCircle(start, getHeight() / 2.0f, radius, mCicrlePaint);
240 | canvas.drawText(mCurrentDrawText, mDrawTextStart, mDrawTextEnd, mTextPaint);
241 | // Log.d("NumberProgressBar", mCurrentDrawText);
242 | // Log.d("NumberProgressBar", "mDrawTextStart:" + mDrawTextStart);
243 | // Log.d("NumberProgressBar", "mDrawTextEnd:" + mDrawTextEnd);
244 | // Log.d("NumberProgressBar", "start:" + start);
245 | }
246 |
247 |
248 | }
249 |
250 | private void initializePainters() {
251 | mReachedBarPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
252 | mReachedBarPaint.setColor(mReachedBarColor);
253 |
254 | mUnreachedBarPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
255 | mUnreachedBarPaint.setColor(mUnreachedBarColor);
256 |
257 | mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
258 | mTextPaint.setColor(mTextColor);
259 | mTextPaint.setTextSize(mTextSize);
260 |
261 | // mCicrlePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
262 | // mCicrlePaint.setColor(Color.parseColor("#66ff6666"));
263 | }
264 |
265 | private void calculateDrawRectFWithoutProgressText() {
266 | mReachedRectF.left = getPaddingLeft();
267 | mReachedRectF.top = getHeight() / 2.0f - mReachedBarHeight / 2.0f;
268 | mReachedRectF.right = (getWidth() - getPaddingLeft() - getPaddingRight()) / (getMax() * 1.0f) * getProgress() + getPaddingLeft();
269 | mReachedRectF.bottom = getHeight() / 2.0f + mReachedBarHeight / 2.0f;
270 |
271 | mUnreachedRectF.left = mReachedRectF.right;
272 | mUnreachedRectF.right = getWidth() - getPaddingRight();
273 | mUnreachedRectF.top = getHeight() / 2.0f + -mUnreachedBarHeight / 2.0f;
274 | mUnreachedRectF.bottom = getHeight() / 2.0f + mUnreachedBarHeight / 2.0f;
275 | }
276 |
277 | private void calculateDrawRectF() {
278 |
279 | mCurrentDrawText = String.format("%d", getProgress() * 100 / getMax());
280 | mCurrentDrawText = mPrefix + mCurrentDrawText + mSuffix;
281 | mDrawTextWidth = mTextPaint.measureText(mCurrentDrawText);
282 |
283 | if (getProgress() == 0) {
284 | mDrawReachedBar = false;
285 | mDrawTextStart = getPaddingLeft();
286 | } else {
287 | mDrawReachedBar = true;
288 | mReachedRectF.left = getPaddingLeft();
289 | mReachedRectF.top = getHeight() / 2.0f - mReachedBarHeight / 2.0f;
290 | mReachedRectF.right = (getWidth() - getPaddingLeft() - getPaddingRight()) / (getMax() * 1.0f) * getProgress() - mOffset + getPaddingLeft();
291 | mReachedRectF.bottom = getHeight() / 2.0f + mReachedBarHeight / 2.0f;
292 | mDrawTextStart = (mReachedRectF.right + mOffset);
293 | }
294 |
295 | mDrawTextEnd = (int) ((getHeight() / 2.0f) - ((mTextPaint.descent() + mTextPaint.ascent()) / 2.0f));
296 |
297 | if ((mDrawTextStart + mDrawTextWidth) >= getWidth() - getPaddingRight()) {
298 | mDrawTextStart = getWidth() - getPaddingRight() - mDrawTextWidth;
299 | mReachedRectF.right = mDrawTextStart - mOffset;
300 | }
301 |
302 | float unreachedBarStart = mDrawTextStart + mDrawTextWidth + mOffset;
303 | if (unreachedBarStart >= getWidth() - getPaddingRight()) {
304 | mDrawUnreachedBar = false;
305 | } else {
306 | mDrawUnreachedBar = true;
307 | mUnreachedRectF.left = unreachedBarStart;
308 | mUnreachedRectF.right = getWidth() - getPaddingRight();
309 | mUnreachedRectF.top = getHeight() / 2.0f + -mUnreachedBarHeight / 2.0f;
310 | mUnreachedRectF.bottom = getHeight() / 2.0f + mUnreachedBarHeight / 2.0f;
311 | }
312 | }
313 |
314 | /**
315 | * Get progress text color.
316 | *
317 | * @return progress text color.
318 | */
319 | public int getTextColor() {
320 | return mTextColor;
321 | }
322 |
323 | /**
324 | * Get progress text size.
325 | *
326 | * @return progress text size.
327 | */
328 | public float getProgressTextSize() {
329 | return mTextSize;
330 | }
331 |
332 | public void setProgressTextSize(float textSize) {
333 | this.mTextSize = textSize;
334 | mTextPaint.setTextSize(mTextSize);
335 | invalidate();
336 | }
337 |
338 | public int getUnreachedBarColor() {
339 | return mUnreachedBarColor;
340 | }
341 |
342 | public void setUnreachedBarColor(int barColor) {
343 | this.mUnreachedBarColor = barColor;
344 | mUnreachedBarPaint.setColor(mUnreachedBarColor);
345 | invalidate();
346 | }
347 |
348 | public int getReachedBarColor() {
349 | return mReachedBarColor;
350 | }
351 |
352 | public void setReachedBarColor(int progressColor) {
353 | this.mReachedBarColor = progressColor;
354 | mReachedBarPaint.setColor(mReachedBarColor);
355 | invalidate();
356 | }
357 |
358 | public int getProgress() {
359 | return mCurrentProgress;
360 | }
361 |
362 | public void setProgress(int progress) {
363 | if (progress <= getMax() && progress >= 0) {
364 | this.mCurrentProgress = progress;
365 | invalidate();
366 | }
367 | }
368 |
369 | public int getMax() {
370 | return mMaxProgress;
371 | }
372 |
373 | public void setMax(int maxProgress) {
374 | if (maxProgress > 0) {
375 | this.mMaxProgress = maxProgress;
376 | invalidate();
377 | }
378 | }
379 |
380 | public float getReachedBarHeight() {
381 | return mReachedBarHeight;
382 | }
383 |
384 | public void setReachedBarHeight(float height) {
385 | mReachedBarHeight = height;
386 | }
387 |
388 | public float getUnreachedBarHeight() {
389 | return mUnreachedBarHeight;
390 | }
391 |
392 | public void setUnreachedBarHeight(float height) {
393 | mUnreachedBarHeight = height;
394 | }
395 |
396 | public void setProgressTextColor(int textColor) {
397 | this.mTextColor = textColor;
398 | mTextPaint.setColor(mTextColor);
399 | invalidate();
400 | }
401 |
402 | public String getSuffix() {
403 | return mSuffix;
404 | }
405 |
406 | public void setSuffix(String suffix) {
407 | if (suffix == null) {
408 | mSuffix = "";
409 | } else {
410 | mSuffix = suffix;
411 | }
412 | }
413 |
414 | public String getPrefix() {
415 | return mPrefix;
416 | }
417 |
418 | public void setPrefix(String prefix) {
419 | if (prefix == null)
420 | mPrefix = "";
421 | else {
422 | mPrefix = prefix;
423 | }
424 | }
425 |
426 | public void incrementProgressBy(int by) {
427 | if (by > 0) {
428 | setProgress(getProgress() + by);
429 | }
430 |
431 | if (mListener != null) {
432 | mListener.onProgressChange(getProgress(), getMax());
433 | }
434 | }
435 |
436 | @Override
437 | protected Parcelable onSaveInstanceState() {
438 | final Bundle bundle = new Bundle();
439 | bundle.putParcelable(INSTANCE_STATE, super.onSaveInstanceState());
440 | bundle.putInt(INSTANCE_TEXT_COLOR, getTextColor());
441 | bundle.putFloat(INSTANCE_TEXT_SIZE, getProgressTextSize());
442 | bundle.putFloat(INSTANCE_REACHED_BAR_HEIGHT, getReachedBarHeight());
443 | bundle.putFloat(INSTANCE_UNREACHED_BAR_HEIGHT, getUnreachedBarHeight());
444 | bundle.putInt(INSTANCE_REACHED_BAR_COLOR, getReachedBarColor());
445 | bundle.putInt(INSTANCE_UNREACHED_BAR_COLOR, getUnreachedBarColor());
446 | bundle.putInt(INSTANCE_MAX, getMax());
447 | bundle.putInt(INSTANCE_PROGRESS, getProgress());
448 | bundle.putString(INSTANCE_SUFFIX, getSuffix());
449 | bundle.putString(INSTANCE_PREFIX, getPrefix());
450 | bundle.putBoolean(INSTANCE_TEXT_VISIBILITY, getProgressTextVisibility());
451 | return bundle;
452 | }
453 |
454 | @Override
455 | protected void onRestoreInstanceState(Parcelable state) {
456 | if (state instanceof Bundle) {
457 | final Bundle bundle = (Bundle) state;
458 | mTextColor = bundle.getInt(INSTANCE_TEXT_COLOR);
459 | mTextSize = bundle.getFloat(INSTANCE_TEXT_SIZE);
460 | mReachedBarHeight = bundle.getFloat(INSTANCE_REACHED_BAR_HEIGHT);
461 | mUnreachedBarHeight = bundle.getFloat(INSTANCE_UNREACHED_BAR_HEIGHT);
462 | mReachedBarColor = bundle.getInt(INSTANCE_REACHED_BAR_COLOR);
463 | mUnreachedBarColor = bundle.getInt(INSTANCE_UNREACHED_BAR_COLOR);
464 | initializePainters();
465 | setMax(bundle.getInt(INSTANCE_MAX));
466 | setProgress(bundle.getInt(INSTANCE_PROGRESS));
467 | setPrefix(bundle.getString(INSTANCE_PREFIX));
468 | setSuffix(bundle.getString(INSTANCE_SUFFIX));
469 | setProgressTextVisibility(bundle.getBoolean(INSTANCE_TEXT_VISIBILITY) ? ProgressTextVisibility.VISIBLE : ProgressTextVisibility.INVISIBLE);
470 | super.onRestoreInstanceState(bundle.getParcelable(INSTANCE_STATE));
471 | return;
472 | }
473 | super.onRestoreInstanceState(state);
474 | }
475 |
476 | public float dp2px(float dp) {
477 | final float scale = getResources().getDisplayMetrics().density;
478 | return dp * scale + 0.5f;
479 | }
480 |
481 | public float sp2px(float sp) {
482 | final float scale = getResources().getDisplayMetrics().scaledDensity;
483 | return sp * scale;
484 | }
485 |
486 | public boolean getProgressTextVisibility() {
487 | return mIfDrawText;
488 | }
489 |
490 | public void setProgressTextVisibility(ProgressTextVisibility visibility) {
491 | mIfDrawText = visibility == ProgressTextVisibility.VISIBLE;
492 | invalidate();
493 | }
494 |
495 | public void setOnProgressBarListener(OnProgressBarListener listener) {
496 | mListener = listener;
497 | }
498 |
499 | public enum ProgressTextVisibility {
500 | VISIBLE, INVISIBLE
501 | }
502 |
503 | public interface OnProgressBarListener {
504 |
505 | void onProgressChange(int current, int max);
506 | }
507 |
508 | }
509 |
--------------------------------------------------------------------------------
/update-app/src/main/res/anim/update_app_window_in.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/update-app/src/main/res/anim/update_app_window_out.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/update-app/src/main/res/drawable/lib_update_app_info_bg.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
7 |
8 |
--------------------------------------------------------------------------------
/update-app/src/main/res/layout/lib_update_app_dialog.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
10 |
15 |
16 |
21 |
22 |
29 |
30 |
37 |
38 |
45 |
46 |
53 |
54 |
55 |
56 |
65 |
66 |
76 |
77 |
83 |
84 |
85 |
86 |
93 |
94 |
100 |
101 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
--------------------------------------------------------------------------------
/update-app/src/main/res/mipmap-hdpi/lib_update_app_top_bg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WVector/AppUpdate/3041a62adeec3006f74a0b50728fe3b8c57d1a29/update-app/src/main/res/mipmap-hdpi/lib_update_app_top_bg.png
--------------------------------------------------------------------------------
/update-app/src/main/res/mipmap-xhdpi/lib_update_app_close.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WVector/AppUpdate/3041a62adeec3006f74a0b50728fe3b8c57d1a29/update-app/src/main/res/mipmap-xhdpi/lib_update_app_close.png
--------------------------------------------------------------------------------
/update-app/src/main/res/mipmap-xhdpi/lib_update_app_update_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WVector/AppUpdate/3041a62adeec3006f74a0b50728fe3b8c57d1a29/update-app/src/main/res/mipmap-xhdpi/lib_update_app_update_icon.png
--------------------------------------------------------------------------------
/update-app/src/main/res/values/attrs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/update-app/src/main/res/values/style.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
20 |
21 |
29 |
--------------------------------------------------------------------------------
/update-app/src/main/res/xml/new_app_file_paths.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
--------------------------------------------------------------------------------
/web/AppVersionManger.rar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WVector/AppUpdate/3041a62adeec3006f74a0b50728fe3b8c57d1a29/web/AppVersionManger.rar
--------------------------------------------------------------------------------