android
Jenly 7 years ago
commit 3ca2356881
  1. 10
      .gitignore
  2. 57
      .idea/assetWizardSettings.xml
  3. BIN
      .idea/caches/build_file_checksums.ser
  4. 29
      .idea/codeStyles/Project.xml
  5. 6
      .idea/encodings.xml
  6. 20
      .idea/gradle.xml
  7. 34
      .idea/misc.xml
  8. 12
      .idea/runConfigurations.xml
  9. 6
      .idea/vcs.xml
  10. BIN
      GIF.gif
  11. 23
      LICENSE
  12. 142
      README.md
  13. 1
      app-dialog/.gitignore
  14. 10
      app-dialog/bintray.gradle
  15. 42
      app-dialog/build.gradle
  16. 21
      app-dialog/proguard-rules.pro
  17. 26
      app-dialog/src/androidTest/java/com/king/app/dialog/ExampleInstrumentedTest.java
  18. 2
      app-dialog/src/main/AndroidManifest.xml
  19. 194
      app-dialog/src/main/java/com/king/app/dialog/AppDialog.java
  20. 156
      app-dialog/src/main/java/com/king/app/dialog/AppDialogConfig.java
  21. 61
      app-dialog/src/main/java/com/king/app/dialog/fragment/AppDialogFragment.java
  22. 59
      app-dialog/src/main/java/com/king/app/dialog/fragment/BaseDialogFragment.java
  23. 10
      app-dialog/src/main/res/anim/app_dialog_in.xml
  24. 10
      app-dialog/src/main/res/anim/app_dialog_out.xml
  25. 5
      app-dialog/src/main/res/color/app_dialog_button_color_selector.xml
  26. 12
      app-dialog/src/main/res/drawable/app_dialog_bg.xml
  27. 60
      app-dialog/src/main/res/layout/app_dialog.xml
  28. 5
      app-dialog/src/main/res/layout/app_dialog_line_h.xml
  29. 5
      app-dialog/src/main/res/layout/app_dialog_line_v.xml
  30. 10
      app-dialog/src/main/res/values/colors.xml
  31. 5
      app-dialog/src/main/res/values/dimens.xml
  32. 7
      app-dialog/src/main/res/values/strings.xml
  33. 21
      app-dialog/src/main/res/values/styles.xml
  34. 17
      app-dialog/src/test/java/com/king/app/dialog/ExampleUnitTest.java
  35. 1
      app-updater/.gitignore
  36. 11
      app-updater/bintray.gradle
  37. 35
      app-updater/build.gradle
  38. 21
      app-updater/proguard-rules.pro
  39. 26
      app-updater/src/androidTest/java/com/king/app/updater/ExampleInstrumentedTest.java
  40. 27
      app-updater/src/main/AndroidManifest.xml
  41. 239
      app-updater/src/main/java/com/king/app/updater/AppUpdater.java
  42. 218
      app-updater/src/main/java/com/king/app/updater/UpdateConfig.java
  43. 26
      app-updater/src/main/java/com/king/app/updater/callback/AppUpdateCallback.java
  44. 46
      app-updater/src/main/java/com/king/app/updater/callback/UpdateCallback.java
  45. 31
      app-updater/src/main/java/com/king/app/updater/constant/Constants.java
  46. 166
      app-updater/src/main/java/com/king/app/updater/http/HttpManager.java
  47. 54
      app-updater/src/main/java/com/king/app/updater/http/IHttpManager.java
  48. 10
      app-updater/src/main/java/com/king/app/updater/provider/AppUpdaterFileProvider.java
  49. 527
      app-updater/src/main/java/com/king/app/updater/service/DownloadService.java
  50. 98
      app-updater/src/main/java/com/king/app/updater/util/AppUtils.java
  51. 36
      app-updater/src/main/java/com/king/app/updater/util/PermissionUtils.java
  52. 175
      app-updater/src/main/java/com/king/app/updater/util/SSLSocketFactoryUtils.java
  53. 18
      app-updater/src/main/res/values/strings.xml
  54. 6
      app-updater/src/main/res/xml/app_updater_paths.xml
  55. 17
      app-updater/src/test/java/com/king/app/updater/ExampleUnitTest.java
  56. 1
      app/.gitignore
  57. 30
      app/build.gradle
  58. 21
      app/proguard-rules.pro
  59. BIN
      app/release/app-release.apk
  60. 1
      app/release/output.json
  61. 26
      app/src/androidTest/java/com/king/appupdater/ExampleInstrumentedTest.java
  62. 27
      app/src/main/AndroidManifest.xml
  63. BIN
      app/src/main/ic_launcher-web.png
  64. 274
      app/src/main/java/com/king/appupdater/MainActivity.java
  65. 34
      app/src/main/res/drawable-v24/ic_launcher_foreground.xml
  66. 12
      app/src/main/res/drawable/dialog_title_bg.xml
  67. 170
      app/src/main/res/drawable/ic_launcher_background.xml
  68. 107
      app/src/main/res/layout/activity_main.xml
  69. 60
      app/src/main/res/layout/dialog.xml
  70. 56
      app/src/main/res/layout/dialog_custom.xml
  71. 5
      app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
  72. 5
      app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
  73. BIN
      app/src/main/res/mipmap-hdpi/ic_launcher.png
  74. BIN
      app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png
  75. BIN
      app/src/main/res/mipmap-hdpi/ic_launcher_round.png
  76. BIN
      app/src/main/res/mipmap-mdpi/ic_launcher.png
  77. BIN
      app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png
  78. BIN
      app/src/main/res/mipmap-mdpi/ic_launcher_round.png
  79. BIN
      app/src/main/res/mipmap-xhdpi/ic_launcher.png
  80. BIN
      app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png
  81. BIN
      app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
  82. BIN
      app/src/main/res/mipmap-xxhdpi/ic_launcher.png
  83. BIN
      app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png
  84. BIN
      app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
  85. BIN
      app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
  86. BIN
      app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png
  87. BIN
      app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
  88. 10
      app/src/main/res/values/colors.xml
  89. 4
      app/src/main/res/values/strings.xml
  90. 16
      app/src/main/res/values/styles.xml
  91. 17
      app/src/test/java/com/king/appupdater/ExampleUnitTest.java
  92. 37
      build.gradle
  93. 13
      gradle.properties
  94. BIN
      gradle/wrapper/gradle-wrapper.jar
  95. 6
      gradle/wrapper/gradle-wrapper.properties
  96. 172
      gradlew
  97. 84
      gradlew.bat
  98. 1
      settings.gradle

10
.gitignore vendored

@ -0,0 +1,10 @@
*.iml
.gradle
/local.properties
/.idea/libraries
/.idea/modules.xml
/.idea/workspace.xml
.DS_Store
/build
/captures
.externalNativeBuild

@ -0,0 +1,57 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="WizardSettings">
<option name="children">
<map>
<entry key="imageWizard">
<value>
<PersistentState>
<option name="children">
<map>
<entry key="imageAssetPanel">
<value>
<PersistentState>
<option name="children">
<map>
<entry key="launcher">
<value>
<PersistentState>
<option name="children">
<map>
<entry key="foregroundImage">
<value>
<PersistentState>
<option name="values">
<map>
<entry key="scalingPercent" value="50" />
</map>
</option>
</PersistentState>
</value>
</entry>
</map>
</option>
<option name="values">
<map>
<entry key="backgroundAssetType" value="COLOR" />
<entry key="backgroundColor" value="ffffff" />
<entry key="foregroundImage" value="C:\app_updater.png" />
</map>
</option>
</PersistentState>
</value>
</entry>
</map>
</option>
</PersistentState>
</value>
</entry>
</map>
</option>
</PersistentState>
</value>
</entry>
</map>
</option>
</component>
</project>

@ -0,0 +1,29 @@
<component name="ProjectCodeStyleConfiguration">
<code_scheme name="Project" version="173">
<Objective-C-extensions>
<file>
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Import" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Macro" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Typedef" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Enum" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Constant" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Global" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Struct" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="FunctionPredecl" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Function" />
</file>
<class>
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Property" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Synthesize" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="InitMethod" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="StaticMethod" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="InstanceMethod" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="DeallocMethod" />
</class>
<extensions>
<pair source="cpp" header="h" fileNamingConvention="NONE" />
<pair source="c" header="h" fileNamingConvention="NONE" />
</extensions>
</Objective-C-extensions>
</code_scheme>
</component>

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Encoding" defaultCharsetForPropertiesFiles="UTF-8">
<file url="PROJECT" charset="UTF-8" />
</component>
</project>

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="GradleSettings">
<option name="linkedExternalProjectsSettings">
<GradleProjectSettings>
<option name="distributionType" value="DEFAULT_WRAPPED" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="modules">
<set>
<option value="$PROJECT_DIR$" />
<option value="$PROJECT_DIR$/app" />
<option value="$PROJECT_DIR$/app-dialog" />
<option value="$PROJECT_DIR$/app-updater" />
</set>
</option>
<option name="resolveModulePerSourceSet" value="false" />
</GradleProjectSettings>
</option>
</component>
</project>

@ -0,0 +1,34 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="NullableNotNullManager">
<option name="myDefaultNullable" value="android.support.annotation.Nullable" />
<option name="myDefaultNotNull" value="android.support.annotation.NonNull" />
<option name="myNullables">
<value>
<list size="5">
<item index="0" class="java.lang.String" itemvalue="org.jetbrains.annotations.Nullable" />
<item index="1" class="java.lang.String" itemvalue="javax.annotation.Nullable" />
<item index="2" class="java.lang.String" itemvalue="javax.annotation.CheckForNull" />
<item index="3" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.Nullable" />
<item index="4" class="java.lang.String" itemvalue="android.support.annotation.Nullable" />
</list>
</value>
</option>
<option name="myNotNulls">
<value>
<list size="4">
<item index="0" class="java.lang.String" itemvalue="org.jetbrains.annotations.NotNull" />
<item index="1" class="java.lang.String" itemvalue="javax.annotation.Nonnull" />
<item index="2" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.NonNull" />
<item index="3" class="java.lang.String" itemvalue="android.support.annotation.NonNull" />
</list>
</value>
</option>
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_7" project-jdk-name="1.8" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" />
</component>
<component name="ProjectType">
<option name="id" value="Android" />
</component>
</project>

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RunConfigurationProducerService">
<option name="ignoredProducers">
<set>
<option value="org.jetbrains.plugins.gradle.execution.test.runner.AllInPackageGradleConfigurationProducer" />
<option value="org.jetbrains.plugins.gradle.execution.test.runner.TestClassGradleConfigurationProducer" />
<option value="org.jetbrains.plugins.gradle.execution.test.runner.TestMethodGradleConfigurationProducer" />
</set>
</option>
</component>
</project>

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 MiB

@ -0,0 +1,23 @@
The MIT License (MIT)
Copyright (c) 2017 Jenly Yu
https://github.com/jenly1314
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files
(the "Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to permit
persons to whom the Software is furnished to do so, subject to the
following conditions:
The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.

@ -0,0 +1,142 @@
# AppUpdater
![Image](app/ic_launcher-web.png)
[![Download](https://img.shields.io/badge/download-App-blue.svg)](https://raw.githubusercontent.com/jenly1314/AppUpdater/master/app/release/app-release.apk)
[![](https://jitpack.io/v/jenly1314/AppUpdater.svg)](https://jitpack.io/#jenly1314/AppUpdater)
[![License](https://img.shields.io/badge/license-MIT-blue.svg)](https://opensource.org/licenses/mit-license.php)
[![Blog](https://img.shields.io/badge/blog-Jenly-9933CC.svg)](http://blog.csdn.net/jenly121)
AppUpdater for Android 是一个专注于App更新一键傻瓜式集成的开源库,主要包括app-updater和app-dialog。
> 下载更新和弹框提示分开,是因为这本来就是两个逻辑。完全独立开来能有效的解耦。
* app-updater 主要负责后台下载更新App,无需担心下载时各种配置相关的细节,一键傻瓜式升级。
* app-dialog 主要是提供常用的Dialog和DialogFragment,简化弹框提示,样式支持高度自定义。
> app-updater + app-dialog 配合使用,谁用谁知道。
## 功能介绍
- [x] 专注于App更新一键傻瓜式升级
- [x] 支持下载监听
- [x] 支持下载失败,重新下载
- [x] 支持通知栏提示内容和过程全部可配置
- [x] 支持Android O
## Gif 展示
![Image](GIF.gif)
## 引入
### Maven:
```maven
//app-updater
<dependency>
<groupId>com.king.app</groupId>
<artifactId>app-updater</artifactId>
<version>1.0</version>
<type>pom</type>
</dependency>
//app-dialog
<dependency>
<groupId>com.king.app</groupId>
<artifactId>app-dialog</artifactId>
<version>1.0</version>
<type>pom</type>
</dependency>
```
### Gradle:
```gradle
//app-updater
compile 'com.king.app:app-updater:1.0'
//app-dialog
compile 'com.king.app:app-dialog:1.0'
```
### Lvy:
```lvy
//app-updater
<dependency org='com.king.app' name='app-dialog' rev='1.0'>
<artifact name='$AID' ext='pom'></artifact>
</dependency>
//app-dialog
<dependency org='com.king.app' name='app-dialog' rev='1.0'>
<artifact name='$AID' ext='pom'></artifact>
</dependency>
```
###### 如果Gradle出现compile失败的情况,可以在Project的build.gradle里面添加如下:(也可以使用上面的GitPack来complie)
```gradle
allprojects {
repositories {
//...
maven { url 'https://dl.bintray.com/jenly/maven' }
}
}
```
## 示例
```Java
//一句代码,傻瓜式更新
new AppUpdater(getContext(),url).start();
```
```Java
//简单弹框升级
AppDialogConfig config = new AppDialogConfig();
config.setTitle("简单弹框升级")
.setOk("升级")
.setContent("1、新增某某功能、\n2、修改某某问题、\n3、优化某某BUG、")
.setOnClickOk(new View.OnClickListener() {
@Override
public void onClick(View v) {
new AppUpdater.Builder()
.serUrl(mUrl)
.setFilename("AppUpdater.apk")
.build(getContext())
.start();
AppDialog.INSTANCE.dismissDialog();
}
});
AppDialog.INSTANCE.showDialog(getContext(),config);
```
```Java
//简单DialogFragment升级
AppDialogConfig config = new AppDialogConfig();
config.setTitle("简单DialogFragment升级")
.setOk("升级")
.setContent("1、新增某某功能、\n2、修改某某问题、\n3、优化某某BUG、")
.setOnClickOk(new View.OnClickListener() {
@Override
public void onClick(View v) {
new AppUpdater.Builder()
.serUrl(mUrl)
.setFilename("AppUpdater.apk")
.build(getContext())
.start();
AppDialog.INSTANCE.dismissDialogFragment(getSupportFragmentManager());
}
});
AppDialog.INSTANCE.showDialogFragment(getSupportFragmentManager(),config);
```
更多使用示例请查看[App](app)。
## 关于我
Name: <a title="关于作者" href="https://about.me/jenly1314" target="_blank">Jenly</a>
Email: <a title="欢迎邮件与我交流" href="mailto:jenly1314@gmail.com" target="_blank">jenly1314#gmail.com</a> / <a title="给我发邮件" href="mailto:jenly1314@vip.qq.com" target="_blank">jenly1314#vip.qq.com</a>
CSDN: <a title="CSDN博客" href="http://blog.csdn.net/jenly121" target="_blank">jenly121</a>
Github: <a title="Github开源项目" href="https://github.com/jenly1314" target="_blank">jenly1314</a>
微信公众号:
![公众号](http://olambmg9j.bkt.clouddn.com/jenly666.jpg)
加入QQ群: <a title="点击加入QQ群" href="http://shang.qq.com/wpa/qunwpa?idkey=8fcc6a2f88552ea44b1411582c94fd124f7bb3ec227e2a400dbbfaad3dc2f5ad" target="_blank">20867961</a>

@ -0,0 +1 @@
/build

@ -0,0 +1,10 @@
apply plugin: 'com.novoda.bintray-release'
//
publish {
userOrg = 'jenly'//bintray.com用户名
groupId = 'com.king.app'//jcenter上的路径
artifactId = 'app-dialog'//
publishVersion = '1.0'//
desc = 'AppDialog for Android'//
website = 'https://github.com/jenly1314/AppUpdater'//
}

@ -0,0 +1,42 @@
apply plugin: 'com.android.library'
apply from: 'bintray.gradle'
android {
compileSdkVersion 27
defaultConfig {
minSdkVersion 15
targetSdkVersion 27
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
compileOnly 'com.android.support:appcompat-v7:27.1.1'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
}

@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# 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 *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile

@ -0,0 +1,26 @@
package com.king.app.dialog;
import android.content.Context;
import android.support.test.InstrumentationRegistry;
import android.support.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
import static org.junit.Assert.*;
/**
* Instrumented test, which will execute on an Android device.
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
@RunWith(AndroidJUnit4.class)
public class ExampleInstrumentedTest {
@Test
public void useAppContext() {
// Context of the app under test.
Context appContext = InstrumentationRegistry.getTargetContext();
assertEquals("com.king.app.dialog.test", appContext.getPackageName());
}
}

@ -0,0 +1,2 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.king.app.dialog" />

@ -0,0 +1,194 @@
package com.king.app.dialog;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.support.annotation.StyleRes;
import android.support.v4.app.DialogFragment;
import android.support.v4.app.FragmentManager;
import android.text.TextUtils;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.TextView;
import com.king.app.dialog.fragment.AppDialogFragment;
/**
* @author Jenly <a href="mailto:jenly1314@gmail.com">Jenly</a>
*/
public enum AppDialog {
INSTANCE;
private final float DEFAULT_WIDTH_RATIO = 0.85f;
private Dialog mDialog;
private String mTag;
//-------------------------------------------
public View createAppDialogView(Context context,AppDialogConfig config){
View view = null;
if(config!=null){
view = LayoutInflater.from(context).inflate(config.getLayoutId(),null);
TextView tvDialogTitle = view.findViewById(config.getTitleId());
setText(tvDialogTitle,config.getTitle());
TextView tvDialogContent = view.findViewById(config.getContentId());
setText(tvDialogContent,config.getContent());
Button btnDialogCancel = view.findViewById(config.getCancelId());
setText(btnDialogCancel,config.getCancel());
btnDialogCancel.setOnClickListener(config.getOnClickCancel() != null ? config.getOnClickCancel() : mOnClickDismissDialog);
btnDialogCancel.setVisibility(config.isHideCancel() ? View.GONE : View.VISIBLE);
try{
//不强制要求要有横线
View line = view.findViewById(R.id.line);
line.setVisibility(config.isHideCancel() ? View.GONE : View.VISIBLE);
}catch (Exception e){
}
Button btnDialogOK = view.findViewById(config.getOkId());
setText(btnDialogOK,config.getOk());
btnDialogOK.setOnClickListener(config.getOnClickOk() != null ? config.getOnClickOk() : mOnClickDismissDialog);
}
return view;
}
//-------------------------------------------
private View.OnClickListener mOnClickDismissDialog = new View.OnClickListener() {
@Override
public void onClick(View v) {
dismissDialog();
}
};
private void setText(TextView tv,CharSequence text){
if(!TextUtils.isEmpty(text)){
tv.setText(text);
}
}
//-------------------------------------------
public void dismissDialogFragment(FragmentManager fragmentManager){
dismissDialogFragment(fragmentManager,mTag);
}
public void dismissDialogFragment(FragmentManager fragmentManager,String tag){
if(tag!=null){
DialogFragment dialogFragment = (DialogFragment) fragmentManager.findFragmentByTag(tag);
dismissDialogFragment(dialogFragment);
}
}
public void dismissDialogFragment(DialogFragment dialogFragment){
if(dialogFragment!=null){
dialogFragment.dismiss();
}
}
//-------------------------------------------
public String showDialogFragment(FragmentManager fragmentManager,AppDialogConfig config){
AppDialogFragment dialogFragment = AppDialogFragment.newInstance(config);
String tag = dialogFragment.getTag() !=null ? dialogFragment.getTag() : dialogFragment.getClass().getSimpleName();
showDialogFragment(fragmentManager,dialogFragment,tag);
mTag = tag;
return tag;
}
public String showDialogFragment(FragmentManager fragmentManager,DialogFragment dialogFragment){
String tag = dialogFragment.getTag() !=null ? dialogFragment.getTag() : dialogFragment.getClass().getSimpleName();
showDialogFragment(fragmentManager,dialogFragment,tag);
mTag = tag;
return tag;
}
public String showDialogFragment(FragmentManager fragmentManager,DialogFragment dialogFragment, String tag) {
dialogFragment.show(fragmentManager,tag);
mTag = tag;
return tag;
}
//-------------------------------------------
public void showDialog(Context context,AppDialogConfig config){
showDialog(context,createAppDialogView(context,config));
}
public void showDialog(Context context,View contentView){
showDialog(context,contentView,DEFAULT_WIDTH_RATIO);
}
public void showDialog(Context context,View contentView,boolean isCancel){
showDialog(context,contentView,R.style.app_dialog,DEFAULT_WIDTH_RATIO,isCancel);
}
public void showDialog(Context context,View contentView,float widthRatio){
showDialog(context,contentView,widthRatio,true);
}
public void showDialog(Context context,View contentView,float widthRatio,boolean isCancel){
showDialog(context,contentView,R.style.app_dialog,widthRatio,isCancel);
}
public void showDialog(Context context, View contentView, @StyleRes int resId, float widthRatio){
showDialog(context,contentView,resId,widthRatio,true);
}
/**
*
* @param context
* @param contentView
* @param resId Dialog样式
* @param widthRatio
* @param isCancel 是否可取消默认为truefalse则拦截back键
*/
public void showDialog(Context context, View contentView, @StyleRes int resId, float widthRatio,final boolean isCancel){
mDialog = new Dialog(context,resId);
mDialog.setContentView(contentView);
mDialog.setCanceledOnTouchOutside(false);
mDialog.setOnKeyListener(new DialogInterface.OnKeyListener() {
@Override
public boolean onKey(DialogInterface dialog, int keyCode, KeyEvent event) {
if(keyCode == KeyEvent.KEYCODE_BACK && isCancel){
dismissDialog();
}
return true;
}
});
setDialogWindow(context,mDialog,widthRatio);
mDialog.show();
}
private void setDialogWindow(Context context,Dialog dialog,float widthRatio){
Window window = dialog.getWindow();
WindowManager.LayoutParams lp = window.getAttributes();
lp.width = (int)(context.getResources().getDisplayMetrics().widthPixels * widthRatio);
window.setAttributes(lp);
}
public void dismissDialog(){
dismissDialog(mDialog);
}
private void dismissDialog(Dialog dialog){
if(dialog!=null){
dialog.dismiss();
}
}
//-------------------------------------------
}

@ -0,0 +1,156 @@
package com.king.app.dialog;
import android.support.annotation.IdRes;
import android.support.annotation.LayoutRes;
import android.view.View;
/**
* @author Jenly <a href="mailto:jenly1314@gmail.com">Jenly</a>
*/
public class AppDialogConfig {
private @LayoutRes int layoutId = R.layout.app_dialog;
private @IdRes int titleId = R.id.tvDialogTitle;
private @IdRes int contentId = R.id.tvDialogContent;
private @IdRes int cancelId = R.id.btnDialogCancel;
private @IdRes int okId = R.id.btnDialogOK;
private @IdRes int line = R.id.line;
private CharSequence title;
private CharSequence content;
private CharSequence cancel;
private CharSequence ok;
private boolean isHideCancel;
private View.OnClickListener onClickCancel;
private View.OnClickListener onClickOk;
public int getLayoutId() {
return layoutId;
}
public AppDialogConfig setLayoutId(int layoutId) {
this.layoutId = layoutId;
return this;
}
public int getTitleId() {
return titleId;
}
public AppDialogConfig setTitleId(int titleId) {
this.titleId = titleId;
return this;
}
public int getContentId() {
return contentId;
}
public AppDialogConfig setContentId(int contentId) {
this.contentId = contentId;
return this;
}
public int getCancelId() {
return cancelId;
}
public AppDialogConfig setCancelId(int cancelId) {
this.cancelId = cancelId;
return this;
}
public int getOkId() {
return okId;
}
public AppDialogConfig setOkId(int okId) {
this.okId = okId;
return this;
}
public int getLine() {
return line;
}
public AppDialogConfig setLine(int line) {
this.line = line;
return this;
}
public CharSequence getTitle() {
return title;
}
public AppDialogConfig setTitle(CharSequence title) {
this.title = title;
return this;
}
public CharSequence getContent() {
return content;
}
public AppDialogConfig setContent(CharSequence content) {
this.content = content;
return this;
}
public CharSequence getCancel() {
return cancel;
}
public AppDialogConfig setCancel(CharSequence cancel) {
this.cancel = cancel;
return this;
}
public CharSequence getOk() {
return ok;
}
public AppDialogConfig setOk(CharSequence ok) {
this.ok = ok;
return this;
}
public boolean isHideCancel() {
return isHideCancel;
}
public AppDialogConfig setHideCancel(boolean hideCancel) {
isHideCancel = hideCancel;
return this;
}
public View.OnClickListener getOnClickCancel() {
return onClickCancel;
}
public AppDialogConfig setOnClickCancel(View.OnClickListener onClickCancel) {
this.onClickCancel = onClickCancel;
return this;
}
public View.OnClickListener getOnClickOk() {
return onClickOk;
}
public AppDialogConfig setOnClickOk(View.OnClickListener onClickOk) {
this.onClickOk = onClickOk;
return this;
}
}

@ -0,0 +1,61 @@
package com.king.app.dialog.fragment;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import com.king.app.dialog.AppDialogConfig;
import com.king.app.dialog.R;
/**
* @author Jenly <a href="mailto:jenly1314@gmail.com">Jenly</a>
*/
public class AppDialogFragment extends BaseDialogFragment {
private AppDialogConfig config;
public static AppDialogFragment newInstance(AppDialogConfig config) {
Bundle args = new Bundle();
AppDialogFragment fragment = new AppDialogFragment();
fragment.config = config;
fragment.setArguments(args);
return fragment;
}
@Override
public int getRootLayoutId() {
if(config == null){
config = new AppDialogConfig();
}
return config.getLayoutId();
}
public void init(View rootView){
if(config!=null){
TextView tvDialogTitle = rootView.findViewById(config.getTitleId());
setText(tvDialogTitle,config.getTitle());
TextView tvDialogContent = rootView.findViewById(config.getContentId());
setText(tvDialogContent,config.getContent());
Button btnDialogCancel = rootView.findViewById(config.getCancelId());
setText(btnDialogCancel,config.getCancel());
btnDialogCancel.setOnClickListener(config.getOnClickCancel() != null ? config.getOnClickCancel() : getOnClickDismiss());
btnDialogCancel.setVisibility(config.isHideCancel() ? View.GONE : View.VISIBLE);
try{
//不强制要求要有横线
View line = rootView.findViewById(R.id.line);
line.setVisibility(config.isHideCancel() ? View.GONE : View.VISIBLE);
}catch (Exception e){
}
Button btnDialogOK = rootView.findViewById(config.getOkId());
setText(btnDialogOK,config.getOk());
btnDialogOK.setOnClickListener(config.getOnClickOk() != null ? config.getOnClickOk() : getOnClickDismiss());
}
}
}

@ -0,0 +1,59 @@
package com.king.app.dialog.fragment;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.DialogFragment;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.widget.TextView;
/**
* @author Jenly <a href="mailto:jenly1314@gmail.com">Jenly</a>
*/
public abstract class BaseDialogFragment extends DialogFragment {
private View mRootView;
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
super.getDialog().requestWindowFeature(Window.FEATURE_NO_TITLE);
super.getDialog().getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
mRootView = inflater.inflate(getRootLayoutId(), container, false);
init(mRootView);
return mRootView;
}
protected View getRootView(){
return mRootView;
}
protected void setText(TextView tv, CharSequence text){
if(!TextUtils.isEmpty(text)){
tv.setText(text);
}
}
protected View.OnClickListener getOnClickDismiss(){
return mOnClickDismissDialog;
}
private View.OnClickListener mOnClickDismissDialog = new View.OnClickListener() {
@Override
public void onClick(View v) {
dismiss();
}
};
public abstract int getRootLayoutId();
public abstract void init(View rootView);
}

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<scale android:duration="200"
android:pivotX="50%"
android:pivotY="50%"
android:fromXScale="0.0"
android:fromYScale="0.0"
android:toXScale="1.0"
android:toYScale="1.0"/>
</set>

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<scale android:duration="200"
android:pivotX="50%"
android:pivotY="50%"
android:fromXScale="1.0"
android:fromYScale="1.0"
android:toXScale="0.0"
android:toYScale="0.0"/>
</set>

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true" android:color="@color/app_dialog_button_pressed_color"/>
<item android:state_pressed="false" android:color="@color/app_dialog_button_normal_color"/>
</selector>

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" >
<solid android:color="@android:color/white" />
<corners
android:bottomLeftRadius="10dp"
android:bottomRightRadius="10dp"
android:topLeftRadius="10dp"
android:topRightRadius="10dp" />
</shape>

@ -0,0 +1,60 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_margin="30dp"
android:background="@drawable/app_dialog_bg"
android:orientation="vertical">
<TextView
android:id="@+id/tvDialogTitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:minHeight="40dp"
android:padding="6dp"
android:lines="1"
android:textSize="16sp"
android:textColor="@color/app_dialog_title_color"
android:text="@string/app_dialog_title"/>
<TextView
android:id="@+id/tvDialogContent"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="10dp"
android:layout_marginBottom="6dp"
android:lineSpacingMultiplier="1" />
<include layout="@layout/app_dialog_line_h"/>
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_horizontal">
<Button
android:id="@+id/btnDialogCancel"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:paddingTop="10dp"
android:paddingBottom="10dp"
android:layout_weight="1"
android:text="@string/app_dialog_cancel"
android:textSize="18sp"
android:textColor="@color/app_dialog_button_color_selector"
android:background="?android:attr/selectableItemBackground"/>
<include
android:id="@+id/line"
layout="@layout/app_dialog_line_v"/>
<Button
android:id="@+id/btnDialogOK"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:paddingTop="10dp"
android:paddingBottom="10dp"
android:layout_weight="1"
android:text="@string/app_dialog_ok"
android:textSize="18sp"
android:textColor="@color/app_dialog_button_color_selector"
android:background="?android:attr/selectableItemBackground"/>
</LinearLayout>
</LinearLayout>

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<View xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="@dimen/app_dialog_line"
android:background="@color/app_dialog_line_color"/>

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<View xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="@dimen/app_dialog_line"
android:layout_height="match_parent"
android:background="@color/app_dialog_line_color"/>

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="app_dialog_title_color">#333333</color>
<color name="app_dialog_content_color">#666666</color>
<color name="app_dialog_button_normal_color">#333333</color>
<color name="app_dialog_button_pressed_color">#FF4081</color>
<color name="app_dialog_line_color">#d2d2d2</color>
</resources>

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<dimen name="app_dialog_line">0.4dp</dimen>
</resources>

@ -0,0 +1,7 @@
<resources>
<string name="app_name">AppDialog</string>
<string name="app_dialog_title">提示</string>
<string name="app_dialog_cancel">取消</string>
<string name="app_dialog_ok">确认</string>
</resources>

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- dialog animation -->
<style name="app_dialog_animation" parent="android:style/Animation.Dialog">
<item name="android:windowEnterAnimation">@anim/app_dialog_in</item>
<item name="android:windowExitAnimation">@anim/app_dialog_out</item>
</style>
<!-- dialog style -->
<style name="app_dialog" parent="android:style/Theme.Dialog">
<item name="android:windowFrame">@null</item>
<item name="android:windowIsFloating">true</item>
<item name="android:windowIsTranslucent">true</item>
<item name="android:windowNoTitle">true</item>
<item name="android:windowBackground">@android:color/transparent</item>
<item name="android:backgroundDimEnabled">true</item>
<item name="android:windowAnimationStyle">@style/app_dialog_animation</item>
</style>
</resources>

@ -0,0 +1,17 @@
package com.king.app.dialog;
import org.junit.Test;
import static org.junit.Assert.*;
/**
* Example local unit test, which will execute on the development machine (host).
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
public class ExampleUnitTest {
@Test
public void addition_isCorrect() {
assertEquals(4, 2 + 2);
}
}

@ -0,0 +1 @@
/build

@ -0,0 +1,11 @@
apply plugin: 'com.novoda.bintray-release'
//
publish {
userOrg = 'jenly'//bintray.com用户名
groupId = 'com.king.app'//jcenter上的路径
artifactId = 'app-updater'//
publishVersion = '1.0'//
desc = 'AppUpdater for Android'//
website = 'https://github.com/jenly1314/AppUpdater'//
}

@ -0,0 +1,35 @@
apply plugin: 'com.android.library'
apply from: 'bintray.gradle'
android {
compileSdkVersion 27
defaultConfig {
minSdkVersion 15
targetSdkVersion 27
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
compileOnly 'com.android.support:appcompat-v7:27.1.1'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
}

@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# 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 *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile

@ -0,0 +1,26 @@
package com.king.app.updater;
import android.content.Context;
import android.support.test.InstrumentationRegistry;
import android.support.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
import static org.junit.Assert.*;
/**
* Instrumented test, which will execute on an Android device.
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
@RunWith(AndroidJUnit4.class)
public class ExampleInstrumentedTest {
@Test
public void useAppContext() {
// Context of the app under test.
Context appContext = InstrumentationRegistry.getTargetContext();
assertEquals("com.king.app.updater.test", appContext.getPackageName());
}
}

@ -0,0 +1,27 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.king.app.updater">
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
<application>
<service android:name=".service.DownloadService"/>
<provider
android:name=".provider.AppUpdaterFileProvider"
android:authorities="${applicationId}.fileProvider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/app_updater_paths"/>
</provider>
</application>
</manifest>

@ -0,0 +1,239 @@
package com.king.app.updater;
import android.app.Activity;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.IBinder;
import android.support.annotation.DrawableRes;
import android.support.annotation.NonNull;
import android.text.TextUtils;
import com.king.app.updater.callback.UpdateCallback;
import com.king.app.updater.constant.Constants;
import com.king.app.updater.http.IHttpManager;
import com.king.app.updater.service.DownloadService;
import com.king.app.updater.util.PermissionUtils;
/**
* @author Jenly <a href="mailto:jenly1314@gmail.com">Jenly</a>
*/
public class AppUpdater {
private Context mContext;
private UpdateConfig mConfig;
private UpdateCallback mCallback;
private IHttpManager mHttpManager;
private ServiceConnection mServiceConnection;
public AppUpdater(@NonNull Context context,@NonNull UpdateConfig config){
this.mContext = context;
this.mConfig = config;
}
public AppUpdater(@NonNull Context context,@NonNull String url){
this.mContext = context;
mConfig = new UpdateConfig();
mConfig.setUrl(url);
}
public AppUpdater setUpdateCallback(UpdateCallback callback){
this.mCallback = callback;
return this;
}
public AppUpdater setHttpManager(IHttpManager httpManager){
this.mHttpManager = httpManager;
return this;
}
/**
* 开始下载
*/
public void start(){
if(mConfig!=null && !TextUtils.isEmpty(mConfig.getUrl())){
//mContext不强制要求是Activity,能传Activity尽量传。AppUpdater本应该只专注于App更新,尽量不涉及动态权限相关的处理。如果mContext不强制要求是Activity是Activity默认会优先校验一次动态权限。
if(mContext instanceof Activity){
PermissionUtils.INSTANCE.verifyReadAndWritePermissions((Activity) mContext,Constants.RE_CODE_STORAGE_PERMISSION);
}
startDownloadService();
}else{
throw new NullPointerException("url = null");
}
}
/**
* 启动下载服务
*/
private void startDownloadService(){
Intent intent = new Intent(mContext, DownloadService.class);
if(mCallback!=null || mHttpManager!=null){//bindService
mServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
DownloadService.DownloadBinder binder = ((DownloadService.DownloadBinder)service);
binder.start(mConfig,mHttpManager,mCallback);
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
mContext.getApplicationContext().bindService(intent,mServiceConnection, Context.BIND_AUTO_CREATE);
}else{//startService
intent.putExtra(Constants.KEY_UPDATE_CONFIG,mConfig);
mContext.startService(intent);
}
}
/**
* AppUpdater构建器
*/
public static class Builder{
private UpdateConfig mConfig;
public Builder(){
mConfig = new UpdateConfig();
}
/**
*
* @param url 下载地址
* @return
*/
public Builder serUrl(@NonNull String url){
mConfig.setUrl(url);
return this;
}
/**
*
* @param path 下载保存的文件路径 默认SD卡/.AppUpdater目录
* @return
*/
public Builder setPath(String path){
mConfig.setPath(path);
return this;
}
/**
*
* @param filename 下载的保存的apk文件名 默认优先取url文件名
* @return
*/
public Builder setFilename(String filename){
mConfig.setFilename(filename);
return this;
}
/**
*
* @param isShowNotification 是否显示通知栏 默认true
* @return
*/
public Builder setShowNotification(boolean isShowNotification) {
mConfig.setShowNotification(isShowNotification);
return this;
}
/**
*
* @param notifyId 通知ID
* @return
*/
public Builder setNotificationId(int notifyId) {
mConfig.setNotificationId(notifyId);
return this;
}
/**
*
* @param channelId 通知渠道ID 默认兼容O
* @return
*/
public Builder setChannelId(String channelId) {
mConfig.setChannelId(channelId);
return this;
}
/**
*
* @param channelName 通知渠道名称 默认兼容O
* @return
*/
public Builder setChannelName(String channelName) {
mConfig.setChannelName(channelName);
return this;
}
/**
*
* @param icon 通知栏图标 默认取App的icon
* @return
*/
public Builder setNotificationIcon(@DrawableRes int icon) {
mConfig.setNotificationIcon(icon);
return this;
}
/**
*
* @param isInstallApk 下载完成后是否自动调用安装APK默认true
* @return
*/
public Builder setInstallApk(boolean isInstallApk){
mConfig.setInstallApk(isInstallApk);
return this;
}
/**
*
* @param authority FileProvider的authority默认兼容N默认值context.getPackageName() + ".fileProvider"
* @return
*/
public Builder setAuthority(String authority){
mConfig.setAuthority(authority);
return this;
}
/**
*
* @param showPercentage 下载时通知栏是否显示百分比
* @return
*/
public Builder setShowPercentage(boolean showPercentage) {
mConfig.setShowPercentage(showPercentage);
return this;
}
/**
*
* @param reDownload 下载失败时是否支持点击通知栏重新下载默认true 最多重新下载3次
* @return
*/
public Builder setReDownload(boolean reDownload) {
mConfig.setReDownload(reDownload);
return this;
}
public AppUpdater build(@NonNull Context context){
AppUpdater appUpdater = new AppUpdater(context,mConfig);
return appUpdater;
}
}
}

@ -0,0 +1,218 @@
package com.king.app.updater;
import android.os.Parcel;
import android.os.Parcelable;
import android.support.annotation.DrawableRes;
import com.king.app.updater.constant.Constants;
/**
* @author Jenly <a href="mailto:jenly1314@gmail.com">Jenly</a>
*/
public class UpdateConfig implements Parcelable {
private String mUrl;
/**
* 保存路径
*/
private String mPath;
/**
* 保存文件名
*/
private String mFilename;
private boolean isShowNotification = true;
/**
* 下载完成后是否自动弹出安装
*/
private boolean isInstallApk = true;
private int mNotificationIcon;
private int mNotificationId = Constants.DEFAULT_NOTIFICATION_ID;
private String mChannelId;
private String mChannelName;
private String mAuthority;
/**
* 下载失败是否支持点击通知栏重复下载
*/
private boolean isReDownload = true;
/**
* 是否显示百分比
*/
private boolean isShowPercentage = true;
public UpdateConfig() {
}
public String getUrl() {
return mUrl;
}
public void setUrl(String url) {
this.mUrl = url;
}
public String getPath() {
return mPath;
}
public void setPath(String path) {
this.mPath = path;
}
public String getFilename() {
return mFilename;
}
public void setFilename(String filename) {
this.mFilename = filename;
}
public boolean isShowNotification() {
return isShowNotification;
}
public void setShowNotification(boolean isShowNotification) {
this.isShowNotification = isShowNotification;
}
public String getChannelId() {
return mChannelId;
}
public void setChannelId(String channelId) {
this.mChannelId = channelId;
}
public String getChannelName() {
return mChannelName;
}
public void setChannelName(String channelName) {
this.mChannelName = channelName;
}
public void setNotificationId(int notificationId){
this.mNotificationId = notificationId;
}
public int getNotificationId(){
return this.mNotificationId;
}
public void setNotificationIcon(@DrawableRes int icon){
this.mNotificationIcon = icon;
}
public int getNotificationIcon(){
return this.mNotificationIcon;
}
public boolean isInstallApk() {
return isInstallApk;
}
public void setInstallApk(boolean isInstallApk) {
this.isInstallApk = isInstallApk;
}
public String getAuthority() {
return mAuthority;
}
public void setAuthority(String authority) {
this.mAuthority = authority;
}
public boolean isShowPercentage() {
return isShowPercentage;
}
public void setShowPercentage(boolean showPercentage) {
isShowPercentage = showPercentage;
}
public boolean isReDownload() {
return isReDownload;
}
public void setReDownload(boolean reDownload) {
isReDownload = reDownload;
}
@Override
public String toString() {
return "UpdateConfig{" +
"mUrl='" + mUrl + '\'' +
", mPath='" + mPath + '\'' +
", mFilename='" + mFilename + '\'' +
", isShowNotification=" + isShowNotification +
", isInstallApk=" + isInstallApk +
", mNotificationIcon=" + mNotificationIcon +
", mNotificationId=" + mNotificationId +
", mChannelId='" + mChannelId + '\'' +
", mChannelName='" + mChannelName + '\'' +
", mAuthority='" + mAuthority + '\'' +
", isShowPercentage=" + isShowPercentage +
", isReDownload=" + isReDownload +
'}';
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(this.mUrl);
dest.writeString(this.mPath);
dest.writeString(this.mFilename);
dest.writeByte(this.isShowNotification ? (byte) 1 : (byte) 0);
dest.writeByte(this.isInstallApk ? (byte) 1 : (byte) 0);
dest.writeInt(this.mNotificationIcon);
dest.writeInt(this.mNotificationId);
dest.writeString(this.mChannelId);
dest.writeString(this.mChannelName);
dest.writeString(this.mAuthority);
dest.writeByte(this.isReDownload ? (byte) 1 : (byte) 0);
dest.writeByte(this.isShowPercentage ? (byte) 1 : (byte) 0);
}
protected UpdateConfig(Parcel in) {
this.mUrl = in.readString();
this.mPath = in.readString();
this.mFilename = in.readString();
this.isShowNotification = in.readByte() != 0;
this.isInstallApk = in.readByte() != 0;
this.mNotificationIcon = in.readInt();
this.mNotificationId = in.readInt();
this.mChannelId = in.readString();
this.mChannelName = in.readString();
this.mAuthority = in.readString();
this.isReDownload = in.readByte() != 0;
this.isShowPercentage = in.readByte() != 0;
}
public static final Creator<UpdateConfig> CREATOR = new Creator<UpdateConfig>() {
@Override
public UpdateConfig createFromParcel(Parcel source) {
return new UpdateConfig(source);
}
@Override
public UpdateConfig[] newArray(int size) {
return new UpdateConfig[size];
}
};
}

@ -0,0 +1,26 @@
package com.king.app.updater.callback;
/**
* @author Jenly <a href="mailto:jenly1314@gmail.com">Jenly</a>
*/
public abstract class AppUpdateCallback implements UpdateCallback {
@Override
public void onDownloading(boolean isDownloading) {
}
@Override
public void onStart(String url) {
}
@Override
public void onError(Exception e) {
}
@Override
public void onCancel() {
}
}

@ -0,0 +1,46 @@
package com.king.app.updater.callback;
import java.io.File;
/**
* @author Jenly <a href="mailto:jenly1314@gmail.com">Jenly</a>
*/
public interface UpdateCallback {
/**
* 最开始调用(在onStart之前调用)
* @param isDownloading true 表示已经在下载false表示准备刚调用下载
*/
void onDownloading(boolean isDownloading);
/**
* 开始
*/
void onStart(String url);
/**
* 加载进度
* @param progress
* @param total
* @param isChange 进度百分比是否有改变主要可以用来过滤无用的刷新从而降低刷新频率
*/
void onProgress(int progress,int total,boolean isChange);
/**
* 完成
* @param file
*/
void onFinish(File file);
/**
* 错误
* @param e
*/
void onError(Exception e);
/**
* 取消
*/
void onCancel();
}

@ -0,0 +1,31 @@
package com.king.app.updater.constant;
import android.os.Environment;
import java.io.File;
/**
* @author Jenly <a href="mailto:jenly1314@gmail.com">Jenly</a>
*/
public final class Constants {
public static final String TAG = "AppUpdater";
public static final String KEY_UPDATE_CONFIG = "app_update_config";
public static final int DEFAULT_NOTIFICATION_ID = 0x66;
public static final String DEFAULT_NOTIFICATION_CHANNEL_ID = "0x66";
public static final String DEFAULT_NOTIFICATION_CHANNEL_NAME = "AppUpdater";
public static final String KEY_STOP_DOWNLOAD_SERVICE = "stop_download_service";
public static final String KEY_RE_DOWNLOAD = "app_update_re_download";
public static final String DEFAULT_DIR_PATH = Environment.getExternalStorageDirectory() + File.separator + ".AppUpdater";
public static final int RE_CODE_STORAGE_PERMISSION = 0x66;
public static final int NONE = -1;
}

@ -0,0 +1,166 @@
package com.king.app.updater.http;
import android.os.AsyncTask;
import com.king.app.updater.util.SSLSocketFactoryUtils;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.net.ConnectException;
import java.net.HttpURLConnection;
import java.net.URL;
import javax.net.ssl.HttpsURLConnection;
/**
* @author Jenly <a href="mailto:jenly1314@gmail.com">Jenly</a>
*/
public class HttpManager implements IHttpManager {
private static final int DEFAULT_TIME_OUT = 20000;
private int mTimeout = DEFAULT_TIME_OUT;
private static HttpManager INSTANCE;
public static HttpManager getInstance(){
if(INSTANCE == null){
synchronized (HttpManager.class){
INSTANCE = new HttpManager();
}
}
return INSTANCE;
}
private HttpManager(){
this(DEFAULT_TIME_OUT);
}
public HttpManager(int timeout){
this.mTimeout = timeout;
}
@Override
public void download(String url, String path, String filename, DownloadCallback callback) {
new DownloadTask(url,path,filename,callback).execute();
}
/**
* 异步下载任务
*/
private class DownloadTask extends AsyncTask<Void,Integer,File> {
private String url;
private String path;
private String filename;
private DownloadCallback callback;
private Exception exception;
public DownloadTask(String url,String path,String filename,DownloadCallback callback){
this.url = url;
this.path = path;
this.filename = filename;
this.callback = callback;
}
@Override
protected File doInBackground(Void... voids) {
try {
HttpsURLConnection.setDefaultSSLSocketFactory(SSLSocketFactoryUtils.createSSLSocketFactory());
HttpsURLConnection.setDefaultHostnameVerifier(SSLSocketFactoryUtils.createTrustAllHostnameVerifier());
HttpURLConnection connect = (HttpURLConnection)new URL(url).openConnection();
connect.setRequestMethod("GET");
connect.setReadTimeout(mTimeout);
connect.setConnectTimeout(mTimeout);
connect.connect();
int responseCode = connect.getResponseCode();
if(responseCode == HttpURLConnection.HTTP_OK){
InputStream is = connect.getInputStream();
int length = connect.getContentLength();
int progress = 0;
byte[] buffer = new byte[4096];
int len;
File file = new File(path,filename);
FileOutputStream fos = new FileOutputStream(file);
while ((len = is.read(buffer)) != -1){
fos.write(buffer,0,len);
progress += len;
//更新进度
publishProgress(progress,length);
}
fos.flush();
fos.close();
is.close();
connect.disconnect();
return file;
}else {//连接失败
throw new ConnectException(String.format("responseCode = %d",responseCode));
}
} catch (Exception e) {
this.exception = e;
e.printStackTrace();
}
return null;
}
@Override
protected void onPreExecute() {
super.onPreExecute();
if(callback!=null){
callback.onStart(url);
}
}
@Override
protected void onPostExecute(File file) {
super.onPostExecute(file);
if(callback!=null){
if(file!=null){
callback.onFinish(file);
}else{
callback.onError(exception);
}
}
}
@Override
protected void onProgressUpdate(Integer... values) {
super.onProgressUpdate(values);
if(callback!=null){
callback.onProgress(values[0],values[1]);
}
}
@Override
protected void onCancelled() {
super.onCancelled();
if(callback!=null){
callback.onCancel();
}
}
}
}

@ -0,0 +1,54 @@
package com.king.app.updater.http;
import java.io.File;
import java.io.Serializable;
/**
* @author Jenly <a href="mailto:jenly1314@gmail.com">Jenly</a>
*/
public interface IHttpManager {
/**
* 下载
* @param url
* @param path
* @param filename
* @param callback
*/
void download(String url,String path,String filename,DownloadCallback callback);
interface DownloadCallback extends Serializable{
/**
* 开始
* @param url
*/
void onStart(String url);
/**
* 加载进度
* @param progress
* @param total
*/
void onProgress(int progress,int total);
/**
* 完成
* @param file
*/
void onFinish(File file);
/**
* 错误
* @param e
*/
void onError(Exception e);
/**
* 取消
*/
void onCancel();
}
}

@ -0,0 +1,10 @@
package com.king.app.updater.provider;
import android.support.v4.content.FileProvider;
/**
* @author Jenly <a href="mailto:jenly1314@gmail.com">Jenly</a>
*/
public class AppUpdaterFileProvider extends FileProvider {
}

@ -0,0 +1,527 @@
package com.king.app.updater.service;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Binder;
import android.os.Build;
import android.os.Environment;
import android.os.IBinder;
import android.support.annotation.DrawableRes;
import android.support.annotation.Nullable;
import android.support.annotation.RequiresApi;
import android.support.v4.app.NotificationCompat;
import android.support.v4.content.FileProvider;
import android.text.TextUtils;
import android.util.Log;
import com.king.app.updater.R;
import com.king.app.updater.UpdateConfig;
import com.king.app.updater.callback.UpdateCallback;
import com.king.app.updater.constant.Constants;
import com.king.app.updater.http.HttpManager;
import com.king.app.updater.http.IHttpManager;
import com.king.app.updater.util.AppUtils;
import java.io.File;
/**
* @author Jenly <a href="mailto:jenly1314@gmail.com">Jenly</a>
*/
public class DownloadService extends Service {
private DownloadBinder mDownloadBinder = new DownloadBinder();
/**
* 是否在下载防止重复下载
*/
private boolean isDownloading;
/**
* 最后更新进度用来降频刷新
*/
private int mLastProgress = -1;
/**
* 最后进度更新时间用来降频刷新
*/
private long mLastTime;
/**
* 失败后重新下载次数
*/
private int mCount = 0;
private Context getContext(){
return this;
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
if(intent != null){
if(!isDownloading){
boolean isStop = intent.getBooleanExtra(Constants.KEY_STOP_DOWNLOAD_SERVICE,false);
if(isStop){
stopService();
}else{
//是否实通过通知栏触发重复下载
boolean isReDownload = intent.getBooleanExtra(Constants.KEY_RE_DOWNLOAD,false);
if(isReDownload){
mCount++;
}
//获取配置信息
UpdateConfig config = intent.getParcelableExtra(Constants.KEY_UPDATE_CONFIG);
startDownload(config,null,null);
}
}else{
Log.w(Constants.TAG,"已经在下载中,请勿重复下载。");
}
}
return super.onStartCommand(intent, flags, startId);
}
//----------------------------------------
/**
* 开始下载
* @param config
* @param httpManager
* @param callback
*/
public void startDownload(UpdateConfig config,IHttpManager httpManager,UpdateCallback callback){
if(config == null){
return;
}
if(callback!=null){
callback.onDownloading(isDownloading);
}
if(isDownloading){
Log.w(Constants.TAG,"已经在下载中,请勿重复下载。");
return;
}
String url = config.getUrl();
String path = config.getPath();
String filename = config.getFilename();
//如果保存路径为空则使用缓存路径
if(TextUtils.isEmpty(path)){
path = getDiskCacheDir(getContext());
}
File dirFile = new File(path);
if(!dirFile.exists()){
dirFile.mkdir();
}
//如果文件名为空则使用路径
if(TextUtils.isEmpty(filename)){
filename = AppUtils.INSTANCE.getAppFullName(getContext(),url,getResources().getString(R.string.app_name));
}
File file = new File(path,filename);
if(file.exists()){
file.delete();
}
if(httpManager != null){
httpManager.download(url,path,filename,new AppDownloadCallback(config,callback));
}else{
HttpManager.getInstance().download(url,path,filename,new AppDownloadCallback(config,callback));
}
}
/**
* 获取缓存路径
* @param context
* @return
*/
public String getDiskCacheDir(Context context) {
if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) {
return Constants.DEFAULT_DIR_PATH;
}
return context.getCacheDir().getAbsolutePath();
}
private void stopService(){
mCount = 0;
stopSelf();
}
//---------------------------------------- DownloadCallback
public class AppDownloadCallback implements IHttpManager.DownloadCallback{
public UpdateConfig config;
private boolean isShowNotification;
private int notifyId;
private String channelId;
private String channelName;
private int notificationIcon;
private boolean isInstallApk;
private String authority;
private boolean isShowPercentage;
private boolean isReDownload;
private UpdateCallback callback;
private AppDownloadCallback(UpdateConfig config,UpdateCallback callback){
this.config = config;
this.callback = callback;
this.isShowNotification = config.isShowNotification();
this.notifyId = config.getNotificationId();
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O){
this.channelId = TextUtils.isEmpty(config.getChannelId()) ? Constants.DEFAULT_NOTIFICATION_CHANNEL_ID : config.getChannelId();
this.channelName = TextUtils.isEmpty(config.getChannelName()) ? Constants.DEFAULT_NOTIFICATION_CHANNEL_NAME : config.getChannelName();
}
if(config.getNotificationIcon()<=0){
this.notificationIcon = AppUtils.INSTANCE.getAppIcon(getContext());
}else{
this.notificationIcon = config.getNotificationIcon();
}
this.isInstallApk = config.isInstallApk();
if(TextUtils.isEmpty(config.getAuthority())){//如果为空默认
authority = getContext().getPackageName() + ".fileProvider";
}else{
this.authority = config.getAuthority();
}
this.isShowPercentage = config.isShowPercentage();
this.isReDownload = config.isReDownload();
}
@Override
public void onStart(String url) {
isDownloading = true;
mLastProgress = 0;
if(isShowNotification){
showStartNotification(notifyId,channelId,channelName,notificationIcon,getString(R.string.app_updater_start_notification_title),getString(R.string.app_updater_start_notification_content));
}
if(callback!=null){
callback.onStart(url);
}
}
@Override
public void onProgress(int progress, int total) {
boolean isChange = false;
long curTime = System.currentTimeMillis();
if(mLastTime + 200 < curTime) {//降低更新频率
mLastTime = curTime;
int currProgress = Math.round(progress * 1.0f / total * 100.0f);
if(currProgress!=mLastProgress){//百分比改变了才更新
isChange = true;
String percentage = currProgress + "%";
if(isShowNotification) {
mLastProgress = currProgress;
String content = getString(R.string.app_updater_progress_notification_content);
if (isShowPercentage) {
content += percentage;
}
showProgressNotification(notifyId, channelId, notificationIcon, getString(R.string.app_updater_progress_notification_title), content, progress, total);
}
}
}
if(callback!=null){
callback.onProgress(progress,total,isChange);
}
}
@Override
public void onFinish(File file) {
isDownloading = false;
showFinishNotification(notifyId,channelId,notificationIcon,getString(R.string.app_updater_finish_notification_title),getString(R.string.app_updater_finish_notification_content),file,authority);
if(isInstallApk){
AppUtils.INSTANCE.installApk(getContext(),file,authority);
}
if(callback!=null){
callback.onFinish(file);
}
stopService();
}
@Override
public void onError(Exception e) {
isDownloading = false;
//支持下载失败重新并最多支持失败下载3次
boolean isReDownload = this.isReDownload && mCount < 3;
String content = isReDownload ? getString(R.string.app_updater_error_notification_content_re_download) : getString(R.string.app_updater_error_notification_content);
showErrorNotification(notifyId,channelId,notificationIcon,getString(R.string.app_updater_error_notification_title),content,isReDownload,config);
if(callback!=null){
callback.onError(e);
}
if(!isReDownload){
stopService();
}
}
@Override
public void onCancel() {
isDownloading = false;
cancelNotification(notifyId);
if(callback!=null){
callback.onCancel();
}
stopService();
}
}
@Override
public boolean onUnbind(Intent intent) {
Log.d(Constants.TAG,"onUnbind");
return super.onUnbind(intent);
}
@Override
public void onDestroy() {
isDownloading = false;
Log.d(Constants.TAG,"onDestroy");
super.onDestroy();
}
//---------------------------------------- Notification
/**
* 显示开始下载是的通知
* @param notifyId
* @param channelId
* @param channelName
* @param icon
* @param title
* @param content
*/
private void showStartNotification(int notifyId,String channelId, String channelName,@DrawableRes int icon,CharSequence title,CharSequence content){
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O){
createNotificationChannel(channelId,channelName);
}
NotificationCompat.Builder builder = buildNotification(channelId,icon,title,content);
builder.setAutoCancel(false);
notifyNotification(notifyId,builder.build());
}
/**
* 显示下载中的通知更新进度
* @param notifyId
* @param channelId
* @param icon
* @param title
* @param content
* @param progress
* @param size
*/
private void showProgressNotification(int notifyId,String channelId,@DrawableRes int icon,CharSequence title,CharSequence content,int progress,int size){
NotificationCompat.Builder builder = buildNotification(channelId,icon,title,content,progress,size);
builder.setAutoCancel(false);
Notification notification = builder.build();
notification.flags = Notification.FLAG_AUTO_CANCEL | Notification.FLAG_ONLY_ALERT_ONCE;
notifyNotification(notifyId,notification);
}
/**
* 显示下载完成时的通知点击安装
* @param notifyId
* @param channelId
* @param icon
* @param title
* @param content
* @param file
*/
private void showFinishNotification(int notifyId,String channelId,@DrawableRes int icon,CharSequence title,CharSequence content,File file,String authority){
cancelNotification(notifyId);
NotificationCompat.Builder builder = buildNotification(channelId,icon,title,content);
builder.setAutoCancel(true);
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.addCategory(Intent.CATEGORY_DEFAULT);
Uri uriData;
String type = "application/vnd.android.package-archive";
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.N){
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
uriData = FileProvider.getUriForFile(getContext(), authority, file);
}else{
uriData = Uri.fromFile(file);
}
intent.setDataAndType(uriData, type);
PendingIntent clickIntent = PendingIntent.getActivity(getContext(), notifyId,intent, PendingIntent.FLAG_UPDATE_CURRENT);
builder.setContentIntent(clickIntent);
Notification notification = builder.build();
notification.flags = Notification.FLAG_AUTO_CANCEL;
notifyNotification(notifyId,notification);
}
private void showErrorNotification(int notifyId,String channelId,@DrawableRes int icon,CharSequence title,CharSequence content,boolean isReDownload,UpdateConfig config){
NotificationCompat.Builder builder = buildNotification(channelId,icon,title,content);
builder.setAutoCancel(true);
if(isReDownload){//重新下载
Intent intent = new Intent(getContext(),DownloadService.class);
intent.putExtra(Constants.KEY_RE_DOWNLOAD,true);
intent.putExtra(Constants.KEY_UPDATE_CONFIG,config);
PendingIntent clickIntent = PendingIntent.getService(getContext(), notifyId,intent, PendingIntent.FLAG_UPDATE_CURRENT);
builder.setContentIntent(clickIntent);
}else{
PendingIntent clickIntent = PendingIntent.getService(getContext(), notifyId,new Intent(), PendingIntent.FLAG_UPDATE_CURRENT);
builder.setContentIntent(clickIntent);
}
Notification notification = builder.build();
notification.flags = Notification.FLAG_AUTO_CANCEL;
notifyNotification(notifyId,notification);
}
/**
* 显示通知信息非第一次
* @param notifyId
* @param channelId
* @param icon
* @param title
* @param content
*/
private void showNotification(int notifyId,String channelId,@DrawableRes int icon,CharSequence title,CharSequence content,boolean isAutoCancel){
NotificationCompat.Builder builder = buildNotification(channelId,icon,title,content);
builder.setAutoCancel(isAutoCancel);
Notification notification = builder.build();
notification.flags = Notification.FLAG_AUTO_CANCEL;
notifyNotification(notifyId,notification);
}
/**
*
* @param notifyId
*/
private void cancelNotification(int notifyId){
getNotificationManager().cancel(notifyId);
}
/**
* 获取通知管理器
* @return
*/
private NotificationManager getNotificationManager(){
return (NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE);
}
/**
* 创建一个通知渠道兼容0以上版本
* @param channelId
* @param channelName
*/
@RequiresApi(api = Build.VERSION_CODES.O)
private void createNotificationChannel(String channelId, String channelName){
NotificationChannel channel = new NotificationChannel(channelId,channelName, NotificationManager.IMPORTANCE_DEFAULT);
getNotificationManager().createNotificationChannel(channel);
}
/**
* 构建一个通知构建器
* @param channelId
* @param icon
* @param title
* @param content
* @return
*/
private NotificationCompat.Builder buildNotification(String channelId, @DrawableRes int icon,CharSequence title,CharSequence content){
return buildNotification(channelId,icon,title,content,Constants.NONE,Constants.NONE);
}
/**
* 构建一个通知构建器
* @param channelId
* @param icon
* @param title
* @param content
* @param progress
* @param size
* @return
*/
private NotificationCompat.Builder buildNotification(String channelId,@DrawableRes int icon,CharSequence title,CharSequence content,int progress,int size){
NotificationCompat.Builder builder = new NotificationCompat.Builder(getContext(),channelId);
builder.setSmallIcon(icon);
builder.setContentTitle(title);
builder.setContentText(content);
builder.setOngoing(true);
if(progress!= Constants.NONE && size!=Constants.NONE){
builder.setProgress(size,progress,false);
}
return builder;
}
/**
* 更新通知栏
* @param id
* @param notification
*/
private void notifyNotification(int id, Notification notification){
getNotificationManager().notify(id,notification);
}
//---------------------------------------- Binder
@Nullable
@Override
public IBinder onBind(Intent intent) {
return mDownloadBinder;
}
/**
* 提供绑定服务的方式下载
*/
public class DownloadBinder extends Binder {
public void start(UpdateConfig config){
start(config,null,null);
}
public void start(UpdateConfig config,UpdateCallback callback){
start(config,null,callback);
}
public void start(UpdateConfig config,IHttpManager httpManager,UpdateCallback callback){
startDownload(config,httpManager,callback);
}
}
}

@ -0,0 +1,98 @@
package com.king.app.updater.util;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Build;
import android.support.v4.content.FileProvider;
import android.text.TextUtils;
import java.io.File;
/**
* @author Jenly <a href="mailto:jenly1314@gmail.com">Jenly</a>
*/
public enum AppUtils {
INSTANCE;
/**
*
* @param context
* @return AppName.apk
*/
public String getAppFullName(Context context,String url,String defaultName){
if(url.endsWith(".apk")){
return url.substring(url.lastIndexOf("/") + 1);
}
String filename = getAppName(context);
if(TextUtils.isEmpty(filename)){
filename = defaultName;
}
return String.format("%s.apk",filename);
}
public PackageInfo getPackageInfo(Context context) throws PackageManager.NameNotFoundException {
PackageManager packageManager = context.getPackageManager();
PackageInfo packageInfo = packageManager.getPackageInfo( context.getPackageName(), 0);
return packageInfo;
}
/**
* 获取App的名称
*/
public String getAppName(Context context) {
try{
int labelRes = getPackageInfo(context).applicationInfo.labelRes;
return context.getResources().getString(labelRes);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 获取App的图标
* @param context
* @return
*/
public int getAppIcon(Context context){
try{
return getPackageInfo(context).applicationInfo.icon;
} catch (Exception e) {
e.printStackTrace();
}
return 0;
}
/**
* 安装apk
* @param context
* @param file
*/
public void installApk(Context context,File file,String authority){
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.addCategory(Intent.CATEGORY_DEFAULT);
Uri uriData = null;
String type = "application/vnd.android.package-archive";
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.N){
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
uriData = FileProvider.getUriForFile(context, authority, file);
}else{
uriData = Uri.fromFile(file);
}
intent.setDataAndType(uriData, type);
context.startActivity(intent);
}
}

@ -0,0 +1,36 @@
package com.king.app.updater.util;
import android.Manifest;
import android.app.Activity;
import android.content.pm.PackageManager;
import android.support.annotation.NonNull;
import android.support.v4.app.ActivityCompat;
/**
* @author Jenly <a href="mailto:jenly1314@gmail.com">Jenly</a>
*/
public enum PermissionUtils {
INSTANCE;
private String[] PERMISSIONS_STORAGE = {
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.WRITE_EXTERNAL_STORAGE,
Manifest.permission.READ_PHONE_STATE};
public boolean verifyReadAndWritePermissions(@NonNull Activity activity,int requestCode){
int readResult = checkPermission(activity,Manifest.permission.READ_EXTERNAL_STORAGE);
int writeResult = checkPermission(activity,Manifest.permission.WRITE_EXTERNAL_STORAGE);
int readPhoneState = checkPermission(activity,Manifest.permission.READ_PHONE_STATE);
if( readResult != PackageManager.PERMISSION_GRANTED || writeResult != PackageManager.PERMISSION_GRANTED || readPhoneState != PackageManager.PERMISSION_GRANTED){
ActivityCompat.requestPermissions(activity,PERMISSIONS_STORAGE,requestCode);
return false;
}
return true;
}
public int checkPermission(@NonNull Activity activity,@NonNull String permission){
return ActivityCompat.checkSelfPermission(activity,permission);
}
}

@ -0,0 +1,175 @@
package com.king.app.updater.util;
import android.content.Context;
import android.support.annotation.RawRes;
import java.io.InputStream;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;
/**
* @author Jenly <a href="mailto:jenly1314@gmail.com">Jenly</a>
*/
public class SSLSocketFactoryUtils {
private SSLSocketFactoryUtils(){
}
public static SSLSocketFactory createSSLSocketFactory() {
SSLSocketFactory sslSocketFactory = null;
try {
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, new TrustManager[]{createTrustAllManager()}, new SecureRandom());
sslSocketFactory = sslContext.getSocketFactory();
} catch (Exception e) {
}
return sslSocketFactory;
}
public static X509TrustManager createTrustAllManager() {
X509TrustManager tm = null;
try {
tm = new X509TrustManager() {
public void checkClientTrusted(X509Certificate[] chain, String authType)
throws CertificateException {
//do nothing
}
public void checkServerTrusted(X509Certificate[] chain, String authType)
throws CertificateException {
//do nothing
}
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}
};
} catch (Exception e) {
e.printStackTrace();
}
return tm;
}
public static TrustAllHostnameVerifier createTrustAllHostnameVerifier(){
return new TrustAllHostnameVerifier();
}
public static class TrustAllHostnameVerifier implements HostnameVerifier {
@Override
public boolean verify(String hostname, SSLSession session) {
return true;
}
}
/**
*
* @param context
* @param keyServerStoreID
* @return
*/
public static SSLSocketFactory createSSLSocketFactory(Context context,@RawRes int keyServerStoreID){
InputStream trustStream = context.getResources().openRawResource(keyServerStoreID);
return createSSLSocketFactory(trustStream);
}
/**
*
* @param certificates
* @return
*/
public static SSLSocketFactory createSSLSocketFactory(InputStream... certificates) {
SSLSocketFactory mSSLSocketFactory = null;
if(mSSLSocketFactory==null){
synchronized (SSLSocketFactoryUtils.class) {
if(mSSLSocketFactory==null){
SSLContext sslContext = null;
try {
sslContext = SSLContext.getInstance("TLS");
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
return null;
}
//获得服务器端证书
TrustManager[] turstManager = getTurstManager(certificates);
//初始化ssl证书库
try {
sslContext.init(null,turstManager,new SecureRandom());
} catch (KeyManagementException e) {
e.printStackTrace();
}
//获得sslSocketFactory
mSSLSocketFactory = sslContext.getSocketFactory();
}
}
}
return mSSLSocketFactory;
}
/**
* 获得指定流中的服务器端证书库
* @param certificates
* @return
*/
public static TrustManager[] getTurstManager(InputStream... certificates) {
try {
CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(null,null);
int index = 0;
for (InputStream certificate : certificates) {
if (certificate == null) {
continue;
}
Certificate certificate1;
try {
certificate1 = certificateFactory.generateCertificate(certificate);
}finally {
certificate.close();
}
String certificateAlias = Integer.toString(index++);
keyStore.setCertificateEntry(certificateAlias,certificate1);
}
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory
.getDefaultAlgorithm());
trustManagerFactory.init(keyStore);
return trustManagerFactory.getTrustManagers();
} catch (Exception e) {
e.printStackTrace();
}
return getTurstAllManager();
}
/**
* 获得信任所有服务器端证书库
* */
public static TrustManager[] getTurstAllManager() {
return new X509TrustManager[] {createTrustAllManager()};
}
}

@ -0,0 +1,18 @@
<resources>
<string name="app_name">AppUpdater</string>
<string name="app_updater_start_notification_title">版本更新</string>
<string name="app_updater_start_notification_content">正在获取下载数据…</string>
<string name="app_updater_progress_notification_title">版本更新</string>
<string name="app_updater_progress_notification_content">下载更新中…</string>
<string name="app_updater_finish_notification_title">下载完成</string>
<string name="app_updater_finish_notification_content">点击安装</string>
<string name="app_updater_error_notification_title">下载失败</string>
<string name="app_updater_error_notification_content">点击关闭通知</string>
<string name="app_updater_error_notification_content_re_download">点击重新下载</string>
</resources>

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<paths>
<external-path name="root_path" path="."/>
<external-path name="app_updater_path" path=".AppUpdater"/>
<cache-path name="data_path" path="data/data"/>
</paths>

@ -0,0 +1,17 @@
package com.king.app.updater;
import org.junit.Test;
import static org.junit.Assert.*;
/**
* Example local unit test, which will execute on the development machine (host).
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
public class ExampleUnitTest {
@Test
public void addition_isCorrect() {
assertEquals(4, 2 + 2);
}
}

1
app/.gitignore vendored

@ -0,0 +1 @@
/build

@ -0,0 +1,30 @@
apply plugin: 'com.android.application'
android {
compileSdkVersion 27
defaultConfig {
applicationId "com.king.appupdater"
minSdkVersion 15
targetSdkVersion 27
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
implementation 'com.android.support:appcompat-v7:27.1.1'
implementation 'com.android.support.constraint:constraint-layout:1.1.2'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
implementation project(':app-updater')
implementation project(':app-dialog')
}

@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# 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 *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile

Binary file not shown.

@ -0,0 +1 @@
[{"outputType":{"type":"APK"},"apkInfo":{"type":"MAIN","splits":[],"versionCode":1,"versionName":"1.0","enabled":true,"outputFile":"app-release.apk","fullName":"release","baseName":"release"},"path":"app-release.apk","properties":{}}]

@ -0,0 +1,26 @@
package com.king.appupdater;
import android.content.Context;
import android.support.test.InstrumentationRegistry;
import android.support.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
import static org.junit.Assert.*;
/**
* Instrumented test, which will execute on an Android device.
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
@RunWith(AndroidJUnit4.class)
public class ExampleInstrumentedTest {
@Test
public void useAppContext() {
// Context of the app under test.
Context appContext = InstrumentationRegistry.getTargetContext();
assertEquals("com.king.appupdater", appContext.getPackageName());
}
}

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.king.appupdater">
<uses-permission android:name="android.permission.INTERNET" />
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity"
android:screenOrientation="portrait">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

@ -0,0 +1,274 @@
package com.king.appupdater;
import android.content.Context;
import android.content.DialogInterface;
import android.os.Environment;
import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.Button;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;
import com.king.app.dialog.AppDialog;
import com.king.app.dialog.AppDialogConfig;
import com.king.app.updater.AppUpdater;
import com.king.app.updater.UpdateConfig;
import com.king.app.updater.callback.AppUpdateCallback;
import com.king.app.updater.callback.UpdateCallback;
import java.io.File;
public class MainActivity extends AppCompatActivity {
private final Object mLock = new Object();
public final String TAG = this.getClass().getSimpleName();
private String mUrl = "https://raw.githubusercontent.com/jenly1314/AppUpdater/master/app/release/app-release.apk";
// private String mUrl = "https://pro-app-qn.fir.im/1ddfe25998acd3d861d746101e6e079e1611b666.apk?attname=app-release.apk_1.2.apk";
private ProgressBar progressBar;
private Toast toast;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
progressBar = findViewById(R.id.progressBar);
progressBar.setVisibility(View.INVISIBLE);
}
public Context getContext(){
return this;
}
public void showToast(String text){
if(toast == null){
synchronized (mLock){
if(toast == null){
toast = Toast.makeText(getContext(),text,Toast.LENGTH_SHORT);
}
}
}
toast.setText(text);
toast.show();
}
/**
* 简单一键后台升级
*/
private void clickBtn1(){
new AppUpdater(getContext(),mUrl).start();
}
/**
* 一键下载并监听
*/
private void clickBtn2(){
UpdateConfig config = new UpdateConfig();
config.setUrl(mUrl);
new AppUpdater(getContext(),config)
.setUpdateCallback(new UpdateCallback() {
@Override
public void onDownloading(boolean isDownloading) {
if(isDownloading){
showToast("已经在下载中,请勿重复下载。");
}
}
@Override
public void onStart(String url) {
progressBar.setProgress(0);
progressBar.setVisibility(View.VISIBLE);
}
@Override
public void onProgress(int progress, int total, boolean isChange) {
if(isChange){
progressBar.setMax(total);
progressBar.setProgress(progress);
}
}
@Override
public void onFinish(File file) {
progressBar.setVisibility(View.INVISIBLE);
}
@Override
public void onError(Exception e) {
progressBar.setVisibility(View.INVISIBLE);
}
@Override
public void onCancel() {
progressBar.setVisibility(View.INVISIBLE);
}
})
.start();
}
/**
* 系统弹框升级
*/
private void clickBtn3(){
new AlertDialog.Builder(this)
.setTitle("发现新版本")
.setMessage("1、新增某某功能、\n2、修改某某问题、\n3、优化某某BUG、")
.setPositiveButton("升级", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
new AppUpdater.Builder()
.serUrl(mUrl)
.build(getContext())
.setUpdateCallback(new AppUpdateCallback() {
@Override
public void onProgress(int progress, int total, boolean isChange) {
}
@Override
public void onFinish(File file) {
showToast("下载完成");
}
})
.start();
}
}).show();
}
/**
* 简单弹框升级
*/
private void clickBtn4(){
AppDialogConfig config = new AppDialogConfig();
config.setTitle("简单弹框升级")
.setOk("升级")
.setContent("1、新增某某功能、\n2、修改某某问题、\n3、优化某某BUG、")
.setOnClickOk(new View.OnClickListener() {
@Override
public void onClick(View v) {
new AppUpdater.Builder()
.serUrl(mUrl)
.setFilename("AppUpdater.apk")
.build(getContext())
.start();
AppDialog.INSTANCE.dismissDialog();
}
});
AppDialog.INSTANCE.showDialog(getContext(),config);
}
/**
* 简单自定义弹框升级
*/
private void clickBtn5(){
AppDialogConfig config = new AppDialogConfig();
config.setLayoutId(R.layout.dialog)
.setOk("升级")
.setHideCancel(true)
.setTitle("简单自定义弹框升级")
.setContent("1、新增某某功能、\n2、修改某某问题、\n3、优化某某BUG、")
.setOnClickOk(new View.OnClickListener() {
@Override
public void onClick(View v) {
new AppUpdater.Builder()
.serUrl(mUrl)
.setFilename(Environment.getExternalStorageDirectory() + "/.AppUpdater")
.setFilename("AppUpdater.apk")
.build(getContext())
.start();
AppDialog.INSTANCE.dismissDialog();
}
});
AppDialog.INSTANCE.showDialog(getContext(),AppDialog.INSTANCE.createAppDialogView(getContext(),config),false);
}
/**
* 自定义弹框升级
*/
private void clickBtn6(){
View view = LayoutInflater.from(getContext()).inflate(R.layout.dialog_custom,null);
TextView tvTitle = view.findViewById(R.id.tvTitle);
tvTitle.setText("自定义弹框升级");
TextView tvContent = view.findViewById(R.id.tvContent);
tvContent.setText("1、新增某某功能、\n2、修改某某问题、\n3、优化某某BUG、");
Button btnCancel = view.findViewById(R.id.btnCancel);
btnCancel.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
AppDialog.INSTANCE.dismissDialog();
}
});
Button btnOK = view.findViewById(R.id.btnOK);
btnOK.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
new AppUpdater.Builder()
.serUrl(mUrl)
.setFilename("AppUpdater.apk")
.build(getContext())
.start();
AppDialog.INSTANCE.dismissDialog();
}
});
AppDialog.INSTANCE.showDialog(getContext(),view);
}
/**
* 简单DialogFragment升级
*/
private void clickBtn7(){
AppDialogConfig config = new AppDialogConfig();
config.setTitle("简单DialogFragment升级")
.setOk("升级")
.setContent("1、新增某某功能、\n2、修改某某问题、\n3、优化某某BUG、")
.setOnClickOk(new View.OnClickListener() {
@Override
public void onClick(View v) {
new AppUpdater.Builder()
.serUrl(mUrl)
.setFilename("AppUpdater.apk")
.build(getContext())
.start();
AppDialog.INSTANCE.dismissDialogFragment(getSupportFragmentManager());
}
});
AppDialog.INSTANCE.showDialogFragment(getSupportFragmentManager(),config);
}
public void OnClick(View v){
switch (v.getId()){
case R.id.btn1:
clickBtn1();
break;
case R.id.btn2:
clickBtn2();
break;
case R.id.btn3:
clickBtn3();
break;
case R.id.btn4:
clickBtn4();
break;
case R.id.btn5:
clickBtn5();
break;
case R.id.btn6:
clickBtn6();
break;
case R.id.btn7:
clickBtn7();
break;
}
}
}

@ -0,0 +1,34 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="108dp"
android:height="108dp"
android:viewportHeight="108"
android:viewportWidth="108">
<path
android:fillType="evenOdd"
android:pathData="M32,64C32,64 38.39,52.99 44.13,50.95C51.37,48.37 70.14,49.57 70.14,49.57L108.26,87.69L108,109.01L75.97,107.97L32,64Z"
android:strokeColor="#00000000"
android:strokeWidth="1">
<aapt:attr name="android:fillColor">
<gradient
android:endX="78.5885"
android:endY="90.9159"
android:startX="48.7653"
android:startY="61.0927"
android:type="linear">
<item
android:color="#ffffff"
android:offset="0.0" />
<item
android:color="#ffffff"
android:offset="1.0" />
</gradient>
</aapt:attr>
</path>
<path
android:fillColor="#FFFFFF"
android:fillType="nonZero"
android:pathData="M66.94,46.02L66.94,46.02C72.44,50.07 76,56.61 76,64L32,64C32,56.61 35.56,50.11 40.98,46.06L36.18,41.19C35.45,40.45 35.45,39.3 36.18,38.56C36.91,37.81 38.05,37.81 38.78,38.56L44.25,44.05C47.18,42.57 50.48,41.71 54,41.71C57.48,41.71 60.78,42.57 63.68,44.05L69.11,38.56C69.84,37.81 70.98,37.81 71.71,38.56C72.44,39.3 72.44,40.45 71.71,41.19L66.94,46.02ZM62.94,56.92C64.08,56.92 65,56.01 65,54.88C65,53.76 64.08,52.85 62.94,52.85C61.8,52.85 60.88,53.76 60.88,54.88C60.88,56.01 61.8,56.92 62.94,56.92ZM45.06,56.92C46.2,56.92 47.13,56.01 47.13,54.88C47.13,53.76 46.2,52.85 45.06,52.85C43.92,52.85 43,53.76 43,54.88C43,56.01 43.92,56.92 45.06,56.92Z"
android:strokeColor="#00000000"
android:strokeWidth="1" />
</vector>

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" >
<solid android:color="#F5F5F5" />
<corners
android:bottomLeftRadius="0dp"
android:bottomRightRadius="0dp"
android:topLeftRadius="10dp"
android:topRightRadius="10dp" />
</shape>

@ -0,0 +1,170 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportHeight="108"
android:viewportWidth="108">
<path
android:fillColor="#ffffff"
android:pathData="M0,0h108v108h-108z" />
<path
android:fillColor="#00000000"
android:pathData="M9,0L9,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,0L19,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M29,0L29,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M39,0L39,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M49,0L49,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M59,0L59,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M69,0L69,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M79,0L79,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M89,0L89,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M99,0L99,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,9L108,9"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,19L108,19"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,29L108,29"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,39L108,39"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,49L108,49"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,59L108,59"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,69L108,69"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,79L108,79"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,89L108,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,99L108,99"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,29L89,29"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,39L89,39"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,49L89,49"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,59L89,59"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,69L89,69"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,79L89,79"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M29,19L29,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M39,19L39,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M49,19L49,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M59,19L59,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M69,19L69,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M79,19L79,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
</vector>

@ -0,0 +1,107 @@
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<ProgressBar
android:id="@+id/progressBar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="20dp"
app:layout_constraintTop_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
style="@style/Widget.AppCompat.ProgressBar.Horizontal"/>
<Button
android:id="@+id/btn1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="20dp"
android:layout_marginRight="20dp"
android:layout_marginTop="40dp"
android:text="简单一键后台升级"
app:layout_constraintTop_toBottomOf="@+id/progressBar"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
style="@style/OnClick"/>
<Button
android:id="@+id/btn2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="20dp"
android:layout_marginRight="20dp"
android:layout_marginTop="10dp"
android:text="一键监听进度后台升级"
app:layout_constraintTop_toBottomOf="@+id/btn1"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
style="@style/OnClick"/>
<Button
android:id="@+id/btn3"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="20dp"
android:layout_marginRight="20dp"
android:layout_marginTop="10dp"
android:text="系统弹框升级"
app:layout_constraintTop_toBottomOf="@+id/btn2"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
style="@style/OnClick"/>
<Button
android:id="@+id/btn4"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="20dp"
android:layout_marginRight="20dp"
android:layout_marginTop="10dp"
android:text="简单弹框升级"
app:layout_constraintTop_toBottomOf="@+id/btn3"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
style="@style/OnClick"/>
<Button
android:id="@+id/btn5"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="20dp"
android:layout_marginRight="20dp"
android:layout_marginTop="10dp"
android:text="简单自定义弹框升级"
app:layout_constraintTop_toBottomOf="@+id/btn4"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
style="@style/OnClick"/>
<Button
android:id="@+id/btn6"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="20dp"
android:layout_marginRight="20dp"
android:layout_marginTop="10dp"
android:text="自定义弹框升级"
app:layout_constraintTop_toBottomOf="@+id/btn5"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
style="@style/OnClick"/>
<Button
android:id="@+id/btn7"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="20dp"
android:layout_marginRight="20dp"
android:layout_marginTop="10dp"
android:text="简单DialogFragment升级"
app:layout_constraintTop_toBottomOf="@+id/btn6"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
style="@style/OnClick"/>
</android.support.constraint.ConstraintLayout>

@ -0,0 +1,60 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_margin="30dp"
android:background="@drawable/app_dialog_bg"
android:orientation="vertical">
<TextView
android:id="@+id/tvDialogTitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:minHeight="40dp"
android:padding="6dp"
android:lines="1"
android:textSize="16sp"
android:background="@drawable/dialog_title_bg"
android:text="@string/app_dialog_title"/>
<TextView
android:id="@+id/tvDialogContent"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="10dp"
android:layout_marginBottom="6dp"
android:lineSpacingMultiplier="1" />
<include layout="@layout/app_dialog_line_h"/>
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_horizontal">
<Button
android:id="@+id/btnDialogCancel"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:paddingTop="10dp"
android:paddingBottom="10dp"
android:layout_weight="1"
android:text="@string/app_dialog_cancel"
android:textSize="18sp"
android:textColor="@color/app_dialog_button_color_selector"
android:background="?android:attr/selectableItemBackground"/>
<include
android:id="@+id/line"
layout="@layout/app_dialog_line_v"/>
<Button
android:id="@+id/btnDialogOK"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:paddingTop="10dp"
android:paddingBottom="10dp"
android:layout_weight="1"
android:text="@string/app_dialog_ok"
android:textSize="18sp"
android:textColor="@color/app_dialog_button_color_selector"
android:background="?android:attr/selectableItemBackground"/>
</LinearLayout>
</LinearLayout>

@ -0,0 +1,56 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_margin="30dp"
android:background="@android:color/white"
android:orientation="vertical">
<TextView
android:id="@+id/tvTitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:minHeight="40dp"
android:padding="6dp"
android:lines="1"
android:textSize="16sp"
android:background="#F5F5F5" />
<TextView
android:id="@+id/tvContent"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="10dp"
android:layout_marginBottom="6dp"
android:lineSpacingMultiplier="1" />
<include layout="@layout/app_dialog_line_h"/>
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_horizontal">
<Button
android:id="@+id/btnCancel"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:paddingTop="10dp"
android:paddingBottom="10dp"
android:layout_weight="1"
android:text="@string/app_dialog_cancel"
android:textSize="16sp"
android:textColor="@color/app_dialog_button_color_selector"
android:background="?android:attr/selectableItemBackground"/>
<Button
android:id="@+id/btnOK"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:paddingTop="10dp"
android:paddingBottom="10dp"
android:layout_weight="1"
android:text="升级"
android:textSize="16sp"
android:textColor="@color/app_dialog_button_color_selector"
android:background="?android:attr/selectableItemBackground"/>
</LinearLayout>
</LinearLayout>

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
</adaptive-icon>

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
</adaptive-icon>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="colorPrimary">#3F51B5</color>
<color name="colorPrimaryDark">#303F9F</color>
<color name="colorAccent">#FF4081</color>
<color name="ic_launcher_background">#FFFFFF</color>
</resources>

@ -0,0 +1,4 @@
<resources>
<string name="app_name">AppUpdater</string>
</resources>

@ -0,0 +1,16 @@
<resources>
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<!-- Customize your theme here. -->
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
<item name="android:textAllCaps">false</item>
</style>
<style name="OnClick">
<item name="android:clickable">true</item>
<item name="android:onClick">OnClick</item>
</style>
</resources>

@ -0,0 +1,17 @@
package com.king.appupdater;
import org.junit.Test;
import static org.junit.Assert.*;
/**
* Example local unit test, which will execute on the development machine (host).
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
public class ExampleUnitTest {
@Test
public void addition_isCorrect() {
assertEquals(4, 2 + 2);
}
}

@ -0,0 +1,37 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.1.2'
classpath 'com.novoda:bintray-release:0.8.1'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects {
repositories {
google()
jcenter()
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
allprojects {
tasks.withType(Javadoc) {
options{
encoding "UTF-8"
charSet 'UTF-8'
links "http://docs.oracle.com/javase/8/docs/api"
}
}
}

@ -0,0 +1,13 @@
# Project-wide Gradle settings.
# IDE (e.g. Android Studio) users:
# Gradle settings configured through the IDE *will override*
# any settings specified in this file.
# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
org.gradle.jvmargs=-Xmx1536m
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true

Binary file not shown.

@ -0,0 +1,6 @@
#Mon Jun 25 18:24:14 CST 2018
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip

172
gradlew vendored

@ -0,0 +1,172 @@
#!/usr/bin/env sh
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn () {
echo "$*"
}
die () {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=$((i+1))
done
case $i in
(0) set -- ;;
(1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=$(save "$@")
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
cd "$(dirname "$0")"
fi
exec "$JAVACMD" "$@"

84
gradlew.bat vendored

@ -0,0 +1,84 @@
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windows variants
if not "%OS%" == "Windows_NT" goto win9xME_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

@ -0,0 +1 @@
include ':app', ':app-updater', ':app-updater', ':app-dialog'
Loading…
Cancel
Save