diff --git a/.classpath b/.classpath deleted file mode 100644 index 17aadf8..0000000 --- a/.classpath +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/.gitignore b/.gitignore index d784722..e3e64c8 100644 --- a/.gitignore +++ b/.gitignore @@ -25,4 +25,10 @@ proguard/ # Log Files *.log -.settings/ \ No newline at end of file +.settings/ + +# Intellij project files +*.iml +*.ipr +*.iws +.idea/ \ No newline at end of file diff --git a/.project b/.project deleted file mode 100644 index eb78f6b..0000000 --- a/.project +++ /dev/null @@ -1,33 +0,0 @@ - - - UpdateHelper - - - - - - com.android.ide.eclipse.adt.ResourceManagerBuilder - - - - - com.android.ide.eclipse.adt.PreCompilerBuilder - - - - - org.eclipse.jdt.core.javabuilder - - - - - com.android.ide.eclipse.adt.ApkBuilder - - - - - - com.android.ide.eclipse.adt.AndroidNature - org.eclipse.jdt.core.javanature - - diff --git a/README.md b/README.md index 568fdf9..f7edff1 100644 --- a/README.md +++ b/README.md @@ -1,25 +1,28 @@ -#UpdateHelper +# UpdateHelper +[![Travis](https://img.shields.io/badge/build-passing-blue.svg)](h) [![Hex.pm](https://img.shields.io/hexpm/l/plug.svg)]() [![Hex.pm](https://img.shields.io/badge/Release-v1.0-blue.svg)]() -##UpdateHelper是什么? -UpdateHelper是一个Android Library项目。 -##UpdateHelper是干什么的? -这个项目是为了简化Android App的迭代升级开发,任何一个项目只要引入这个library便集成了在线检查新版本的功能以及下载APK功能。 -##UpdateHelper要怎么使用? -**1.首先服务器端需要提供一个接口,返回json数据格式如下:** + +## UpdateHelper 是什么? +UpdateHelper 是一个只需一行代码即可实现APP在线升级的 Android Library。 +## UpdateHelper 是干什么的? +这个项目是为了简化 Android APP 的迭代升级开发,任何一个项目只要引入这个 library 便集成了在线检查新版本的功能以及下载 APK 功能。 +## UpdateHelper 要怎么使用? +**1.首先服务器端需要提供一个接口,返回 JSON 数据格式如下:** ```Java { "appName": "TestUpdate", "versionCode": "1", "versionName": "1.0", - "apkUrl": "http://java.linuxlearn.net/shelwee/Finances.apk", + "apkUrl": "http://www.shelwee.com/404.apk", "changeLog":"1.修复xxx Bug;\n2.更新UI界面.", - "updateTips": "更新提示" + "updateTips": "更新提示" , + "forceUpgrade": true //当有新版本时,APP 将会自动下载安装包 } ``` **2.客户端操作如下:** ```Java UpdateHelper updateHelper = new UpdateHelper.Builder(this) - .checkUrl("http://localhost/check.jsp") + .checkUrl("http://www.shelwee.com/check.jsp") .isAutoInstall(false) //设置为false需在下载完手动点击安装;默认值为true,下载后自动安装。 .build(); updateHelper.check(); @@ -59,34 +62,42 @@ UpdateHelper是一个Android Library项目。 }); */ ``` -###UpdateHelper需要哪些权限? +### UpdateHelper 需要哪些权限? ```java - - - + + + ``` -###UpdateHelper原理是什么? -UpdateHelper通过接口返回的数据versionCode,与当前app的versionCode匹配,如果版本号比当前app的versionCode大,则存在新版本,弹出更新提示对话框;否则提示当前版本是最新版。 -Note:当遇到网络中断或阻塞等问题时,处理方式也为提示当前版本是最新版。 +### UpdateHelper 原理是什么? +UpdateHelper 通过接口返回的数据 versionCode,与当前 APP 的 versionCode 匹配,如果版本号比当前 APP 的 versionCode 大,则存在新版本,弹出更新提示对话框;否则提示当前版本是最新版。 + +**注:当遇到网络中断或阻塞等问题时,处理方式也为提示当前版本是最新版。** -###截图 +### UpdateHelper 运行截图 ![](https://github.com/shelwee/ImageStorage/raw/master/UpdateHelper/UpdateDialog.png) ![](https://github.com/shelwee/ImageStorage/raw/master/UpdateHelper/Downloading.png) ![](https://github.com/shelwee/ImageStorage/raw/master/UpdateHelper/Downloaded.png) -###License ->Copyright 2014 ShelWee(http://www.shelwee.com) - ->Licensed under the Apache License, Version 2.0 (the "License"); ->you may not use this file except in compliance with the License. ->You may obtain a copy of the License at +### Change Logs +--- +Version 1.0 - Released date: 1-8-2017 +``` +- 从 ADT 转换成 Android Studio 项目 +- 优化部分代码实现逻辑 +- 网络部分全部改为 HttpURLConnection 实现 +- 修复下载过程与通知栏进度条卡顿情况 +- 提示文字全部提取到 strings.xml,方便国际化操作 +- 新增强制升级功能,通过后台接口 JSON 返回 forceUpgrade:true/false +``` +Version 0.2 - Released date: 3-26-2016 +``` +- 新增校验文件是否已下载 +``` -> http://www.apache.org/licenses/LICENSE-2.0 +Version 0.1 - Released date: 5-8-2014 +``` +- 发布UpdateHelper第一个版本 ->Unless required by applicable law or agreed to in writing, software ->distributed under the License is distributed on an "AS IS" BASIS, ->WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ->See the License for the specific language governing permissions and ->limitations under the License. +``` diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..c0f51c2 --- /dev/null +++ b/build.gradle @@ -0,0 +1,22 @@ +apply plugin: 'com.android.library' + +android { + compileSdkVersion 23 + buildToolsVersion "23.0.2" + + defaultConfig { + minSdkVersion 14 + targetSdkVersion 23 + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt' + } + } +} + +dependencies { + compile 'com.android.support:appcompat-v7:23.4.0' +} diff --git a/libs/android-support-v4.jar b/libs/android-support-v4.jar deleted file mode 100644 index 96644ed..0000000 Binary files a/libs/android-support-v4.jar and /dev/null differ diff --git a/proguard-project.txt b/proguard-project.txt deleted file mode 100644 index f2fe155..0000000 --- a/proguard-project.txt +++ /dev/null @@ -1,20 +0,0 @@ -# To enable ProGuard in your project, edit project.properties -# to define the proguard.config property as described in that file. -# -# Add project specific ProGuard rules here. -# By default, the flags in this file are appended to flags specified -# in ${sdk.dir}/tools/proguard/proguard-android.txt -# You can edit the include path and order by changing the ProGuard -# include property in project.properties. -# -# For more details, see -# http://developer.android.com/guide/developing/tools/proguard.html - -# Add any project specific keep options here: - -# If your project uses WebView with JS, uncomment the following -# and specify the fully qualified class name to the JavaScript interface -# class: -#-keepclassmembers class fqcn.of.javascript.interface.for.webview { -# public *; -#} diff --git a/project.properties b/project.properties deleted file mode 100644 index 03d0617..0000000 --- a/project.properties +++ /dev/null @@ -1,15 +0,0 @@ -# This file is automatically generated by Android Tools. -# Do not modify this file -- YOUR CHANGES WILL BE ERASED! -# -# This file must be checked in Version Control Systems. -# -# To customize properties used by the Ant build system edit -# "ant.properties", and override values to adapt the script to your -# project structure. -# -# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home): -#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt - -# Project target. -target=android-10 -android.library=true diff --git a/res/drawable-hdpi/ic_launcher.png b/res/drawable-hdpi/ic_launcher.png deleted file mode 100644 index 96a442e..0000000 Binary files a/res/drawable-hdpi/ic_launcher.png and /dev/null differ diff --git a/res/drawable-mdpi/ic_launcher.png b/res/drawable-mdpi/ic_launcher.png deleted file mode 100644 index 359047d..0000000 Binary files a/res/drawable-mdpi/ic_launcher.png and /dev/null differ diff --git a/res/drawable-xhdpi/ic_launcher.png b/res/drawable-xhdpi/ic_launcher.png deleted file mode 100644 index 71c6d76..0000000 Binary files a/res/drawable-xhdpi/ic_launcher.png and /dev/null differ diff --git a/res/values/strings.xml b/res/values/strings.xml deleted file mode 100644 index ea98f1a..0000000 --- a/res/values/strings.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - android-updater - - diff --git a/src/com/shelwee/update/UpdateHelper.java b/src/com/shelwee/update/UpdateHelper.java deleted file mode 100644 index eba8f34..0000000 --- a/src/com/shelwee/update/UpdateHelper.java +++ /dev/null @@ -1,466 +0,0 @@ -package com.shelwee.update; - -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.util.HashMap; - -import org.apache.http.HttpEntity; -import org.apache.http.HttpResponse; -import org.apache.http.HttpStatus; -import org.apache.http.client.HttpClient; -import org.apache.http.client.methods.HttpGet; -import org.apache.http.impl.client.DefaultHttpClient; - -import android.app.AlertDialog; -import android.app.NotificationManager; -import android.app.PendingIntent; -import android.content.Context; -import android.content.DialogInterface; -import android.content.Intent; -import android.content.SharedPreferences; -import android.content.pm.PackageInfo; -import android.content.pm.PackageManager; -import android.net.Uri; -import android.os.AsyncTask; -import android.os.Environment; -import android.os.Handler; -import android.os.Message; -import android.support.v4.app.NotificationCompat; -import android.util.Log; -import android.widget.Toast; - -import com.shelwee.update.listener.OnUpdateListener; -import com.shelwee.update.pojo.UpdateInfo; -import com.shelwee.update.utils.HttpRequest; -import com.shelwee.update.utils.JSONHandler; -import com.shelwee.update.utils.NetWorkUtils; -import com.shelwee.update.utils.URLUtils; - -/** - * Created by ShelWee on 14-5-8.
- * Usage: - * - *
- * UpdateManager updateManager = new UpdateManager.Builder(this)
- * 		.checkUrl("http://localhost/examples/version.jsp")
- * 		.isAutoInstall(false)
- * 		.build();
- * updateManager.check();
- * 
- * - * @author ShelWee(http://www.shelwee.com) - * @version 0.1 beta - */ -public class UpdateHelper { - - private Context mContext; - private String checkUrl; - private boolean isAutoInstall; - private boolean isHintVersion; - private OnUpdateListener updateListener; - private NotificationManager notificationManager; - private NotificationCompat.Builder ntfBuilder; - - private static final int UPDATE_NOTIFICATION_PROGRESS = 0x1; - private static final int COMPLETE_DOWNLOAD_APK = 0x2; - private static final int DOWNLOAD_NOTIFICATION_ID = 0x3; - private static final String PATH = Environment - .getExternalStorageDirectory().getPath(); - private static final String SUFFIX = ".apk"; - private static final String APK_PATH = "APK_PATH"; - private static final String APP_NAME = "APP_NAME"; - private SharedPreferences preferences_update; - - private HashMap cache = new HashMap(); - - private Handler handler = new Handler() { - @Override - public void handleMessage(Message msg) { - super.handleMessage(msg); - switch (msg.what) { - case UPDATE_NOTIFICATION_PROGRESS: - showDownloadNotificationUI((UpdateInfo) msg.obj, msg.arg1); - break; - case COMPLETE_DOWNLOAD_APK: - if (UpdateHelper.this.isAutoInstall) { - installApk(Uri.parse("file://" + cache.get(APK_PATH))); - } else { - if (ntfBuilder == null) { - ntfBuilder = new NotificationCompat.Builder(mContext); - } - ntfBuilder.setSmallIcon(mContext.getApplicationInfo().icon) - .setContentTitle(cache.get(APP_NAME)) - .setContentText("下载完成,点击安装").setTicker("任务下载完成"); - Intent intent = new Intent(Intent.ACTION_VIEW); - intent.setDataAndType( - Uri.parse("file://" + cache.get(APK_PATH)), - "application/vnd.android.package-archive"); - PendingIntent pendingIntent = PendingIntent.getActivity( - mContext, 0, intent, 0); - ntfBuilder.setContentIntent(pendingIntent); - if (notificationManager == null) { - notificationManager = (NotificationManager) mContext - .getSystemService(Context.NOTIFICATION_SERVICE); - } - notificationManager.notify(DOWNLOAD_NOTIFICATION_ID, - ntfBuilder.build()); - } - break; - } - } - - }; - - private UpdateHelper(Builder builder) { - this.mContext = builder.context; - this.checkUrl = builder.checkUrl; - this.isAutoInstall = builder.isAutoInstall; - this.isHintVersion = builder.isHintNewVersion; - preferences_update = mContext.getSharedPreferences("Updater", - Context.MODE_PRIVATE); - } - - /** - * 检查app是否有新版本,check之前先Builer所需参数 - */ - public void check() { - check(null); - } - - public void check(OnUpdateListener listener) { - if (listener != null) { - this.updateListener = listener; - } - if (mContext == null) { - Log.e("NullPointerException", "The context must not be null."); - return; - } - AsyncCheck asyncCheck = new AsyncCheck(); - asyncCheck.execute(checkUrl); - } - - /** - * 2014-10-27新增流量提示框,当网络为数据流量方式时,下载就会弹出此对话框提示 - * @param updateInfo - */ - private void showNetDialog(final UpdateInfo updateInfo){ - AlertDialog.Builder netBuilder = new AlertDialog.Builder(mContext); - netBuilder.setTitle("下载提示"); - netBuilder.setMessage("您在目前的网络环境下继续下载将可能会消耗手机流量,请确认是否继续下载?"); - netBuilder.setNegativeButton("取消下载", - new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - dialog.dismiss(); - } - }); - netBuilder.setPositiveButton("继续下载", - new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - dialog.dismiss(); - AsyncDownLoad asyncDownLoad = new AsyncDownLoad(); - asyncDownLoad.execute(updateInfo); - } - }); - AlertDialog netDialog = netBuilder.create(); - netDialog.setCanceledOnTouchOutside(false); - netDialog.show(); - } - - /** - * 弹出提示更新窗口 - * - * @param updateInfo - */ - private void showUpdateUI(final UpdateInfo updateInfo) { - AlertDialog.Builder upDialogBuilder = new AlertDialog.Builder(mContext); - upDialogBuilder.setTitle(updateInfo.getUpdateTips()); - upDialogBuilder.setMessage(updateInfo.getChangeLog()); - upDialogBuilder.setNegativeButton("下次再说", - new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - dialog.dismiss(); - } - }); - upDialogBuilder.setPositiveButton("下载", - new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - dialog.dismiss(); - NetWorkUtils netWorkUtils = new NetWorkUtils(mContext); - int type = netWorkUtils.getNetType(); - if (type != 1) { - showNetDialog(updateInfo); - }else { - AsyncDownLoad asyncDownLoad = new AsyncDownLoad(); - asyncDownLoad.execute(updateInfo); - } - } - }); - AlertDialog updateDialog = upDialogBuilder.create(); - updateDialog.setCanceledOnTouchOutside(false); - updateDialog.show(); - } - - /** - * 通知栏弹出下载提示进度 - * - * @param updateInfo - * @param progress - */ - private void showDownloadNotificationUI(UpdateInfo updateInfo, - final int progress) { - if (mContext != null) { - String contentText = new StringBuffer().append(progress) - .append("%").toString(); - PendingIntent contentIntent = PendingIntent.getActivity(mContext, - 0, new Intent(), PendingIntent.FLAG_CANCEL_CURRENT); - if (notificationManager == null) { - notificationManager = (NotificationManager) mContext - .getSystemService(Context.NOTIFICATION_SERVICE); - } - if (ntfBuilder == null) { - ntfBuilder = new NotificationCompat.Builder(mContext) - .setSmallIcon(mContext.getApplicationInfo().icon) - .setTicker("开始下载...") - .setContentTitle(updateInfo.getAppName()) - .setContentIntent(contentIntent); - } - ntfBuilder.setContentText(contentText); - ntfBuilder.setProgress(100, progress, false); - notificationManager.notify(DOWNLOAD_NOTIFICATION_ID, - ntfBuilder.build()); - } - } - - /** - * 获取当前app版本 - * - * @return - * @throws android.content.pm.PackageManager.NameNotFoundException - */ - private PackageInfo getPackageInfo() { - PackageInfo pinfo = null; - if (mContext != null) { - try { - pinfo = mContext.getPackageManager().getPackageInfo( - mContext.getPackageName(), 0); - } catch (PackageManager.NameNotFoundException e) { - e.printStackTrace(); - } - } - return pinfo; - } - - /** - * 检查更新任务 - */ - private class AsyncCheck extends AsyncTask { - @Override - protected void onPreExecute() { - super.onPreExecute(); - if (UpdateHelper.this.updateListener != null) { - UpdateHelper.this.updateListener.onStartCheck(); - } - } - - @Override - protected UpdateInfo doInBackground(String... params) { - UpdateInfo updateInfo = null; - if (params.length == 0) { - Log.e("NullPointerException", - " Url parameter must not be null."); - return null; - } - String url = params[0]; - if (!URLUtils.isNetworkUrl(url)) { - Log.e("IllegalArgumentException", "The URL is invalid."); - return null; - } - try { - updateInfo = JSONHandler.toUpdateInfo(HttpRequest.get(url)); - } catch (Exception e) { - e.printStackTrace(); - return null; - } - return updateInfo; - } - - @Override - protected void onPostExecute(UpdateInfo updateInfo) { - super.onPostExecute(updateInfo); - SharedPreferences.Editor editor = preferences_update.edit(); - if (mContext != null && updateInfo != null) { - if (Integer.parseInt(updateInfo.getVersionCode()) > getPackageInfo().versionCode) { - showUpdateUI(updateInfo); - editor.putBoolean("hasNewVersion", true); - editor.putString("lastestVersionCode", - updateInfo.getVersionCode()); - editor.putString("lastestVersionName", - updateInfo.getVersionName()); - } else { - if (isHintVersion) { - Toast.makeText(mContext, "当前已是最新版", Toast.LENGTH_LONG).show(); - } - editor.putBoolean("hasNewVersion", false); - } - } else { - if (isHintVersion) { - Toast.makeText(mContext, "当前已是最新版", Toast.LENGTH_LONG).show(); - } - } - editor.putString("currentVersionCode", getPackageInfo().versionCode - + ""); - editor.putString("currentVersionName", getPackageInfo().versionName); - editor.commit(); - if (UpdateHelper.this.updateListener != null) { - UpdateHelper.this.updateListener.onFinishCheck(updateInfo); - } - } - } - - /** - * 异步下载app任务 - */ - private class AsyncDownLoad extends AsyncTask { - @Override - protected Boolean doInBackground(UpdateInfo... params) { - HttpClient httpClient = new DefaultHttpClient(); - HttpGet httpGet = new HttpGet(params[0].getApkUrl()); - try { - HttpResponse response = httpClient.execute(httpGet); - if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) { - Log.e("IllegalArgumentException", "APK路径出错,请检查服务端配置接口。"); - return false; - } else { - HttpEntity entity = response.getEntity(); - InputStream inputStream = entity.getContent(); - long total = entity.getContentLength(); - String apkName = params[0].getAppName() - + params[0].getVersionName() + SUFFIX; - cache.put(APP_NAME, params[0].getAppName()); - cache.put(APK_PATH, - PATH + File.separator + params[0].getAppName() - + File.separator + apkName); - File savePath = new File(PATH + File.separator - + params[0].getAppName()); - if (!savePath.exists()) - savePath.mkdirs(); - File apkFile = new File(savePath, apkName); - if (apkFile.exists()) { - return true; - } - FileOutputStream fos = new FileOutputStream(apkFile); - byte[] buf = new byte[1024]; - int count = 0; - int length = -1; - while ((length = inputStream.read(buf)) != -1) { - fos.write(buf, 0, length); - count += length; - int progress = (int) ((count / (float) total) * 100); - if (progress % 5 == 0) { - handler.obtainMessage(UPDATE_NOTIFICATION_PROGRESS, - progress, -1, params[0]).sendToTarget(); - } - if (UpdateHelper.this.updateListener != null) { - UpdateHelper.this.updateListener - .onDownloading(progress); - } - } - inputStream.close(); - fos.close(); - } - - } catch (IOException e) { - e.printStackTrace(); - return false; - } - return true; - } - - @Override - protected void onPostExecute(Boolean flag) { - if (flag) { - handler.obtainMessage(COMPLETE_DOWNLOAD_APK).sendToTarget(); - if (UpdateHelper.this.updateListener != null) { - UpdateHelper.this.updateListener.onFinshDownload(); - } - } else { - Log.e("Error", "下载失败。"); - } - } - } - - public static class Builder { - private Context context; - private String checkUrl; - private boolean isAutoInstall = true; - private boolean isHintNewVersion = true; - - public Builder(Context ctx) { - this.context = ctx; - } - - /** - * 检查是否有新版本App的URL接口路径 - * - * @param checkUrl - * @return - */ - public Builder checkUrl(String checkUrl) { - this.checkUrl = checkUrl; - return this; - } - - /** - * 是否需要自动安装, 不设置默认自动安装 - * - * @param isAuto - * true下载完成后自动安装,false下载完成后需在通知栏手动点击安装 - * @return - */ - public Builder isAutoInstall(boolean isAuto) { - this.isAutoInstall = isAuto; - return this; - } - - /** - * 当没有新版本时,是否Toast提示 - * @param isHint - * @return true提示,false不提示 - */ - public Builder isHintNewVersion(boolean isHint){ - this.isHintNewVersion = isHint; - return this; - } - - /** - * 构造UpdateManager对象 - * - * @return - */ - public UpdateHelper build() { - return new UpdateHelper(this); - } - } - - private void installApk(Uri data) { - if (mContext != null) { - Intent i = new Intent(Intent.ACTION_VIEW); - i.setDataAndType(data, "application/vnd.android.package-archive"); - i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - mContext.startActivity(i); - if (notificationManager != null) { - notificationManager.cancel(DOWNLOAD_NOTIFICATION_ID); - } - } else { - Log.e("NullPointerException", "The context must not be null."); - } - - } - -} \ No newline at end of file diff --git a/src/com/shelwee/update/utils/HttpRequest.java b/src/com/shelwee/update/utils/HttpRequest.java deleted file mode 100644 index 9b5df6d..0000000 --- a/src/com/shelwee/update/utils/HttpRequest.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.shelwee.update.utils; - -import java.io.InputStream; -import java.net.HttpURLConnection; -import java.net.URL; - -import android.util.Log; - -/** - * - * @author ShelWee - * - */ -public class HttpRequest { - - public static InputStream get(String url){ - try { - URL urlPath = new URL(url); - HttpURLConnection httpURLConnection = (HttpURLConnection) urlPath.openConnection(); - httpURLConnection.setConnectTimeout(3000); - httpURLConnection.setReadTimeout(3000); - httpURLConnection.connect(); - InputStream inputStream = null; - if(httpURLConnection.getResponseCode() == HttpURLConnection.HTTP_OK){ - inputStream = httpURLConnection.getInputStream(); - } - return inputStream; - } catch (Exception e) { - e.printStackTrace(); - Log.e("TimeOut","the connection is timeout, maybe the server was closed."); - return null; - } - } -} diff --git a/AndroidManifest.xml b/src/main/AndroidManifest.xml similarity index 83% rename from AndroidManifest.xml rename to src/main/AndroidManifest.xml index 51a80b9..d9c0696 100644 --- a/AndroidManifest.xml +++ b/src/main/AndroidManifest.xml @@ -1,5 +1,5 @@ @@ -9,7 +9,6 @@ diff --git a/src/main/java/com/shelwee/update/UpdateHelper.java b/src/main/java/com/shelwee/update/UpdateHelper.java new file mode 100644 index 0000000..a74c0a1 --- /dev/null +++ b/src/main/java/com/shelwee/update/UpdateHelper.java @@ -0,0 +1,466 @@ +package com.shelwee.update; + +import android.Manifest; +import android.app.Activity; +import android.app.AlertDialog; +import android.app.Notification; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.SharedPreferences; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.net.Uri; +import android.os.AsyncTask; +import android.os.Build; +import android.os.Environment; +import android.os.Handler; +import android.os.Message; +import android.support.v4.app.ActivityCompat; +import android.support.v4.app.NotificationCompat; +import android.util.Log; +import android.widget.RemoteViews; +import android.widget.Toast; + +import com.shelwee.update.listener.OnUpdateListener; +import com.shelwee.update.pojo.UpdateInfo; +import com.shelwee.update.utils.HttpRequest; +import com.shelwee.update.utils.JSONHandler; +import com.shelwee.update.utils.NetWorkUtils; +import com.shelwee.update.utils.URLUtils; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.HashMap; + +/** + * Created by Shelwee on 14-5-8.
+ * Usage: + *
+ * UpdateManager updateManager = new UpdateManager.Builder(this)
+ * 		.checkUrl("http://localhost/examples/version.jsp")
+ * 		.isAutoInstall(false)
+ * 		.build();
+ * updateManager.check();
+ * 
+ * + * @author Shelwee(http://www.shelwee.com) + * @version 1.0 + */ +public class UpdateHelper { + + private Context mContext; + private String checkUrl; + private boolean isAutoInstall; + private boolean isHintVersion; + private OnUpdateListener updateListener; + private NotificationManager notificationManager; + private NotificationCompat.Builder ntfBuilder; + private static final int REQUEST_PERMISSION_CODE = 0x1; + private static final int COMPLETE_DOWNLOAD_APK = 0x2; + private static final int DOWNLOAD_NOTIFICATION_ID = 0x3; + private static final String PATH = Environment + .getExternalStorageDirectory().getPath(); + private static final String SUFFIX = ".apk"; + private static final String APK_PATH = "APK_PATH"; + private static final String APP_NAME = "APP_NAME"; + private SharedPreferences preferences_update; + private Notification mNotification; + private String permission = Manifest.permission.WRITE_EXTERNAL_STORAGE; + + private HashMap cache = new HashMap(); + + private Handler handler = new Handler() { + @Override + public void handleMessage(Message msg) { + super.handleMessage(msg); + switch (msg.what) { + case COMPLETE_DOWNLOAD_APK: + if (UpdateHelper.this.isAutoInstall) { + installApk(Uri.parse("file://" + cache.get(APK_PATH))); + } else { + ntfBuilder.setContentTitle(cache.get(APP_NAME)) + .setContentText(mContext.getText(R.string.finished_install)) + .setTicker(mContext.getText(R.string.download_done)) + .setAutoCancel(true) + .setProgress(0, 0 ,true); + Intent intent = new Intent(Intent.ACTION_VIEW); + intent.setDataAndType( + Uri.parse("file://" + cache.get(APK_PATH)), + "application/vnd.android.package-archive"); + PendingIntent pendingIntent = PendingIntent.getActivity( + mContext, 0, intent, 0); + ntfBuilder.setContentIntent(pendingIntent); + notificationManager.notify(DOWNLOAD_NOTIFICATION_ID, + ntfBuilder.build()); + } + break; + } + } + + }; + + private UpdateHelper(Builder builder) { + this.mContext = builder.context; + this.checkUrl = builder.checkUrl; + this.isAutoInstall = builder.isAutoInstall; + this.isHintVersion = builder.isHintNewVersion; + preferences_update = mContext.getSharedPreferences("Updater", + Context.MODE_PRIVATE); + notificationManager = (NotificationManager) mContext + .getSystemService(Context.NOTIFICATION_SERVICE); + } + + /** + * 检查app是否有新版本,check之前先Builer所需参数 + */ + public void check() { + check(null); + } + + public void check(OnUpdateListener listener) { + if (listener != null) { + this.updateListener = listener; + } + if (mContext == null) { + Log.e("NullPointerException", "The context must not be null."); + return; + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + if (ActivityCompat.checkSelfPermission(mContext, permission) != PackageManager.PERMISSION_GRANTED) { + ActivityCompat.requestPermissions((Activity) mContext, new String[]{permission}, REQUEST_PERMISSION_CODE); + }else { + startCheck(); + } + }else { + startCheck(); + } + } + + private void startCheck() { + AsyncCheck asyncCheck = new AsyncCheck(); + asyncCheck.execute(checkUrl); + } + + /** + * 2014-10-27新增流量提示框,当网络为数据流量方式时,下载就会弹出此对话框提示 + * + * @param updateInfo + */ + private void showNetDialog(final UpdateInfo updateInfo) { + AlertDialog.Builder netBuilder = new AlertDialog.Builder(mContext); + netBuilder.setTitle(mContext.getText(R.string.download_tips)); + netBuilder.setMessage(mContext.getText(R.string.download_tips_network)); + netBuilder.setNegativeButton(mContext.getText(R.string.download_cancel), + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + dialog.dismiss(); + } + }); + netBuilder.setPositiveButton(mContext.getText(R.string.download_continue), + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + dialog.dismiss(); + AsyncDownLoad asyncDownLoad = new AsyncDownLoad(); + asyncDownLoad.execute(updateInfo); + } + }); + AlertDialog netDialog = netBuilder.create(); + netDialog.setCanceledOnTouchOutside(false); + netDialog.show(); + } + + /** + * 弹出提示更新窗口 + * + * @param updateInfo + */ + private void showUpdateUI(final UpdateInfo updateInfo) { + NetWorkUtils netWorkUtils = new NetWorkUtils(mContext); + final int type = netWorkUtils.getNetType(); + if (updateInfo.isForceUpgrade()){ + if (type != 1) { + showNetDialog(updateInfo); + } else { + AsyncDownLoad asyncDownLoad = new AsyncDownLoad(); + asyncDownLoad.execute(updateInfo); + } + }else { + AlertDialog.Builder upDialogBuilder = new AlertDialog.Builder(mContext); + upDialogBuilder.setTitle(updateInfo.getUpdateTips()); + upDialogBuilder.setMessage(updateInfo.getChangeLog()); + upDialogBuilder.setNegativeButton(mContext.getText(R.string.next_time), + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + dialog.dismiss(); + } + }); + upDialogBuilder.setPositiveButton(mContext.getText(R.string.download), + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + dialog.dismiss(); + if (type != 1) { + showNetDialog(updateInfo); + } else { + AsyncDownLoad asyncDownLoad = new AsyncDownLoad(); + asyncDownLoad.execute(updateInfo); + } + } + }); + AlertDialog updateDialog = upDialogBuilder.create(); + updateDialog.setCanceledOnTouchOutside(false); + updateDialog.show(); + } + } + + /** + * 获取当前app版本 + * + * @return + */ + private PackageInfo getPackageInfo() { + PackageInfo pinfo = null; + if (mContext != null) { + try { + pinfo = mContext.getPackageManager().getPackageInfo( + mContext.getPackageName(), 0); + } catch (PackageManager.NameNotFoundException e) { + e.printStackTrace(); + } + } + return pinfo; + } + + /** + * 检查更新任务 + */ + private class AsyncCheck extends AsyncTask { + @Override + protected void onPreExecute() { + super.onPreExecute(); + if (UpdateHelper.this.updateListener != null) { + UpdateHelper.this.updateListener.onStartCheck(); + } + } + + @Override + protected UpdateInfo doInBackground(String... params) { + UpdateInfo updateInfo = null; + if (params.length == 0) { + Log.e("NullPointerException", + " Url parameter must not be null."); + return null; + } + String url = params[0]; + if (!URLUtils.isNetworkUrl(url)) { + Log.e("IllegalArgument", "The URL is invalid."); + return null; + } + try { + updateInfo = JSONHandler.toUpdateInfo(new HttpRequest(url).get()); + } catch (Exception e) { + e.printStackTrace(); + return null; + } + return updateInfo; + } + + @Override + protected void onPostExecute(UpdateInfo updateInfo) { + super.onPostExecute(updateInfo); + SharedPreferences.Editor editor = preferences_update.edit(); + if (mContext != null && updateInfo != null) { + if (Integer.parseInt(updateInfo.getVersionCode()) > getPackageInfo().versionCode) { + showUpdateUI(updateInfo); + editor.putBoolean("hasNewVersion", true); + editor.putString("latestVersionCode", + updateInfo.getVersionCode()); + editor.putString("latestVersionName", + updateInfo.getVersionName()); + } else { + if (isHintVersion) { + Toast.makeText(mContext, R.string.was_latest_version, Toast.LENGTH_LONG).show(); + } + editor.putBoolean("hasNewVersion", false); + } + } else { + if (isHintVersion) { + Toast.makeText(mContext, R.string.was_latest_version, Toast.LENGTH_LONG).show(); + } + } + editor.putString("currentVersionCode", getPackageInfo().versionCode + ""); + editor.putString("currentVersionName", getPackageInfo().versionName); + editor.apply(); + if (UpdateHelper.this.updateListener != null) { + UpdateHelper.this.updateListener.onFinishCheck(updateInfo); + } + } + } + + /** + * 异步下载app任务 + */ + private class AsyncDownLoad extends AsyncTask { + @Override + protected void onPreExecute() { + notificationManager.cancel(DOWNLOAD_NOTIFICATION_ID); + PendingIntent contentIntent = PendingIntent.getActivity(mContext, + 0, new Intent(), PendingIntent.FLAG_UPDATE_CURRENT); + ntfBuilder = new NotificationCompat.Builder(mContext) + .setOngoing(true) + .setSmallIcon(mContext.getApplicationInfo().icon) + .setTicker(mContext.getResources().getText(R.string.start_download) + "...") + .setContentIntent(contentIntent) + .setProgress(100, 0, false); + mNotification = ntfBuilder.build(); + notificationManager.notify(DOWNLOAD_NOTIFICATION_ID, mNotification); + } + + @Override + protected Boolean doInBackground(UpdateInfo... params) { + String apkUrl = params[0].getApkUrl(); + HttpRequest request = new HttpRequest(apkUrl); + try { + long apkTotalLength = request.getContentLength(); + String apkName = params[0].getAppName() + + params[0].getVersionName() + SUFFIX; + cache.put(APP_NAME, params[0].getAppName()); + cache.put(APK_PATH, + PATH + File.separator + params[0].getAppName() + + File.separator + apkName); + File savePath = new File(PATH + File.separator + + params[0].getAppName()); + if (!savePath.exists()) + savePath.mkdirs(); + File apkFile = new File(savePath, apkName); + if (apkFile.exists() && apkFile.length() == apkTotalLength) { + return true; + } + FileOutputStream fos = new FileOutputStream(apkFile); + InputStream inputStream = request.get(); + if (inputStream == null) { + return false; + } + byte[] buf = new byte[2048]; + int count = 0; + int length = 0; + int progress = 0; + while ((length = inputStream.read(buf)) > 0) { + fos.write(buf, 0, length); + count += length; + if ((progress == 0) + || (int) (count * 100 / apkTotalLength) > progress) { + progress += 1; + publishProgress(progress); + } + if (UpdateHelper.this.updateListener != null) { + UpdateHelper.this.updateListener + .onDownloading(progress); + } + } + inputStream.close(); + fos.close(); + } catch (IOException e) { + e.printStackTrace(); + return false; + } + return true; + } + + @Override + protected void onProgressUpdate(Integer... values) { + ntfBuilder.setContentTitle(cache.get(APP_NAME)) + .setContentText(mContext.getResources().getText(R.string.download_progress) + ": " + values[0] + "%") + .setProgress(100, values[0], false); + mNotification = ntfBuilder.build(); + notificationManager.notify(DOWNLOAD_NOTIFICATION_ID, mNotification); + } + + @Override + protected void onPostExecute(Boolean flag) { + if (flag) { + handler.obtainMessage(COMPLETE_DOWNLOAD_APK).sendToTarget(); + if (UpdateHelper.this.updateListener != null) { + UpdateHelper.this.updateListener.onFinshDownload(); + } + } else { + Log.e("Error", "check your network connction"); + } + } + } + + public static class Builder { + private Context context; + private String checkUrl; + private boolean isAutoInstall = true; + private boolean isHintNewVersion = true; + + public Builder(Context ctx) { + this.context = ctx; + } + + /** + * 检查是否有新版本App的URL接口路径 + * + * @param checkUrl + * @return + */ + public Builder checkUrl(String checkUrl) { + this.checkUrl = checkUrl; + return this; + } + + /** + * 是否需要自动安装, 不设置默认自动安装 + * + * @param isAuto true下载完成后自动安装,false下载完成后需在通知栏手动点击安装 + * @return + */ + public Builder isAutoInstall(boolean isAuto) { + this.isAutoInstall = isAuto; + return this; + } + + /** + * 当没有新版本时,是否Toast提示 + * + * @param isHint + * @return true提示,false不提示 + */ + public Builder isHintNewVersion(boolean isHint) { + this.isHintNewVersion = isHint; + return this; + } + + /** + * 构造UpdateManager对象 + * + * @return + */ + public UpdateHelper build() { + return new UpdateHelper(this); + } + } + + private void installApk(Uri data) { + if (mContext != null) { + Intent i = new Intent(Intent.ACTION_VIEW); + i.setDataAndType(data, "application/vnd.android.package-archive"); + i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + mContext.startActivity(i); + if (notificationManager != null) { + notificationManager.cancel(DOWNLOAD_NOTIFICATION_ID); + } + } else { + Log.e("NullPointerException", "The context must not be null."); + } + } +} \ No newline at end of file diff --git a/src/com/shelwee/update/listener/OnUpdateListener.java b/src/main/java/com/shelwee/update/listener/OnUpdateListener.java similarity index 92% rename from src/com/shelwee/update/listener/OnUpdateListener.java rename to src/main/java/com/shelwee/update/listener/OnUpdateListener.java index 2820c77..ba645d9 100644 --- a/src/com/shelwee/update/listener/OnUpdateListener.java +++ b/src/main/java/com/shelwee/update/listener/OnUpdateListener.java @@ -3,7 +3,7 @@ import com.shelwee.update.pojo.UpdateInfo; /** - * Created by ShelWee on 14-5-16. + * Created by Shelwee on 14-5-16. */ public interface OnUpdateListener { /** diff --git a/src/com/shelwee/update/pojo/UpdateInfo.java b/src/main/java/com/shelwee/update/pojo/UpdateInfo.java similarity index 85% rename from src/com/shelwee/update/pojo/UpdateInfo.java rename to src/main/java/com/shelwee/update/pojo/UpdateInfo.java index b53d834..ec0cbe3 100644 --- a/src/com/shelwee/update/pojo/UpdateInfo.java +++ b/src/main/java/com/shelwee/update/pojo/UpdateInfo.java @@ -2,7 +2,7 @@ /** - * Created by ShelWee on 14-5-8. + * Created by Shelwee on 14-5-8. */ public class UpdateInfo { private String appName; @@ -12,6 +12,7 @@ public class UpdateInfo { private String apkUrl; private String changeLog; private String updateTips; + private boolean forceUpgrade; public String getAppName() { return appName; @@ -68,4 +69,12 @@ public String getChangeLog() { public void setChangeLog(String changeLog) { this.changeLog = changeLog; } + + public boolean isForceUpgrade() { + return forceUpgrade; + } + + public void setForceUpgrade(boolean forceUpgrade) { + this.forceUpgrade = forceUpgrade; + } } diff --git a/src/main/java/com/shelwee/update/utils/HttpRequest.java b/src/main/java/com/shelwee/update/utils/HttpRequest.java new file mode 100644 index 0000000..e62493a --- /dev/null +++ b/src/main/java/com/shelwee/update/utils/HttpRequest.java @@ -0,0 +1,62 @@ +package com.shelwee.update.utils; + +import java.io.IOException; +import java.io.InputStream; +import java.net.HttpURLConnection; +import java.net.URL; + +import android.util.Log; + +/** + * Created by Shelwee on 14-5-8. + */ +public class HttpRequest { + + private String url; + + public HttpRequest(String url) { + this.url = url; + } + + private HttpURLConnection buildConnection() throws IOException { + URL urlPath = new URL(url); + HttpURLConnection httpURLConnection = (HttpURLConnection) urlPath.openConnection(); + httpURLConnection.setConnectTimeout(8000); + httpURLConnection.setReadTimeout(5000); + return httpURLConnection; + } + + public int getContentLength(){ + HttpURLConnection httpURLConnection = null; + try { + httpURLConnection = buildConnection(); + httpURLConnection.connect(); + int length = 0; + if(httpURLConnection.getResponseCode() == HttpURLConnection.HTTP_OK){ + length = httpURLConnection.getContentLength(); + } + return length; + } catch (Exception e) { + e.printStackTrace(); + Log.e("Request Exception","the connection is timeout or maybe the server was closed."); + return 0; + } + } + + public InputStream get(){ + HttpURLConnection httpURLConnection = null; + InputStream inputStream = null; + try { + httpURLConnection = buildConnection(); + httpURLConnection.connect(); + if(httpURLConnection.getResponseCode() == HttpURLConnection.HTTP_OK){ + inputStream = httpURLConnection.getInputStream(); + } + return inputStream; + } catch (Exception e) { + e.printStackTrace(); + Log.e("Request Exception","the connection is timeout or maybe the server was closed."); + return null; + } + } +} diff --git a/src/com/shelwee/update/utils/JSONHandler.java b/src/main/java/com/shelwee/update/utils/JSONHandler.java similarity index 77% rename from src/com/shelwee/update/utils/JSONHandler.java rename to src/main/java/com/shelwee/update/utils/JSONHandler.java index 715616b..dab7d38 100644 --- a/src/com/shelwee/update/utils/JSONHandler.java +++ b/src/main/java/com/shelwee/update/utils/JSONHandler.java @@ -9,12 +9,12 @@ import com.shelwee.update.pojo.UpdateInfo; /** - * Created by ShelWee on 14-5-8. + * Created by Shelwee on 14-5-8. */ public class JSONHandler { public static UpdateInfo toUpdateInfo(InputStream is) throws Exception { - if (is == null){ + if (is == null) { return null; } String byteData = new String(readStream(is)); @@ -27,15 +27,16 @@ public static UpdateInfo toUpdateInfo(InputStream is) throws Exception { updateInfo.setVersionName(jsonObject.getString("versionName")); updateInfo.setChangeLog(jsonObject.getString("changeLog")); updateInfo.setUpdateTips(jsonObject.getString("updateTips")); + updateInfo.setForceUpgrade(jsonObject.getBoolean("forceUpgrade")); return updateInfo; } private static byte[] readStream(InputStream inputStream) throws IOException { - ByteArrayOutputStream outputStream=new ByteArrayOutputStream(); - byte [] array=new byte[1024]; + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + byte[] array = new byte[1024]; int len = 0; - while( (len = inputStream.read(array)) != -1){ - outputStream.write(array,0,len); + while ((len = inputStream.read(array)) != -1) { + outputStream.write(array, 0, len); } inputStream.close(); outputStream.close(); diff --git a/src/com/shelwee/update/utils/NetWorkUtils.java b/src/main/java/com/shelwee/update/utils/NetWorkUtils.java similarity index 96% rename from src/com/shelwee/update/utils/NetWorkUtils.java rename to src/main/java/com/shelwee/update/utils/NetWorkUtils.java index b456d68..ea7da20 100644 --- a/src/com/shelwee/update/utils/NetWorkUtils.java +++ b/src/main/java/com/shelwee/update/utils/NetWorkUtils.java @@ -5,7 +5,7 @@ import android.net.NetworkInfo; /** - * Created by ShelWee on 14-5-8. + * Created by Shelwee on 14-5-8. */ public class NetWorkUtils { diff --git a/src/com/shelwee/update/utils/URLUtils.java b/src/main/java/com/shelwee/update/utils/URLUtils.java similarity index 94% rename from src/com/shelwee/update/utils/URLUtils.java rename to src/main/java/com/shelwee/update/utils/URLUtils.java index ac296a8..6107d91 100644 --- a/src/com/shelwee/update/utils/URLUtils.java +++ b/src/main/java/com/shelwee/update/utils/URLUtils.java @@ -1,7 +1,7 @@ package com.shelwee.update.utils; /** - * Created by ShelWee on 14-5-8. + * Created by Shelwee on 14-5-8. */ public class URLUtils { diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml new file mode 100644 index 0000000..729068b --- /dev/null +++ b/src/main/res/values/strings.xml @@ -0,0 +1,17 @@ + + + android-updater + 开始下载 + 下载进度 + 当前已是最新版 + 下载完成,点击安装 + 任务下载完成 + 下载提示 + 您在目前的网络环境下继续下载将可能会消耗手机流量,请确认是否继续下载? + 取消下载 + 继续下载 + 下次再说 + 下载 + 请允许应用添加存储权限 + + diff --git a/res/values/styles.xml b/src/main/res/values/styles.xml similarity index 100% rename from res/values/styles.xml rename to src/main/res/values/styles.xml