├── app
├── .gitignore
├── src
│ ├── main
│ │ ├── res
│ │ │ ├── values
│ │ │ │ ├── strings.xml
│ │ │ │ ├── colors.xml
│ │ │ │ ├── dimens.xml
│ │ │ │ └── styles.xml
│ │ │ ├── mipmap-hdpi
│ │ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-mdpi
│ │ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-xhdpi
│ │ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-xxhdpi
│ │ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-xxxhdpi
│ │ │ │ └── ic_launcher.png
│ │ │ ├── xml
│ │ │ │ └── file_paths.xml
│ │ │ ├── values-w820dp
│ │ │ │ └── dimens.xml
│ │ │ └── layout
│ │ │ │ └── activity_main.xml
│ │ ├── java
│ │ │ └── com
│ │ │ │ └── zyw
│ │ │ │ └── horrarndoo
│ │ │ │ └── updatedemo
│ │ │ │ ├── net
│ │ │ │ ├── HttpCallback.java
│ │ │ │ └── HttpUtils.java
│ │ │ │ ├── update
│ │ │ │ ├── OnCheckUpdateListener.java
│ │ │ │ ├── OnUpdateListener.java
│ │ │ │ └── UpdateManager.java
│ │ │ │ ├── constant
│ │ │ │ └── Constant.java
│ │ │ │ ├── download
│ │ │ │ ├── OnDownloadListener.java
│ │ │ │ ├── DownloadHelper.java
│ │ │ │ ├── DownloadManager.java
│ │ │ │ └── DownloadTask.java
│ │ │ │ ├── bean
│ │ │ │ ├── UpdateBean.java
│ │ │ │ └── DataBean.java
│ │ │ │ ├── MyApplication.java
│ │ │ │ ├── utils
│ │ │ │ ├── ToastUtils.java
│ │ │ │ ├── FileUtils.java
│ │ │ │ ├── StringUtils.java
│ │ │ │ ├── SpUtils.java
│ │ │ │ └── AppUtils.java
│ │ │ │ └── MainActivity.java
│ │ └── AndroidManifest.xml
│ ├── test
│ │ └── java
│ │ │ └── com
│ │ │ └── zyw
│ │ │ └── horrarndoo
│ │ │ └── updatedemo
│ │ │ └── ExampleUnitTest.java
│ └── androidTest
│ │ └── java
│ │ └── com
│ │ └── zyw
│ │ └── horrarndoo
│ │ └── updatedemo
│ │ └── ExampleInstrumentedTest.java
├── proguard-rules.pro
└── build.gradle
├── settings.gradle
├── .idea
├── copyright
│ └── profiles_settings.xml
├── vcs.xml
├── modules.xml
├── runConfigurations.xml
├── gradle.xml
├── compiler.xml
└── misc.xml
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── .gitignore
├── gradle.properties
├── gradlew.bat
├── gradlew
└── README.md
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 |
--------------------------------------------------------------------------------
/.idea/copyright/profiles_settings.xml:
--------------------------------------------------------------------------------
1 |
6 | */
7 |
8 | public interface OnCheckUpdateListener {
9 | /**
10 | * 发现新版本
11 | *
12 | * @param versionName 新版Apk版本名称
13 | * @param newVersionContent 新版Apk更新内容
14 | */
15 | void onFindNewVersion(String versionName, String newVersionContent);
16 |
17 | /**
18 | * 当前版本已是最新版本
19 | */
20 | void onNewest();
21 | }
22 |
--------------------------------------------------------------------------------
/.idea/runConfigurations.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 | */
7 |
8 | public class Constant {
9 | public static final String APK_URL = "http://192.168.16.113:8080/update_demo/demo.apk";
10 |
11 | public static final String VERSION_INFO_URL =
12 | "http://192.168.16.113:8080/update_demo/update_demo_info.json";
13 |
14 | public static final String SP_KEY_CACHE_VALID_TIME = "sp_key_cache_valid_time";
15 |
16 | public static final String SP_KEY_CACHE_APK_VERSION_CODE = "sp_key_cache_apk_version_code";
17 | }
18 |
--------------------------------------------------------------------------------
/.idea/gradle.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 | */ 7 | 8 | public interface OnUpdateListener { 9 | /** 10 | * 开始更新 11 | */ 12 | void onStartUpdate(); 13 | 14 | /** 15 | * 更新进度变化 16 | * 17 | * @param progress 当前更新进度 18 | */ 19 | void onProgress(int progress); 20 | 21 | /** 22 | * 新的Apk下载完成 23 | * 24 | * @param apkPath apk 全路径 25 | */ 26 | void onApkDownloadFinish(String apkPath); 27 | 28 | /** 29 | * 更新失败 30 | */ 31 | void onUpdateFailed(); 32 | 33 | /** 34 | * 更新已取消 35 | */ 36 | void onUpdateCanceled(); 37 | 38 | /** 39 | * 更新异常 40 | * 41 | * 主要是下载APK文件不完整或者APK包异常 42 | */ 43 | void onUpdateException(); 44 | } 45 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/zyw/horrarndoo/updatedemo/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.zyw.horrarndoo.updatedemo; 2 | 3 | import android.content.Context; 4 | import android.support.test.InstrumentationRegistry; 5 | import android.support.test.runner.AndroidJUnit4; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import static org.junit.Assert.*; 11 | 12 | /** 13 | * 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.zyw.horrarndoo.updatedemo", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 25 5 | buildToolsVersion "26.0.2" 6 | defaultConfig { 7 | applicationId "com.zyw.horrarndoo.updatedemo" 8 | minSdkVersion 15 9 | targetSdkVersion 25 10 | versionCode 1 11 | versionName "1.0.1" 12 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 13 | } 14 | buildTypes { 15 | release { 16 | minifyEnabled false 17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 18 | } 19 | } 20 | } 21 | 22 | dependencies { 23 | compile fileTree(dir: 'libs', include: ['*.jar']) 24 | androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { 25 | exclude group: 'com.android.support', module: 'support-annotations' 26 | }) 27 | compile 'com.android.support:appcompat-v7:25.3.1' 28 | compile 'com.squareup.okhttp3:okhttp:3.4.1' 29 | testCompile 'junit:junit:4.12' 30 | 31 | } 32 | -------------------------------------------------------------------------------- /app/src/main/java/com/zyw/horrarndoo/updatedemo/bean/UpdateBean.java: -------------------------------------------------------------------------------- 1 | package com.zyw.horrarndoo.updatedemo.bean; 2 | 3 | /** 4 | * Created by Horrarndoo on 2018/2/1. 5 | *
6 | */ 7 | 8 | public class UpdateBean { 9 | private DataBean data; 10 | private String msg; 11 | private int status; 12 | 13 | public void setData(DataBean data) { 14 | this.data = data; 15 | } 16 | 17 | public DataBean getData() { 18 | return data; 19 | } 20 | 21 | public void setMsg(String msg) { 22 | this.msg = msg; 23 | } 24 | 25 | public String getMsg() { 26 | return msg; 27 | } 28 | 29 | public void setStatus(int status) { 30 | this.status = status; 31 | } 32 | 33 | public int getStatus() { 34 | return status; 35 | } 36 | 37 | @Override 38 | public String toString() { 39 | return "UpdateBean{" + 40 | "data=" + data + 41 | ", msg='" + msg + '\'' + 42 | ", status=" + status + 43 | '}'; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /app/src/main/java/com/zyw/horrarndoo/updatedemo/MyApplication.java: -------------------------------------------------------------------------------- 1 | package com.zyw.horrarndoo.updatedemo; 2 | 3 | import android.app.Application; 4 | import android.content.Context; 5 | import android.os.Handler; 6 | 7 | /** 8 | * Created by Horrarndoo on 2018/2/1. 9 | *
10 | */ 11 | 12 | public class MyApplication extends Application { 13 | protected static Context context; 14 | protected static Handler handler; 15 | protected static int mainThreadId; 16 | 17 | @Override 18 | public void onCreate() { 19 | super.onCreate(); 20 | context = getApplicationContext(); 21 | handler = new Handler(); 22 | mainThreadId = android.os.Process.myTid(); 23 | } 24 | 25 | /** 26 | * 获取上下文对象 27 | * 28 | * @return context 29 | */ 30 | public static Context getContext() { 31 | return context; 32 | } 33 | 34 | /** 35 | * 获取全局handler 36 | * 37 | * @return 全局handler 38 | */ 39 | public static Handler getHandler() { 40 | return handler; 41 | } 42 | 43 | /** 44 | * 获取主线程id 45 | * 46 | * @return 主线程id 47 | */ 48 | public static int getMainThreadId() { 49 | return mainThreadId; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /app/src/main/java/com/zyw/horrarndoo/updatedemo/download/DownloadHelper.java: -------------------------------------------------------------------------------- 1 | package com.zyw.horrarndoo.updatedemo.download; 2 | 3 | import android.os.Environment; 4 | import android.support.annotation.NonNull; 5 | 6 | import java.io.File; 7 | 8 | /** 9 | * Created by Horrarndoo on 2018/2/1. 10 | *
11 | */
12 |
13 | public class DownloadHelper {
14 | private static String DIR_DOWNLOAD = "UpdateDemo";
15 |
16 | /**
17 | * 获取下载文件url中的文件名
18 | *
19 | * @param url 下载文件的url
20 | * @return 下载文件url中的文件名
21 | * 和path组成完整的file path
22 | */
23 | @NonNull
24 | public static String getUrlFileName(String url) {
25 | return url.substring(url.lastIndexOf("/") + 1);
26 | }
27 |
28 | /**
29 | * 获取文件下载父目录
30 | *
31 | * @return 文件下载父目录
32 | */
33 | public static File getDownloadParentFile() {
34 | File appDir = new File(Environment.getExternalStorageDirectory(), DIR_DOWNLOAD);
35 | if (!appDir.exists()) {
36 | appDir.mkdir();
37 | }
38 | return appDir;
39 | }
40 |
41 | /**
42 | * 获取下载文件父目录路径
43 | *
44 | * @return 下载文件父目录路径
45 | */
46 | @NonNull
47 | public static String getDownloadParentFilePath() {
48 | return getDownloadParentFile().getPath();
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 | */
7 |
8 | public class DataBean {
9 | private String content;
10 | private int id;
11 | private String api_key;
12 | private int version_code;
13 | private String version_name;
14 |
15 | public void setContent(String content) {
16 | this.content = content;
17 | }
18 |
19 | public String getContent() {
20 | return content;
21 | }
22 |
23 | public void setId(int id) {
24 | this.id = id;
25 | }
26 |
27 | public int getId() {
28 | return id;
29 | }
30 |
31 | public void setApiKey(String api_key) {
32 | this.api_key = api_key;
33 | }
34 |
35 | public String getApiKey() {
36 | return api_key;
37 | }
38 |
39 | public void setVersionCode(int version_code) {
40 | this.version_code = version_code;
41 | }
42 |
43 | public int getVersionCode() {
44 | return version_code;
45 | }
46 |
47 | public String getVersionName() {
48 | return version_name;
49 | }
50 |
51 | public void setVersionName(String version_name) {
52 | this.version_name = version_name;
53 | }
54 |
55 | @Override
56 | public String toString() {
57 | return "DataBean{" +
58 | "content='" + content + '\'' +
59 | ", id=" + id +
60 | ", api_key='" + api_key + '\'' +
61 | ", version_code=" + version_code +
62 | ", version_name='" + version_name + '\'' +
63 | '}';
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 | */ 10 | 11 | public class ToastUtils { 12 | private static Toast mToast = null; 13 | 14 | /** 15 | * 显示一个toast提示 16 | * 17 | * @param resouceId toast字符串资源id 18 | */ 19 | public static void showToast(int resouceId) { 20 | showToast(AppUtils.getString(resouceId)); 21 | } 22 | 23 | /** 24 | * 显示一个toast提示 25 | * 26 | * @param text toast字符串 27 | */ 28 | public static void showToast(String text) { 29 | showToast(text, Toast.LENGTH_SHORT); 30 | } 31 | 32 | /** 33 | * 显示一个toast提示 34 | * 35 | * @param text toast字符串 36 | * @param duration toast显示时间 37 | */ 38 | public static void showToast(String text, int duration) { 39 | showToast(AppUtils.getContext(), text, duration); 40 | } 41 | 42 | /** 43 | * 显示一个toast提示 44 | * 45 | * @param context context 上下文对象 46 | * @param text toast字符串 47 | * @param duration toast显示时间 48 | */ 49 | public static void showToast(final Context context, final String text, final int duration) { 50 | /** 51 | * 保证运行在主线程 52 | */ 53 | AppUtils.runOnUIThread(new Runnable() { 54 | @Override 55 | public void run() { 56 | if (mToast == null) { 57 | mToast = Toast.makeText(context, text, duration); 58 | } else { 59 | mToast.setText(text); 60 | mToast.setDuration(duration); 61 | } 62 | mToast.show(); 63 | } 64 | }); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /app/src/main/java/com/zyw/horrarndoo/updatedemo/utils/FileUtils.java: -------------------------------------------------------------------------------- 1 | package com.zyw.horrarndoo.updatedemo.utils; 2 | 3 | import java.io.File; 4 | 5 | /** 6 | * Created by Horrarndoo on 2018/2/1. 7 | *
8 | */ 9 | 10 | public class FileUtils { 11 | /** 12 | * 删除指定文件 13 | * 14 | * @param filePath 文件path 15 | * @return 删除结果 16 | */ 17 | public static boolean delFile(String filePath) { 18 | if (StringUtils.isEmpty(filePath)) 19 | return false; 20 | 21 | File file = new File(filePath); 22 | if (file.exists()) { 23 | return file.delete(); 24 | } 25 | return false; 26 | } 27 | 28 | /** 29 | * 删除指定文件夹下所有文件 30 | * 31 | * @param path 文件夹完整绝对路径 32 | * @return 删除结果 33 | */ 34 | public static boolean delAllFile(String path) { 35 | boolean flag = false; 36 | File file = new File(path); 37 | if (!file.exists()) { 38 | return flag; 39 | } 40 | if (!file.isDirectory()) { 41 | return flag; 42 | } 43 | String[] tempList = file.list(); 44 | File temp = null; 45 | for (int i = 0; i < tempList.length; i++) { 46 | if (path.endsWith(File.separator)) { 47 | temp = new File(path + tempList[i]); 48 | } else { 49 | temp = new File(path + File.separator + tempList[i]); 50 | } 51 | if (temp.isFile()) { 52 | temp.delete(); 53 | } 54 | if (temp.isDirectory()) { 55 | delAllFile(path + "/" + tempList[i]);//先删除文件夹里面的文件 56 | delFolder(path + "/" + tempList[i]);//再删除空文件夹 57 | flag = true; 58 | } 59 | } 60 | return flag; 61 | } 62 | 63 | /** 64 | * 删除文件夹 65 | * 66 | * @param folderPath 文件夹完整绝对路径 67 | */ 68 | public static void delFolder(String folderPath) { 69 | try { 70 | delAllFile(folderPath); //删除完里面所有内容 71 | String filePath = folderPath; 72 | filePath = filePath.toString(); 73 | java.io.File myFilePath = new java.io.File(filePath); 74 | myFilePath.delete(); //删除空文件夹 75 | } catch (Exception e) { 76 | e.printStackTrace(); 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /app/src/main/java/com/zyw/horrarndoo/updatedemo/net/HttpUtils.java: -------------------------------------------------------------------------------- 1 | package com.zyw.horrarndoo.updatedemo.net; 2 | 3 | import java.io.BufferedReader; 4 | import java.io.InputStream; 5 | import java.io.InputStreamReader; 6 | import java.net.HttpURLConnection; 7 | import java.net.URL; 8 | 9 | import okhttp3.OkHttpClient; 10 | import okhttp3.Request; 11 | 12 | /** 13 | * Created by Horrarndoo on 2018/2/1. 14 | *
15 | */
16 |
17 | public class HttpUtils {
18 | public static void sendHttpRequest(final String address, final HttpCallback callback) {
19 | new Thread(new Runnable() {
20 | @Override
21 | public void run() {
22 | HttpURLConnection connection = null;
23 | try {
24 | URL url = new URL(address);
25 | connection = (HttpURLConnection) url.openConnection();
26 | connection.setRequestMethod("GET");
27 | connection.setConnectTimeout(8000);
28 | connection.setReadTimeout(8000);
29 | connection.setDoInput(true);
30 | connection.setDoOutput(true);
31 | InputStream in = connection.getInputStream();
32 | BufferedReader reader = new BufferedReader(new InputStreamReader(in, "UTF-8"));
33 | StringBuilder response = new StringBuilder();
34 | String line;
35 | while ((line = reader.readLine()) != null) {
36 | response.append(line);
37 | }
38 | if (callback != null) {
39 | // 回调onFinish()方法
40 | callback.onFinish(response.toString());
41 | }
42 | } catch (Exception e) {
43 | if (callback != null) {
44 | // 回调onError()方法
45 | callback.onError(e);
46 | }
47 | } finally {
48 | if (connection != null) {
49 | connection.disconnect();
50 | }
51 | }
52 | }
53 | }).start();
54 | }
55 |
56 | public static void sendOkHttpRequest(final String address, final okhttp3.Callback callback) {
57 | OkHttpClient client = new OkHttpClient();
58 | Request request = new Request.Builder()
59 | .url(address)
60 | .build();
61 | client.newCall(request).enqueue(callback);
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
12 |
17 |
22 |
27 |
54 |
56 |
11 | */
12 |
13 | public class DownloadManager {
14 | private static volatile DownloadManager manager = null;
15 |
16 | private DownloadTask downloadTask;
17 |
18 | private String mFileName;
19 |
20 | private String mFileParentPath;
21 |
22 | private DownloadManager() {
23 | mFileParentPath = DownloadHelper.getDownloadParentFilePath();
24 | }
25 |
26 | public static DownloadManager getInstance() {
27 | if (manager == null) {
28 | synchronized (DownloadManager.class) {
29 | if (manager == null) {
30 | manager = new DownloadManager();
31 | }
32 | }
33 | }
34 | return manager;
35 | }
36 |
37 | /**
38 | * 开启下载任务
39 | *
40 | * @param url 下载链接
41 | * @param onDownloadListener onDownloadListener
42 | */
43 | public void startDownload(String url, OnDownloadListener onDownloadListener) {
44 | startDownload(url, DownloadHelper.getDownloadParentFilePath(), DownloadHelper
45 | .getUrlFileName(url)
46 | , onDownloadListener);
47 | }
48 |
49 | /**
50 | * 开启下载任务
51 | *
52 | * @param url 下载链接
53 | * @param fileName 指定下载文件名
54 | * @param onDownloadListener onDownloadListener
55 | */
56 | public void startDownload(String url, @NonNull String fileName, OnDownloadListener
57 | onDownloadListener) {
58 | startDownload(url, DownloadHelper.getDownloadParentFilePath(), fileName,
59 | onDownloadListener);
60 | }
61 |
62 | /**
63 | * 开启下载任务
64 | *
65 | * @param url 下载链接
66 | * @param fileParentPath 指定下载文件目录
67 | * @param fileName 指定下载文件名
68 | * @param onDownloadListener onDownloadListener
69 | */
70 | public void startDownload(String url, @NonNull String fileParentPath, @NonNull String fileName,
71 | OnDownloadListener onDownloadListener) {
72 | if (StringUtils.isEmpty(fileParentPath))
73 | fileParentPath = DownloadHelper.getDownloadParentFilePath();
74 |
75 | if (StringUtils.isEmpty(fileName))
76 | fileName = DownloadHelper.getUrlFileName(url);
77 |
78 | mFileParentPath = fileParentPath;
79 | mFileName = fileName;
80 |
81 | if (downloadTask == null) {
82 | downloadTask = new DownloadTask(onDownloadListener);
83 | downloadTask.execute(url, fileParentPath, fileName);
84 | downloadTask.setOnDownloadTaskFinshedListener(new DownloadTask
85 | .OnDownloadTaskFinshedListener() {
86 | @Override
87 | public void onFinished() {
88 | downloadTask = null;
89 | }
90 |
91 | @Override
92 | public void onCanceled() {
93 | //下载任务取消,删除已下载的文件
94 | clearCacheFile(getDownloadFilePath());
95 | }
96 |
97 | @Override
98 | public void onException() {
99 | //下载任务异常,删除已下载的文件
100 | clearCacheFile(getDownloadFilePath());
101 | }
102 | });
103 | }
104 | }
105 |
106 | /**
107 | * 暂停下载任务
108 | */
109 | public void pauseDownload() {
110 | if (downloadTask != null) {
111 | downloadTask.pauseDownload();
112 | }
113 | }
114 |
115 | /**
116 | * 取消下载任务
117 | */
118 | public void cancelDownload() {
119 | if (downloadTask != null) {
120 | downloadTask.cancelDownload();
121 | }
122 | }
123 |
124 | /**
125 | * 清除下载的全部cache文件
126 | */
127 | public void clearAllCacheFile() {
128 | if (StringUtils.isEmpty(mFileParentPath))
129 | return;
130 |
131 | FileUtils.delAllFile(mFileParentPath);
132 | }
133 |
134 | /**
135 | * 清除下载的cache文件
136 | *
137 | * @param filePath 要删除文件的绝对路径
138 | */
139 | public void clearCacheFile(String filePath) {
140 | if (StringUtils.isEmpty(filePath))
141 | return;
142 |
143 | FileUtils.delFile(filePath);
144 | }
145 |
146 | /**
147 | * 获取下载文件的全路径
148 | *
149 | * @return 下载文件的全路径
150 | */
151 | public String getDownloadFilePath() {
152 | if (StringUtils.isEmpty(mFileParentPath) || StringUtils.isEmpty(mFileName))
153 | return null;
154 |
155 | return mFileParentPath + "/" + mFileName;
156 | }
157 | }
158 |
--------------------------------------------------------------------------------
/app/src/main/java/com/zyw/horrarndoo/updatedemo/utils/AppUtils.java:
--------------------------------------------------------------------------------
1 | package com.zyw.horrarndoo.updatedemo.utils;
2 |
3 | import android.content.Context;
4 | import android.content.pm.PackageInfo;
5 | import android.content.pm.PackageManager;
6 | import android.content.res.ColorStateList;
7 | import android.graphics.Bitmap;
8 | import android.graphics.BitmapFactory;
9 | import android.graphics.drawable.Drawable;
10 | import android.os.Handler;
11 | import android.support.annotation.ColorRes;
12 | import android.support.annotation.DrawableRes;
13 | import android.view.View;
14 |
15 | import com.zyw.horrarndoo.updatedemo.MyApplication;
16 |
17 | import static android.R.attr.version;
18 |
19 | /**
20 | * Created by Horrarndoo on 2017/4/5.
21 | * 系统界面工具类
22 | */
23 | public class AppUtils {
24 | /**
25 | * 获取上下文对象
26 | *
27 | * @return
28 | */
29 | public static Context getContext() {
30 | return MyApplication.getContext();
31 | }
32 |
33 | /**
34 | * 获取全局handler
35 | *
36 | * @return
37 | */
38 | public static Handler getHandler() {
39 | return MyApplication.getHandler();
40 | }
41 |
42 | /**
43 | * 获取主线程id
44 | *
45 | * @return
46 | */
47 | public static int getMainThreadId() {
48 | return MyApplication.getMainThreadId();
49 | }
50 |
51 | ///////////////////加载资源文件 /////////////////////
52 |
53 | /**
54 | * 获取strings.xml资源文件字符串
55 | *
56 | * @param id 资源文件id
57 | * @return 资源文件对应字符串
58 | */
59 | public static String getString(int id) {
60 | return getContext().getResources().getString(id);
61 | }
62 |
63 | /**
64 | * 获取strings.xml资源文件字符串数组
65 | *
66 | * @param id 资源文件id
67 | * @return 资源文件对应字符串数组
68 | */
69 | public static String[] getStringArray(int id) {
70 | return getContext().getResources().getStringArray(id);
71 | }
72 |
73 | /**
74 | * 获取drawable资源文件图片
75 | *
76 | * @param id 资源文件id
77 | * @return 资源文件对应图片
78 | */
79 | public static Drawable getDrawable(@DrawableRes int id) {
80 | return getContext().getResources().getDrawable(id);
81 | }
82 |
83 | /**
84 | * 获取drawable资源文件图片bitmap
85 | *
86 | * @param id 资源文件id
87 | * @return 资源文件对应图片bitmap
88 | */
89 | public static Bitmap getBitmap(@DrawableRes int id) {
90 | return BitmapFactory.decodeResource(getContext().getResources(), id);
91 | }
92 |
93 | /**
94 | * 获取colors.xml资源文件颜色
95 | *
96 | * @param id 资源文件id
97 | * @return 资源文件对应颜色值
98 | */
99 | public static int getColor(@ColorRes int id) {
100 | return getContext().getResources().getColor(id);
101 | }
102 |
103 | /**
104 | * 获取颜色的状态选择器
105 | *
106 | * @param id 资源文件id
107 | * @return 资源文件对应颜色状态
108 | */
109 | public static ColorStateList getColorStateList(int id) {
110 | return getContext().getResources().getColorStateList(id);
111 | }
112 |
113 | /**
114 | * 获取dimens资源文件中具体像素值
115 | *
116 | * @param id 资源文件id
117 | * @return 资源文件对应像素值
118 | */
119 | public static int getDimen(int id) {
120 | return getContext().getResources().getDimensionPixelSize(id);// 返回具体像素值
121 | }
122 |
123 | /**
124 | * 加载布局文件
125 | *
126 | * @param id 布局文件id
127 | * @return 布局view
128 | */
129 | public static View inflate(int id) {
130 | return View.inflate(getContext(), id, null);
131 | }
132 |
133 | /**
134 | * 判断是否运行在主线程
135 | *
136 | * @return true:当前线程运行在主线程
137 | * fasle:当前线程没有运行在主线程
138 | */
139 | public static boolean isRunOnUIThread() {
140 | // 获取当前线程id, 如果当前线程id和主线程id相同, 那么当前就是主线程
141 | int myTid = android.os.Process.myTid();
142 | if (myTid == getMainThreadId()) {
143 | return true;
144 | }
145 | return false;
146 | }
147 |
148 | /**
149 | * 运行在主线程
150 | *
151 | * @param r 运行的Runnable对象
152 | */
153 | public static void runOnUIThread(Runnable r) {
154 | if (isRunOnUIThread()) {
155 | // 已经是主线程, 直接运行
156 | r.run();
157 | } else {
158 | // 如果是子线程, 借助handler让其运行在主线程
159 | getHandler().post(r);
160 | }
161 | }
162 |
163 | /**
164 | * 获取APP版本名
165 | */
166 | public static String getAppVersionName() {
167 | String version = null;
168 | try {
169 | PackageInfo info = getContext().getPackageManager().getPackageInfo
170 | (getContext().getPackageName(), 0);
171 | version = info.versionName;
172 | } catch (PackageManager.NameNotFoundException e) {
173 | e.printStackTrace();
174 | }
175 | return version;
176 | }
177 |
178 | /**
179 | * 获取APP版本号
180 | */
181 | public static int getAppVersionCode() {
182 | int versionCode = 0;
183 | try {
184 | PackageInfo info = getContext().getPackageManager().getPackageInfo
185 | (getContext().getPackageName(), 0);
186 | versionCode = info.versionCode;
187 | } catch (PackageManager.NameNotFoundException e) {
188 | e.printStackTrace();
189 | }
190 | return versionCode;
191 | }
192 | }
193 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/app/src/main/java/com/zyw/horrarndoo/updatedemo/MainActivity.java:
--------------------------------------------------------------------------------
1 | package com.zyw.horrarndoo.updatedemo;
2 |
3 | import android.Manifest;
4 | import android.app.ProgressDialog;
5 | import android.content.DialogInterface;
6 | import android.content.pm.PackageManager;
7 | import android.os.Bundle;
8 | import android.support.annotation.NonNull;
9 | import android.support.v4.app.ActivityCompat;
10 | import android.support.v4.content.ContextCompat;
11 | import android.support.v7.app.AlertDialog;
12 | import android.support.v7.app.AppCompatActivity;
13 | import android.util.Log;
14 | import android.view.View;
15 | import android.widget.Button;
16 | import android.widget.TextView;
17 | import android.widget.Toast;
18 |
19 | import com.zyw.horrarndoo.updatedemo.constant.Constant;
20 | import com.zyw.horrarndoo.updatedemo.update.OnCheckUpdateListener;
21 | import com.zyw.horrarndoo.updatedemo.update.OnUpdateListener;
22 | import com.zyw.horrarndoo.updatedemo.update.UpdateManager;
23 | import com.zyw.horrarndoo.updatedemo.utils.AppUtils;
24 |
25 | import static com.zyw.horrarndoo.updatedemo.utils.ToastUtils.showToast;
26 |
27 | public class MainActivity extends AppCompatActivity implements View.OnClickListener {
28 |
29 | private UpdateManager mUpdateManager;
30 |
31 | private ProgressDialog mProgressDialog;
32 |
33 | @Override
34 | protected void onCreate(Bundle savedInstanceState) {
35 | super.onCreate(savedInstanceState);
36 | setContentView(R.layout.activity_main);
37 |
38 | init();
39 | }
40 |
41 | private void init() {
42 | TextView tvVersionName = (TextView) findViewById(R.id.tv_version_name);
43 | Button btnCheckUpdate = (Button) findViewById(R.id.btn_check_update);
44 | Button btnClearApk = (Button) findViewById(R.id.btn_clear_apk);
45 |
46 | btnCheckUpdate.setOnClickListener(this);
47 | btnClearApk.setOnClickListener(this);
48 | tvVersionName.setText(AppUtils.getAppVersionName());
49 |
50 | initProgressDialog();
51 |
52 | initPermission();
53 |
54 | mUpdateManager = UpdateManager.getInstance();
55 | }
56 |
57 | private void initProgressDialog() {
58 | mProgressDialog = new ProgressDialog(this, R.style.DialogTheme);
59 | mProgressDialog.setTitle("UpdateDemo");
60 | mProgressDialog.setMessage("Downloading, Please wait...");
61 | mProgressDialog.setMax(100);
62 | mProgressDialog.setIcon(R.mipmap.ic_launcher);
63 | mProgressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
64 | mProgressDialog.setCancelable(false);
65 | mProgressDialog.setCanceledOnTouchOutside(false);
66 | mProgressDialog.setButton(DialogInterface.BUTTON_NEGATIVE, "取消", new DialogInterface
67 | .OnClickListener() {
68 | @Override
69 | public void onClick(DialogInterface dialog, int which) {
70 | mUpdateManager.cancleUpdate();
71 | }
72 | });
73 | }
74 |
75 | private void initPermission() {
76 | if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission
77 | .WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
78 | ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission
79 | .WRITE_EXTERNAL_STORAGE}, 1);
80 | }
81 | }
82 |
83 | @Override
84 | public void onClick(View v) {
85 | switch (v.getId()) {
86 | case R.id.btn_check_update:
87 | mUpdateManager.checkUpdate(Constant.VERSION_INFO_URL, new OnCheckUpdateListener() {
88 | @Override
89 | public void onFindNewVersion(String versionName, String newVersionContent) {
90 | String content = "最新版: V" + versionName + "\n" + newVersionContent;
91 | buildNewVersionDialog(content);
92 | }
93 |
94 | @Override
95 | public void onNewest() {
96 | showToast("app is newest version.");
97 | dismissProgressDialog();
98 | }
99 | });
100 |
101 | break;
102 | case R.id.btn_clear_apk:
103 | mUpdateManager.clearCacheApkFile();
104 | break;
105 | default:
106 | break;
107 | }
108 | }
109 |
110 | @Override
111 | public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
112 | @NonNull int[] grantResults) {
113 | switch (requestCode) {
114 | case 1:
115 | if (grantResults.length > 0 && grantResults[0] != PackageManager
116 | .PERMISSION_GRANTED) {
117 | Toast.makeText(this, "拒绝权限将无法使用程序", Toast.LENGTH_SHORT).show();
118 | finish();
119 | }
120 | break;
121 | default:
122 | }
123 | }
124 |
125 | private void dismissProgressDialog() {
126 | mProgressDialog.setProgress(0);
127 | if (mProgressDialog.isShowing())
128 | mProgressDialog.dismiss();
129 | }
130 |
131 | /**
132 | * 创建发现新版本apk alert dialog
133 | *
134 | * @param message dialog显示消息
135 | */
136 | private void buildNewVersionDialog(String message) {
137 | AlertDialog dialog = new AlertDialog.Builder(this)
138 | .setTitle("发现新版本")
139 | .setIcon(R.mipmap.ic_launcher)
140 | .setMessage(message)
141 | .setPositiveButton("更新", new DialogInterface.OnClickListener() {
142 | @Override
143 | public void onClick(DialogInterface dialog, int which) {
144 | dialog.dismiss();
145 | mUpdateManager.startToUpdate(Constant.APK_URL, mOnUpdateListener);
146 | }
147 | })
148 | .setNegativeButton("取消", null)
149 | .create();
150 | dialog.show();
151 | }
152 |
153 | private OnUpdateListener mOnUpdateListener = new OnUpdateListener() {
154 | @Override
155 | public void onStartUpdate() {
156 | mProgressDialog.show();
157 | }
158 |
159 | @Override
160 | public void onProgress(int progress) {
161 | mProgressDialog.setProgress(progress);
162 | }
163 |
164 | @Override
165 | public void onApkDownloadFinish(String apkPath) {
166 | showToast("newest apk download finish. apkPath: " + apkPath);
167 | Log.e("tag", "newest apk download finish. apkPath: " + apkPath);
168 | dismissProgressDialog();
169 | //所有的更新全部在updateManager中完成,Activity在这里只是做一些界面上的处理
170 | }
171 |
172 | @Override
173 | public void onUpdateFailed() {
174 | showToast("update failed.");
175 | dismissProgressDialog();
176 | }
177 |
178 | @Override
179 | public void onUpdateCanceled() {
180 | showToast("update cancled.");
181 | dismissProgressDialog();
182 | }
183 |
184 | @Override
185 | public void onUpdateException() {
186 | showToast("update exception.");
187 | dismissProgressDialog();
188 | }
189 | };
190 | }
191 |
--------------------------------------------------------------------------------
/app/src/main/java/com/zyw/horrarndoo/updatedemo/download/DownloadTask.java:
--------------------------------------------------------------------------------
1 | package com.zyw.horrarndoo.updatedemo.download;
2 |
3 | import android.os.AsyncTask;
4 |
5 | import java.io.File;
6 | import java.io.IOException;
7 | import java.io.InputStream;
8 | import java.io.RandomAccessFile;
9 |
10 | import okhttp3.OkHttpClient;
11 | import okhttp3.Request;
12 | import okhttp3.Response;
13 |
14 |
15 | public class DownloadTask extends AsyncTask
38 | */
39 |
40 | public class UpdateManager {
41 | private static volatile UpdateManager manager = null;
42 |
43 | private static final int MSG_ON_START = 1;
44 | private static final int MSG_ON_PROGRESS = 2;
45 | private static final int MSG_ON_DOWNLOAD_FINISH = 3;
46 | private static final int MSG_ON_FAILED = 4;
47 | private static final int MSG_ON_CANCLE = 5;
48 | private static final int MSG_ON_FIND_NEW_VERSION = 6;
49 | private static final int MSG_ON_NEWEST = 7;
50 | private static final int MSG_ON_UPDATE_EXCEPTION = 8;
51 |
52 | private DownloadManager mDownloadManager;
53 |
54 | private OnUpdateListener mOnUpdateListener;
55 | private OnCheckUpdateListener mOnCheckUpdateListener;
56 |
57 | private int mNewestVersionCode;
58 | private String mNewestVersionName;
59 | private String mNewVersionContent;
60 |
61 | /**
62 | * 最后一次保存cache的时间
63 | */
64 | private long mLastCacheSaveTime = 0;
65 |
66 | private UpdateManager() {
67 | mDownloadManager = DownloadManager.getInstance();
68 | }
69 |
70 | /**
71 | * 获取updateManager实例
72 | *
73 | * @return updateManager实例
74 | */
75 | public static UpdateManager getInstance() {
76 | if (manager == null) {
77 | synchronized (UpdateManager.class) {
78 | if (manager == null) {
79 | manager = new UpdateManager();
80 | }
81 | }
82 | }
83 | return manager;
84 | }
85 |
86 | private Handler mHandler = new Handler() {
87 | @Override
88 | public void handleMessage(Message msg) {
89 | super.handleMessage(msg);
90 | switch (msg.what) {
91 | case MSG_ON_START:
92 | if (mOnUpdateListener != null)
93 | mOnUpdateListener.onStartUpdate();
94 | break;
95 |
96 | case MSG_ON_PROGRESS:
97 | if (mOnUpdateListener != null)
98 | mOnUpdateListener.onProgress((Integer) msg.obj);
99 | break;
100 |
101 | case MSG_ON_DOWNLOAD_FINISH:
102 | if (mOnUpdateListener != null)
103 | mOnUpdateListener.onApkDownloadFinish((String) msg.obj);
104 | installApk((String) msg.obj);
105 | break;
106 |
107 | case MSG_ON_FAILED:
108 | if (mOnUpdateListener != null)
109 | mOnUpdateListener.onUpdateFailed();
110 | break;
111 |
112 | case MSG_ON_CANCLE:
113 | if (mOnUpdateListener != null)
114 | mOnUpdateListener.onUpdateCanceled();
115 | break;
116 |
117 | case MSG_ON_UPDATE_EXCEPTION:
118 | if (mOnUpdateListener != null)
119 | mOnUpdateListener.onUpdateException();
120 | break;
121 |
122 | case MSG_ON_FIND_NEW_VERSION:
123 | DataBean dataBean = (DataBean) msg.obj;
124 |
125 | mNewestVersionCode = dataBean.getVersionCode();
126 | mNewestVersionName = dataBean.getVersionName();
127 | mNewVersionContent = dataBean.getContent();
128 |
129 | if (mOnCheckUpdateListener != null)
130 | mOnCheckUpdateListener.onFindNewVersion(mNewestVersionName,
131 | mNewVersionContent);
132 | break;
133 |
134 | case MSG_ON_NEWEST:
135 | if (mOnCheckUpdateListener != null)
136 | mOnCheckUpdateListener.onNewest();
137 | break;
138 | }
139 | }
140 | };
141 |
142 | /**
143 | * 检查更新
144 | *
145 | * @param apkInfoUrl 服务器端保存新版apk相关信息json的url
146 | * @param onCheckUpdateListener onCheckUpdateListener
147 | */
148 | public void checkUpdate(String apkInfoUrl, OnCheckUpdateListener onCheckUpdateListener) {
149 | mOnCheckUpdateListener = onCheckUpdateListener;
150 | HttpUtils.sendOkHttpRequest(apkInfoUrl, new Callback() {
151 | @Override
152 | public void onFailure(Call call, IOException e) {
153 | ToastUtils.showToast("check update failed.");
154 | }
155 |
156 | @Override
157 | public void onResponse(Call call, Response response) throws IOException {
158 | String strJson = response.body().string();
159 | Log.e("onResponse", "response.body().string() = " + strJson);
160 | if (parseJson(strJson).getVersionCode() > AppUtils.getAppVersionCode()) {
161 | //最后一次缓存的时间超过缓存文件有效期,或者最后一次缓存的apk不是最新版本的apk,删除缓存apk
162 | if ((System.currentTimeMillis() - mLastCacheSaveTime > getCacheSaveValidTime())
163 | || (getCacheApkVersionCode() != parseJson(strJson).getVersionCode())) {
164 | clearCacheApkFile();
165 | setCacheApkVersionCode(parseJson(strJson).getVersionCode());
166 | }
167 | sendMessage(MSG_ON_FIND_NEW_VERSION, parseJson(strJson));
168 | } else {
169 | sendMessage(MSG_ON_NEWEST, null);
170 | //当前已经是最新版本APK,清除本地已经缓存的apk安装包
171 | clearCacheApkFile();
172 | }
173 | }
174 | });
175 | }
176 |
177 | /**
178 | * 开始更新App
179 | *
180 | * 此时开始正式下载更新Apk
181 | *
182 | * @param apkUrl 服务端最新apk文件url
183 | * @param onUpdateListener onUpdateListener
184 | */
185 | public void startToUpdate(String apkUrl, OnUpdateListener onUpdateListener) {
186 | mOnUpdateListener = onUpdateListener;
187 |
188 | if (StringUtils.isEmpty(mNewestVersionName) || mNewestVersionCode == 0)
189 | return;
190 |
191 | downloadNewestApkFile(apkUrl, mNewestVersionCode, mNewestVersionName);
192 | }
193 |
194 | /**
195 | * 设置缓存文件有效时间,单位:秒
196 | *
197 | * 默认缓存有效期为7天
198 | *
199 | * @param cacheValidTime 缓存文件有效时间
200 | */
201 | public void setCacheSaveValidTime(long cacheValidTime) {
202 | SpUtils.putLong(SP_KEY_CACHE_VALID_TIME, cacheValidTime);
203 | }
204 |
205 | /**
206 | * 获取缓存文件有效时间,单位:秒
207 | *
208 | * 默认缓存有效期为7天
209 | *
210 | * @return 缓存文件有效时间
211 | */
212 | public long getCacheSaveValidTime() {
213 | return SpUtils.getLong(SP_KEY_CACHE_VALID_TIME, 60 * 60 * 24 * 7);
214 | }
215 |
216 | /**
217 | * 设置缓存文件版本号
218 | *
219 | * @param versionCode cacheApk版本号
220 | */
221 | public void setCacheApkVersionCode(int versionCode) {
222 | SpUtils.putInt(SP_KEY_CACHE_APK_VERSION_CODE, versionCode);
223 | }
224 |
225 | /**
226 | * 获取缓存文件版本号
227 | *
228 | * @return 缓存文件版本号
229 | */
230 | public int getCacheApkVersionCode() {
231 | return SpUtils.getInt(SP_KEY_CACHE_APK_VERSION_CODE, 0);
232 | }
233 |
234 | /**
235 | * 取消更新
236 | */
237 | public void cancleUpdate() {
238 | //保留下载已完成的部分apk cache文件,cache文件最多保留7天
239 | mDownloadManager.pauseDownload();
240 | }
241 |
242 | /**
243 | * 清除已下载的APK缓存
244 | */
245 | public void clearCacheApkFile() {
246 | Log.e("tag", "清除所有的apk文件");
247 | mDownloadManager.clearAllCacheFile();
248 | }
249 |
250 | /**
251 | * 下载最新版本的APK文件
252 | *
253 | * @param url 服务端最新apk文件url
254 | * @param newestVersionCode 最新版本APK版本号
255 | * @param newestVersionName 最新版本APK版本名称
256 | */
257 | private void downloadNewestApkFile(String url, int newestVersionCode, String
258 | newestVersionName) {
259 | String apkFileName = getApkNameWithVersionName(DownloadHelper.getUrlFileName(url),
260 | newestVersionName);
261 |
262 | sendMessage(MSG_ON_START, null);
263 |
264 | mDownloadManager.startDownload(url, apkFileName, new
265 | OnDownloadListener() {
266 | @Override
267 | public void onException() {
268 | sendMessage(MSG_ON_UPDATE_EXCEPTION, null);
269 | }
270 |
271 | @Override
272 | public void onProgress(int progress) {
273 | sendMessage(MSG_ON_PROGRESS, progress);
274 | }
275 |
276 | @Override
277 | public void onSuccess() {
278 | mLastCacheSaveTime = System.currentTimeMillis();
279 | sendMessage(MSG_ON_DOWNLOAD_FINISH, mDownloadManager.getDownloadFilePath());
280 | }
281 |
282 | @Override
283 | public void onFailed() {
284 | mLastCacheSaveTime = System.currentTimeMillis();
285 | sendMessage(MSG_ON_FAILED, null);
286 | }
287 |
288 | @Override
289 | public void onPaused() {
290 | mLastCacheSaveTime = System.currentTimeMillis();
291 | //取消升级时,调用download pause,保留已下载的部分apk文件
292 | sendMessage(MSG_ON_CANCLE, null);
293 | }
294 |
295 | @Override
296 | public void onCanceled() {
297 | //为了保证断点续传,升级时,调用download pause,不使用cancle,onCancle不会被调用
298 | mLastCacheSaveTime = System.currentTimeMillis();
299 | sendMessage(MSG_ON_CANCLE, null);
300 | }
301 | });
302 | }
303 |
304 | private void sendMessage(int msgWhat, Object o) {
305 | Message msg = Message.obtain();
306 | msg.what = msgWhat;
307 | msg.obj = o;
308 | mHandler.sendMessage(msg);
309 | }
310 |
311 | /**
312 | * 解析json数据
313 | *
314 | * @param jsonData json数据
315 | * {
316 | * "data": {
317 | * "content": "更新内容如下:1.xxxxxx;/n 2.xxxxxx;/n 3.xxxxxx;/n",
318 | * "id": "1",
319 | * "api_key": "update test",
320 | * "version_code": "2"
321 | * },
322 | * "msg": "获取成功",
323 | * "status": 1
324 | * }
325 | * @return dataBean
326 | */
327 | private DataBean parseJson(String jsonData) {
328 | DataBean dataBean = new DataBean();
329 |
330 | try {
331 | JSONObject jsonObject = new JSONObject(jsonData);
332 | JSONObject dataObject = jsonObject.getJSONObject("data");
333 | dataBean.setContent(dataObject.getString("content"));
334 | dataBean.setId(dataObject.getInt("id"));
335 | dataBean.setApiKey(dataObject.getString("api_key"));
336 | dataBean.setVersionCode(dataObject.getInt("version_code"));
337 | dataBean.setVersionName(dataObject.getString("version_name"));
338 | // Log.e("parseJson", "content " + dataObject.getString("content"));
339 | // Log.e("parseJson", "id " + dataObject.getInt("id"));
340 | // Log.e("parseJson", "api_key " + dataObject.getString("api_key"));
341 | // Log.e("parseJson", "version_code " + dataObject.getInt("version_code"));
342 | // Log.e("parseJson", "version_name " + dataObject.getString
343 | // ("version_name"));
344 | } catch (Exception e) {
345 | e.printStackTrace();
346 | }
347 | return dataBean;
348 | }
349 |
350 | /**
351 | * 获取带版本名称的apk文件名
352 | *
353 | * @param apkName apk原名
354 | * @return 带版本名称的apk文件名
355 | */
356 | private String getApkNameWithVersionName(String apkName, String versionName) {
357 | if (StringUtils.isEmpty(apkName))
358 | return apkName;
359 |
360 | apkName = apkName.substring(apkName.lastIndexOf("/") + 1, apkName.indexOf("" +
361 | ".apk"));
362 | Log.e("tag", "newApkName = " + apkName + "_v" + versionName + ".apk");
363 | return apkName + "_v" + versionName + ".apk";
364 | }
365 |
366 | /**
367 | * 安装 apk
368 | *
369 | * @param apkPath apk全路径
370 | */
371 | public void installApk(String apkPath) {
372 | if (StringUtils.isEmpty(apkPath)) {
373 | Log.e("tag", "apkPath is null.");
374 | return;
375 | }
376 |
377 | File file = new File(apkPath);
378 | Intent intent = new Intent(Intent.ACTION_VIEW);
379 | // 由于没有在Activity环境下启动Activity,设置下面的标签
380 | intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
381 | if (Build.VERSION.SDK_INT >= 24) { //判断版本是否在7.0以上
382 | //参数1 上下文, 参数2 Provider主机地址 和配置文件中保持一致 参数3 共享的文件
383 | Uri apkUri = FileProvider.getUriForFile(AppUtils.getContext(), BuildConfig
384 | .APPLICATION_ID +
385 | ".fileProvider", file);
386 | //添加这一句表示对目标应用临时授权该Uri所代表的文件
387 | intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
388 | intent.setDataAndType(apkUri, "application/vnd.android.package-archive");
389 | } else {
390 | intent.setDataAndType(Uri.fromFile(file), "application/vnd.android.package-archive");
391 | }
392 | AppUtils.getContext().startActivity(intent);
393 | }
394 | }
395 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # UpdateDemo
2 | 应用内更新demo
3 |
4 | 对于Android app来说,应用内更新几乎成了一个标配的功能了。原理其实不难,今天我们就从零开始撸一个自己的应用内更新的demo出来。
5 |
6 | 先看看最终实现的效果:
7 |
8 | 
9 |
10 | 上图的效果,稍微将功能拆分一下,可以总结为以下几点。
11 |
12 | 1. 检查更新;
13 | 2. 最新apk下载;
14 | 3. apk下载成功后应用内跳转安装;
15 |
16 | ### 1.检查更新
17 |
18 | 为了检验检查更新的效果,我们需要一个tomcat服务器。至于tomcat怎么搭建,这里就不花篇幅去讲了,网上资料还是很多的。
19 | Tomcat部署完成后,在Tomcat ROOT目录上新建一个本次demo的目录,并且将新版的apk文件和一个保存了新版apk相关信息的json文件放在demo目录下。如下图所示:
20 |
21 | 
22 |
23 | 各位应该已经想到检查更新的原理了,其实就是解析保存了新版apk信息的json文件,然后根据json中新版apk的版本信息来判断当前apk是否有可以更新。
24 | 我们这里模拟一个新版apk相关信息的json文件内容。
25 |
26 | ```
27 | {"data":{"content":"更新内容如下:\n 1.xxxxxx;\n 2.xxxxxx;\n 3.xxxxxx;\n","id":"1","api_key":"android","version_code":"2","version_name":"1.0.2"},"msg":"获取成功","status":1}
28 | ```
29 |
30 | 可以看到,对于检查更新来说,最重要的几个信息都包含在data字段中,包括了更新内容,新版apk版本号,新版apk版本名称等。当然,根据实际需求,这个json可能会有所不同,具体项目中可以做一些修改,届时解析的时候稍作改动就好了。
31 |
32 | 接下来完成检查更新相关代码:
33 |
34 | ```
35 | /**
36 | * 检查更新
37 | */
38 | public void checkUpdate(OnCheckUpdateListener onCheckUpdateListener) {
39 | mOnCheckUpdateListener = onCheckUpdateListener;
40 | HttpUtils.sendOkHttpRequest(mUpdateHelper.getNewestApkVersionInfoUrl(), new Callback() {
41 | @Override
42 | public void onFailure(Call call, IOException e) {
43 | ToastUtils.showToast("check update failed.");
44 | }
45 |
46 | @Override
47 | public void onResponse(Call call, Response response) throws IOException {
48 | String strJson = response.body().string();
49 | Log.e("onResponse", "response.body().string() = " + strJson);
50 | if (parseJson(strJson).getVersionCode() > AppUtils.getAppVersionCode()) {
51 | sendMessage(MSG_ON_FIND_NEW_VERSION, parseJson(strJson));
52 | } else {
53 | sendMessage(MSG_ON_NEWEST, null);
54 | }
55 | }
56 | });
57 | }
58 |
59 | /**
60 | * 解析json数据
61 | *
62 | * @param jsonData json数据
63 | * {
64 | * "data": {
65 | * "content": "更新内容如下:1.xxxxxx;/n 2.xxxxxx;/n 3.xxxxxx;/n",
66 | * "id": "1",
67 | * "api_key": "update test",
68 | * "version_code": "2"
69 | * },
70 | * "msg": "获取成功",
71 | * "status": 1
72 | * }
73 | * @return dataBean
74 | */
75 | private DataBean parseJson(String jsonData) {
76 | DataBean dataBean = new DataBean();
77 |
78 | try {
79 | JSONObject jsonObject = new JSONObject(jsonData);
80 | JSONObject dataObject = jsonObject.getJSONObject("data");
81 | dataBean.setContent(dataObject.getString("content"));
82 | dataBean.setId(dataObject.getInt("id"));
83 | dataBean.setApiKey(dataObject.getString("api_key"));
84 | dataBean.setVersionCode(dataObject.getInt("version_code"));
85 | dataBean.setVersionName(dataObject.getString("version_name"));
86 | // Log.e("parseJson", "content " + dataObject.getString("content"));
87 | // Log.e("parseJson", "id " + dataObject.getInt("id"));
88 | // Log.e("parseJson", "api_key " + dataObject.getString("api_key"));
89 | // Log.e("parseJson", "version_code " + dataObject.getInt("version_code"));
90 | // Log.e("parseJson", "version_name " + dataObject.getString
91 | // ("version_name"));
92 | } catch (Exception e) {
93 | e.printStackTrace();
94 | }
95 | return dataBean;
96 | }
97 | ```
98 |
99 | ### 2.最新apk文件下载
100 |
101 | 如何检查更新的代码搞定了,接下来是稍微麻烦一点的,就是下载apk文件。
102 | 由于下载文件可以作为一个单独的功能,所以将下载文件这一块单独独立出来作为一个子模块来编写。其实谷歌官方有提供专门的DownloadManager来下载文件,但是很多国内的rom把DownloadManager阉割了。所以这里我们自己来撸一个DownloadManager,具体代码如下,注释还是比较清晰的,就不多做解释了。
103 |
104 | DownloadManager.java:
105 |
106 | ```
107 | public class DownloadManager {
108 | private static volatile DownloadManager manager = null;
109 |
110 | private DownloadTask downloadTask;
111 |
112 | private String mFileName;
113 |
114 | private String mFileParentPath;
115 |
116 | private DownloadManager() {
117 | mFileParentPath = DownloadHelper.getDownloadParentFilePath();
118 | }
119 |
120 | public static DownloadManager getInstance() {
121 | if (manager == null) {
122 | synchronized (DownloadManager.class) {
123 | if (manager == null) {
124 | manager = new DownloadManager();
125 | }
126 | }
127 | }
128 | return manager;
129 | }
130 |
131 | /**
132 | * 开启下载任务
133 | *
134 | * @param url 下载链接
135 | * @param onDownloadListener onDownloadListener
136 | */
137 | public void startDownload(String url, OnDownloadListener onDownloadListener) {
138 | startDownload(url, DownloadHelper.getDownloadParentFilePath(), DownloadHelper
139 | .getUrlFileName(url)
140 | , onDownloadListener);
141 | }
142 |
143 | /**
144 | * 开启下载任务
145 | *
146 | * @param url 下载链接
147 | * @param fileName 指定下载文件名
148 | * @param onDownloadListener onDownloadListener
149 | */
150 | public void startDownload(String url, @NonNull String fileName, OnDownloadListener
151 | onDownloadListener) {
152 | startDownload(url, DownloadHelper.getDownloadParentFilePath(), fileName,
153 | onDownloadListener);
154 | }
155 |
156 | /**
157 | * 开启下载任务
158 | *
159 | * @param url 下载链接
160 | * @param fileParentPath 指定下载文件目录
161 | * @param fileName 指定下载文件名
162 | * @param onDownloadListener onDownloadListener
163 | */
164 | public void startDownload(String url, @NonNull String fileParentPath, @NonNull String fileName,
165 | OnDownloadListener onDownloadListener) {
166 | if (StringUtils.isEmpty(fileParentPath))
167 | fileParentPath = DownloadHelper.getDownloadParentFilePath();
168 |
169 | if (StringUtils.isEmpty(fileName))
170 | fileName = DownloadHelper.getUrlFileName(url);
171 |
172 | mFileParentPath = fileParentPath;
173 | mFileName = fileName;
174 |
175 | if (downloadTask == null) {
176 | downloadTask = new DownloadTask(onDownloadListener);
177 | downloadTask.execute(url, fileParentPath, fileName);
178 | downloadTask.setOnDownloadTaskFinshedListener(new DownloadTask
179 | .OnDownloadTaskFinshedListener() {
180 | @Override
181 | public void onFinished() {
182 | downloadTask = null;
183 | }
184 |
185 | @Override
186 | public void onCanceled() {
187 | //下载任务取消,删除已下载的文件
188 | clearCacheFile(getDownloadFilePath());
189 | }
190 |
191 | @Override
192 | public void onException() {
193 | //下载任务异常,删除已下载的文件
194 | clearCacheFile(getDownloadFilePath());
195 | }
196 | });
197 | }
198 | }
199 |
200 | /**
201 | * 暂停下载任务
202 | */
203 | public void pauseDownload() {
204 | if (downloadTask != null) {
205 | downloadTask.pauseDownload();
206 | }
207 | }
208 |
209 | /**
210 | * 取消下载任务
211 | */
212 | public void cancelDownload() {
213 | if (downloadTask != null) {
214 | downloadTask.cancelDownload();
215 | }
216 | }
217 |
218 | /**
219 | * 清除下载的全部cache文件
220 | */
221 | public void clearAllCacheFile() {
222 | if (StringUtils.isEmpty(mFileParentPath))
223 | return;
224 |
225 | FileUtils.delAllFile(mFileParentPath);
226 | }
227 |
228 | /**
229 | * 清除下载的cache文件
230 | *
231 | * @param filePath 要删除文件的绝对路径
232 | */
233 | public void clearCacheFile(String filePath) {
234 | if (StringUtils.isEmpty(filePath))
235 | return;
236 |
237 | FileUtils.delFile(filePath);
238 | }
239 |
240 | /**
241 | * 获取下载文件的全路径
242 | *
243 | * @return 下载文件的全路径
244 | */
245 | public String getDownloadFilePath() {
246 | if (StringUtils.isEmpty(mFileParentPath) || StringUtils.isEmpty(mFileName))
247 | return null;
248 |
249 | return mFileParentPath + "/" + mFileName;
250 | }
251 | }
252 | ```
253 |
254 | DownloadTask.java
255 |
256 | ```
257 | public class DownloadTask extends AsyncTask
566 | */
567 |
568 | public class UpdateManager {
569 | private static volatile UpdateManager manager = null;
570 |
571 | private static final int MSG_ON_START = 1;
572 | private static final int MSG_ON_PROGRESS = 2;
573 | private static final int MSG_ON_DOWNLOAD_FINISH = 3;
574 | private static final int MSG_ON_FAILED = 4;
575 | private static final int MSG_ON_CANCLE = 5;
576 | private static final int MSG_ON_FIND_NEW_VERSION = 6;
577 | private static final int MSG_ON_NEWEST = 7;
578 | private static final int MSG_ON_UPDATE_EXCEPTION = 8;
579 |
580 | private DownloadManager mDownloadManager;
581 |
582 | private OnUpdateListener mOnUpdateListener;
583 | private OnCheckUpdateListener mOnCheckUpdateListener;
584 |
585 | private int mNewestVersionCode;
586 | private String mNewestVersionName;
587 | private String mNewVersionContent;
588 |
589 | /**
590 | * 最后一次保存cache的时间
591 | */
592 | private long mLastCacheSaveTime = 0;
593 |
594 | private UpdateManager() {
595 | mDownloadManager = DownloadManager.getInstance();
596 | }
597 |
598 | /**
599 | * 获取updateManager实例
600 | *
601 | * @return updateManager实例
602 | */
603 | public static UpdateManager getInstance() {
604 | if (manager == null) {
605 | synchronized (UpdateManager.class) {
606 | if (manager == null) {
607 | manager = new UpdateManager();
608 | }
609 | }
610 | }
611 | return manager;
612 | }
613 |
614 | private Handler mHandler = new Handler() {
615 | @Override
616 | public void handleMessage(Message msg) {
617 | super.handleMessage(msg);
618 | switch (msg.what) {
619 | case MSG_ON_START:
620 | if (mOnUpdateListener != null)
621 | mOnUpdateListener.onStartUpdate();
622 | break;
623 |
624 | case MSG_ON_PROGRESS:
625 | if (mOnUpdateListener != null)
626 | mOnUpdateListener.onProgress((Integer) msg.obj);
627 | break;
628 |
629 | case MSG_ON_DOWNLOAD_FINISH:
630 | if (mOnUpdateListener != null)
631 | mOnUpdateListener.onApkDownloadFinish((String) msg.obj);
632 | installApk((String) msg.obj);
633 | break;
634 |
635 | case MSG_ON_FAILED:
636 | if (mOnUpdateListener != null)
637 | mOnUpdateListener.onUpdateFailed();
638 | break;
639 |
640 | case MSG_ON_CANCLE:
641 | if (mOnUpdateListener != null)
642 | mOnUpdateListener.onUpdateCanceled();
643 | break;
644 |
645 | case MSG_ON_UPDATE_EXCEPTION:
646 | if (mOnUpdateListener != null)
647 | mOnUpdateListener.onUpdateException();
648 | break;
649 |
650 | case MSG_ON_FIND_NEW_VERSION:
651 | DataBean dataBean = (DataBean) msg.obj;
652 |
653 | mNewestVersionCode = dataBean.getVersionCode();
654 | mNewestVersionName = dataBean.getVersionName();
655 | mNewVersionContent = dataBean.getContent();
656 |
657 | if (mOnCheckUpdateListener != null)
658 | mOnCheckUpdateListener.onFindNewVersion(mNewestVersionName,
659 | mNewVersionContent);
660 | break;
661 |
662 | case MSG_ON_NEWEST:
663 | if (mOnCheckUpdateListener != null)
664 | mOnCheckUpdateListener.onNewest();
665 | break;
666 | }
667 | }
668 | };
669 |
670 | /**
671 | * 检查更新
672 | *
673 | * @param apkInfoUrl 服务器端保存新版apk相关信息json的url
674 | * @param onCheckUpdateListener onCheckUpdateListener
675 | */
676 | public void checkUpdate(String apkInfoUrl, OnCheckUpdateListener onCheckUpdateListener) {
677 | mOnCheckUpdateListener = onCheckUpdateListener;
678 | HttpUtils.sendOkHttpRequest(apkInfoUrl, new Callback() {
679 | @Override
680 | public void onFailure(Call call, IOException e) {
681 | ToastUtils.showToast("check update failed.");
682 | }
683 |
684 | @Override
685 | public void onResponse(Call call, Response response) throws IOException {
686 | String strJson = response.body().string();
687 | Log.e("onResponse", "response.body().string() = " + strJson);
688 | if (parseJson(strJson).getVersionCode() > AppUtils.getAppVersionCode()) {
689 | //最后一次缓存的时间超过缓存文件有效期,或者最后一次缓存的apk不是最新版本的apk,删除缓存apk
690 | if ((System.currentTimeMillis() - mLastCacheSaveTime > getCacheSaveValidTime())
691 | || (getCacheApkVersionCode() != parseJson(strJson).getVersionCode())) {
692 | clearCacheApkFile();
693 | setCacheApkVersionCode(parseJson(strJson).getVersionCode());
694 | }
695 | sendMessage(MSG_ON_FIND_NEW_VERSION, parseJson(strJson));
696 | } else {
697 | sendMessage(MSG_ON_NEWEST, null);
698 | //当前已经是最新版本APK,清除本地已经缓存的apk安装包
699 | clearCacheApkFile();
700 | }
701 | }
702 | });
703 | }
704 |
705 | /**
706 | * 开始更新App
707 | *
708 | * 此时开始正式下载更新Apk
709 | *
710 | * @param apkUrl 服务端最新apk文件url
711 | * @param onUpdateListener onUpdateListener
712 | */
713 | public void startToUpdate(String apkUrl, OnUpdateListener onUpdateListener) {
714 | mOnUpdateListener = onUpdateListener;
715 |
716 | if (StringUtils.isEmpty(mNewestVersionName) || mNewestVersionCode == 0)
717 | return;
718 |
719 | downloadNewestApkFile(apkUrl, mNewestVersionCode, mNewestVersionName);
720 | }
721 |
722 | /**
723 | * 设置缓存文件有效时间,单位:秒
724 | *
725 | * 默认缓存有效期为7天
726 | *
727 | * @param cacheValidTime 缓存文件有效时间
728 | */
729 | public void setCacheSaveValidTime(long cacheValidTime) {
730 | SpUtils.putLong(SP_KEY_CACHE_VALID_TIME, cacheValidTime);
731 | }
732 |
733 | /**
734 | * 获取缓存文件有效时间,单位:秒
735 | *
736 | * 默认缓存有效期为7天
737 | *
738 | * @return 缓存文件有效时间
739 | */
740 | public long getCacheSaveValidTime() {
741 | return SpUtils.getLong(SP_KEY_CACHE_VALID_TIME, 60 * 60 * 24 * 7);
742 | }
743 |
744 | /**
745 | * 设置缓存文件版本号
746 | *
747 | * @param versionCode cacheApk版本号
748 | */
749 | public void setCacheApkVersionCode(int versionCode) {
750 | SpUtils.putInt(SP_KEY_CACHE_APK_VERSION_CODE, versionCode);
751 | }
752 |
753 | /**
754 | * 获取缓存文件版本号
755 | *
756 | * @return 缓存文件版本号
757 | */
758 | public int getCacheApkVersionCode() {
759 | return SpUtils.getInt(SP_KEY_CACHE_APK_VERSION_CODE, 0);
760 | }
761 |
762 | /**
763 | * 取消更新
764 | */
765 | public void cancleUpdate() {
766 | //保留下载已完成的部分apk cache文件,cache文件最多保留7天
767 | mDownloadManager.pauseDownload();
768 | }
769 |
770 | /**
771 | * 清除已下载的APK缓存
772 | */
773 | public void clearCacheApkFile() {
774 | Log.e("tag", "清除所有的apk文件");
775 | mDownloadManager.clearAllCacheFile();
776 | }
777 |
778 | /**
779 | * 下载最新版本的APK文件
780 | *
781 | * @param url 服务端最新apk文件url
782 | * @param newestVersionCode 最新版本APK版本号
783 | * @param newestVersionName 最新版本APK版本名称
784 | */
785 | private void downloadNewestApkFile(String url, int newestVersionCode, String
786 | newestVersionName) {
787 | String apkFileName = getApkNameWithVersionName(DownloadHelper.getUrlFileName(url),
788 | newestVersionName);
789 |
790 | sendMessage(MSG_ON_START, null);
791 |
792 | mDownloadManager.startDownload(url, apkFileName, new
793 | OnDownloadListener() {
794 | @Override
795 | public void onException() {
796 | sendMessage(MSG_ON_UPDATE_EXCEPTION, null);
797 | }
798 |
799 | @Override
800 | public void onProgress(int progress) {
801 | sendMessage(MSG_ON_PROGRESS, progress);
802 | }
803 |
804 | @Override
805 | public void onSuccess() {
806 | mLastCacheSaveTime = System.currentTimeMillis();
807 | sendMessage(MSG_ON_DOWNLOAD_FINISH, mDownloadManager.getDownloadFilePath());
808 | }
809 |
810 | @Override
811 | public void onFailed() {
812 | mLastCacheSaveTime = System.currentTimeMillis();
813 | sendMessage(MSG_ON_FAILED, null);
814 | }
815 |
816 | @Override
817 | public void onPaused() {
818 | mLastCacheSaveTime = System.currentTimeMillis();
819 | //取消升级时,调用download pause,保留已下载的部分apk文件
820 | sendMessage(MSG_ON_CANCLE, null);
821 | }
822 |
823 | @Override
824 | public void onCanceled() {
825 | //为了保证断点续传,升级时,调用download pause,不使用cancle,onCancle不会被调用
826 | mLastCacheSaveTime = System.currentTimeMillis();
827 | sendMessage(MSG_ON_CANCLE, null);
828 | }
829 | });
830 | }
831 |
832 | private void sendMessage(int msgWhat, Object o) {
833 | Message msg = Message.obtain();
834 | msg.what = msgWhat;
835 | msg.obj = o;
836 | mHandler.sendMessage(msg);
837 | }
838 |
839 | /**
840 | * 解析json数据
841 | *
842 | * @param jsonData json数据
843 | * @return dataBean
844 | */
845 | private DataBean parseJson(String jsonData) {
846 | ...
847 | return dataBean;
848 | }
849 |
850 | /**
851 | * 获取带版本名称的apk文件名
852 | *
853 | * @param apkName apk原名
854 | * @return 带版本名称的apk文件名
855 | */
856 | private String getApkNameWithVersionName(String apkName, String versionName) {
857 | if (StringUtils.isEmpty(apkName))
858 | return apkName;
859 |
860 | apkName = apkName.substring(apkName.lastIndexOf("/") + 1, apkName.indexOf("" +
861 | ".apk"));
862 | Log.e("tag", "newApkName = " + apkName + "_v" + versionName + ".apk");
863 | return apkName + "_v" + versionName + ".apk";
864 | }
865 |
866 | /**
867 | * 安装 apk
868 | *
869 | * @param apkPath apk全路径
870 | */
871 | public void installApk(String apkPath) {
872 | ...
873 | }
874 | }
875 | ```
876 |
877 | 最后在Activity中调用UpdateManager就可以简单的检查更新我们的apk啦,不过有一点稍微要注意下,就是运行时权限的获取,如果6.0以上的手机没有获取sd卡权限的话,我们的程序是无法正常运行的,所以如果用户没有给予权限的话,就退出程序并给出提示。
878 |
879 | ```
880 | public class MainActivity extends AppCompatActivity implements View.OnClickListener {
881 |
882 | private UpdateManager mUpdateManager;
883 |
884 | private ProgressDialog mProgressDialog;
885 |
886 | @Override
887 | protected void onCreate(Bundle savedInstanceState) {
888 | super.onCreate(savedInstanceState);
889 | setContentView(R.layout.activity_main);
890 |
891 | init();
892 | }
893 |
894 | private void init() {
895 | ...
896 |
897 | initPermission();
898 |
899 | mUpdateManager = UpdateManager.getInstance();
900 | }
901 |
902 | private void initProgressDialog() {
903 | ...
904 | }
905 |
906 | private void initPermission() {
907 | if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission
908 | .WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
909 | ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission
910 | .WRITE_EXTERNAL_STORAGE}, 1);
911 | }
912 | }
913 |
914 | @Override
915 | public void onClick(View v) {
916 | switch (v.getId()) {
917 | case R.id.btn_check_update:
918 | mUpdateManager.checkUpdate(Constant.VERSION_INFO_URL, new OnCheckUpdateListener() {
919 | @Override
920 | public void onFindNewVersion(String versionName, String newVersionContent) {
921 | String content = "最新版: V" + versionName + "\n" + newVersionContent;
922 | buildNewVersionDialog(content);
923 | }
924 |
925 | @Override
926 | public void onNewest() {
927 | showToast("app is newest version.");
928 | dismissProgressDialog();
929 | }
930 | });
931 |
932 | break;
933 | case R.id.btn_clear_apk:
934 | mUpdateManager.clearCacheApkFile();
935 | break;
936 | default:
937 | break;
938 | }
939 | }
940 |
941 | @Override
942 | public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
943 | @NonNull int[] grantResults) {
944 | switch (requestCode) {
945 | case 1:
946 | if (grantResults.length > 0 && grantResults[0] != PackageManager
947 | .PERMISSION_GRANTED) {
948 | Toast.makeText(this, "拒绝权限将无法使用程序", Toast.LENGTH_SHORT).show();
949 | finish();
950 | }
951 | break;
952 | default:
953 | }
954 | }
955 |
956 | private void dismissProgressDialog() {
957 | mProgressDialog.setProgress(0);
958 | if (mProgressDialog.isShowing())
959 | mProgressDialog.dismiss();
960 | }
961 |
962 | /**
963 | * 创建发现新版本apk alert dialog
964 | *
965 | * @param message dialog显示消息
966 | */
967 | private void buildNewVersionDialog(String message) {
968 | AlertDialog dialog = new AlertDialog.Builder(this)
969 | .setTitle("发现新版本")
970 | .setIcon(R.mipmap.ic_launcher)
971 | .setMessage(message)
972 | .setPositiveButton("更新", new DialogInterface.OnClickListener() {
973 | @Override
974 | public void onClick(DialogInterface dialog, int which) {
975 | dialog.dismiss();
976 | mUpdateManager.startToUpdate(Constant.APK_URL, mOnUpdateListener);
977 | }
978 | })
979 | .setNegativeButton("取消", null)
980 | .create();
981 | dialog.show();
982 | }
983 |
984 | private OnUpdateListener mOnUpdateListener = new OnUpdateListener() {
985 | @Override
986 | public void onStartUpdate() {
987 | mProgressDialog.show();
988 | }
989 |
990 | @Override
991 | public void onProgress(int progress) {
992 | mProgressDialog.setProgress(progress);
993 | }
994 |
995 | @Override
996 | public void onApkDownloadFinish(String apkPath) {
997 | showToast("newest apk download finish. apkPath: " + apkPath);
998 | Log.e("tag", "newest apk download finish. apkPath: " + apkPath);
999 | dismissProgressDialog();
1000 | //所有的更新全部在updateManager中完成,Activity在这里只是做一些界面上的处理
1001 | }
1002 |
1003 | @Override
1004 | public void onUpdateFailed() {
1005 | showToast("update failed.");
1006 | dismissProgressDialog();
1007 | }
1008 |
1009 | @Override
1010 | public void onUpdateCanceled() {
1011 | showToast("update cancled.");
1012 | dismissProgressDialog();
1013 | }
1014 |
1015 | @Override
1016 | public void onUpdateException() {
1017 | showToast("update exception.");
1018 | dismissProgressDialog();
1019 | }
1020 | };
1021 | }
1022 | ```
1023 |
1024 | 至此已经基本完成此次的应用内更新模块了,用起来还是很简单的,UpdateManager只做更新相关判断处理,DownloadManager则处理下载文件、缓存文件续传及相关状态处理,并且UpdateManager和DownloadManager都是单独可以作为一个独立模块在实际项目中使用的。
1025 |
1026 | 最后附上源码:[https://github.com/Horrarndoo/UpdateDemo](https://github.com/Horrarndoo/UpdateDemo)
1027 |
--------------------------------------------------------------------------------