Compare commits

...

41 Commits

Author SHA1 Message Date
Jenly 3dfb90809a 优化细节 3 years ago
Jenly 7f23dfb4ef 优化细节 3 years ago
Jenly 08e5c8fbb6 优化细节 3 years ago
Jenly ac88d07c1b 优化细节 3 years ago
Jenly 4ee839fe2c 优化细节 3 years ago
Jenly 1bf77be43a Update README 3 years ago
Jenly 5c848ee830 新增 ViewHolder 3 years ago
Jenly 5183b3b9fe 新增几种组合动画 3 years ago
Jenly 9a426841c9 对外提供更多可配置参数 3 years ago
Jenly 09972ded00 优化日志 3 years ago
Jenly 5139dfbfff Update README 3 years ago
Jenly 41a1f88d33 Update README 3 years ago
Jenly ff6eca1ab2 优化细节 3 years ago
Jenly e93a677d1b 优化细节 4 years ago
Jenly 3ee62c3b32 优化细节 4 years ago
Jenly 727cd08b98 优化细节 4 years ago
Jenly 898b3912b6 优化细节 4 years ago
Jenly 3a685c958f CI 4 years ago
Jenly 2677d67d70 CI 4 years ago
Jenly b5832c92da CI 4 years ago
Jenly 6d04cf4575 Update targetSdk to 29 4 years ago
Jenly 372b1ad30f maven publish 4 years ago
Jenly d2874564d7 优化细节 4 years ago
Jenly dbfbac55de * AppDialogConfig添加构造参数,简化自定义扩展用法 4 years ago
Jenly d3a759374c 优化细节 4 years ago
Jenly 5c1c0c1ad6 * 优化默认Dialog样式的显示细节 4 years ago
Jenly 2d56132692 优化细节 4 years ago
jenly1314 8e657d1ecb 支持MD5校验 5 years ago
jenly1314 636e202e2f 对外提供获取Dialog方法(#16) 5 years ago
jenly1314 e38db4de11 CI 5 years ago
Jenly 69a93148a2 CI 5 years ago
jenly1314 3838ad6685 优化细节 5 years ago
jenly1314 d29c1348ad 增加混淆 5 years ago
jenly1314 82a9b36a7d 优化细节 5 years ago
jenly1314 a10687ad07 优化细节 5 years ago
jenly1314 0a7e40dfce 优化细节 5 years ago
jenly1314 ea8f2603a8 CI 5 years ago
jenly1314 0a8de018b8 支持取消下载 5 years ago
jenly1314 4eee3649c9 支持AndroidX版本 5 years ago
jenly1314 ef8b918f1f 支持AndroidX版本 5 years ago
jenly1314 55e92e5f17 支持AndroidX版本 5 years ago
  1. 27
      .circleci/config.yml
  2. 20
      .github/workflows/gradle.yml
  3. 4
      .gitignore
  4. BIN
      .idea/caches/build_file_checksums.ser
  5. 52
      .idea/codeStyles/Project.xml
  6. 8
      .idea/compiler.xml
  7. 456
      .idea/dbnavigator.xml
  8. 6
      .idea/encodings.xml
  9. 20
      .idea/gradle.xml
  10. 83
      .idea/markdown-navigator.xml
  11. 3
      .idea/markdown-navigator/profiles_settings.xml
  12. 38
      .idea/misc.xml
  13. 12
      .idea/runConfigurations.xml
  14. 6
      .idea/vcs.xml
  15. 6
      .travis.yml
  16. 167
      README.md
  17. 15
      app-dialog/build.gradle
  18. 3
      app-dialog/gradle.properties
  19. 8
      app-dialog/proguard-rules.pro
  20. 6
      app-dialog/src/androidTest/java/com/king/app/dialog/ExampleInstrumentedTest.java
  21. 464
      app-dialog/src/main/java/com/king/app/dialog/AppDialog.java
  22. 767
      app-dialog/src/main/java/com/king/app/dialog/AppDialogConfig.java
  23. 669
      app-dialog/src/main/java/com/king/app/dialog/BaseDialogConfig.java
  24. 47
      app-dialog/src/main/java/com/king/app/dialog/fragment/AppDialogFragment.java
  25. 84
      app-dialog/src/main/java/com/king/app/dialog/fragment/BaseDialogFragment.java
  26. 9
      app-dialog/src/main/res/anim/app_dialog_bottom_in.xml
  27. 9
      app-dialog/src/main/res/anim/app_dialog_bottom_out.xml
  28. 9
      app-dialog/src/main/res/anim/app_dialog_left_in.xml
  29. 9
      app-dialog/src/main/res/anim/app_dialog_left_out.xml
  30. 9
      app-dialog/src/main/res/anim/app_dialog_right_in.xml
  31. 9
      app-dialog/src/main/res/anim/app_dialog_right_out.xml
  32. 16
      app-dialog/src/main/res/anim/app_dialog_scale_bottom_in.xml
  33. 16
      app-dialog/src/main/res/anim/app_dialog_scale_bottom_out.xml
  34. 5
      app-dialog/src/main/res/anim/app_dialog_scale_in.xml
  35. 16
      app-dialog/src/main/res/anim/app_dialog_scale_left_in.xml
  36. 16
      app-dialog/src/main/res/anim/app_dialog_scale_left_out.xml
  37. 5
      app-dialog/src/main/res/anim/app_dialog_scale_out.xml
  38. 16
      app-dialog/src/main/res/anim/app_dialog_scale_right_in.xml
  39. 16
      app-dialog/src/main/res/anim/app_dialog_scale_right_out.xml
  40. 16
      app-dialog/src/main/res/anim/app_dialog_scale_top_in.xml
  41. 16
      app-dialog/src/main/res/anim/app_dialog_scale_top_out.xml
  42. 9
      app-dialog/src/main/res/anim/app_dialog_top_in.xml
  43. 9
      app-dialog/src/main/res/anim/app_dialog_top_out.xml
  44. 20
      app-dialog/src/main/res/layout/app_dialog.xml
  45. 4
      app-dialog/src/main/res/values/colors.xml
  46. 2
      app-dialog/src/main/res/values/dimens.xml
  47. 4
      app-dialog/src/main/res/values/strings.xml
  48. 51
      app-dialog/src/main/res/values/styles.xml
  49. 17
      app-updater/build.gradle
  50. 3
      app-updater/gradle.properties
  51. 10
      app-updater/proguard-rules.pro
  52. 5
      app-updater/src/androidTest/java/com/king/app/updater/ExampleInstrumentedTest.java
  53. 9
      app-updater/src/main/AndroidManifest.xml
  54. 119
      app-updater/src/main/java/com/king/app/updater/AppUpdater.java
  55. 168
      app-updater/src/main/java/com/king/app/updater/UpdateConfig.java
  56. 4
      app-updater/src/main/java/com/king/app/updater/callback/UpdateCallback.java
  57. 11
      app-updater/src/main/java/com/king/app/updater/constant/Constants.java
  58. 131
      app-updater/src/main/java/com/king/app/updater/http/HttpManager.java
  59. 10
      app-updater/src/main/java/com/king/app/updater/http/IHttpManager.java
  60. 235
      app-updater/src/main/java/com/king/app/updater/http/OkHttpManager.java
  61. 25
      app-updater/src/main/java/com/king/app/updater/notify/INotification.java
  62. 38
      app-updater/src/main/java/com/king/app/updater/notify/NotificationImpl.java
  63. 3
      app-updater/src/main/java/com/king/app/updater/provider/AppUpdaterFileProvider.java
  64. 487
      app-updater/src/main/java/com/king/app/updater/service/DownloadService.java
  65. 167
      app-updater/src/main/java/com/king/app/updater/util/AppUtils.java
  66. 316
      app-updater/src/main/java/com/king/app/updater/util/LogUtils.java
  67. 260
      app-updater/src/main/java/com/king/app/updater/util/NotificationUtils.java
  68. 46
      app-updater/src/main/java/com/king/app/updater/util/PermissionUtils.java
  69. 73
      app-updater/src/main/java/com/king/app/updater/util/SSLSocketFactoryUtils.java
  70. 2
      app-updater/src/main/res/values/strings.xml
  71. 1
      app-updater/src/main/res/xml/app_updater_paths.xml
  72. 16
      app/build.gradle
  73. BIN
      app/release/app-release.apk
  74. 18
      app/release/output-metadata.json
  75. 1
      app/release/output.json
  76. 5
      app/src/androidTest/java/com/king/appupdater/ExampleInstrumentedTest.java
  77. 3
      app/src/main/AndroidManifest.xml
  78. 183
      app/src/main/java/com/king/appupdater/MainActivity.java
  79. 5
      app/src/main/res/color/btn_cancel_color_selector.xml
  80. 18
      app/src/main/res/drawable/btn_confirm_selector.xml
  81. 30
      app/src/main/res/layout/activity_main.xml
  82. 15
      app/src/main/res/layout/dialog.xml
  83. 44
      app/src/main/res/layout/dialog_custom.xml
  84. 25
      app/src/main/res/layout/dialog_heads_up.xml
  85. 43
      app/src/main/res/layout/dialog_progress.xml
  86. 11
      app/src/main/res/values/colors.xml
  87. 2
      app/src/main/res/values/strings.xml
  88. 13
      app/src/main/res/values/styles.xml
  89. 21
      build.gradle
  90. 23
      gradle.properties
  91. 2
      gradle/wrapper/gradle-wrapper.properties
  92. 47
      versions.gradle

@ -0,0 +1,27 @@
version: 2
jobs:
build:
working_directory: ~/code
docker:
- image: circleci/android:api-28
environment:
JVM_OPTS: -Xmx3200m
steps:
- checkout
- restore_cache:
key: jars-{{ checksum "build.gradle" }}-{{ checksum "app/build.gradle" }}
- run:
name: Download Dependencies
command: ./gradlew androidDependencies
- save_cache:
paths:
- ~/.gradle
key: jars-{{ checksum "build.gradle" }}-{{ checksum "app/build.gradle" }}
- run:
name: Run Tests
command: ./gradlew lint test
- store_artifacts:
path: app/build/reports
destination: reports
- store_test_results:
path: app/build/test-results

@ -0,0 +1,20 @@
name: Android CI
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up JDK 1.8
uses: actions/setup-java@v1
with:
java-version: 1.8
- name: Build with Gradle
run: ./gradlew build

4
.gitignore vendored

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

@ -1,52 +0,0 @@
<component name="ProjectCodeStyleConfiguration">
<code_scheme name="Project" version="173">
<DBN-PSQL>
<case-options enabled="true">
<option name="KEYWORD_CASE" value="lower" />
<option name="FUNCTION_CASE" value="lower" />
<option name="PARAMETER_CASE" value="lower" />
<option name="DATATYPE_CASE" value="lower" />
<option name="OBJECT_CASE" value="preserve" />
</case-options>
<formatting-settings enabled="false" />
</DBN-PSQL>
<DBN-SQL>
<case-options enabled="true">
<option name="KEYWORD_CASE" value="lower" />
<option name="FUNCTION_CASE" value="lower" />
<option name="PARAMETER_CASE" value="lower" />
<option name="DATATYPE_CASE" value="lower" />
<option name="OBJECT_CASE" value="preserve" />
</case-options>
<formatting-settings enabled="false">
<option name="STATEMENT_SPACING" value="one_line" />
<option name="CLAUSE_CHOP_DOWN" value="chop_down_if_statement_long" />
<option name="ITERATION_ELEMENTS_WRAPPING" value="chop_down_if_not_single" />
</formatting-settings>
</DBN-SQL>
<DBN-PSQL>
<case-options enabled="true">
<option name="KEYWORD_CASE" value="lower" />
<option name="FUNCTION_CASE" value="lower" />
<option name="PARAMETER_CASE" value="lower" />
<option name="DATATYPE_CASE" value="lower" />
<option name="OBJECT_CASE" value="preserve" />
</case-options>
<formatting-settings enabled="false" />
</DBN-PSQL>
<DBN-SQL>
<case-options enabled="true">
<option name="KEYWORD_CASE" value="lower" />
<option name="FUNCTION_CASE" value="lower" />
<option name="PARAMETER_CASE" value="lower" />
<option name="DATATYPE_CASE" value="lower" />
<option name="OBJECT_CASE" value="preserve" />
</case-options>
<formatting-settings enabled="false">
<option name="STATEMENT_SPACING" value="one_line" />
<option name="CLAUSE_CHOP_DOWN" value="chop_down_if_statement_long" />
<option name="ITERATION_ELEMENTS_WRAPPING" value="chop_down_if_not_single" />
</formatting-settings>
</DBN-SQL>
</code_scheme>
</component>

@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<annotationProcessing>
<profile default="true" name="Default" enabled="true" />
</annotationProcessing>
</component>
</project>

@ -1,456 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DBNavigator.Project.DataEditorManager">
<record-view-column-sorting-type value="BY_INDEX" />
<value-preview-text-wrapping value="true" />
<value-preview-pinned value="false" />
</component>
<component name="DBNavigator.Project.DataExportManager">
<export-instructions>
<create-header value="true" />
<quote-values-containing-separator value="true" />
<quote-all-values value="false" />
<value-separator value="" />
<file-name value="" />
<file-location value="" />
<scope value="GLOBAL" />
<destination value="FILE" />
<format value="EXCEL" />
<charset value="GBK" />
</export-instructions>
</component>
<component name="DBNavigator.Project.DatabaseBrowserManager">
<autoscroll-to-editor value="false" />
<autoscroll-from-editor value="true" />
<show-object-properties value="true" />
<loaded-nodes />
</component>
<component name="DBNavigator.Project.DatabaseFileManager">
<open-files />
</component>
<component name="DBNavigator.Project.EditorStateManager">
<last-used-providers />
</component>
<component name="DBNavigator.Project.MethodExecutionManager">
<method-browser />
<execution-history>
<group-entries value="true" />
<execution-inputs />
</execution-history>
<argument-values-cache />
</component>
<component name="DBNavigator.Project.ObjectDependencyManager">
<last-used-dependency-type value="INCOMING" />
</component>
<component name="DBNavigator.Project.ObjectQuickFilterManager">
<last-used-operator value="EQUAL" />
<filters />
</component>
<component name="DBNavigator.Project.ScriptExecutionManager" clear-outputs="true">
<recently-used-interfaces />
</component>
<component name="DBNavigator.Project.Settings">
<connections />
<browser-settings>
<general>
<display-mode value="TABBED" />
<navigation-history-size value="100" />
<show-object-details value="false" />
</general>
<filters>
<object-type-filter>
<object-type name="SCHEMA" enabled="true" />
<object-type name="USER" enabled="true" />
<object-type name="ROLE" enabled="true" />
<object-type name="PRIVILEGE" enabled="true" />
<object-type name="CHARSET" enabled="true" />
<object-type name="TABLE" enabled="true" />
<object-type name="VIEW" enabled="true" />
<object-type name="MATERIALIZED_VIEW" enabled="true" />
<object-type name="NESTED_TABLE" enabled="true" />
<object-type name="COLUMN" enabled="true" />
<object-type name="INDEX" enabled="true" />
<object-type name="CONSTRAINT" enabled="true" />
<object-type name="DATASET_TRIGGER" enabled="true" />
<object-type name="DATABASE_TRIGGER" enabled="true" />
<object-type name="SYNONYM" enabled="true" />
<object-type name="SEQUENCE" enabled="true" />
<object-type name="PROCEDURE" enabled="true" />
<object-type name="FUNCTION" enabled="true" />
<object-type name="PACKAGE" enabled="true" />
<object-type name="TYPE" enabled="true" />
<object-type name="TYPE_ATTRIBUTE" enabled="true" />
<object-type name="ARGUMENT" enabled="true" />
<object-type name="DIMENSION" enabled="true" />
<object-type name="CLUSTER" enabled="true" />
<object-type name="DBLINK" enabled="true" />
</object-type-filter>
</filters>
<sorting>
<object-type name="COLUMN" sorting-type="NAME" />
<object-type name="FUNCTION" sorting-type="NAME" />
<object-type name="PROCEDURE" sorting-type="NAME" />
<object-type name="ARGUMENT" sorting-type="POSITION" />
</sorting>
<default-editors>
<object-type name="VIEW" editor-type="SELECTION" />
<object-type name="PACKAGE" editor-type="SELECTION" />
<object-type name="TYPE" editor-type="SELECTION" />
</default-editors>
</browser-settings>
<navigation-settings>
<lookup-filters>
<lookup-objects>
<object-type name="SCHEMA" enabled="true" />
<object-type name="USER" enabled="false" />
<object-type name="ROLE" enabled="false" />
<object-type name="PRIVILEGE" enabled="false" />
<object-type name="CHARSET" enabled="false" />
<object-type name="TABLE" enabled="true" />
<object-type name="VIEW" enabled="true" />
<object-type name="MATERIALIZED VIEW" enabled="true" />
<object-type name="NESTED TABLE" enabled="false" />
<object-type name="COLUMN" enabled="false" />
<object-type name="INDEX" enabled="true" />
<object-type name="CONSTRAINT" enabled="true" />
<object-type name="DATASET TRIGGER" enabled="true" />
<object-type name="DATABASE TRIGGER" enabled="true" />
<object-type name="SYNONYM" enabled="false" />
<object-type name="SEQUENCE" enabled="true" />
<object-type name="PROCEDURE" enabled="true" />
<object-type name="FUNCTION" enabled="true" />
<object-type name="PACKAGE" enabled="true" />
<object-type name="TYPE" enabled="true" />
<object-type name="TYPE ATTRIBUTE" enabled="false" />
<object-type name="ARGUMENT" enabled="false" />
<object-type name="DIMENSION" enabled="false" />
<object-type name="CLUSTER" enabled="false" />
<object-type name="DBLINK" enabled="true" />
</lookup-objects>
<force-database-load value="false" />
<prompt-connection-selection value="true" />
<prompt-schema-selection value="true" />
</lookup-filters>
</navigation-settings>
<dataset-grid-settings>
<general>
<enable-zooming value="true" />
<enable-column-tooltip value="true" />
</general>
<sorting>
<nulls-first value="true" />
<max-sorting-columns value="4" />
</sorting>
<tracking-columns>
<columnNames value="" />
<visible value="true" />
<editable value="false" />
</tracking-columns>
</dataset-grid-settings>
<dataset-editor-settings>
<text-editor-popup>
<active value="false" />
<active-if-empty value="false" />
<data-length-threshold value="100" />
<popup-delay value="1000" />
</text-editor-popup>
<values-actions-popup>
<show-popup-button value="true" />
<element-count-threshold value="1000" />
<data-length-threshold value="250" />
</values-actions-popup>
<general>
<fetch-block-size value="100" />
<fetch-timeout value="30" />
<trim-whitespaces value="true" />
<convert-empty-strings-to-null value="true" />
<select-content-on-cell-edit value="true" />
<large-value-preview-active value="true" />
</general>
<filters>
<prompt-filter-dialog value="true" />
<default-filter-type value="BASIC" />
</filters>
<qualified-text-editor text-length-threshold="300">
<content-types>
<content-type name="Text" enabled="true" />
<content-type name="Properties" enabled="true" />
<content-type name="XML" enabled="true" />
<content-type name="DTD" enabled="true" />
<content-type name="HTML" enabled="true" />
<content-type name="XHTML" enabled="true" />
<content-type name="Java" enabled="true" />
<content-type name="SQL" enabled="true" />
<content-type name="PL/SQL" enabled="true" />
<content-type name="Groovy" enabled="true" />
<content-type name="AIDL" enabled="true" />
<content-type name="YAML" enabled="true" />
<content-type name="Manifest" enabled="true" />
</content-types>
</qualified-text-editor>
<record-navigation>
<navigation-target value="VIEWER" />
</record-navigation>
</dataset-editor-settings>
<code-editor-settings>
<general>
<show-object-navigation-gutter value="false" />
<show-spec-declaration-navigation-gutter value="true" />
<enable-spellchecking value="true" />
<enable-reference-spellchecking value="false" />
</general>
<confirmations>
<save-changes value="false" />
<revert-changes value="true" />
</confirmations>
</code-editor-settings>
<code-completion-settings>
<filters>
<basic-filter>
<filter-element type="RESERVED_WORD" id="keyword" selected="true" />
<filter-element type="RESERVED_WORD" id="function" selected="true" />
<filter-element type="RESERVED_WORD" id="parameter" selected="true" />
<filter-element type="RESERVED_WORD" id="datatype" selected="true" />
<filter-element type="RESERVED_WORD" id="exception" selected="true" />
<filter-element type="OBJECT" id="schema" selected="true" />
<filter-element type="OBJECT" id="role" selected="true" />
<filter-element type="OBJECT" id="user" selected="true" />
<filter-element type="OBJECT" id="privilege" selected="true" />
<user-schema>
<filter-element type="OBJECT" id="table" selected="true" />
<filter-element type="OBJECT" id="view" selected="true" />
<filter-element type="OBJECT" id="materialized view" selected="true" />
<filter-element type="OBJECT" id="index" selected="true" />
<filter-element type="OBJECT" id="constraint" selected="true" />
<filter-element type="OBJECT" id="trigger" selected="true" />
<filter-element type="OBJECT" id="synonym" selected="false" />
<filter-element type="OBJECT" id="sequence" selected="true" />
<filter-element type="OBJECT" id="procedure" selected="true" />
<filter-element type="OBJECT" id="function" selected="true" />
<filter-element type="OBJECT" id="package" selected="true" />
<filter-element type="OBJECT" id="type" selected="true" />
<filter-element type="OBJECT" id="dimension" selected="true" />
<filter-element type="OBJECT" id="cluster" selected="true" />
<filter-element type="OBJECT" id="dblink" selected="true" />
</user-schema>
<public-schema>
<filter-element type="OBJECT" id="table" selected="false" />
<filter-element type="OBJECT" id="view" selected="false" />
<filter-element type="OBJECT" id="materialized view" selected="false" />
<filter-element type="OBJECT" id="index" selected="false" />
<filter-element type="OBJECT" id="constraint" selected="false" />
<filter-element type="OBJECT" id="trigger" selected="false" />
<filter-element type="OBJECT" id="synonym" selected="false" />
<filter-element type="OBJECT" id="sequence" selected="false" />
<filter-element type="OBJECT" id="procedure" selected="false" />
<filter-element type="OBJECT" id="function" selected="false" />
<filter-element type="OBJECT" id="package" selected="false" />
<filter-element type="OBJECT" id="type" selected="false" />
<filter-element type="OBJECT" id="dimension" selected="false" />
<filter-element type="OBJECT" id="cluster" selected="false" />
<filter-element type="OBJECT" id="dblink" selected="false" />
</public-schema>
<any-schema>
<filter-element type="OBJECT" id="table" selected="true" />
<filter-element type="OBJECT" id="view" selected="true" />
<filter-element type="OBJECT" id="materialized view" selected="true" />
<filter-element type="OBJECT" id="index" selected="true" />
<filter-element type="OBJECT" id="constraint" selected="true" />
<filter-element type="OBJECT" id="trigger" selected="true" />
<filter-element type="OBJECT" id="synonym" selected="true" />
<filter-element type="OBJECT" id="sequence" selected="true" />
<filter-element type="OBJECT" id="procedure" selected="true" />
<filter-element type="OBJECT" id="function" selected="true" />
<filter-element type="OBJECT" id="package" selected="true" />
<filter-element type="OBJECT" id="type" selected="true" />
<filter-element type="OBJECT" id="dimension" selected="true" />
<filter-element type="OBJECT" id="cluster" selected="true" />
<filter-element type="OBJECT" id="dblink" selected="true" />
</any-schema>
</basic-filter>
<extended-filter>
<filter-element type="RESERVED_WORD" id="keyword" selected="true" />
<filter-element type="RESERVED_WORD" id="function" selected="true" />
<filter-element type="RESERVED_WORD" id="parameter" selected="true" />
<filter-element type="RESERVED_WORD" id="datatype" selected="true" />
<filter-element type="RESERVED_WORD" id="exception" selected="true" />
<filter-element type="OBJECT" id="schema" selected="true" />
<filter-element type="OBJECT" id="user" selected="true" />
<filter-element type="OBJECT" id="role" selected="true" />
<filter-element type="OBJECT" id="privilege" selected="true" />
<user-schema>
<filter-element type="OBJECT" id="table" selected="true" />
<filter-element type="OBJECT" id="view" selected="true" />
<filter-element type="OBJECT" id="materialized view" selected="true" />
<filter-element type="OBJECT" id="index" selected="true" />
<filter-element type="OBJECT" id="constraint" selected="true" />
<filter-element type="OBJECT" id="trigger" selected="true" />
<filter-element type="OBJECT" id="synonym" selected="true" />
<filter-element type="OBJECT" id="sequence" selected="true" />
<filter-element type="OBJECT" id="procedure" selected="true" />
<filter-element type="OBJECT" id="function" selected="true" />
<filter-element type="OBJECT" id="package" selected="true" />
<filter-element type="OBJECT" id="type" selected="true" />
<filter-element type="OBJECT" id="dimension" selected="true" />
<filter-element type="OBJECT" id="cluster" selected="true" />
<filter-element type="OBJECT" id="dblink" selected="true" />
</user-schema>
<public-schema>
<filter-element type="OBJECT" id="table" selected="true" />
<filter-element type="OBJECT" id="view" selected="true" />
<filter-element type="OBJECT" id="materialized view" selected="true" />
<filter-element type="OBJECT" id="index" selected="true" />
<filter-element type="OBJECT" id="constraint" selected="true" />
<filter-element type="OBJECT" id="trigger" selected="true" />
<filter-element type="OBJECT" id="synonym" selected="true" />
<filter-element type="OBJECT" id="sequence" selected="true" />
<filter-element type="OBJECT" id="procedure" selected="true" />
<filter-element type="OBJECT" id="function" selected="true" />
<filter-element type="OBJECT" id="package" selected="true" />
<filter-element type="OBJECT" id="type" selected="true" />
<filter-element type="OBJECT" id="dimension" selected="true" />
<filter-element type="OBJECT" id="cluster" selected="true" />
<filter-element type="OBJECT" id="dblink" selected="true" />
</public-schema>
<any-schema>
<filter-element type="OBJECT" id="table" selected="true" />
<filter-element type="OBJECT" id="view" selected="true" />
<filter-element type="OBJECT" id="materialized view" selected="true" />
<filter-element type="OBJECT" id="index" selected="true" />
<filter-element type="OBJECT" id="constraint" selected="true" />
<filter-element type="OBJECT" id="trigger" selected="true" />
<filter-element type="OBJECT" id="synonym" selected="true" />
<filter-element type="OBJECT" id="sequence" selected="true" />
<filter-element type="OBJECT" id="procedure" selected="true" />
<filter-element type="OBJECT" id="function" selected="true" />
<filter-element type="OBJECT" id="package" selected="true" />
<filter-element type="OBJECT" id="type" selected="true" />
<filter-element type="OBJECT" id="dimension" selected="true" />
<filter-element type="OBJECT" id="cluster" selected="true" />
<filter-element type="OBJECT" id="dblink" selected="true" />
</any-schema>
</extended-filter>
</filters>
<sorting enabled="true">
<sorting-element type="RESERVED_WORD" id="keyword" />
<sorting-element type="RESERVED_WORD" id="datatype" />
<sorting-element type="OBJECT" id="column" />
<sorting-element type="OBJECT" id="table" />
<sorting-element type="OBJECT" id="view" />
<sorting-element type="OBJECT" id="materialized view" />
<sorting-element type="OBJECT" id="index" />
<sorting-element type="OBJECT" id="constraint" />
<sorting-element type="OBJECT" id="trigger" />
<sorting-element type="OBJECT" id="synonym" />
<sorting-element type="OBJECT" id="sequence" />
<sorting-element type="OBJECT" id="procedure" />
<sorting-element type="OBJECT" id="function" />
<sorting-element type="OBJECT" id="package" />
<sorting-element type="OBJECT" id="type" />
<sorting-element type="OBJECT" id="dimension" />
<sorting-element type="OBJECT" id="cluster" />
<sorting-element type="OBJECT" id="dblink" />
<sorting-element type="OBJECT" id="schema" />
<sorting-element type="OBJECT" id="role" />
<sorting-element type="OBJECT" id="user" />
<sorting-element type="RESERVED_WORD" id="function" />
<sorting-element type="RESERVED_WORD" id="parameter" />
</sorting>
<format>
<enforce-code-style-case value="true" />
</format>
</code-completion-settings>
<execution-engine-settings>
<statement-execution>
<fetch-block-size value="100" />
<execution-timeout value="20" />
<debug-execution-timeout value="600" />
<focus-result value="false" />
<prompt-execution value="false" />
</statement-execution>
<script-execution>
<command-line-interfaces />
<execution-timeout value="300" />
</script-execution>
<method-execution>
<execution-timeout value="30" />
<debug-execution-timeout value="600" />
<parameter-history-size value="10" />
</method-execution>
</execution-engine-settings>
<operation-settings>
<transactions>
<uncommitted-changes>
<on-project-close value="ASK" />
<on-disconnect value="ASK" />
<on-autocommit-toggle value="ASK" />
</uncommitted-changes>
<multiple-uncommitted-changes>
<on-commit value="ASK" />
<on-rollback value="ASK" />
</multiple-uncommitted-changes>
</transactions>
<session-browser>
<disconnect-session value="ASK" />
<kill-session value="ASK" />
<reload-on-filter-change value="false" />
</session-browser>
<compiler>
<compile-type value="KEEP" />
<compile-dependencies value="ASK" />
<always-show-controls value="false" />
</compiler>
<debugger>
<debugger-type value="ASK" />
<use-generic-runners value="true" />
</debugger>
</operation-settings>
<ddl-file-settings>
<extensions>
<mapping file-type-id="VIEW" extensions="vw" />
<mapping file-type-id="TRIGGER" extensions="trg" />
<mapping file-type-id="PROCEDURE" extensions="prc" />
<mapping file-type-id="FUNCTION" extensions="fnc" />
<mapping file-type-id="PACKAGE" extensions="pkg" />
<mapping file-type-id="PACKAGE_SPEC" extensions="pks" />
<mapping file-type-id="PACKAGE_BODY" extensions="pkb" />
<mapping file-type-id="TYPE" extensions="tpe" />
<mapping file-type-id="TYPE_SPEC" extensions="tps" />
<mapping file-type-id="TYPE_BODY" extensions="tpb" />
</extensions>
<general>
<lookup-ddl-files value="true" />
<create-ddl-files value="false" />
<synchronize-ddl-files value="true" />
<use-qualified-names value="false" />
<make-scripts-rerunnable value="true" />
</general>
</ddl-file-settings>
<general-settings>
<regional-settings>
<date-format value="MEDIUM" />
<number-format value="UNGROUPED" />
<locale value="SYSTEM_DEFAULT" />
<use-custom-formats value="false" />
</regional-settings>
<environment>
<environment-types>
<environment-type id="development" name="Development" description="Development environment" color="-2430209/-12296320" readonly-code="false" readonly-data="false" />
<environment-type id="integration" name="Integration" description="Integration environment" color="-2621494/-12163514" readonly-code="true" readonly-data="false" />
<environment-type id="production" name="Production" description="Productive environment" color="-11574/-10271420" readonly-code="true" readonly-data="true" />
<environment-type id="other" name="Other" description="" color="-1576/-10724543" readonly-code="false" readonly-data="false" />
</environment-types>
<visibility-settings>
<connection-tabs value="true" />
<dialog-headers value="true" />
<object-editor-tabs value="true" />
<script-editor-tabs value="false" />
<execution-result-tabs value="true" />
</visibility-settings>
</environment>
</general-settings>
</component>
<component name="DBNavigator.Project.StatementExecutionManager">
<execution-variables />
</component>
</project>

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

@ -1,20 +0,0 @@
<?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>

@ -1,83 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="MarkdownProjectSettings" wasCopied="true">
<PreviewSettings splitEditorLayout="SPLIT" splitEditorPreview="PREVIEW" useGrayscaleRendering="false" zoomFactor="1.0" maxImageWidth="0" showGitHubPageIfSynced="false" allowBrowsingInPreview="false" synchronizePreviewPosition="true" highlightPreviewType="NONE" highlightFadeOut="5" highlightOnTyping="true" synchronizeSourcePosition="true" verticallyAlignSourceAndPreviewSyncPosition="true" showSearchHighlightsInPreview="true" showSelectionInPreview="true" openRemoteLinks="true" replaceUnicodeEmoji="false" lastLayoutSetsDefault="false">
<PanelProvider>
<provider providerId="com.vladsch.idea.multimarkdown.editor.swing.html.panel" providerName="Default - Swing" />
</PanelProvider>
</PreviewSettings>
<ParserSettings gitHubSyntaxChange="false" emojiShortcuts="0" emojiImages="0">
<PegdownExtensions>
<option name="ABBREVIATIONS" value="false" />
<option name="ANCHORLINKS" value="true" />
<option name="ASIDE" value="false" />
<option name="ATXHEADERSPACE" value="true" />
<option name="AUTOLINKS" value="true" />
<option name="DEFINITIONS" value="false" />
<option name="DEFINITION_BREAK_DOUBLE_BLANK_LINE" value="false" />
<option name="FENCED_CODE_BLOCKS" value="true" />
<option name="FOOTNOTES" value="false" />
<option name="HARDWRAPS" value="false" />
<option name="HTML_DEEP_PARSER" value="false" />
<option name="INSERTED" value="false" />
<option name="QUOTES" value="false" />
<option name="RELAXEDHRULES" value="true" />
<option name="SMARTS" value="false" />
<option name="STRIKETHROUGH" value="true" />
<option name="SUBSCRIPT" value="false" />
<option name="SUPERSCRIPT" value="false" />
<option name="SUPPRESS_HTML_BLOCKS" value="false" />
<option name="SUPPRESS_INLINE_HTML" value="false" />
<option name="TABLES" value="true" />
<option name="TASKLISTITEMS" value="true" />
<option name="TOC" value="false" />
<option name="WIKILINKS" value="true" />
</PegdownExtensions>
<ParserOptions>
<option name="ADMONITION_EXT" value="false" />
<option name="ATTRIBUTES_EXT" value="false" />
<option name="COMMONMARK_LISTS" value="false" />
<option name="DUMMY" value="false" />
<option name="EMOJI_SHORTCUTS" value="true" />
<option name="ENUMERATED_REFERENCES_EXT" value="false" />
<option name="FLEXMARK_FRONT_MATTER" value="false" />
<option name="GFM_LOOSE_BLANK_LINE_AFTER_ITEM_PARA" value="true" />
<option name="GFM_TABLE_RENDERING" value="true" />
<option name="GITBOOK_URL_ENCODING" value="false" />
<option name="GITHUB_LISTS" value="true" />
<option name="GITHUB_WIKI_LINKS" value="true" />
<option name="GITLAB_EXT" value="false" />
<option name="GITLAB_MATH_EXT" value="false" />
<option name="GITLAB_MERMAID_EXT" value="false" />
<option name="HEADER_ID_NON_ASCII_TO_LOWERCASE" value="false" />
<option name="HEADER_ID_NO_DUPED_DASHES" value="false" />
<option name="JEKYLL_FRONT_MATTER" value="false" />
<option name="MACROS_EXT" value="false" />
<option name="NO_TEXT_ATTRIBUTES" value="false" />
<option name="PARSE_HTML_ANCHOR_ID" value="false" />
<option name="SIM_TOC_BLANK_LINE_SPACER" value="true" />
</ParserOptions>
</ParserSettings>
<HtmlSettings headerTopEnabled="false" headerBottomEnabled="false" bodyTopEnabled="false" bodyBottomEnabled="false" embedUrlContent="false" addPageHeader="true" embedImages="false" embedHttpImages="false" imageUriSerials="false" addDocTypeHtml="true" noParaTags="false">
<GeneratorProvider>
<provider providerId="com.vladsch.idea.multimarkdown.editor.swing.html.generator" providerName="Default Swing HTML Generator" />
</GeneratorProvider>
<headerTop />
<headerBottom />
<bodyTop />
<bodyBottom />
</HtmlSettings>
<CssSettings previewScheme="UI_SCHEME" cssUri="" isCssUriEnabled="false" isCssUriSerial="false" isCssTextEnabled="false" isDynamicPageWidth="true">
<StylesheetProvider>
<provider providerId="com.vladsch.idea.multimarkdown.editor.swing.html.css" providerName="Default Swing Stylesheet" />
</StylesheetProvider>
<ScriptProviders />
<cssText />
<cssUriHistory />
</CssSettings>
<HtmlExportSettings updateOnSave="false" parentDir="$ProjectFileDir$" targetDir="$ProjectFileDir$" cssDir="" scriptDir="" plainHtml="false" imageDir="" copyLinkedImages="false" imageUniquifyType="0" targetPathType="2" targetExt="" useTargetExt="false" noCssNoScripts="false" useElementStyleAttribute="false" linkToExportedHtml="true" exportOnSettingsChange="true" regenerateOnProjectOpen="false" linkFormatType="HTTP_ABSOLUTE" />
<LinkMapSettings>
<textMaps />
</LinkMapSettings>
</component>
</project>

@ -1,3 +0,0 @@
<component name="MarkdownNavigator.ProfileManager">
<settings default="" pdf-export="" plain-text-search-scope="Project Files" />
</component>

@ -1,38 +0,0 @@
<?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="7">
<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" />
<item index="5" class="java.lang.String" itemvalue="androidx.annotation.Nullable" />
<item index="6" class="java.lang.String" itemvalue="androidx.annotation.RecentlyNullable" />
</list>
</value>
</option>
<option name="myNotNulls">
<value>
<list size="6">
<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" />
<item index="4" class="java.lang.String" itemvalue="androidx.annotation.NonNull" />
<item index="5" class="java.lang.String" itemvalue="androidx.annotation.RecentlyNonNull" />
</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>

@ -1,12 +0,0 @@
<?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>

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

@ -1,12 +1,12 @@
language: android
jdk: oraclejdk8
before_install:
- yes | sdkmanager "platforms;android-28"
- yes | sdkmanager "platforms;android-29"
env:
global:
- ANDROID_API_LEVEL=28
- ANDROID_BUILD_TOOLS_VERSION=28.0.3
- ANDROID_API_LEVEL=29
- ANDROID_BUILD_TOOLS_VERSION=29.0.3
- TRAVIS_SECURE_ENV_VARS=true
android:

@ -3,90 +3,85 @@
![Image](app/src/main/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)
[![Jitpack](https://jitpack.io/v/jenly1314/AppUpdater.svg)](https://jitpack.io/#jenly1314/AppUpdater)
[![MavenCentral](https://img.shields.io/maven-central/v/com.github.jenly1314.AppUpdater/app-updater)](https://repo1.maven.org/maven2/com/github/jenly1314/AppUpdater)
[![JCenter](https://img.shields.io/badge/JCenter-1.0.10-46C018.svg)](https://bintray.com/beta/#/jenly/maven/app-updater)
[![JitPack](https://jitpack.io/v/jenly1314/AppUpdater.svg)](https://jitpack.io/#jenly1314/AppUpdater)
[![CI](https://travis-ci.org/jenly1314/AppUpdater.svg?branch=master)](https://travis-ci.org/jenly1314/AppUpdater)
[![CircleCI](https://circleci.com/gh/jenly1314/AppUpdater.svg?style=svg)](https://circleci.com/gh/jenly1314/AppUpdater)
[![API](https://img.shields.io/badge/API-15%2B-blue.svg?style=flat)](https://android-arsenal.com/api?level=15)
[![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)
[![Blog](https://img.shields.io/badge/blog-Jenly-9933CC.svg)](https://jenly1314.github.io/)
[![QQGroup](https://img.shields.io/badge/QQGroup-20867961-blue.svg)](http://shang.qq.com/wpa/qunwpa?idkey=8fcc6a2f88552ea44b1411582c94fd124f7bb3ec227e2a400dbbfaad3dc2f5ad)
AppUpdater for Android 是一个专注于App更新,一键傻瓜式集成App版本升级的开源库。(无需担心通知栏适配;无需担心重复点击下载;无需担心App安装等问题;这些AppUpdater都已帮您处理好。)
AppUpdater for Android 是一个专注于App更新,一键傻瓜式集成App版本升级的轻量开源库。(无需担心通知栏适配;无需担心重复点击下载;无需担心App安装等问题;这些AppUpdater都已帮您处理好。)
核心库主要包括app-updater和app-dialog。
> 下载更新和弹框提示分开,是因为这本来就是两个逻辑。完全独立开来能有效的解耦。
* app-updater 主要负责后台下载更新App,无需担心下载时各种配置相关的细节,一键傻瓜式升级。
* app-dialog 主要是提供常用的Dialog和DialogFragment,简化弹框提示,样式支持高度自定义。
* app-dialog 主要是提供常用的Dialog和DialogFragment,简化弹框提示,布局样式支持自定义。
> app-updater + app-dialog 配合使用,谁用谁知道。
## 功能介绍
- [x] 专注于App更新一键傻瓜式升级
- [x] 支持下载监听
- [x] 够轻量,体积小
- [x] 支持监听下载过程
- [x] 支持下载失败,重新下载
- [x] 支持下载优先取本地缓存
- [x] 支持通知栏提示内容和过程全部可配置
- [x] 支持Android O
- [x] 支持取消下载
- [x] 支持使用OkHttpClient下载
- [x] 支持Android 10(Q)
- [x] 支持Android 11(R)
- [x] 支持Android 12(S)
## Gif 展示
![Image](GIF.gif)
> 你也可以直接下载 [演示App](https://raw.githubusercontent.com/jenly1314/AppUpdater/master/app/release/app-release.apk) 体验效果
## 引入
### Maven:
```maven
//app-updater
<dependency>
<groupId>com.king.app</groupId>
<artifactId>app-updater</artifactId>
<version>1.0.4</version>
<type>pom</type>
</dependency>
### Gradle:
//app-dialog
<dependency>
<groupId>com.king.app</groupId>
<artifactId>app-dialog</artifactId>
<version>1.0.4</version>
<type>pom</type>
</dependency>
1. 在Project的 **build.gradle** 里面添加远程仓库
```gradle
allprojects {
repositories {
//...
mavenCentral()
}
}
```
### Gradle:
2. 在Module的 **build.gradle** 里面添加引入依赖项
```gradle
//----------AndroidX 版本
//app-updater
implementation 'com.king.app:app-updater:1.0.4-androidx'
implementation 'com.github.jenly1314.AppUpdater:app-updater:1.1.3'
//app-dialog
implementation 'com.king.app:app-dialog:1.0.4-androidx'
implementation 'com.github.jenly1314.AppUpdater:app-dialog:1.1.3'
//----------Android 版本
//app-updater
implementation 'com.king.app:app-updater:1.0.4'
//app-dialog
implementation 'com.king.app:app-dialog:1.0.4'
```
### Lvy:
```lvy
//app-updater
<dependency org='com.king.app' name='app-dialog' rev='1.0.4'>
<artifact name='$AID' ext='pom'></artifact>
</dependency>
//app-dialog
<dependency org='com.king.app' name='app-dialog' rev='1.0.4'>
<artifact name='$AID' ext='pom'></artifact>
</dependency>
```
###### 如果Gradle出现compile失败的情况,可以在Project的build.gradle里面添加如下:(也可以使用上面的GitPack来complie)
以前发布至JCenter的版本
```gradle
allprojects {
repositories {
//...
maven { url 'https://dl.bintray.com/jenly/maven' }
}
}
//----------AndroidX 版本
//app-updater
implementation 'com.king.app:app-updater:1.0.10-androidx'
//app-dialog
implementation 'com.king.app:app-dialog:1.0.10-androidx'
//----------Android Support 版本
//app-updater
implementation 'com.king.app:app-updater:1.0.10'
//app-dialog
implementation 'com.king.app:app-dialog:1.0.10'
```
## 示例
@ -97,16 +92,15 @@ AppUpdater for Android 是一个专注于App更新,一键傻瓜式集成App版
```
```Java
//简单弹框升级
AppDialogConfig config = new AppDialogConfig();
AppDialogConfig config = new AppDialogConfig(context);
config.setTitle("简单弹框升级")
.setOk("升级")
.setConfirm("升级") //旧版本使用setOk
.setContent("1、新增某某功能、\n2、修改某某问题、\n3、优化某某BUG、")
.setOnClickOk(new View.OnClickListener() {
.setOnClickConfirm(new View.OnClickListener() { //旧版本使用setOnClickOk
@Override
public void onClick(View v) {
new AppUpdater.Builder()
.serUrl(mUrl)
.setFilename("AppUpdater.apk")
.setUrl(mUrl)
.build(getContext())
.start();
AppDialog.INSTANCE.dismissDialog();
@ -116,17 +110,18 @@ AppUpdater for Android 是一个专注于App更新,一键傻瓜式集成App版
```
```Java
//简单DialogFragment升级
AppDialogConfig config = new AppDialogConfig();
AppDialogConfig config = new AppDialogConfig(context);
config.setTitle("简单DialogFragment升级")
.setOk("升级")
.setConfirm("升级") //旧版本使用setOk
.setContent("1、新增某某功能、\n2、修改某某问题、\n3、优化某某BUG、")
.setOnClickOk(new View.OnClickListener() {
.setOnClickConfirm(new View.OnClickListener() { //旧版本使用setOnClickOk
@Override
public void onClick(View v) {
new AppUpdater.Builder()
.serUrl(mUrl)
.setUrl(mUrl)
.setFilename("AppUpdater.apk")
.build(getContext())
.setHttpManager(OkHttpManager.getInstance())//不设置HttpManager时,默认使用HttpsURLConnection下载,如果使用OkHttpClient实现下载,需依赖okhttp库
.start();
AppDialog.INSTANCE.dismissDialogFragment(getSupportFragmentManager());
}
@ -135,11 +130,54 @@ AppUpdater for Android 是一个专注于App更新,一键傻瓜式集成App版
```
更多使用示例请查看[App](app)。
更多使用详情,请查看[app](app)中的源码使用示例或直接查看[API帮助文档](https://javadoc.jitpack.io/com/github/jenly1314/AppUpdater/1.1.0/javadoc/index.html)
## 混淆
**app-updater** [Proguard rules](app-updater/proguard-rules.pro)
**app-dialog** [Proguard rules](app-dialog/proguard-rules.pro)
## 版本记录
#### v1.0.4:2019-6-4 //支持AndroidX版本
#### v1.1.3:2022-04-25
* 统一日志管理
* 适配Android 12(S)
* 优化细节
#### v1.1.2:2021-11-18
* AppDialog对外提供更多与WindowManager.LayoutParams相关的配置
#### v1.1.1:2021-9-14
* 对外提供更多可配置参数
* 优化细节
#### v1.1.0:2021-7-2 (从v1.1.0开始不再发布至JCenter)
* 后续版本只支持androidx,版本名称不再带有androidx标识
* 优化细节
#### v1.0.10:2021-3-4
* AppDialogConfig添加构造参数,简化自定义扩展用法
* 优化细节
#### v1.0.9:2020-12-11
* 优化默认Dialog样式的显示细节
#### v1.0.8:2020-1-2
* 支持MD5校验
* 对外提供获取Dialog方法
#### v1.0.7:2019-12-18
* 优化细节
#### v1.0.6:2019-11-27
* 新增OkHttpManager 如果使用了OkHttpManager则必须依赖[okhttp](https://github.com/square/okhttp)
* 优化细节 (progress,total 变更 int -> long)
#### v1.0.5:2019-9-4
* 支持取消下载
#### v1.0.4:2019-6-4 [开始支持AndroidX版本](https://github.com/jenly1314/AppUpdater/tree/androidx)
* 支持添加请求头
#### v1.0.3:2019-5-9
@ -173,10 +211,15 @@ AppUpdater for Android 是一个专注于App更新,一键傻瓜式集成App版
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>
CNBlogs: <a title="博客园" href="https://www.cnblogs.com/jenly" target="_blank">jenly</a>
GitHub: <a title="GitHub开源项目" href="https://github.com/jenly1314" target="_blank">jenly1314</a>
Gitee: <a title="Gitee开源项目" href="https://gitee.com/jenly1314" target="_blank">jenly1314</a>
加入QQ群: <a title="点击加入QQ群" href="http://shang.qq.com/wpa/qunwpa?idkey=8fcc6a2f88552ea44b1411582c94fd124f7bb3ec227e2a400dbbfaad3dc2f5ad" target="_blank">20867961</a>
<div>
<img src="https://jenly1314.github.io/image/jenly666.png">
<img src="https://jenly1314.github.io/image/qqgourp.png">
</div>

@ -1,5 +1,6 @@
apply plugin: 'com.android.library'
apply from: 'bintray.gradle'
//apply from: 'bintray.gradle'
apply plugin: "com.vanniktech.maven.publish"
android {
@ -11,7 +12,7 @@ android {
targetSdkVersion build_versions.targetSdk
versionCode app_version.versionCode
versionName app_version.versionName
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
@ -31,11 +32,11 @@ android {
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
testImplementation deps.test.junit
androidTestImplementation deps.test.runner
androidTestImplementation deps.test.espresso
//support
compileOnly deps.support.appcompat
testImplementation "junit:junit:$versions.junit"
androidTestImplementation "androidx.test.ext:junit:$versions.androidExtJunit"
androidTestImplementation "androidx.test.espresso:espresso-core:$versions.espressoCore"
compileOnly "androidx.appcompat:appcompat:$versions.appcompat"
}

@ -0,0 +1,3 @@
POM_NAME=App Dialog
POM_ARTIFACT_ID=app-dialog
POM_PACKAGING=aar

@ -19,3 +19,11 @@
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
-dontwarn com.king.app.dialog.**
-keep class com.king.app.dialog.**{ *;}
-keepclassmembers enum * {
public static **[] values();
public static ** valueOf(java.lang.String);
}

@ -1,11 +1,10 @@
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 androidx.test.InstrumentationRegistry;
import static org.junit.Assert.*;
@ -14,7 +13,6 @@ import static org.junit.Assert.*;
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
@RunWith(AndroidJUnit4.class)
public class ExampleInstrumentedTest {
@Test
public void useAppContext() {

@ -3,91 +3,58 @@ package com.king.app.dialog;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.support.annotation.NonNull;
import android.support.annotation.StyleRes;
import android.support.v4.app.DialogFragment;
import android.support.v4.app.FragmentManager;
import android.text.TextUtils;
import android.view.Gravity;
import android.view.KeyEvent;
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;
import androidx.annotation.StyleRes;
import androidx.fragment.app.DialogFragment;
import androidx.fragment.app.FragmentManager;
/**
* @author Jenly <a href="mailto:jenly1314@gmail.com">Jenly</a>
* App对话框封装便捷的对话框API使用时更简单
* @author <a href="mailto:jenly1314@gmail.com">Jenly</a>
*/
public enum AppDialog {
INSTANCE;
private final float DEFAULT_WIDTH_RATIO = 0.85f;
final float DEFAULT_WIDTH_RATIO = 0.85f;
private Dialog mDialog;
private String mTag;
//-------------------------------------------
/**
* 通过{@link AppDialogConfig} 创建一个视图
* @param context
* @param config 弹框配置 {@link AppDialogConfig}
* @return
*/
public View createAppDialogView(@NonNull Context context,@NonNull AppDialogConfig config){
View view = config.getView(context);
TextView tvDialogTitle = view.findViewById(config.getTitleId());
setText(tvDialogTitle,config.getTitle());
tvDialogTitle.setVisibility(config.isHideTitle() ? View.GONE : View.VISIBLE);
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() {
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);
}
}
//-------------------------------------------
/**
* {@link DialogFragment#dismiss()}
* @param fragmentManager {@link FragmentManager}
*/
public void dismissDialogFragment(FragmentManager fragmentManager){
dismissDialogFragment(fragmentManager,mTag);
mTag = null;
}
/**
* {@link DialogFragment#dismiss()}
* @param fragmentManager {@link FragmentManager}
* @param tag dialogFragment对应的标签
*/
public void dismissDialogFragment(FragmentManager fragmentManager,String tag){
if(tag!=null){
DialogFragment dialogFragment = (DialogFragment) fragmentManager.findFragmentByTag(tag);
@ -95,8 +62,12 @@ public enum AppDialog {
}
}
/**
* {@link DialogFragment#dismiss()}
* @param dialogFragment {@link DialogFragment}
*/
public void dismissDialogFragment(DialogFragment dialogFragment){
if(dialogFragment!=null){
if(dialogFragment != null){
dialogFragment.dismiss();
}
}
@ -105,7 +76,8 @@ public enum AppDialog {
/**
* 显示DialogFragment
* @param fragmentManager
* @param fragmentManager {@link FragmentManager}
* @param config {@link AppDialogConfig}
* @return
*/
public String showDialogFragment(FragmentManager fragmentManager,AppDialogConfig config){
@ -118,8 +90,8 @@ public enum AppDialog {
/**
* 显示DialogFragment
* @param fragmentManager
* @param dialogFragment
* @param fragmentManager {@link FragmentManager}
* @param dialogFragment {@link DialogFragment}
* @return
*/
public String showDialogFragment(FragmentManager fragmentManager,DialogFragment dialogFragment){
@ -131,9 +103,9 @@ public enum AppDialog {
/**
* 显示DialogFragment
* @param fragmentManager
* @param dialogFragment
* @param tag
* @param fragmentManager {@link FragmentManager}
* @param dialogFragment {@link DialogFragment}
* @param tag dialogFragment对应的标签
* @return
*/
public String showDialogFragment(FragmentManager fragmentManager,DialogFragment dialogFragment, String tag) {
@ -147,7 +119,24 @@ public enum AppDialog {
/**
* 显示弹框
* @param context
* @param config 弹框配置 {@link AppDialogConfig}
*/
public void showDialog(AppDialogConfig config){
showDialog(config,true);
}
/**
* 显示弹框
* @param config 弹框配置 {@link AppDialogConfig}
* @param isCancel 是否可取消默认为truefalse则拦截back键
*/
public void showDialog(AppDialogConfig config,boolean isCancel){
showDialog(config.getContext(),config, isCancel);
}
/**
* 显示弹框
* @param context 上下文
* @param config 弹框配置 {@link AppDialogConfig}
*/
public void showDialog(Context context,AppDialogConfig config){
@ -156,17 +145,19 @@ public enum AppDialog {
/**
* 显示弹框
* @param context
* @param context 上下文
* @param config 弹框配置 {@link AppDialogConfig}
* @param isCancel 是否可取消默认为truefalse则拦截back键
*/
public void showDialog(Context context,AppDialogConfig config,boolean isCancel){
showDialog(context,createAppDialogView(context,config),R.style.app_dialog,DEFAULT_WIDTH_RATIO,isCancel);
showDialog(context, config.buildAppDialogView(), config.getStyleId(), config.getGravity(),
config.getWidthRatio(), config.x, config.y, config.horizontalMargin, config.verticalMargin,
config.horizontalWeight, config.verticalWeight, isCancel);
}
/**
* 显示弹框
* @param context
* @param context 上下文
* @param contentView 弹框内容视图
*/
public void showDialog(Context context,View contentView){
@ -175,7 +166,7 @@ public enum AppDialog {
/**
* 显示弹框
* @param context
* @param context 上下文
* @param contentView 弹框内容视图
* @param isCancel 是否可取消默认为truefalse则拦截back键
*/
@ -185,7 +176,7 @@ public enum AppDialog {
/**
* 显示弹框
* @param context
* @param context 上下文
* @param contentView 弹框内容视图
* @param widthRatio 宽度比例根据屏幕宽度计算得来
*/
@ -195,7 +186,7 @@ public enum AppDialog {
/**
* 显示弹框
* @param context
* @param context 上下文
* @param contentView 弹框内容视图
* @param widthRatio 宽度比例根据屏幕宽度计算得来
* @param isCancel 是否可取消默认为truefalse则拦截back键
@ -204,57 +195,356 @@ public enum AppDialog {
showDialog(context,contentView,R.style.app_dialog,widthRatio,isCancel);
}
/**
* 显示弹框
* @param context 上下文
* @param contentView 弹框内容视图
* @param styleId Dialog样式
* @param widthRatio 宽度比例根据屏幕宽度计算得来
*/
public void showDialog(Context context, View contentView, @StyleRes int styleId, float widthRatio){
showDialog(context,contentView,styleId,widthRatio,true);
}
/**
* 显示弹框
* @param context 上下文
* @param contentView 弹框内容视图
* @param styleId Dialog样式
* @param widthRatio 宽度比例根据屏幕宽度计算得来
*/
public void showDialog(Context context, View contentView, @StyleRes int styleId, int gravity, float widthRatio){
showDialog(context,contentView,styleId,gravity, widthRatio, true);
}
/**
* 显示弹框
* @param context 上下文
* @param contentView 弹框内容视图
* @param styleId Dialog样式
* @param widthRatio 宽度比例根据屏幕宽度计算得来
* @param isCancel 是否可取消默认为truefalse则拦截back键
*/
public void showDialog(Context context, View contentView, @StyleRes int styleId, float widthRatio,final boolean isCancel){
showDialog(context,contentView,styleId,Gravity.NO_GRAVITY,widthRatio,isCancel);
}
/**
* 显示弹框
* @param context 上下文
* @param contentView 弹框内容视图
* @param styleId Dialog样式
* @param gravity Dialog的对齐方式
* @param widthRatio 宽度比例根据屏幕宽度计算得来
* @param isCancel 是否可取消默认为truefalse则拦截back键
*/
public void showDialog(Context context, View contentView, @StyleRes int styleId,int gravity, float widthRatio,final boolean isCancel){
showDialog(context, contentView, styleId, gravity, widthRatio, 0, 0, isCancel);
}
/**
* 显示弹框
* @param context 上下文
* @param contentView 弹框内容视图
* @param styleId Dialog样式
* @param gravity Dialog的对齐方式
* @param widthRatio 宽度比例根据屏幕宽度计算得来
* @param x x轴偏移量需与 gravity 结合使用
* @param y y轴偏移量需与 gravity 结合使用
* @param isCancel 是否可取消默认为truefalse则拦截back键
* @return
*/
public void showDialog(Context context, View contentView, @StyleRes int styleId,int gravity, float widthRatio, int x, int y, final boolean isCancel){
showDialog(context, contentView, styleId, gravity, widthRatio, x, y, 0, 0, isCancel);
}
/**
* 显示弹框
* @param context
* @param context 上下文
* @param contentView 弹框内容视图
* @param resId Dialog样式
* @param styleId Dialog样式
* @param gravity Dialog的对齐方式
* @param widthRatio 宽度比例根据屏幕宽度计算得来
* @param x x轴偏移量需与 gravity 结合使用
* @param y y轴偏移量需与 gravity 结合使用
* @param horizontalMargin 水平方向边距
* @param verticalMargin 垂直方向边距
* @param isCancel 是否可取消默认为truefalse则拦截back键
* @return
*/
public void showDialog(Context context, View contentView, @StyleRes int resId, float widthRatio){
showDialog(context,contentView,resId,widthRatio,true);
public void showDialog(Context context, View contentView, @StyleRes int styleId,int gravity, float widthRatio, int x, int y, float horizontalMargin, float verticalMargin, final boolean isCancel){
showDialog(context, contentView, styleId, gravity, widthRatio, x, y, horizontalMargin, verticalMargin, 0, 0, isCancel);
}
/**
* 显示弹框
* @param context
* @param context 上下文
* @param contentView 弹框内容视图
* @param resId Dialog样式
* @param styleId Dialog样式
* @param gravity Dialog的对齐方式
* @param widthRatio 宽度比例根据屏幕宽度计算得来
* @param x x轴偏移量需与 gravity 结合使用
* @param y y轴偏移量需与 gravity 结合使用
* @param horizontalMargin 水平方向边距
* @param verticalMargin 垂直方向边距
* @param horizontalWeight 水平方向权重
* @param verticalWeight 垂直方向权重
* @param isCancel 是否可取消默认为truefalse则拦截back键
* @return
*/
public void showDialog(Context context, View contentView, @StyleRes int resId, float widthRatio,final boolean isCancel){
public void showDialog(Context context, View contentView, @StyleRes int styleId,int gravity, float widthRatio, int x, int y, float horizontalMargin, float verticalMargin, float horizontalWeight, float verticalWeight, final boolean isCancel){
dismissDialog();
mDialog = new Dialog(context,resId);
mDialog.setContentView(contentView);
mDialog.setCanceledOnTouchOutside(false);
mDialog.setOnKeyListener(new DialogInterface.OnKeyListener() {
mDialog = createDialog(context, contentView, styleId, gravity, widthRatio, x, y, horizontalMargin, verticalMargin, horizontalWeight, verticalWeight, isCancel);
mDialog.show();
}
/**
* 设置弹框窗口配置
* @param context 上下文
* @param dialog Dialog对话框
* @param gravity Dialog的对齐方式
* @param widthRatio 宽度比例根据屏幕宽度计算得来
* @param x x轴偏移量需与 gravity 结合使用
* @param y y轴偏移量需与 gravity 结合使用
* @param horizontalMargin 水平方向边距
* @param verticalMargin 垂直方向边距
* @param horizontalWeight 水平方向权重
* @param verticalWeight 垂直方向权重
*/
private void setDialogWindow(Context context,Dialog dialog,int gravity,float widthRatio, int x, int y, float horizontalMargin, float verticalMargin, float horizontalWeight, float verticalWeight){
Window window = dialog.getWindow();
WindowManager.LayoutParams lp = window.getAttributes();
lp.width = (int)(context.getResources().getDisplayMetrics().widthPixels * widthRatio);
lp.gravity = gravity;
lp.x = x;
lp.y = y;
lp.horizontalMargin = horizontalMargin;
lp.verticalMargin = verticalMargin;
lp.horizontalWeight = horizontalWeight;
lp.verticalWeight = verticalWeight;
window.setAttributes(lp);
}
/**
* 创建弹框
* @param config 弹框配置 {@link AppDialogConfig}
*/
public Dialog createDialog(AppDialogConfig config){
return createDialog(config,true);
}
/**
* 创建弹框
* @param config 弹框配置 {@link AppDialogConfig}
* @param isCancel 是否可取消默认为truefalse则拦截back键
*/
public Dialog createDialog(AppDialogConfig config,boolean isCancel){
return createDialog(config.getContext(),config.buildAppDialogView(),config.getStyleId(),DEFAULT_WIDTH_RATIO,isCancel);
}
/**
* 创建弹框
* @param context 上下文
* @param config 弹框配置 {@link AppDialogConfig}
*/
public Dialog createDialog(Context context,AppDialogConfig config){
return createDialog(context,config,true);
}
/**
* 创建弹框
* @param context 上下文
* @param config 弹框配置 {@link AppDialogConfig}
* @param isCancel 是否可取消默认为truefalse则拦截back键
*/
public Dialog createDialog(Context context,AppDialogConfig config,boolean isCancel){
return createDialog(context,config.buildAppDialogView(),config.getStyleId(),DEFAULT_WIDTH_RATIO,isCancel);
}
/**
* 创建弹框
* @param context 上下文
* @param contentView 弹框内容视图
*/
public Dialog createDialog(Context context,View contentView){
return createDialog(context,contentView,DEFAULT_WIDTH_RATIO);
}
/**
* 创建弹框
* @param context 上下文
* @param contentView 弹框内容视图
* @param isCancel 是否可取消默认为truefalse则拦截back键
*/
public Dialog createDialog(Context context,View contentView,boolean isCancel){
return createDialog(context,contentView,R.style.app_dialog,DEFAULT_WIDTH_RATIO,isCancel);
}
/**
* 创建弹框
* @param context 上下文
* @param contentView 弹框内容视图
* @param widthRatio 宽度比例根据屏幕宽度计算得来
*/
public Dialog createDialog(Context context,View contentView,float widthRatio){
return createDialog(context,contentView,widthRatio,true);
}
/**
* 创建弹框
* @param context 上下文
* @param contentView 弹框内容视图
* @param widthRatio 宽度比例根据屏幕宽度计算得来
* @param isCancel 是否可取消默认为truefalse则拦截back键
*/
public Dialog createDialog(Context context,View contentView,float widthRatio,boolean isCancel){
return createDialog(context,contentView,R.style.app_dialog,widthRatio,isCancel);
}
/**
* 创建弹框
* @param context 上下文
* @param contentView 弹框内容视图
* @param styleId Dialog样式
* @param widthRatio 宽度比例根据屏幕宽度计算得来
*/
public Dialog createDialog(Context context, View contentView, @StyleRes int styleId, float widthRatio){
return createDialog(context,contentView,styleId,Gravity.NO_GRAVITY,widthRatio,true);
}
/**
* 创建弹框
* @param context 上下文
* @param contentView 弹框内容视图
* @param styleId Dialog样式
* @param gravity Dialog的对齐方式
* @param widthRatio 宽度比例根据屏幕宽度计算得来
*/
public Dialog createDialog(Context context, View contentView, @StyleRes int styleId,int gravity, float widthRatio){
return createDialog(context,contentView,styleId,gravity,widthRatio,true);
}
/**
* 创建弹框
* @param context 上下文
* @param contentView 弹框内容视图
* @param styleId Dialog样式
* @param widthRatio 宽度比例根据屏幕宽度计算得来
* @param isCancel 是否可取消默认为truefalse则拦截back键
*/
public Dialog createDialog(Context context, View contentView, @StyleRes int styleId, float widthRatio,final boolean isCancel){
return createDialog(context,contentView,styleId,Gravity.NO_GRAVITY,widthRatio,isCancel);
}
/**
* 创建弹框
* @param context 上下文
* @param contentView 弹框内容视图
* @param styleId Dialog样式
* @param gravity Dialog的对齐方式
* @param widthRatio 宽度比例根据屏幕宽度计算得来
* @param isCancel 是否可取消默认为truefalse则拦截back键
*/
public Dialog createDialog(Context context, View contentView, @StyleRes int styleId,int gravity, float widthRatio,final boolean isCancel){
return createDialog(context, contentView, styleId, gravity, widthRatio, 0, 0, isCancel);
}
/**
* 创建弹框
* @param context 上下文
* @param contentView 弹框内容视图
* @param styleId Dialog样式
* @param gravity Dialog的对齐方式
* @param widthRatio 宽度比例根据屏幕宽度计算得来
* @param x x轴偏移量需与 gravity 结合使用
* @param y y轴偏移量需与 gravity 结合使用
* @param isCancel 是否可取消默认为truefalse则拦截back键
* @return
*/
public Dialog createDialog(Context context, View contentView, @StyleRes int styleId,int gravity, float widthRatio, int x, int y,final boolean isCancel){
return createDialog(context, contentView, styleId, gravity, widthRatio, x, y, 0, 0, isCancel);
}
/**
* 创建弹框
* @param context 上下文
* @param contentView 弹框内容视图
* @param styleId Dialog样式
* @param gravity Dialog的对齐方式
* @param widthRatio 宽度比例根据屏幕宽度计算得来
* @param x x轴偏移量需与 gravity 结合使用
* @param y y轴偏移量需与 gravity 结合使用
* @param horizontalMargin 水平方向边距
* @param verticalMargin 垂直方向边距
* @param isCancel 是否可取消默认为truefalse则拦截back键
* @return
*/
public Dialog createDialog(Context context, View contentView, @StyleRes int styleId,int gravity, float widthRatio, int x, int y, float horizontalMargin, float verticalMargin,final boolean isCancel){
return createDialog(context, contentView, styleId, gravity, widthRatio, x, y, horizontalMargin, verticalMargin, 0, 0, isCancel);
}
/**
* 创建弹框
* @param context 上下文
* @param contentView 弹框内容视图
* @param styleId Dialog样式
* @param gravity Dialog的对齐方式
* @param widthRatio 宽度比例根据屏幕宽度计算得来
* @param x x轴偏移量需与 gravity 结合使用
* @param y y轴偏移量需与 gravity 结合使用
* @param horizontalMargin 水平方向边距
* @param verticalMargin 垂直方向边距
* @param horizontalWeight 水平方向权重
* @param verticalWeight 垂直方向权重
* @param isCancel 是否可取消默认为truefalse则拦截back键
* @return
*/
public Dialog createDialog(Context context, View contentView, @StyleRes int styleId,int gravity, float widthRatio, int x, int y, float horizontalMargin, float verticalMargin, float horizontalWeight, float verticalWeight,final boolean isCancel){
Dialog dialog = new Dialog(context,styleId);
dialog.setContentView(contentView);
dialog.setCanceledOnTouchOutside(false);
dialog.setOnKeyListener(new DialogInterface.OnKeyListener() {
@Override
public boolean onKey(DialogInterface dialog, int keyCode, KeyEvent event) {
if(keyCode == KeyEvent.KEYCODE_BACK && isCancel){
if(keyCode == KeyEvent.KEYCODE_BACK){
if(isCancel){
dismissDialog();
}
return true;
}
return false;
}
});
setDialogWindow(context,mDialog,widthRatio);
mDialog.show();
setDialogWindow(context, dialog, gravity, widthRatio, x, y, horizontalMargin, verticalMargin, horizontalWeight, verticalWeight);
return dialog;
}
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);
/**
* 获取Dialog
* @return {@link #mDialog}
*/
public Dialog getDialog(){
return mDialog;
}
/**
* {@link Dialog#dismiss()}
*/
public void dismissDialog(){
dismissDialog(mDialog);
mDialog = null;
}
private void dismissDialog(Dialog dialog){
if(dialog!=null){
/**
* {@link Dialog#dismiss()}
* @param dialog {@link Dialog}
*/
public void dismissDialog(Dialog dialog){
if(dialog != null){
dialog.dismiss();
}
}

@ -1,204 +1,761 @@
package com.king.app.dialog;
import android.annotation.TargetApi;
import android.content.Context;
import android.support.annotation.IdRes;
import android.support.annotation.LayoutRes;
import android.support.annotation.NonNull;
import android.content.res.ColorStateList;
import android.graphics.Bitmap;
import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
import android.text.util.Linkify;
import android.util.SparseArray;
import android.util.TypedValue;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.Button;
import android.widget.CompoundButton;
import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.RatingBar;
import android.widget.TextView;
import java.util.regex.Pattern;
import androidx.annotation.ColorInt;
import androidx.annotation.DrawableRes;
import androidx.annotation.IdRes;
import androidx.annotation.LayoutRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
/**
* @author Jenly <a href="mailto:jenly1314@gmail.com">Jenly</a>
* App 对话框配置
* @author <a href="mailto:jenly1314@gmail.com">Jenly</a>
*/
public class AppDialogConfig {
public class AppDialogConfig extends BaseDialogConfig{
private Context context;
private SparseArray<View> views;
private View view;
private ViewHolder viewHolder;
/**
* 布局ID
* 构造
* @param context 上下文
*/
private @LayoutRes int layoutId = R.layout.app_dialog;
public AppDialogConfig(@NonNull Context context){
this(context, R.layout.app_dialog);
}
/**
* 标题视图ID
* 构造
* @param context 上下文
* @param layoutId 布局ID
*/
private @IdRes int titleId = R.id.tvDialogTitle;
public AppDialogConfig(@NonNull Context context, @LayoutRes int layoutId){
super(layoutId);
this.context = context;
views = new SparseArray<>();
}
/**
* 内容视图ID
* 获取上下文
* @return {@link #context}
*/
private @IdRes int contentId = R.id.tvDialogContent;
public Context getContext(){
return context;
}
/**
* 取消视图ID左边按钮
* 获取对话框视图
* @param context 上下文
* @return 对话框视图
* @deprecated 即将废弃下一个版本可能会移除此方法
*/
private @IdRes int cancelId = R.id.btnDialogCancel;
@Deprecated
public View getView(@NonNull Context context){
return getDialogView();
}
/**
* 确定视图ID右边按钮
* 获取对话框视图
* @return 对话框视图
*/
private @IdRes int okId = R.id.btnDialogOK;
private View getDialogView(){
if(view == null){
view = LayoutInflater.from(context).inflate(getLayoutId(),null);
}
return view;
}
/**
* 按钮中间分割线ID
* 通过视图ID查找对应的视图
* @param id 视图ID
* @param <T> 对应的视图类
* @return 视图ID对应的视图
*/
private @IdRes int line = R.id.line;
private <T extends View> T findView(@IdRes int id){
return getDialogView().findViewById(id);
}
/**
* 标题文本
* 根据视图ID获取对应的视图
* @param id 视图ID
* @param <T> 对应的视图类
* @return 视图ID对应的视图
*/
private CharSequence title;
public <T extends View> T getView(@IdRes int id){
View v = views.get(id);
if(v == null){
v = findView(id);
views.put(id,v);
}
return (T)v;
}
/**
* 内容文本
* 通过{@link AppDialogConfig} 创建一个视图
* @return {@link View}
*/
private CharSequence content;
View buildAppDialogView(){
TextView tvDialogTitle = getView(titleId);
if(tvDialogTitle != null){
setText(tvDialogTitle,title);
tvDialogTitle.setVisibility(isHideTitle ? View.GONE : View.VISIBLE);
}
TextView tvDialogContent = getView(contentId);
if(tvDialogContent != null){
setText(tvDialogContent,content);
}
Button btnDialogCancel = getView(cancelId);
if(btnDialogCancel != null){
setText(btnDialogCancel,cancel);
btnDialogCancel.setOnClickListener(onClickCancel != null ? onClickCancel : AppDialog.INSTANCE.mOnClickDismissDialog);
btnDialogCancel.setVisibility(isHideCancel ? View.GONE : View.VISIBLE);
}
View line = getView(lineId);
if(line != null){
line.setVisibility(isHideCancel ? View.GONE : View.VISIBLE);
}
Button btnDialogConfirm = getView(confirmId);
if(btnDialogConfirm != null){
setText(btnDialogConfirm,confirm);
btnDialogConfirm.setOnClickListener(onClickConfirm != null ? onClickConfirm : AppDialog.INSTANCE.mOnClickDismissDialog);
}
return view;
}
private void setText(TextView tv, CharSequence text){
if(text != null){
tv.setText(text);
}
}
/**
* 取消按钮文本
* 获取 {@link ViewHolder}
* @return
*/
private CharSequence cancel;
public final ViewHolder getViewHolder(){
if(viewHolder == null){
viewHolder = new ViewHolder();
}
return viewHolder;
}
/**
* 确定按钮文本
* ViewHolder主要提供控件的一些常用设置适用于Dialog不适用于DialogFragment
*/
private CharSequence ok;
public final class ViewHolder {
private ViewHolder(){
}
//---------------------- 控件常用设置
/**
* 是否隐藏取消按钮如果隐藏取消则底部只显示一个按钮
* 设置视图的背景色
* {@link View#setBackgroundResource(int)}
* @param id 视图ID
* @param resId Drawable资源ID
* @return {@link View}
*/
private boolean isHideCancel;
public View setBackgroundResource(@IdRes int id,@DrawableRes int resId){
View v = getView(id);
v.setBackgroundResource(resId);
return v;
}
/**
* 是否隐藏标题
* 设置视图的背景色
* {@link View#setBackground(Drawable)}
* @param id 视图ID
* @param drawable {@link Drawable}
* @return {@link View}
*/
private boolean isHideTitle;
@TargetApi(16)
public View setBackground(@IdRes int id, Drawable drawable){
View v = getView(id);
v.setBackground(drawable);
return v;
}
private View.OnClickListener onClickCancel;
/**
* 设置视图的背景色
* {@link View#setBackgroundColor(int)}
* @param id 视图ID
* @param color 颜色
* @return {@link View}
*/
public View setBackgroundColor(@IdRes int id,@ColorInt int color){
View v = getView(id);
v.setBackgroundColor(color);
return v;
}
private View.OnClickListener onClickOk;
/**
* 设置视图的标签
* {@link View#setTag(Object)}
* @param id 视图ID
* @param tag 标签
* @return {@link View}
*/
public View setTag(@IdRes int id,Object tag){
View v = getView(id);
v.setTag(tag);
return v;
}
private View view;
/**
* 设置视图的标签
* {@link View#setTag(int, Object)}
* @param id 视图ID
* @param key 标签的key
* @param tag 标签
* @return {@link View}
*/
public View setTag(@IdRes int id,int key,Object tag){
View v = getView(id);
v.setTag(key,tag);
return v;
}
public @LayoutRes int getLayoutId() {
return layoutId;
/**
* 设置视图的可见性
* {@link View#setVisibility(int)}
* @param id 视图ID
* @param visibility 可见性
* @return {@link View}
*/
public View setVisibility(@IdRes int id, int visibility){
View v = getView(id);
v.setVisibility(visibility);
return v;
}
public AppDialogConfig setLayoutId(@LayoutRes int layoutId) {
this.layoutId = layoutId;
return this;
/**
* 设置视图的可见性
* {@link View#setVisibility(int)}
* @param id 视图ID
* @param isVisible 是否可见
* @return {@link View}
*/
public View setVisibility(@IdRes int id,boolean isVisible){
View v = getView(id);
if(isVisible){
v.setVisibility(View.VISIBLE);
}else{
v.setVisibility(View.GONE);
}
return v;
}
public int getTitleId() {
return titleId;
/**
* 设置视图的透明度
* {@link View#setAlpha(float)}
* @param id 视图ID
* @param alpha 透明度
* @return {@link View}
*/
public View setAlpha(@IdRes int id,float alpha){
View v = getView(id);
v.setAlpha(alpha);
return v;
}
public AppDialogConfig setTitleId(@IdRes int titleId) {
this.titleId = titleId;
return this;
/**
* 设置视图左方的复合绘图 {@link Drawable}
* {@link #setCompoundDrawables(int, Drawable, Drawable, Drawable, Drawable)}
* @param id 视图ID
* @param drawable {@link Drawable}
* @return {@link TextView}
*/
public TextView setCompoundDrawableLeft(@IdRes int id,Drawable drawable){
return setCompoundDrawables(id,drawable,null,null,null);
}
public @IdRes int getContentId() {
return contentId;
/**
* 设置视图上方的复合绘图 {@link Drawable}
* {@link #setCompoundDrawables(int, Drawable, Drawable, Drawable, Drawable)}
* @param id 视图ID
* @param drawable {@link Drawable}
* @return {@link TextView}
*/
public TextView setCompoundDrawableTop(@IdRes int id,Drawable drawable){
return setCompoundDrawables(id,null,drawable,null,null);
}
public AppDialogConfig setContentId(@IdRes int contentId) {
this.contentId = contentId;
return this;
/**
* 设置视图右方的复合绘图 {@link Drawable}
* {@link #setCompoundDrawables(int, Drawable, Drawable, Drawable, Drawable)}
* @param id 视图ID
* @param drawable {@link Drawable}
* @return {@link TextView}
*/
public TextView setCompoundDrawableRight(@IdRes int id,Drawable drawable){
return setCompoundDrawables(id,null,null,drawable,null);
}
public @IdRes int getCancelId() {
return cancelId;
/**
* 设置视图下方的复合绘图 {@link Drawable}
* {@link #setCompoundDrawables(int, Drawable, Drawable, Drawable, Drawable)}
* @param id 视图ID
* @param drawable {@link Drawable}
* @return {@link TextView}
*/
public TextView setCompoundDrawableBottom(@IdRes int id,Drawable drawable){
return setCompoundDrawables(id,null,null,null,drawable);
}
public AppDialogConfig setCancelId(@IdRes int cancelId) {
this.cancelId = cancelId;
return this;
/**
* 设置视图的复合绘图 {@link Drawable}
* {@link TextView#setCompoundDrawables(Drawable, Drawable, Drawable, Drawable)}
* @param id 视图ID
* @param left 左方的Drawable
* @param top 上方的Drawable
* @param right 右方的Drawable
* @param bottom 下方的Drawable
* @return {@link TextView}
*/
public TextView setCompoundDrawables(@IdRes int id,Drawable left,Drawable top,Drawable right,Drawable bottom){
TextView tv = getView(id);
tv.setCompoundDrawables(left, top, right, bottom);
return tv;
}
public @IdRes int getOkId() {
return okId;
/**
* 设置视图的可填充内距
* {@link TextView#setCompoundDrawablePadding(int)}
* @param id 视图ID
* @param padding 内填充间距
* @return
*/
public TextView setCompoundDrawablePadding(@IdRes int id,int padding){
TextView tv = getView(id);
tv.setCompoundDrawablePadding(padding);
return tv;
}
public AppDialogConfig setOkId(@IdRes int okId) {
this.okId = okId;
return this;
/**
* 设置视图的内在的复合绘图 {@link Drawable}
* {@link TextView#setCompoundDrawablesWithIntrinsicBounds(int, int, int, int)}
* @param id 视图ID
* @param left 左方的Drawable
* @param top 上方的Drawable
* @param right 右方的Drawable
* @param bottom 下方的Drawable
* @return {@link TextView}
*/
public TextView setCompoundDrawablesWithIntrinsicBounds(@IdRes int id, @Nullable Drawable left,@Nullable Drawable top,@Nullable Drawable right,@Nullable Drawable bottom){
TextView tv = getView(id);
tv.setCompoundDrawablesWithIntrinsicBounds(left, top, right, bottom);
return tv;
}
public @IdRes int getLine() {
return line;
/**
* 设置文本内容
* {@link TextView#setText(int)}
* @param id 视图ID
* @param resId 字符串资源ID
* @return {@link TextView}
*/
public TextView setText(@IdRes int id,@StringRes int resId){
TextView tv = getView(id);
tv.setText(resId);
return tv;
}
public AppDialogConfig setLine(@IdRes int line) {
this.line = line;
return this;
/**
* 设置文本内容
* {@link TextView#setText(CharSequence)}
* @param id 视图ID
* @param text 文本
* @return {@link TextView}
*/
public TextView setText(@IdRes int id,CharSequence text){
TextView tv = getView(id);
tv.setText(text);
return tv;
}
public CharSequence getTitle() {
return title;
/**
* 设置字体颜色
* {@link TextView#setTextColor(int)}
* @param id 视图ID
* @param color 颜色
* @return {@link TextView}
*/
public TextView setTextColor(@IdRes int id,int color){
TextView tv = getView(id);
tv.setTextColor(color);
return tv;
}
public AppDialogConfig setTitle(CharSequence title) {
this.title = title;
return this;
/**
* 设置字体颜色
* {@link TextView#setTextColor(ColorStateList)}
* @param id 视图ID
* @param colors 颜色状态列表
* @return {@link TextView}
*/
public TextView setTextColor(@IdRes int id,@NonNull ColorStateList colors){
TextView tv = getView(id);
tv.setTextColor(colors);
return tv;
}
public CharSequence getContent() {
return content;
/**
* 设置字体大小
* {@link TextView#setTextSize(float)}
* @param id 视图ID
* @param size 字体大小单位sp
* @return {@link TextView}
*/
public TextView setTextSize(@IdRes int id,float size){
return setTextSize(id, size);
}
public AppDialogConfig setContent(CharSequence content) {
this.content = content;
return this;
/**
* 设置字体大小
* {@link TextView#setTextSize(int, float)}
* @param id 视图ID
* @param unit 单位推荐使用 {@link TypedValue#COMPLEX_UNIT_SP}
* @param size 字体大小
* @return {@link TextView}
*/
public TextView setTextSize(@IdRes int id,int unit, float size){
TextView tv = getView(id);
tv.setTextSize(unit,size);
return tv;
}
public CharSequence getCancel() {
return cancel;
/**
* 设置字体
* {@link TextView#setTypeface(Typeface)}
* @param id 视图ID
* @param tf 字体
* @return {@link TextView}
*/
public TextView setTypeface(@IdRes int id,@Nullable Typeface tf){
TextView tv = getView(id);
tv.setTypeface(tf);
return tv;
}
public AppDialogConfig setCancel(CharSequence cancel) {
this.cancel = cancel;
return this;
/**
* 设置字体
* {@link TextView#setTypeface(Typeface, int)}
* @param id 视图ID
* @param tf 字体
* @param style 字体样式
* @return {@link TextView}
*/
public TextView setTypeface(@IdRes int id,@Nullable Typeface tf, int style){
TextView tv = getView(id);
tv.setTypeface(tf,style);
return tv;
}
public CharSequence getOk() {
return ok;
/**
* 添加链接
* {@link #addLinks(int, int)}
* @param id 视图ID
* @return {@link TextView}
*/
public TextView addLinks(@IdRes int id){
return addLinks(id,Linkify.ALL);
}
public AppDialogConfig setOk(CharSequence ok) {
this.ok = ok;
return this;
/**
* 添加链接
* {@link Linkify#addLinks(TextView, int)}
* @param id 视图ID
* @param mask 连接掩码{@link Linkify#ALL}
* @return {@link TextView}
*/
public TextView addLinks(@IdRes int id,int mask){
TextView tv = getView(id);
Linkify.addLinks(tv,mask);
return tv;
}
public boolean isHideCancel() {
return isHideCancel;
/**
* 添加链接
* {@link Linkify#addLinks(TextView, Pattern, String)}
* @param id 视图ID
* @param pattern 正则表达式模式
* @param scheme 方案
* @return {@link TextView}
*/
public TextView addLinks(@IdRes int id,@NonNull Pattern pattern,@Nullable String scheme){
TextView tv = getView(id);
Linkify.addLinks(tv,pattern,scheme);
return tv;
}
public AppDialogConfig setHideCancel(boolean hideCancel) {
isHideCancel = hideCancel;
return this;
/**
* 根据Drawable资源ID设置图像
* {@link ImageView#setImageResource(int)}
* @param id 视图ID
* @param resId Drawable资源ID
* @return {@link ImageView}
*/
public ImageView setImageResource(@IdRes int id,@DrawableRes int resId){
ImageView iv = getView(id);
iv.setImageResource(resId);
return iv;
}
public boolean isHideTitle(){
return isHideTitle;
/**
* 根据位图设置图像
* {@link ImageView#setImageBitmap(Bitmap)}
* @param id 视图ID
* @param bitmap 位图
* @return {@link ImageView}
*/
public ImageView setImageBitmap(@IdRes int id, Bitmap bitmap){
ImageView iv = getView(id);
iv.setImageBitmap(bitmap);
return iv;
}
public AppDialogConfig setHideTitle(boolean hideTitle){
isHideTitle = hideTitle;
return this;
/**
* 根据 {@link Drawable} 设置图像
* {@link ImageView#setImageResource(int)}
* @param id 视图ID
* @param drawable {@link Drawable}
* @return {@link ImageView}
*/
public ImageView setImageDrawable(@IdRes int id,Drawable drawable){
ImageView iv = getView(id);
iv.setImageDrawable(drawable);
return iv;
}
public View.OnClickListener getOnClickCancel() {
return onClickCancel;
/**
* 设置是否选中
* {@link CompoundButton#setChecked(boolean)}
* @param id 视图ID
* @param isChecked 是否选中
* @return {@link CompoundButton}
*/
public CompoundButton setChecked(@IdRes int id, boolean isChecked){
CompoundButton cb = getView(id);
cb.setChecked(isChecked);
return cb;
}
public AppDialogConfig setOnClickCancel(View.OnClickListener onClickCancel) {
this.onClickCancel = onClickCancel;
return this;
/**
* 是否选中
* {@link CompoundButton#isChecked()}
* @param id 视图ID
* @return {@code true} or {@code false}
*/
public boolean isChecked(@IdRes int id){
CompoundButton cb = getView(id);
return cb.isChecked();
}
public View.OnClickListener getOnClickOk() {
return onClickOk;
/**
* 切换
* {@link CompoundButton#toggle()}
* @param id 视图ID
* @return {@link CompoundButton}
*/
public CompoundButton toggle(@IdRes int id){
CompoundButton cb = getView(id);
cb.toggle();
return cb;
}
public AppDialogConfig setOnClickOk(View.OnClickListener onClickOk) {
this.onClickOk = onClickOk;
return this;
/**
* 设置进度值
* {@link ProgressBar#setProgress(int)}
* @param id 视图ID
* @param progress 进度
* @return {@link ProgressBar}
*/
public ProgressBar setProgress(@IdRes int id, int progress){
ProgressBar progressBar = getView(id);
progressBar.setProgress(progress);
return progressBar;
}
public View getView(@NonNull Context context){
if(view == null){
view = LayoutInflater.from(context).inflate(layoutId,null);
/**
* 设置最大进度值
* {@link ProgressBar#setMax(int)}
* @param id 视图ID
* @param max 最大进度值
* @return {@link ProgressBar}
*/
public ProgressBar setMax(@IdRes int id,int max){
ProgressBar progressBar = getView(id);
progressBar.setMax(max);
return progressBar;
}
/**
* 设置评分
* {@link RatingBar#setRating(float)}
* @param id 视图ID
* @param rating 评分
* @return {@link RatingBar}
*/
public RatingBar setRating(@IdRes int id, float rating){
RatingBar ratingBar = getView(id);
ratingBar.setRating(rating);
return ratingBar;
}
/**
* 设置评分和最大评分值
* {@link RatingBar#setRating(float)} and {@link RatingBar#setMax(int)}
* @param id 视图ID
* @param rating 评分
* @param max 最大评分值
* @return {@link RatingBar}
*/
public RatingBar setRating(@IdRes int id,float rating,int max){
RatingBar ratingBar = getView(id);
ratingBar.setRating(rating);
ratingBar.setMax(max);
return ratingBar;
}
/**
* 设置星星数量
* {@link RatingBar#setNumStars(int)}
* @param id 视图ID
* @param numStars 星星数量
* @return {@link RatingBar}
*/
public RatingBar setNumStars(@IdRes int id,int numStars){
RatingBar ratingBar = getView(id);
ratingBar.setNumStars(numStars);
return ratingBar;
}
/**
* 设置是否选择
* {@link View#setSelected(boolean)}
* @param id 视图ID
* @param selected 是否选择
* @return {@link View}
*/
public View setSelected(@IdRes int id,boolean selected){
View view = getView(id);
view.setSelected(selected);
return view;
}
/**
* 是否选择
* {@link View#isSelected()}
* @param id 视图ID
* @return {@code true} or {@code false}
*/
public boolean isSelected(@IdRes int id){
return getView(id).isSelected();
}
/**
* 设置是否启用
* {@link View#setEnabled(boolean)}
* @param id 视图ID
* @param enabled 是否启用
* @return {@link View}
*/
public View setEnabled(@IdRes int id,boolean enabled){
View view = getView(id);
view.setEnabled(enabled);
return view;
}
/**
* 是否启用
* {@link View#isEnabled()}
* @param id 视图ID
* @return {@code true} or {@code false}
*/
public boolean isEnabled(@IdRes int id){
return getView(id).isEnabled();
}
//---------------------- 监听事件
/**
* 设置点击监听事
* {@link View#setOnClickListener(View.OnClickListener)}
* @param id 视图ID
* @param onClickListener {@link View.OnClickListener}
*/
public void setOnClickListener(@IdRes int id, View.OnClickListener onClickListener){
getView(id).setOnClickListener(onClickListener);
}
/**
* 设置触摸监听
* {@link View#setOnTouchListener(View.OnTouchListener)}
* @param id 视图ID
* @param onTouchListener {@link View.OnTouchListener}
*/
public void setOnTouchListener(@IdRes int id, View.OnTouchListener onTouchListener){
getView(id).setOnTouchListener(onTouchListener);
}
/**
* 设置长按监听
* {@link View#setOnLongClickListener(View.OnLongClickListener)}
* @param id 视图ID
* @param onLongClickListener {@link View.OnLongClickListener}
*/
public void setOnLongClickListener(@IdRes int id, View.OnLongClickListener onLongClickListener){
getView(id).setOnLongClickListener(onLongClickListener);
}
/**
* 设置按键监听
* {@link View#setOnKeyListener(View.OnKeyListener)}
* @param id 视图ID
* @param onKeyListener {@link View.OnKeyListener}
*/
public void setOnKeyListener(@IdRes int id, View.OnKeyListener onKeyListener){
getView(id).setOnKeyListener(onKeyListener);
}
}
}

@ -0,0 +1,669 @@
package com.king.app.dialog;
import android.content.Context;
import android.view.Gravity;
import android.view.View;
import android.view.WindowManager;
import androidx.annotation.IdRes;
import androidx.annotation.LayoutRes;
import androidx.annotation.NonNull;
import androidx.annotation.StringRes;
import androidx.annotation.StyleRes;
/**
* 基础对话框配置
* @author <a href="mailto:jenly1314@gmail.com">Jenly</a>
*/
public class BaseDialogConfig {
/**
* 布局ID
*/
@LayoutRes int layoutId;
/**
* 标题视图ID
*/
@IdRes int titleId = R.id.tvDialogTitle;
/**
* 内容视图ID
*/
@IdRes int contentId = R.id.tvDialogContent;
/**
* 取消视图ID左边按钮
*/
@IdRes int cancelId = R.id.btnDialogCancel;
/**
* 确定视图ID右边按钮
*/
@IdRes int confirmId = R.id.btnDialogConfirm;
/**
* 按钮中间分割线ID
*/
@IdRes int lineId = R.id.line;
/**
* 样式ID
*/
@StyleRes int styleId = R.style.app_dialog;
/**
* 对话框动画样式ID
*/
@StyleRes int animationStyleId = R.style.app_dialog_scale_animation;
/**
* 标题文本
*/
CharSequence title;
/**
* 内容文本
*/
CharSequence content;
/**
* 取消按钮文本
*/
CharSequence cancel;
/**
* 确定按钮文本
*/
CharSequence confirm;
/**
* 是否隐藏取消按钮如果隐藏取消则底部只显示一个按钮
*/
boolean isHideCancel;
/**
* 是否隐藏标题
*/
boolean isHideTitle;
/**
* 宽度比例根据屏幕宽度计算得来
*/
float widthRatio = AppDialog.INSTANCE.DEFAULT_WIDTH_RATIO;
/**
* Dialog对齐方式 {@link WindowManager.LayoutParams#gravity}
*
*/
int gravity = Gravity.NO_GRAVITY;
/**
* {@link WindowManager.LayoutParams#x}
*/
int x;
/**
* {@link WindowManager.LayoutParams#y}
*/
int y;
/**
* {@link WindowManager.LayoutParams#verticalMargin}
*/
float verticalMargin;
/**
* {@link WindowManager.LayoutParams#horizontalMargin}
*/
float horizontalMargin;
/**
* {@link WindowManager.LayoutParams#horizontalWeight}
*/
float horizontalWeight;
/**
* {@link WindowManager.LayoutParams#verticalWeight}
*/
float verticalWeight;
View.OnClickListener onClickCancel;
View.OnClickListener onClickConfirm;
/**
* 构造
*/
public BaseDialogConfig(){
this(R.layout.app_dialog);
}
/**
* 构造
* @param layoutId 布局ID
*/
public BaseDialogConfig(@LayoutRes int layoutId){
this.layoutId = layoutId;
}
/**
* 布局ID
* @return 布局ID
*/
public @LayoutRes int getLayoutId() {
return layoutId;
}
/**
* 此方法即将废弃请通过构造{@link #BaseDialogConfig(int)}来初始化
* @param layoutId 布局ID
* @return {@link BaseDialogConfig}
* @deprecated 即将废弃下一个版本可能会移除此方法
*/
@Deprecated
public BaseDialogConfig setLayoutId(@LayoutRes int layoutId) {
this.layoutId = layoutId;
return this;
}
/**
* 标题视图ID
* @return 视图ID
*/
public int getTitleId() {
return titleId;
}
/**
* 设置标题视图ID
* @param titleId 视图ID
* @return {@link BaseDialogConfig}
*/
public BaseDialogConfig setTitleId(@IdRes int titleId) {
this.titleId = titleId;
return this;
}
/**
* 样式ID
* @return 样式ID
*/
public int getStyleId() {
return styleId;
}
/**
* 设置Dialog样式ID(仅对Dialog有效如果使用的是DialogFragment请使用{@link #setAnimationStyleId(int)})
* @param styleId
* @return {@link BaseDialogConfig}
*/
public BaseDialogConfig setStyleId(@StyleRes int styleId) {
this.styleId = styleId;
return this;
}
/**
* 对话框动画样式ID
* @return
*/
public int getAnimationStyleId(){
return animationStyleId;
}
/**
* 对话框动画样式ID (仅对DialogFragment有效如果使用的是Dialog请使用{@link #setStyleId(int)})
* @param animationStyleId
* @return {@link BaseDialogConfig}
*/
public BaseDialogConfig setAnimationStyleId(@StyleRes int animationStyleId) {
this.animationStyleId = animationStyleId;
return this;
}
/**
* 内容视图ID
* @return 视图ID
*/
public @IdRes int getContentId() {
return contentId;
}
/**
* 设置内容视图ID
* @param contentId 内容视图ID
* @return {@link BaseDialogConfig}
*/
public BaseDialogConfig setContentId(@IdRes int contentId) {
this.contentId = contentId;
return this;
}
/**
* 取消按钮视图ID
* @return 视图ID
*/
public @IdRes int getCancelId() {
return cancelId;
}
/**
* 设置取消按钮视图ID
* @param cancelId 取消按钮视图ID
* @return {@link BaseDialogConfig}
*/
public BaseDialogConfig setCancelId(@IdRes int cancelId) {
this.cancelId = cancelId;
return this;
}
/**
* 获取确定按钮视图ID
* @return 确定按钮视图ID
* @Deprecated 请使用 {@link #getConfirmId()}来代替后续版本可能会移除此方法
*/
@Deprecated
public @IdRes int getOkId() {
return getConfirmId();
}
/**
* 设置确定按钮视图ID
* @param okId 确定按钮视图ID
* @return {@link BaseDialogConfig}
* @Deprecated 请使用 {@link #setConfirmId(int)}来代替后续版本可能移除此方法
*/
@Deprecated
public BaseDialogConfig setOkId(@IdRes int okId) {
return setConfirmId(okId);
}
/**
* 获取确定按钮视图ID
* @return 视图ID
*/
public @IdRes int getConfirmId() {
return confirmId;
}
/**
* 设置确定按钮视图ID
* @param confirmId 确定按钮视图ID
* @return {@link BaseDialogConfig}
*/
public BaseDialogConfig setConfirmId(@IdRes int confirmId) {
this.confirmId = confirmId;
return this;
}
/**
* 分割线视图ID
* @return 视图ID
*/
public @IdRes int getLineId() {
return lineId;
}
/**
* 设置分割线视图ID
* @param lineId 分割线视图ID
* @return {@link BaseDialogConfig}
*/
public BaseDialogConfig setLineId(@IdRes int lineId) {
this.lineId = lineId;
return this;
}
/**
* 标题
* @return 标题
*/
public CharSequence getTitle() {
return title;
}
/**
* 设置标题
* @param title 标题
* @return {@link BaseDialogConfig}
*/
public BaseDialogConfig setTitle(CharSequence title) {
this.title = title;
return this;
}
/**
* 设置标题
* @param context 上下文
* @param resId 标题资源ID
* @return {@link BaseDialogConfig}
*/
public BaseDialogConfig setTitle(@NonNull Context context, @StringRes int resId) {
this.title = context.getString(resId);
return this;
}
/**
* 文本内容
* @return 文本内容
*/
public CharSequence getContent() {
return content;
}
/**
* 设置文本内容
* @param content 文本内容
* @return {@link BaseDialogConfig}
*/
public BaseDialogConfig setContent(CharSequence content) {
this.content = content;
return this;
}
/**
* 取消按钮文本内容
* @return 取消按钮文本内容
*/
public CharSequence getCancel() {
return cancel;
}
/**
* 设置取消按钮文本内容
* @param cancel 取消按钮文本内容
* @return {@link BaseDialogConfig}
*/
public BaseDialogConfig setCancel(CharSequence cancel) {
this.cancel = cancel;
return this;
}
/**
* 设置取消按钮文本内容
* @param context 上下文
* @param resId 取消按钮文本内容资源ID
* @return {@link BaseDialogConfig}
*/
public BaseDialogConfig setCancel(@NonNull Context context, @StringRes int resId) {
this.cancel = context.getString(resId);
return this;
}
/**
* 获取确定按钮文本内容
* @return 确定按钮文本内容
* @deprecated 请使用 {@link #getConfirm()} 来代替后续版本可能会移除此方法
*/
public CharSequence getOk() {
return getConfirm();
}
/**
* 设置确定按钮文本内容
* @param ok 确定按钮文本内容
* @return {@link BaseDialogConfig}
* @deprecated 请使用 {@link #setConfirm(CharSequence)} 来代替后续版本可能会移除此方法
*/
public BaseDialogConfig setOk(CharSequence ok) {
return setConfirm(ok);
}
/**
* 设置确定按钮文本内容
* @param context 上下文
* @param resId 确定按钮文本内容资源ID
* @return {@link BaseDialogConfig}
* @deprecated 请使用 {@link #setConfirm(Context, int)}来代替后续版本可能会移除此方法
*/
public BaseDialogConfig setOk(@NonNull Context context, @StringRes int resId) {
return setConfirm(context,resId);
}
/**
* 确定按钮文本内容
* @return 确定按钮文本内容
*/
public CharSequence getConfirm() {
return confirm;
}
/**
* 设置确定按钮文本内容
* @param confirm 确定按钮文本内容
* @return {@link BaseDialogConfig}
*/
public BaseDialogConfig setConfirm(CharSequence confirm) {
this.confirm = confirm;
return this;
}
/**
* 设置确定按钮文本内容
* @param context 上下文
* @param resId 确定按钮文本内容资源ID
* @return {@link BaseDialogConfig}
*
*/
public BaseDialogConfig setConfirm(@NonNull Context context, @StringRes int resId) {
this.confirm = context.getString(resId);
return this;
}
/**
* 是否隐藏取消按钮
* @return {@link #isHideCancel}
*/
public boolean isHideCancel() {
return isHideCancel;
}
/**
* 设置是否隐藏取消按钮
* @param hideCancel 是否隐藏取消按钮
* @return {@link BaseDialogConfig}
*/
public BaseDialogConfig setHideCancel(boolean hideCancel) {
isHideCancel = hideCancel;
return this;
}
/**
* 是否隐藏标题
* @return {@link #isHideTitle}
*/
public boolean isHideTitle(){
return isHideTitle;
}
/**
* 设置是否隐藏标题
* @param hideTitle 是否隐藏标题
* @return {@link BaseDialogConfig}
*/
public BaseDialogConfig setHideTitle(boolean hideTitle){
isHideTitle = hideTitle;
return this;
}
/**
* Dialog的宽度比例根据屏幕宽度计算得来
* @return {@link #widthRatio}
*/
public float getWidthRatio() {
return widthRatio;
}
/**
* 设置Dialog的宽度比例根据屏幕宽度计算得来
* @param widthRatio Dialog的宽度比例默认值{@link AppDialog#DEFAULT_WIDTH_RATIO}
* @return {@link BaseDialogConfig}
*/
public BaseDialogConfig setWidthRatio(float widthRatio){
this.widthRatio = widthRatio;
return this;
}
/**
* Dialog的对齐方式 {@link WindowManager.LayoutParams#gravity}
* @return Dialog的对齐方式
*/
public int getGravity() {
return gravity;
}
/**
* 设置Dialog的对齐方式 {@link WindowManager.LayoutParams#gravity}
* @param gravity Dialog的对齐方式
* @return {@link BaseDialogConfig}
*/
public BaseDialogConfig setGravity(int gravity){
this.gravity = gravity;
return this;
}
/**
* 取消按钮点击监听不设置默认点击关闭弹框
* @return 取消按钮点击监听
*/
public View.OnClickListener getOnClickCancel() {
return onClickCancel;
}
/**
* 设置取消按钮点击监听不设置默认点击关闭弹框
* @param onClickCancel 取消按钮点击监听
* @return {@link BaseDialogConfig}
*/
public BaseDialogConfig setOnClickCancel(View.OnClickListener onClickCancel) {
this.onClickCancel = onClickCancel;
return this;
}
/**
* 确定按钮点击监听不设置默认点击关闭弹框
* @return 确定按钮点击监听
*/
public View.OnClickListener getOnClickConfirm() {
return onClickConfirm;
}
/**
* 设置确定按钮点击监听不设置默认点击关闭弹框
* @param onClickConfirm 确定按钮点击监听
* @return {@link BaseDialogConfig}
*/
public BaseDialogConfig setOnClickConfirm(View.OnClickListener onClickConfirm) {
this.onClickConfirm = onClickConfirm;
return this;
}
/**
* 获取确定按钮点击监听不设置默认点击关闭弹框
* @return 确定按钮点击监听
* @deprecated 请使用 {@link #getOnClickConfirm()}来代替后续版本可能会移除此方法
*/
public View.OnClickListener getOnClickOk() {
return getOnClickConfirm();
}
/**
* 设置确定按钮点击监听不设置默认点击关闭弹框
* @param onClickOk 确定按钮点击监听
* @return {@link BaseDialogConfig}
* @deprecated 请使用 {@link #setOnClickConfirm(View.OnClickListener)}来代替后续版本可能会移除此方法
*/
public BaseDialogConfig setOnClickOk(View.OnClickListener onClickOk) {
return setOnClickConfirm(onClickOk);
}
/**
* {@link WindowManager.LayoutParams#x}
* @return {@link #x}
*/
public int getX() {
return x;
}
/**
* {@link WindowManager.LayoutParams#x}
* @param x x轴坐标
*/
public BaseDialogConfig setX(int x) {
this.x = x;
return this;
}
/**
* {@link WindowManager.LayoutParams#y}
* @return {@link #y}
*/
public int getY() {
return y;
}
/**
* {@link WindowManager.LayoutParams#y}
* @param y y轴坐标
* @return {@link BaseDialogConfig}
*/
public BaseDialogConfig setY(int y) {
this.y = y;
return this;
}
/**
* {@link WindowManager.LayoutParams#verticalMargin}
* @return {@link #verticalMargin}
*/
public float getVerticalMargin() {
return verticalMargin;
}
/**
* {@link WindowManager.LayoutParams#verticalMargin}
* @param verticalMargin 垂直边距
*/
public void setVerticalMargin(float verticalMargin) {
this.verticalMargin = verticalMargin;
}
/**
* {@link WindowManager.LayoutParams#horizontalMargin}
* @return {@link #horizontalMargin}
*/
public float getHorizontalMargin() {
return horizontalMargin;
}
/**
* {@link WindowManager.LayoutParams#horizontalMargin}
* @param horizontalMargin 水平边距
* @return {@link BaseDialogConfig}
*/
public BaseDialogConfig setHorizontalMargin(float horizontalMargin) {
this.horizontalMargin = horizontalMargin;
return this;
}
/**
* {@link WindowManager.LayoutParams#horizontalWeight}
* @return {@link #horizontalWeight}
*/
public float getHorizontalWeight() {
return horizontalWeight;
}
/**
* {@link WindowManager.LayoutParams#horizontalWeight}
* @param horizontalWeight 水平方向权重
* @return {@link BaseDialogConfig}
*/
public BaseDialogConfig setHorizontalWeight(float horizontalWeight) {
this.horizontalWeight = horizontalWeight;
return this;
}
/**
* {@link WindowManager.LayoutParams#verticalWeight}
* @return {@link #verticalWeight}
*/
public float getVerticalWeight() {
return verticalWeight;
}
/**
* {@link WindowManager.LayoutParams#verticalWeight}
* @param verticalWeight 垂直方向权重
* @return {@link BaseDialogConfig}
*/
public BaseDialogConfig setVerticalWeight(float verticalWeight) {
this.verticalWeight = verticalWeight;
return this;
}
}

@ -1,22 +1,24 @@
package com.king.app.dialog.fragment;
import android.app.Dialog;
import android.content.Context;
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.BaseDialogConfig;
import com.king.app.dialog.R;
/**
* App对话框 Fragment封装便捷的对话框API使用时更简单
* @author Jenly <a href="mailto:jenly1314@gmail.com">Jenly</a>
*/
public class AppDialogFragment extends BaseDialogFragment {
private AppDialogConfig config;
public static AppDialogFragment newInstance(AppDialogConfig config) {
private BaseDialogConfig config;
public static AppDialogFragment newInstance(BaseDialogConfig config) {
Bundle args = new Bundle();
AppDialogFragment fragment = new AppDialogFragment();
fragment.config = config;
@ -26,38 +28,53 @@ public class AppDialogFragment extends BaseDialogFragment {
@Override
public int getRootLayoutId() {
if(config == null){
config = new AppDialogConfig();
}
if(config != null){
return config.getLayoutId();
}
return R.layout.app_dialog;
}
public void init(View rootView){
if(config!=null){
if(config != null){
TextView tvDialogTitle = rootView.findViewById(config.getTitleId());
if(tvDialogTitle != null){
setText(tvDialogTitle,config.getTitle());
tvDialogTitle.setVisibility(config.isHideTitle() ? View.GONE : View.VISIBLE);
}
TextView tvDialogContent = rootView.findViewById(config.getContentId());
if(tvDialogContent != null){
setText(tvDialogContent,config.getContent());
}
Button btnDialogCancel = rootView.findViewById(config.getCancelId());
if(btnDialogCancel != null){
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);
View line = rootView.findViewById(config.getLineId());
if(line != null){
line.setVisibility(config.isHideCancel() ? View.GONE : View.VISIBLE);
}catch (Exception e){
}
Button btnDialogConfirm = rootView.findViewById(config.getConfirmId());
if(btnDialogConfirm != null){
setText(btnDialogConfirm,config.getConfirm());
btnDialogConfirm.setOnClickListener(config.getOnClickConfirm() != null ? config.getOnClickConfirm() : getOnClickDismiss());
}
Button btnDialogOK = rootView.findViewById(config.getOkId());
setText(btnDialogOK,config.getOk());
btnDialogOK.setOnClickListener(config.getOnClickOk() != null ? config.getOnClickOk() : getOnClickDismiss());
}
}
@Override
protected void initDialogWindow(Context context, Dialog dialog, int gravity, float widthRatio, int x, int y, float horizontalMargin, float verticalMargin, float horizontalWeight, float verticalWeight, int animationStyleId) {
if(config != null){
super.initDialogWindow(context,dialog,config.getGravity(), config.getWidthRatio(), config.getX(), config.getY(), config.getHorizontalMargin(), config.getVerticalMargin(), config.getHorizontalWeight(), config.getVerticalWeight(), config.getAnimationStyleId());
}else{
super.initDialogWindow(context, dialog, gravity, widthRatio, x, y, horizontalMargin, verticalMargin, horizontalWeight, verticalWeight, animationStyleId);
}
}
}

@ -5,10 +5,7 @@ import android.content.Context;
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.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@ -18,7 +15,12 @@ import android.widget.TextView;
import com.king.app.dialog.R;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.DialogFragment;
/**
* 基础对话框 Fragment
* @author Jenly <a href="mailto:jenly1314@gmail.com">Jenly</a>
*/
public abstract class BaseDialogFragment extends DialogFragment {
@ -27,38 +29,84 @@ 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;
}
@NonNull
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
super.getDialog().getWindow().getAttributes().windowAnimations = R.style.app_dialog_animation;
getDialog().setCanceledOnTouchOutside(false);
setDialogWindow(getContext(),getDialog(),DEFAULT_WIDTH_RATIO);
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
Dialog dialog = super.onCreateDialog(savedInstanceState);
dialog.setCanceledOnTouchOutside(false);
dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
initDialogWindow(getContext(),dialog,Gravity.NO_GRAVITY,DEFAULT_WIDTH_RATIO,0, 0, 0, 0, 0, 0, R.style.app_dialog_scale_animation);
return dialog;
}
private void setDialogWindow(Context context, Dialog dialog, float widthRatio){
/**
* 初始化对话框视图
* @param context 上下文
* @param dialog 对话框
* @param gravity 对齐方式
* @param widthRatio 宽度比例根据屏幕宽度计算得来
* @param x x轴偏移量需与 gravity 结合使用
* @param y y轴偏移量需与 gravity 结合使用
* @param horizontalMargin 水平方向边距
* @param verticalMargin 垂直方向边距
* @param horizontalWeight 水平方向权重
* @param verticalWeight 垂直方向权重
* @param animationStyleId 话框动画样式ID
*/
protected void initDialogWindow(Context context,Dialog dialog,int gravity,float widthRatio, int x, int y, float horizontalMargin, float verticalMargin, float horizontalWeight, float verticalWeight, int animationStyleId){
setDialogWindow(context, dialog, gravity, widthRatio, x, y, horizontalMargin, verticalMargin, horizontalWeight, verticalWeight, animationStyleId);
}
/**
* 设置弹框窗口配置
* @param context
* @param dialog
* @param gravity Dialog的对齐方式
* @param widthRatio 宽度比例根据屏幕宽度计算得来
* @param x x轴偏移量需与 gravity 结合使用
* @param y y轴偏移量需与 gravity 结合使用
* @param horizontalMargin 水平方向边距
* @param verticalMargin 垂直方向边距
* @param horizontalWeight 水平方向权重
* @param verticalWeight 垂直方向权重
* @param animationStyleId 动画样式
*/
private void setDialogWindow(Context context,Dialog dialog,int gravity,float widthRatio,int x, int y, float horizontalMargin, float verticalMargin, float horizontalWeight, float verticalWeight, int animationStyleId){
Window window = dialog.getWindow();
window.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
WindowManager.LayoutParams lp = window.getAttributes();
lp.windowAnimations = animationStyleId;
lp.width = (int)(context.getResources().getDisplayMetrics().widthPixels * widthRatio);
lp.gravity = gravity;
lp.x = x;
lp.y = y;
lp.horizontalMargin = horizontalMargin;
lp.verticalMargin = verticalMargin;
lp.horizontalWeight = horizontalWeight;
lp.verticalWeight = verticalWeight;
window.setAttributes(lp);
}
/**
* 根视图
* @return
*/
protected View getRootView(){
return mRootView;
}
protected void setText(TextView tv, CharSequence text){
if(!TextUtils.isEmpty(text)){
if(text != null){
tv.setText(text);
}
}
@ -74,8 +122,16 @@ public abstract class BaseDialogFragment extends DialogFragment {
}
};
/**
* 获取根布局ID
* @return 根布局ID
*/
public abstract int getRootLayoutId();
/**
* 初始化
* @param rootView
*/
public abstract void init(View rootView);
}

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="200">
<translate
android:fromXDelta="0"
android:toXDelta="0"
android:fromYDelta="100%"
android:toYDelta="0%" />
</set>

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="200">
<translate
android:fromXDelta="0"
android:toXDelta="0"
android:fromYDelta="0%"
android:toYDelta="100%" />
</set>

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="200">
<translate
android:fromXDelta="-100%"
android:toXDelta="0%"
android:fromYDelta="0"
android:toYDelta="0" />
</set>

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="200">
<translate
android:fromXDelta="0%"
android:toXDelta="-100%"
android:fromYDelta="0"
android:toYDelta="0" />
</set>

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="200">
<translate
android:fromXDelta="100%"
android:toXDelta="0%"
android:fromYDelta="0"
android:toYDelta="0" />
</set>

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="200">
<translate
android:fromXDelta="0%"
android:toXDelta="100%"
android:fromYDelta="0"
android:toYDelta="0" />
</set>

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

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

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

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

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

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

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

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

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

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

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="200">
<translate
android:fromXDelta="0"
android:toXDelta="0"
android:fromYDelta="-100%"
android:toYDelta="0%" />
</set>

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="200">
<translate
android:fromXDelta="0"
android:toXDelta="0"
android:fromYDelta="0%"
android:toYDelta="-100%" />
</set>

@ -11,8 +11,10 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:minHeight="40dp"
android:padding="6dp"
android:paddingTop="10dp"
android:paddingBottom="10dp"
android:paddingLeft="16dp"
android:paddingRight="16dp"
android:lines="1"
android:textSize="@dimen/app_dialog_title_text_size"
android:textColor="@color/app_dialog_title_color"
@ -21,12 +23,14 @@
android:id="@+id/tvDialogContent"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="10dp"
android:paddingTop="10dp"
android:paddingBottom="10dp"
android:paddingLeft="16dp"
android:paddingRight="16dp"
android:textSize="@dimen/app_dialog_content_text_size"
android:textColor="@color/app_dialog_content_color"
android:layout_marginTop="16dp"
android:layout_marginBottom="16dp"
android:lineSpacingMultiplier="1" />
android:textColor="@color/app_dialog_content_color"
android:lineSpacingMultiplier="1.2" />
<include layout="@layout/app_dialog_line_h"/>
<LinearLayout
android:orientation="horizontal"
@ -48,13 +52,13 @@
android:id="@+id/line"
layout="@layout/app_dialog_line_v"/>
<Button
android:id="@+id/btnDialogOK"
android:id="@+id/btnDialogConfirm"
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:text="@string/app_dialog_confirm"
android:textSize="@dimen/app_dialog_button_text_size"
android:textColor="@color/app_dialog_button_color_selector"
android:background="?android:attr/selectableItemBackground"/>

@ -1,13 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="colorAccent">#FF4081</color>
<color name="colorAccent">#5588FF</color>
<color name="app_dialog_title_color">#333333</color>
<color name="app_dialog_content_color">#333333</color>
<color name="app_dialog_button_normal_color">#333333</color>
<color name="app_dialog_button_pressed_color">@color/colorAccent</color>
<color name="app_dialog_line_color">#d2d2d2</color>
<color name="app_dialog_line_color">#F2F2F2</color>
</resources>

@ -6,5 +6,5 @@
<dimen name="app_dialog_title_text_size">16sp</dimen>
<dimen name="app_dialog_content_text_size">16sp</dimen>
<dimen name="app_dialog_button_text_size">18sp</dimen>
<dimen name="app_dialog_button_text_size">16sp</dimen>
</resources>

@ -3,5 +3,7 @@
<string name="app_dialog_title">提示</string>
<string name="app_dialog_cancel">取消</string>
<string name="app_dialog_ok">确认</string>
<string name="app_dialog_confirm">确定</string>
<string name="app_dialog_ok">确定</string>
</resources>

@ -2,9 +2,52 @@
<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 name="app_dialog_scale_animation" parent="android:style/Animation.Dialog">
<item name="android:windowEnterAnimation">@anim/app_dialog_scale_in</item>
<item name="android:windowExitAnimation">@anim/app_dialog_scale_out</item>
</style>
<style name="app_dialog_animation" parent="app_dialog_scale_animation">
</style>
<style name="app_dialog_left_animation" parent="android:style/Animation.Dialog">
<item name="android:windowEnterAnimation">@anim/app_dialog_left_in</item>
<item name="android:windowExitAnimation">@anim/app_dialog_left_out</item>
</style>
<style name="app_dialog_top_animation" parent="android:style/Animation.Dialog">
<item name="android:windowEnterAnimation">@anim/app_dialog_top_in</item>
<item name="android:windowExitAnimation">@anim/app_dialog_top_out</item>
</style>
<style name="app_dialog_right_animation" parent="android:style/Animation.Dialog">
<item name="android:windowEnterAnimation">@anim/app_dialog_right_in</item>
<item name="android:windowExitAnimation">@anim/app_dialog_right_out</item>
</style>
<style name="app_dialog_bottom_animation" parent="android:style/Animation.Dialog">
<item name="android:windowEnterAnimation">@anim/app_dialog_bottom_in</item>
<item name="android:windowExitAnimation">@anim/app_dialog_bottom_out</item>
</style>
<style name="app_dialog_scale_left_animation" parent="android:style/Animation.Dialog">
<item name="android:windowEnterAnimation">@anim/app_dialog_scale_left_in</item>
<item name="android:windowExitAnimation">@anim/app_dialog_scale_left_out</item>
</style>
<style name="app_dialog_scale_top_animation" parent="android:style/Animation.Dialog">
<item name="android:windowEnterAnimation">@anim/app_dialog_scale_top_in</item>
<item name="android:windowExitAnimation">@anim/app_dialog_scale_top_out</item>
</style>
<style name="app_dialog_scale_right_animation" parent="android:style/Animation.Dialog">
<item name="android:windowEnterAnimation">@anim/app_dialog_scale_right_in</item>
<item name="android:windowExitAnimation">@anim/app_dialog_scale_right_out</item>
</style>
<style name="app_dialog_scale_bottom_animation" parent="android:style/Animation.Dialog">
<item name="android:windowEnterAnimation">@anim/app_dialog_scale_bottom_in</item>
<item name="android:windowExitAnimation">@anim/app_dialog_scale_bottom_out</item>
</style>
<!-- dialog style -->
@ -15,7 +58,7 @@
<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>
<item name="android:windowAnimationStyle">@style/app_dialog_scale_animation</item>
</style>
</resources>

@ -1,5 +1,6 @@
apply plugin: 'com.android.library'
apply from: 'bintray.gradle'
//apply from: 'bintray.gradle'
apply plugin: "com.vanniktech.maven.publish"
android {
@ -11,7 +12,7 @@ android {
targetSdkVersion build_versions.targetSdk
versionCode app_version.versionCode
versionName app_version.versionName
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
@ -30,11 +31,11 @@ android {
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
testImplementation deps.test.junit
androidTestImplementation deps.test.runner
androidTestImplementation deps.test.espresso
//support
compileOnly deps.support.appcompat
testImplementation "junit:junit:$versions.junit"
androidTestImplementation "androidx.test.ext:junit:$versions.androidExtJunit"
androidTestImplementation "androidx.test.espresso:espresso-core:$versions.espressoCore"
compileOnly "androidx.appcompat:appcompat:$versions.appcompat"
compileOnly "com.squareup.okhttp3:okhttp:$versions.okhttp"
}

@ -0,0 +1,3 @@
POM_NAME=App Updater
POM_ARTIFACT_ID=app-updater
POM_PACKAGING=aar

@ -19,3 +19,13 @@
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
-dontwarn com.king.app.updater.**
-keep class com.king.app.updater.**{ *;}
-keep class * extends com.king.app.updater.**{ *;}
-keep class * implements com.king.app.updater.**{ *;}
-keepattributes InnerClasses
-keep class * implements android.os.Parcelable {
public static final android.os.Parcelable$Creator *;
}

@ -1,12 +1,13 @@
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 androidx.test.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
import static org.junit.Assert.*;
/**

@ -2,20 +2,17 @@
package="com.king.app.updater">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.VIBRATE"/>
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
<application>
<service android:name=".service.DownloadService"/>
<service android:name="com.king.app.updater.service.DownloadService"/>
<provider
android:name=".provider.AppUpdaterFileProvider"
android:authorities="${applicationId}.fileProvider"
android:name="com.king.app.updater.provider.AppUpdaterFileProvider"
android:authorities="${applicationId}.AppUpdaterFileProvider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data

@ -6,19 +6,22 @@ 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 android.util.Log;
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.http.OkHttpManager;
import com.king.app.updater.service.DownloadService;
import com.king.app.updater.util.PermissionUtils;
import java.util.Map;
import androidx.annotation.DrawableRes;
import androidx.annotation.NonNull;
/**
* @author Jenly <a href="mailto:jenly1314@gmail.com">Jenly</a>
@ -54,11 +57,24 @@ public class AppUpdater {
mConfig.setUrl(url);
}
/**
* 设置下载更新进度回调
* @param callback
* @return
*/
public AppUpdater setUpdateCallback(UpdateCallback callback){
this.mCallback = callback;
return this;
}
/**
* 设置一个IHttpManager
* @param httpManager AppUpdater内置提供{@link HttpManager} {@link OkHttpManager}两种实现
* 如果不设置将默认使用{@link HttpManager},你也可以使用{@link OkHttpManager}或自己去实现一个
* {@link IHttpManager}
* 当使用{@link OkHttpManager}必需依赖okhttp库
* @return
*/
public AppUpdater setHttpManager(IHttpManager httpManager){
this.mHttpManager = httpManager;
return this;
@ -68,19 +84,19 @@ public class AppUpdater {
* 开始下载
*/
public void start(){
if(mConfig!=null && !TextUtils.isEmpty(mConfig.getUrl())){
//如果mContext是Activity,则默认会校验一次动态权限。
if(mContext instanceof Activity){
PermissionUtils.INSTANCE.verifyReadAndWritePermissions((Activity) mContext,Constants.RE_CODE_STORAGE_PERMISSION);
if(mConfig != null && !TextUtils.isEmpty(mConfig.getUrl())){
//如果mContext是Activity,并且配置了下载路径,则默认会校验一次动态权限。
if(mContext instanceof Activity && !TextUtils.isEmpty(mConfig.getPath())){
PermissionUtils.verifyReadAndWritePermissions((Activity) mContext,Constants.RE_CODE_STORAGE_PERMISSION);
}
if(mConfig.isShowNotification() && !PermissionUtils.INSTANCE.isNotificationEnabled(mContext)){
if(mConfig.isShowNotification() && !PermissionUtils.isNotificationEnabled(mContext)){
Log.w(Constants.TAG,"Notification permission not enabled.");
}
startDownloadService();
}else{
throw new NullPointerException("Url = null");
throw new NullPointerException("Url must not be empty.");
}
}
@ -88,7 +104,6 @@ public class AppUpdater {
* 启动下载服务
*/
private void startDownloadService(){
Intent intent = new Intent(mContext, DownloadService.class);
if(mCallback!=null || mHttpManager!=null){//bindService
mServiceConnection = new ServiceConnection() {
@ -111,12 +126,28 @@ public class AppUpdater {
}
}
/**
* 取消下载
*/
public void stop(){
stopDownloadService();
}
/**
* AppUpdater构建器
* 停止下载服务
*/
private void stopDownloadService(){
Intent intent = new Intent(mContext, DownloadService.class);
intent.putExtra(Constants.KEY_STOP_DOWNLOAD_SERVICE,true);
mContext.startService(intent);
}
/**
* AppUpdater建造者
*/
public static class Builder{
private UpdateConfig mConfig;
public Builder(){
@ -128,16 +159,18 @@ public class AppUpdater {
* @param url 下载地址
* @return
*/
public Builder serUrl(@NonNull String url){
public Builder setUrl(@NonNull String url){
mConfig.setUrl(url);
return this;
}
/**
* 设置保存的路径
* @param path 下载保存的文件路径 默认SD卡/.AppUpdater目录
* 设置保存的路径建议使用默认不做设置
* @param path 下载保存的文件路径
* @return
* @deprecated 因为适配Android Q的分区存储所以此方法已弃用不建议再使用
*/
@Deprecated
public Builder setPath(String path){
mConfig.setPath(path);
return this;
@ -165,11 +198,11 @@ public class AppUpdater {
/**
* 设置通知ID
* @param notifyId 通知ID
* @param notificationId 通知ID
* @return
*/
public Builder setNotificationId(int notifyId) {
mConfig.setNotificationId(notifyId);
public Builder setNotificationId(int notificationId) {
mConfig.setNotificationId(notificationId);
return this;
}
@ -236,7 +269,7 @@ public class AppUpdater {
/**
* 设置FileProvider的authority
* @param authority FileProvider的authority默认兼容N默认值context.getPackageName() + ".fileProvider"
* @param authority FileProvider的authority默认兼容N默认值{@link Context#getPackageName() + ".AppUpdaterFileProvider"}
* @return
*/
public Builder setAuthority(String authority){
@ -255,8 +288,8 @@ public class AppUpdater {
}
/**
* 设置下载失败是是否支持点击通知栏重新下载
* @param reDownload 下载失败时是否支持点击通知栏重新下载默认true 最多重新下载3次
* 设置下载失败是否支持点击通知栏重新下载与之相关联的方法{@link #setReDownloads(int)}
* @param reDownload 下载失败时是否支持点击通知栏重新下载默认true
* @return
*/
public Builder setReDownload(boolean reDownload) {
@ -265,7 +298,19 @@ public class AppUpdater {
}
/**
* 设置要下载APK的versionCode
* 设置下载失败时最多重新下载次数与之相关联的方法{@link #setReDownload(boolean)}
* @param reDownloads 下载失败时是否支持点击通知栏重新下载默认最多重新下载3次
* @return
*/
public Builder setReDownloads(int reDownloads) {
mConfig.setReDownloads(reDownloads);
return this;
}
/**
* 设置要下载APK的versionCode用于优先取缓存时通过versionCode校验APK文件是否一致
* 缓存校验目前支持两种方式一种是通过versionCode校验{@link #setVersionCode(Integer)}一种是文件MD5校验{@link #setApkMD5(String)}推荐使用MD5校验方式
* 如果两种方式都设置了则只校验MD5
* @param versionCode 为null表示不处理默认不存在则下载存在则重新下载不为null时表示会优先校验本地是否存在已下载版本号为versionCode的APK
* 如果存在则不会重新下载(AppUpdater会自动校验packageName一致性)直接取本地APK反之重新下载
* @return
@ -275,6 +320,17 @@ public class AppUpdater {
return this;
}
/**
* 设置APK文件的MD5用于优先取缓存时通过MD5校验文件APK是否一致
* 缓存校验目前支持两种方式一种是通过versionCode校验{@link #setVersionCode(Integer)}一种是文件MD5校验{@link #setApkMD5(String)}推荐使用MD5校验方式
* 如果两种方式都设置了则只校验MD5
* @param md5 为null表示不处理如果设置了MD5则缓存APK的MD5相同时只下载一次优先取本地缓存
* @return
*/
public Builder setApkMD5(String md5) {
mConfig.setApkMD5(md5);
return this;
}
/**
* 请求头添加参数
* @param key
@ -296,9 +352,28 @@ public class AppUpdater {
return this;
}
/**
* 设置是否自动删除取消下载的文件
* @param deleteCancelFile 是否删除取消下载的文件默认为true
*/
public Builder setDeleteCancelFile(boolean deleteCancelFile){
mConfig.setDeleteCancelFile(deleteCancelFile);
return this;
}
/**
* 是否支持通过删除通知栏来取消下载默认为false
* @param cancelDownload
* @return
*/
public Builder setCancelDownload(boolean cancelDownload){
mConfig.setCancelDownload(cancelDownload);
return this;
}
public AppUpdater build(@NonNull Context context){
AppUpdater appUpdater = new AppUpdater(context,mConfig);
return appUpdater;
return new AppUpdater(context,mConfig);
}
}

@ -4,20 +4,23 @@ package com.king.app.updater;
import android.content.Context;
import android.os.Parcel;
import android.os.Parcelable;
import android.support.annotation.DrawableRes;
import com.king.app.updater.constant.Constants;
import java.util.HashMap;
import java.util.Map;
import androidx.annotation.DrawableRes;
/**
* @author Jenly <a href="mailto:jenly1314@gmail.com">Jenly</a>
*/
public class UpdateConfig implements Parcelable {
/**
* APK下载的Url
*/
private String mUrl;
/**
* 保存路径
@ -55,13 +58,17 @@ public class UpdateConfig implements Parcelable {
*/
private String mChannelName;
/**
* 默认{@link Context#getPackageName() + ".fileProvider"}
* 默认{@link Context#getPackageName() + ".AppUpdaterFileProvider"}
*/
private String mAuthority;
/**
* 下载失败是否支持点击通知栏重下载
* 下载失败是否支持点击通知栏重下载
*/
private boolean isReDownload = true;
/**
* 下载失败后最大重新下载次数
*/
private int reDownloads = 3;
/**
* 是否显示百分比
*/
@ -87,6 +94,21 @@ public class UpdateConfig implements Parcelable {
*/
private Map<String,String> mRequestProperty;
/**
* 是否删除取消下载的文件
*/
private boolean isDeleteCancelFile = true;
/**
* 是否支持通过删除通知栏来取消下载
*/
private boolean isCancelDownload = false;
/**
* APK文件的MD5
*/
private String apkMD5;
public UpdateConfig() {
@ -96,6 +118,10 @@ public class UpdateConfig implements Parcelable {
return mUrl;
}
/**
* 设置APK下载地址
* @param url 下载地址
*/
public void setUrl(String url) {
this.mUrl = url;
}
@ -104,6 +130,13 @@ public class UpdateConfig implements Parcelable {
return mPath;
}
/**
* 设置保存的路径建议使用默认不做设置
* @param path 下载保存的文件路径
* @return
* @deprecated 因为适配Android Q的分区存储所以此方法已弃用不建议再使用
*/
@Deprecated
public void setPath(String path) {
this.mPath = path;
}
@ -112,6 +145,10 @@ public class UpdateConfig implements Parcelable {
return mFilename;
}
/**
* 设置保存的文件名
* @param filename 下载的保存的apk文件名 默认优先取url文件名
*/
public void setFilename(String filename) {
this.mFilename = filename;
}
@ -120,6 +157,10 @@ public class UpdateConfig implements Parcelable {
return isShowNotification;
}
/**
* 设置是否显示通知栏
* @param isShowNotification 是否显示通知栏 默认true
*/
public void setShowNotification(boolean isShowNotification) {
this.isShowNotification = isShowNotification;
}
@ -128,6 +169,10 @@ public class UpdateConfig implements Parcelable {
return mChannelId;
}
/**
* 设置通知渠道ID
* @param channelId 通知渠道ID 默认兼容O
*/
public void setChannelId(String channelId) {
this.mChannelId = channelId;
}
@ -136,10 +181,17 @@ public class UpdateConfig implements Parcelable {
return mChannelName;
}
/**
* 设置通知渠道名称
* @param channelName 通知渠道名称 默认兼容O
*/
public void setChannelName(String channelName) {
this.mChannelName = channelName;
}
/**
* 设置通知ID
* @param notificationId 通知ID
*/
public void setNotificationId(int notificationId){
this.mNotificationId = notificationId;
}
@ -148,6 +200,11 @@ public class UpdateConfig implements Parcelable {
return this.mNotificationId;
}
/**
* 设置通知图标
* @param icon 通知栏图标 默认取App的icon
*/
public void setNotificationIcon(@DrawableRes int icon){
this.mNotificationIcon = icon;
}
@ -160,6 +217,10 @@ public class UpdateConfig implements Parcelable {
return isInstallApk;
}
/**
* 设置下载完成后知否自动触发安装APK
* @param isInstallApk 下载完成后是否自动调用安装APK默认true
*/
public void setInstallApk(boolean isInstallApk) {
this.isInstallApk = isInstallApk;
}
@ -168,6 +229,10 @@ public class UpdateConfig implements Parcelable {
return mAuthority;
}
/**
* 设置FileProvider的authority
* @param authority FileProvider的authority默认兼容N默认值{@link Context#getPackageName() + ".AppUpdaterFileProvider"}
*/
public void setAuthority(String authority) {
this.mAuthority = authority;
}
@ -176,6 +241,10 @@ public class UpdateConfig implements Parcelable {
return isShowPercentage;
}
/**
* 设置下载时通知栏是否显示下载百分比
* @param showPercentage 下载时通知栏是否显示百分比
*/
public void setShowPercentage(boolean showPercentage) {
isShowPercentage = showPercentage;
}
@ -184,14 +253,34 @@ public class UpdateConfig implements Parcelable {
return isReDownload;
}
/**
* 设置下载失败时是否支持点击通知栏重新下载与之相关联的方法{@link #setReDownloads(int)}
* @param reDownload 下载失败时是否支持点击通知栏重新下载默认true
*/
public void setReDownload(boolean reDownload) {
isReDownload = reDownload;
}
public int getReDownloads() {
return reDownloads;
}
/**
* 设置下载失败时最多重新下载次数与之相关联的方法{@link #setReDownload(boolean)}
* @param reDownloads 下载失败时是否支持点击通知栏重新下载默认最多重新下载3次
*/
public void setReDownloads(int reDownloads) {
this.reDownloads = reDownloads;
}
public boolean isVibrate() {
return isVibrate;
}
/**
* 设置通知是否震动提示
* @param vibrate 是否震动提示为true时使用通知默认震动Android O(8.0)以上设置只有初次创建channel时有效后续修改属性无效想要重新有效需修改channelId或卸载App重装
*/
public void setVibrate(boolean vibrate) {
isVibrate = vibrate;
}
@ -200,6 +289,10 @@ public class UpdateConfig implements Parcelable {
return isSound;
}
/**
* 设置通知是否铃声提示
* @param sound 是否铃声提示为true时使用通知默认铃声Android O(8.0)以上设置只有初次创建channel时有效后续修改属性无效想要重新有效需修改channelId或卸载App重装
*/
public void setSound(boolean sound) {
isSound = sound;
}
@ -208,6 +301,13 @@ public class UpdateConfig implements Parcelable {
return versionCode;
}
/**
* 设置要下载APK的versionCode用于优先取缓存时通过versionCode校验APK文件是否一致
* 缓存校验目前支持两种方式一种是通过versionCode校验{@link #setVersionCode(Integer)}一种是文件MD5校验{@link #setApkMD5(String)}推荐使用MD5校验方式
* 如果两种方式都设置了则只校验MD5
* @param versionCode 为null表示不处理默认不存在则下载存在则重新下载不为null时表示会优先校验本地是否存在已下载版本号为versionCode的APK
* 如果存在则不会重新下载(AppUpdater会自动校验packageName一致性)直接取本地APK反之重新下载
*/
public void setVersionCode(Integer versionCode){
this.versionCode = versionCode;
}
@ -216,11 +316,34 @@ public class UpdateConfig implements Parcelable {
return mRequestProperty;
}
/**
* 设置APK文件的MD5用于优先取缓存时通过MD5校验文件APK是否一致
* 缓存校验目前支持两种方式一种是通过versionCode校验{@link #setVersionCode(Integer)}一种是文件MD5校验{@link #setApkMD5(String)}推荐使用MD5校验方式
* 如果两种方式都设置了则只校验MD5
* @param md5 为null表示不处理如果设置了MD5则缓存APK的MD5相同时只下载一次优先取本地缓存
*/
public void setApkMD5(String md5){
this.apkMD5 = md5;
}
public String getApkMD5(){
return apkMD5;
}
/**
* 请求头添加参数
* @param key
* @param value
*/
public void addHeader(String key, String value){
initRequestProperty();
mRequestProperty.put(key,value);
}
/**
* 请求头添加参数
* @param headers
*/
public void addHeader(Map<String,String> headers){
initRequestProperty();
mRequestProperty.putAll(headers);
@ -232,6 +355,29 @@ public class UpdateConfig implements Parcelable {
}
}
public boolean isDeleteCancelFile() {
return isDeleteCancelFile;
}
/**
* 设置是否自动删除取消下载的文件
* @param deleteCancelFile 是否删除取消下载的文件默认为true
*/
public void setDeleteCancelFile(boolean deleteCancelFile) {
isDeleteCancelFile = deleteCancelFile;
}
public boolean isCancelDownload(){
return isCancelDownload;
}
/**
* 是否支持通过删除通知栏来取消下载默认为false
* @param cancelDownload
*/
public void setCancelDownload(boolean cancelDownload) {
isCancelDownload = cancelDownload;
}
@Override
public int describeContents() {
@ -251,18 +397,24 @@ public class UpdateConfig implements Parcelable {
dest.writeString(this.mChannelName);
dest.writeString(this.mAuthority);
dest.writeByte(this.isReDownload ? (byte) 1 : (byte) 0);
dest.writeInt(this.reDownloads);
dest.writeByte(this.isShowPercentage ? (byte) 1 : (byte) 0);
dest.writeByte(this.isVibrate ? (byte) 1 : (byte) 0);
dest.writeByte(this.isSound ? (byte) 1 : (byte) 0);
dest.writeValue(this.versionCode);
dest.writeInt(mRequestProperty!=null ? this.mRequestProperty.size():0);
if(mRequestProperty!=null){
dest.writeInt(this.mRequestProperty.size());
for (Map.Entry<String, String> entry : this.mRequestProperty.entrySet()) {
dest.writeString(entry.getKey());
dest.writeString(entry.getValue());
}
}else{
dest.writeInt(0);
}
dest.writeByte(this.isDeleteCancelFile ? (byte) 1 : (byte) 0);
dest.writeByte(this.isCancelDownload ? (byte) 1 : (byte) 0);
dest.writeString(this.apkMD5);
}
protected UpdateConfig(Parcel in) {
@ -277,6 +429,7 @@ public class UpdateConfig implements Parcelable {
this.mChannelName = in.readString();
this.mAuthority = in.readString();
this.isReDownload = in.readByte() != 0;
this.reDownloads = in.readInt();
this.isShowPercentage = in.readByte() != 0;
this.isVibrate = in.readByte() != 0;
this.isSound = in.readByte() != 0;
@ -288,6 +441,9 @@ public class UpdateConfig implements Parcelable {
String value = in.readString();
this.mRequestProperty.put(key, value);
}
this.isDeleteCancelFile = in.readByte() != 0;
this.isCancelDownload = in.readByte() != 0;
this.apkMD5 = in.readString();
}
public static final Creator<UpdateConfig> CREATOR = new Creator<UpdateConfig>() {

@ -9,7 +9,7 @@ public interface UpdateCallback {
/**
* 最开始调用(在onStart之前调用)
* @param isDownloading true 表示已经在下载false表示准备刚调用下载
* @param isDownloading true 表示已经在下载false表示当前未开始下载即将开始下载
*/
void onDownloading(boolean isDownloading);
@ -24,7 +24,7 @@ public interface UpdateCallback {
* @param total
* @param isChange 进度百分比是否有改变主要可以用来过滤无用的刷新从而降低刷新频率
*/
void onProgress(int progress,int total,boolean isChange);
void onProgress(long progress,long total,boolean isChange);
/**
* 完成

@ -1,9 +1,5 @@
package com.king.app.updater.constant;
import android.os.Environment;
import java.io.File;
/**
* @author Jenly <a href="mailto:jenly1314@gmail.com">Jenly</a>
*/
@ -23,9 +19,12 @@ public final class Constants {
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;
public static final String DEFAULT_FILE_PROVIDER = ".AppUpdaterFileProvider";
public static final String DEFAULT_DIR = "apk";
}

@ -2,10 +2,8 @@ package com.king.app.updater.http;
import android.os.AsyncTask;
import android.os.Build;
import android.support.annotation.Nullable;
import android.util.Log;
import com.king.app.updater.constant.Constants;
import com.king.app.updater.util.LogUtils;
import com.king.app.updater.util.SSLSocketFactoryUtils;
import java.io.File;
@ -18,23 +16,33 @@ import java.util.Map;
import javax.net.ssl.HttpsURLConnection;
import androidx.annotation.Nullable;
/**
* HttpManager使用 {@link HttpURLConnection} 实现的 {@link IHttpManager}
* @author Jenly <a href="mailto:jenly1314@gmail.com">Jenly</a>
*/
public class HttpManager implements IHttpManager {
private static final int HTTP_TEMP_REDIRECT = 307;
private static final int HTTP_PERM_REDIRECT = 308;
private static final int DEFAULT_TIME_OUT = 20000;
private int mTimeout;
private static HttpManager INSTANCE;
private DownloadTask mDownloadTask;
private static volatile HttpManager INSTANCE;
public static HttpManager getInstance(){
if(INSTANCE == null){
synchronized (HttpManager.class){
if(INSTANCE == null){
INSTANCE = new HttpManager();
}
}
}
return INSTANCE;
}
@ -43,20 +51,31 @@ public class HttpManager implements IHttpManager {
this(DEFAULT_TIME_OUT);
}
/**
* HttpManager对外暴露如果没有特殊需求推荐使用{@link HttpManager#getInstance()}
*/
public HttpManager(int timeout){
this.mTimeout = timeout;
}
@Override
public void download(String url, String path, String filename, @Nullable Map<String,String> requestProperty, DownloadCallback callback) {
new DownloadTask(url,path,filename,requestProperty,callback).execute();
mDownloadTask = new DownloadTask(url, path, filename, mTimeout, requestProperty, callback);
mDownloadTask.execute();
}
@Override
public void cancel() {
if(mDownloadTask != null){
mDownloadTask.isCancel = true;
}
}
/**
* 异步下载任务
*/
private class DownloadTask extends AsyncTask<Void,Integer,File> {
private static class DownloadTask extends AsyncTask<Void,Long,File> {
private String url;
private String path;
@ -69,59 +88,67 @@ public class HttpManager implements IHttpManager {
private Exception exception;
public DownloadTask(String url, String path, String filename ,@Nullable Map<String,String> requestProperty, DownloadCallback callback){
private int timeout;
private volatile boolean isCancel;
public DownloadTask(String url, String path, String filename, int timeout, @Nullable Map<String,String> requestProperty, DownloadCallback callback){
this.url = url;
this.path = path;
this.filename = filename;
this.timeout = timeout;
this.callback = callback;
this.requestProperty = requestProperty;
}
@Override
protected File doInBackground(Void... voids) {
try {
HttpsURLConnection.setDefaultSSLSocketFactory(SSLSocketFactoryUtils.createSSLSocketFactory());
HttpsURLConnection.setDefaultHostnameVerifier(SSLSocketFactoryUtils.createTrustAllHostnameVerifier());
private File download(String url) throws Exception{
HttpURLConnection connect = (HttpURLConnection)new URL(url).openConnection();
connect.setRequestMethod("GET");
connect.setRequestProperty("Accept-Encoding", "identity");
connect.setReadTimeout(mTimeout);
connect.setConnectTimeout(mTimeout);
connect.setReadTimeout(timeout);
connect.setConnectTimeout(timeout);
if(requestProperty!=null){
if(requestProperty != null){
for(Map.Entry<String,String> entry : requestProperty.entrySet()){
connect.setRequestProperty(entry.getKey(),entry.getValue());
}
}
connect.connect();
int responseCode = connect.getResponseCode();
Log.d(Constants.TAG,"Content-Type:" + connect.getContentType());
if(responseCode == HttpURLConnection.HTTP_OK){
LogUtils.d("Content-Type:" + connect.getContentType());
int responseCode = connect.getResponseCode();
switch (responseCode){
case HttpURLConnection.HTTP_OK: {
InputStream is = connect.getInputStream();
int length = connect.getContentLength();
long length = connect.getContentLength();
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
length = (int)connect.getContentLengthLong();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
length = connect.getContentLengthLong();
}
int progress = 0;
LogUtils.d( "contentLength:" + length);
long progress = 0;
byte[] buffer = new byte[4096];
byte[] buffer = new byte[8192];
int len;
File file = new File(path,filename);
File file = new File(path, filename);
FileOutputStream fos = new FileOutputStream(file);
while ((len = is.read(buffer)) != -1){
fos.write(buffer,0,len);
while ((len = is.read(buffer)) != -1) {
if (isCancel) {
cancel(true);
break;
}
fos.write(buffer, 0, len);
progress += len;
//更新进度
publishProgress(progress,length);
// 更新进度
if (length > 0) {
publishProgress(progress, length);
}
}
fos.flush();
@ -130,11 +157,36 @@ public class HttpManager implements IHttpManager {
connect.disconnect();
if(progress <= 0 && length <= 0){
throw new IllegalStateException(String.format("contentLength = %d",length));
}
return file;
}else {//连接失败
}
case HttpURLConnection.HTTP_MULT_CHOICE:
case HttpURLConnection.HTTP_MOVED_PERM:
case HttpURLConnection.HTTP_MOVED_TEMP:
case HttpURLConnection.HTTP_SEE_OTHER:
case HTTP_TEMP_REDIRECT:
case HTTP_PERM_REDIRECT: {// 重定向
String redirectUrl = connect.getHeaderField("Location");
LogUtils.d("redirectUrl = " + redirectUrl);
connect.disconnect();
return download(redirectUrl);
}
default://连接失败
throw new ConnectException(String.format("responseCode = %d",responseCode));
}
}
@Override
protected File doInBackground(Void... voids) {
try{
HttpsURLConnection.setDefaultSSLSocketFactory(SSLSocketFactoryUtils.createSSLSocketFactory());
HttpsURLConnection.setDefaultHostnameVerifier(SSLSocketFactoryUtils.createTrustAllHostnameVerifier());
return download(url);
} catch (Exception e) {
this.exception = e;
e.printStackTrace();
@ -146,7 +198,7 @@ public class HttpManager implements IHttpManager {
@Override
protected void onPreExecute() {
super.onPreExecute();
if(callback!=null){
if(callback != null){
callback.onStart(url);
}
}
@ -154,8 +206,8 @@ public class HttpManager implements IHttpManager {
@Override
protected void onPostExecute(File file) {
super.onPostExecute(file);
if(callback!=null){
if(file!=null){
if(callback != null){
if(file != null){
callback.onFinish(file);
}else{
callback.onError(exception);
@ -165,23 +217,24 @@ public class HttpManager implements IHttpManager {
}
@Override
protected void onProgressUpdate(Integer... values) {
protected void onProgressUpdate(Long... values) {
super.onProgressUpdate(values);
if(callback!=null){
if(callback != null){
if(!isCancelled()){
callback.onProgress(values[0],values[1]);
}
}
}
}
@Override
protected void onCancelled() {
super.onCancelled();
if(callback!=null){
if(callback != null){
callback.onCancel();
}
}
}
}

@ -1,12 +1,14 @@
package com.king.app.updater.http;
import android.support.annotation.Nullable;
import java.io.File;
import java.io.Serializable;
import java.util.Map;
import androidx.annotation.Nullable;
/**
* IHttpManager 默认提供 {@link HttpManager} {@link OkHttpManager} 两种实现
* @author Jenly <a href="mailto:jenly1314@gmail.com">Jenly</a>
*/
public interface IHttpManager {
@ -22,6 +24,10 @@ public interface IHttpManager {
*/
void download(String url, String path, String filename, @Nullable Map<String,String> requestProperty, DownloadCallback callback);
/**
* 取消下载
*/
void cancel();
interface DownloadCallback extends Serializable{
/**
@ -35,7 +41,7 @@ public interface IHttpManager {
* @param progress
* @param total
*/
void onProgress(int progress,int total);
void onProgress(long progress,long total);
/**
* 完成

@ -0,0 +1,235 @@
package com.king.app.updater.http;
import android.os.AsyncTask;
import com.king.app.updater.util.LogUtils;
import com.king.app.updater.util.SSLSocketFactoryUtils;
import org.apache.http.conn.ssl.SSLSocketFactory;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.net.ConnectException;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import okhttp3.Call;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
/**
* OkHttpManager使用 {@link OkHttpClient} 实现的 {@link IHttpManager}
* <p>使用 OkHttpManager 时必须依赖 OkHttp
* @author <a href="mailto:jenly1314@gmail.com">Jenly</a>
*/
public class OkHttpManager implements IHttpManager {
private static final int DEFAULT_TIME_OUT = 20000;
private OkHttpClient okHttpClient;
private DownloadTask mDownloadTask;
private static volatile OkHttpManager INSTANCE;
public static OkHttpManager getInstance(){
if(INSTANCE == null){
synchronized (HttpManager.class){
if(INSTANCE == null){
INSTANCE = new OkHttpManager();
}
}
}
return INSTANCE;
}
private OkHttpManager(){
this(DEFAULT_TIME_OUT);
}
/**
* HttpManager对外暴露如果没有特殊需求推荐使用{@link HttpManager#getInstance()}
* @param timeout 超时时间单位毫秒
*/
public OkHttpManager(int timeout){
this(new OkHttpClient.Builder()
.readTimeout(timeout, TimeUnit.MILLISECONDS)
.connectTimeout(timeout, TimeUnit.MILLISECONDS)
.sslSocketFactory(SSLSocketFactoryUtils.createSSLSocketFactory(),SSLSocketFactoryUtils.createTrustAllManager())
.hostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER)
.build());
}
/**
* HttpManager对外暴露推荐使用{@link HttpManager#getInstance()}
* @param okHttpClient {@link OkHttpClient}
*/
public OkHttpManager(@NonNull OkHttpClient okHttpClient){
this.okHttpClient = okHttpClient;
}
@Override
public void download(String url,final String path,final String filename, @Nullable Map<String, String> requestProperty,final DownloadCallback callback) {
mDownloadTask = new DownloadTask(okHttpClient, url, path, filename, requestProperty, callback);
mDownloadTask.execute();
}
@Override
public void cancel() {
if(mDownloadTask != null){
mDownloadTask.isCancel = true;
}
}
/**
* 异步下载任务
*/
private static class DownloadTask extends AsyncTask<Void,Long,File> {
private String url;
private String path;
private String filename;
private Map<String,String> requestProperty;
private DownloadCallback callback;
private Exception exception;
private OkHttpClient okHttpClient;
private volatile boolean isCancel;
public DownloadTask(OkHttpClient okHttpClient,String url, String path, String filename ,@Nullable Map<String,String> requestProperty, DownloadCallback callback){
this.okHttpClient = okHttpClient;
this.url = url;
this.path = path;
this.filename = filename;
this.callback = callback;
this.requestProperty = requestProperty;
}
@Override
protected File doInBackground(Void... voids) {
try{
Request.Builder builder = new Request.Builder()
.url(url)
.addHeader("Accept-Encoding", "identity")
.get();
if(requestProperty!=null){
for(Map.Entry<String,String> entry : requestProperty.entrySet()){
builder.addHeader(entry.getKey(),entry.getValue());
}
}
Call call = okHttpClient.newCall(builder.build());
Response response = call.execute();
if(response.isSuccessful()){
InputStream is = response.body().byteStream();
long length = response.body().contentLength();
LogUtils.d("contentLength:" + length);
long progress = 0;
byte[] buffer = new byte[8192];
int len;
File file = new File(path,filename);
FileOutputStream fos = new FileOutputStream(file);
while ((len = is.read(buffer)) != -1){
if(isCancel){
if(call != null){
call.cancel();
}
cancel(true);
break;
}
fos.write(buffer,0,len);
progress += len;
// 更新进度
if(length > 0){
publishProgress(progress,length);
}
}
fos.flush();
fos.close();
is.close();
response.close();
if(progress <= 0 && length <= 0){
throw new IllegalStateException(String.format("contentLength = %d",length));
}
return file;
}else {// 连接失败
throw new ConnectException(String.format("responseCode = %d",response.code()));
}
}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(Long... values) {
super.onProgressUpdate(values);
if(callback != null){
if(!isCancelled()){
callback.onProgress(values[0],values[1]);
}
}
}
@Override
protected void onCancelled() {
super.onCancelled();
if(callback != null){
callback.onCancel();
}
}
}
}

@ -0,0 +1,25 @@
package com.king.app.updater.notify;
import android.content.Context;
import com.king.app.updater.UpdateConfig;
import java.io.File;
import androidx.annotation.DrawableRes;
/**
* @author <a href="mailto:jenly1314@gmail.com">Jenly</a>
*/
public interface INotification {
void onStart(Context context, int notifyId, String channelId, String channelName, @DrawableRes int icon, CharSequence title, CharSequence content, boolean isVibrate, boolean isSound, boolean isCancelDownload);
void onProgress(Context context, int notifyId, String channelId, @DrawableRes int icon, CharSequence title, CharSequence content, int progress, int size, boolean isCancelDownload);
void onFinish(Context context, int notifyId, String channelId, @DrawableRes int icon, CharSequence title, CharSequence content, File file, String authority);
void onError(Context context, int notifyId, String channelId, @DrawableRes int icon, CharSequence title, CharSequence content, boolean isReDownload, UpdateConfig config);
void onCancel(Context context, int notifyId);
}

@ -0,0 +1,38 @@
package com.king.app.updater.notify;
import android.content.Context;
import com.king.app.updater.UpdateConfig;
import com.king.app.updater.util.NotificationUtils;
import java.io.File;
/**
* @author <a href="mailto:jenly1314@gmail.com">Jenly</a>
*/
public class NotificationImpl implements INotification {
@Override
public void onStart(Context context, int notifyId, String channelId, String channelName, int icon, CharSequence title, CharSequence content, boolean isVibrate, boolean isSound, boolean isCancelDownload) {
NotificationUtils.showStartNotification(context, notifyId, channelId, channelName, icon, title, content, isVibrate, isSound, isCancelDownload);
}
@Override
public void onProgress(Context context, int notifyId, String channelId, int icon, CharSequence title, CharSequence content, int progress, int size, boolean isCancelDownload) {
NotificationUtils.showProgressNotification(context, notifyId, channelId, icon, title, content, progress, size, isCancelDownload);
}
@Override
public void onFinish(Context context, int notifyId, String channelId, int icon, CharSequence title, CharSequence content, File file, String authority) {
NotificationUtils.showFinishNotification(context, notifyId, channelId, icon, title, content, file, authority);
}
@Override
public void onError(Context context, int notifyId, String channelId, int icon, CharSequence title, CharSequence content, boolean isReDownload, UpdateConfig config) {
NotificationUtils.showErrorNotification(context, notifyId, channelId, icon, title, content, isReDownload, config);
}
@Override
public void onCancel(Context context, int notifyId) {
NotificationUtils.cancelNotification(context, notifyId);
}
}

@ -1,6 +1,7 @@
package com.king.app.updater.provider;
import android.support.v4.content.FileProvider;
import androidx.core.content.FileProvider;
/**
* @author Jenly <a href="mailto:jenly1314@gmail.com">Jenly</a>

@ -1,24 +1,12 @@
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;
@ -26,12 +14,22 @@ 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.notify.INotification;
import com.king.app.updater.notify.NotificationImpl;
import com.king.app.updater.util.AppUtils;
import com.king.app.updater.util.LogUtils;
import java.io.File;
import java.util.Locale;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import androidx.core.content.ContextCompat;
/**
* 下载服务
* @author Jenly <a href="mailto:jenly1314@gmail.com">Jenly</a>
*/
public class DownloadService extends Service {
@ -41,18 +39,16 @@ public class DownloadService extends Service {
* 是否在下载防止重复下载
*/
private boolean isDownloading;
/**
* 最后更新进度用来降频刷新
*/
private int mLastProgress = -1;
/**
* 最后进度更新时间用来降频刷新
*/
private long mLastTime;
/**
* 失败后重新下载次数
*/
private int mCount = 0;
/**
* Http管理器
*/
private IHttpManager mHttpManager;
private File mApkFile;
private Context getContext(){
return this;
@ -60,13 +56,11 @@ public class DownloadService extends Service {
@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{
stopDownload();
} else if(!isDownloading){
//是否实通过通知栏触发重复下载
boolean isReDownload = intent.getBooleanExtra(Constants.KEY_RE_DOWNLOAD,false);
if(isReDownload){
@ -74,10 +68,9 @@ public class DownloadService extends Service {
}
//获取配置信息
UpdateConfig config = intent.getParcelableExtra(Constants.KEY_UPDATE_CONFIG);
startDownload(config,null,null);
}
startDownload(config,null,null, new NotificationImpl());
}else{
Log.w(Constants.TAG,"Please do not repeat the download.");
LogUtils.w("Please do not duplicate downloads.");
}
}
@ -94,18 +87,13 @@ public class DownloadService extends Service {
* @param httpManager
* @param callback
*/
public void startDownload(UpdateConfig config,IHttpManager httpManager,UpdateCallback callback){
if(config == null){
return;
}
if(callback!=null){
public void startDownload(@NonNull UpdateConfig config,@Nullable IHttpManager httpManager,@Nullable UpdateCallback callback, @Nullable INotification notification){
if(callback != null){
callback.onDownloading(isDownloading);
}
if(isDownloading){
Log.w(Constants.TAG,"Please do not repeat the download.");
LogUtils.w("Please do not duplicate downloads.");
return;
}
@ -115,53 +103,67 @@ public class DownloadService extends Service {
//如果保存路径为空则使用缓存路径
if(TextUtils.isEmpty(path)){
path = getDiskCacheDir(getContext());
path = getCacheFilesDir(getContext());
}
File dirFile = new File(path);
if(!dirFile.exists()){
dirFile.mkdir();
dirFile.mkdirs();
}
//如果文件名为空则使用路径
if(TextUtils.isEmpty(filename)){
filename = AppUtils.INSTANCE.getAppFullName(getContext(),url,getResources().getString(R.string.app_name));
filename = AppUtils.getAppFullName(getContext(),url,getResources().getString(R.string.app_name));
}
File file = new File(path,filename);
if(file.exists()){//文件是否存在
mApkFile = new File(path, filename);
if(mApkFile.exists()){//文件是否存在
Integer versionCode = config.getVersionCode();
if(versionCode!=null){
try{
if(AppUtils.INSTANCE.apkExists(getContext(),versionCode,file)){
String apkMD5 = config.getApkMD5();
//是否存在相同的apk
boolean isExistApk = false;
if(!TextUtils.isEmpty(apkMD5)){//如果存在MD5,则优先校验MD5
LogUtils.d(String.format(Locale.getDefault(),"UpdateConfig.apkMD5:%s",apkMD5));
isExistApk = AppUtils.checkFileMD5(mApkFile,apkMD5);
}else if(versionCode != null){//如果存在versionCode,则校验versionCode
LogUtils.d(String.format(Locale.getDefault(),"UpdateConfig.versionCode:%d",versionCode));
isExistApk = AppUtils.apkExists(getContext(),versionCode,mApkFile);
}
if(isExistApk){
//本地已经存在要下载的APK
Log.d(Constants.TAG,"CacheFile:" + file);
LogUtils.d("CacheFile:" + mApkFile);
if(config.isInstallApk()){
String authority = config.getAuthority();
if(TextUtils.isEmpty(authority)){//如果为空则默认
authority = getContext().getPackageName() + ".fileProvider";
authority = AppUtils.getFileProviderAuthority(getContext());
}
AppUtils.INSTANCE.installApk(getContext(),file,authority);
AppUtils.installApk(getContext(), mApkFile, authority);
}
if(callback!=null){
callback.onFinish(file);
if(callback != null){
callback.onFinish(mApkFile);
}
stopService();
return;
}
}catch (Exception e){
Log.w(Constants.TAG,e);
}
}
//删除旧文件
file.delete();
mApkFile.delete();
}
Log.d(Constants.TAG,"File:" + file);
if(httpManager != null){
httpManager.download(url,path,filename,config.getRequestProperty(),new AppDownloadCallback(config,callback));
}else{
HttpManager.getInstance().download(url,path,filename,config.getRequestProperty(),new AppDownloadCallback(config,callback));
LogUtils.d("File:" + mApkFile);
mHttpManager = httpManager != null ? httpManager : HttpManager.getInstance();
IHttpManager.DownloadCallback downloadCallback = new AppDownloadCallback(getContext(),this, config, mApkFile, callback, notification);
mHttpManager.download(url,path,filename,config.getRequestProperty(), downloadCallback);
}
/**
* 停止下载
*/
public void stopDownload(){
if(mHttpManager != null){
mHttpManager.cancel();
}
}
/**
@ -169,12 +171,19 @@ public class DownloadService extends Service {
* @param context
* @return
*/
public String getDiskCacheDir(Context context) {
if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) {
return Constants.DEFAULT_DIR_PATH;
private String getCacheFilesDir(Context context) {
File[] files = ContextCompat.getExternalFilesDirs(context, Constants.DEFAULT_DIR);
if(files != null && files.length > 0){
return files[0].getAbsolutePath();
}
File externalFilesDir = context.getExternalFilesDir(Constants.DEFAULT_DIR);
if(externalFilesDir != null){
return externalFilesDir.getAbsolutePath();
}
return context.getCacheDir().getAbsolutePath();
return new File(context.getFilesDir(), Constants.DEFAULT_DIR).getAbsolutePath();
}
/**
@ -188,7 +197,14 @@ public class DownloadService extends Service {
//---------------------------------------- DownloadCallback
public class AppDownloadCallback implements IHttpManager.DownloadCallback{
/**
* App下载回调接口
*/
public static class AppDownloadCallback implements IHttpManager.DownloadCallback {
private Context context;
private DownloadService downloadService;
public UpdateConfig config;
@ -210,12 +226,35 @@ public class DownloadService extends Service {
private boolean isReDownload;
private boolean isDeleteCancelFile;
private boolean isCancelDownload;
private UpdateCallback callback;
private INotification notification;
private AppDownloadCallback(UpdateConfig config,UpdateCallback callback){
/**
* 最后更新进度用来降频刷新
*/
private int lastProgress;
/**
* 最后进度更新时间用来降频刷新
*/
private long lastTime;
/**
* APK文件
*/
private File apkFile;
private AppDownloadCallback(Context context, DownloadService downloadService, UpdateConfig config, File apkFile, UpdateCallback callback, INotification notification){
this.context = context;
this.downloadService = downloadService;
this.config = config;
this.apkFile = apkFile;
this.callback = callback;
this.notification = notification;
this.isShowNotification = config.isShowNotification();
this.notifyId = config.getNotificationId();
@ -223,8 +262,8 @@ public class DownloadService extends Service {
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());
if(config.getNotificationIcon() <= 0){
this.notificationIcon = AppUtils.getAppIcon(context);
}else{
this.notificationIcon = config.getNotificationIcon();
}
@ -233,327 +272,173 @@ public class DownloadService extends Service {
this.authority = config.getAuthority();
if(TextUtils.isEmpty(config.getAuthority())){//如果为空则默认
authority = getContext().getPackageName() + ".fileProvider";
authority = AppUtils.getFileProviderAuthority(context);
}
this.isShowPercentage = config.isShowPercentage();
this.isReDownload = config.isReDownload();
this.isDeleteCancelFile = config.isDeleteCancelFile();
this.isCancelDownload = config.isCancelDownload();
}
//支持下载失败时重新下载,当重新下载次数不超过限制时才被允许
this.isReDownload = config.isReDownload() && downloadService.mCount < config.getReDownloads();
}
@Override
public void onStart(String url) {
Log.d(Constants.TAG,"onStart:" + 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),config.isVibrate(),config.isSound());
LogUtils.i("url:" + url);
downloadService.isDownloading = true;
lastProgress = 0;
if(isShowNotification && notification != null){
notification.onStart(context,notifyId,channelId,channelName,notificationIcon,getString(R.string.app_updater_start_notification_title),getString(R.string.app_updater_start_notification_content),config.isVibrate(),config.isSound(),isCancelDownload);
}
if(callback!=null){
if(callback != null){
callback.onStart(url);
}
}
@Override
public void onProgress(int progress, int total) {
public void onProgress(long progress, long total) {
boolean isChange = false;
long curTime = System.currentTimeMillis();
if(mLastTime + 200 < curTime) {//降低更新频率
mLastTime = curTime;
if(lastTime + 200 < curTime || progress == total) {//降低更新频率
lastTime = curTime;
int currProgress = Math.round(progress * 1.0f / total * 100.0f);
if(currProgress!=mLastProgress){//百分比改变了才更新
if(currProgress != lastProgress){//百分比改变了才更新
isChange = true;
lastProgress = currProgress;
String percentage = currProgress + "%";
if(isShowNotification) {
mLastProgress = currProgress;
String content = getString(R.string.app_updater_progress_notification_content);
LogUtils.i(String.format(Locale.getDefault(),"%s \t(%d/%d)", percentage, progress, total));
if(isShowNotification && notification != null) {
String content = context.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);
notification.onProgress(context,notifyId, channelId, notificationIcon, context.getString(R.string.app_updater_progress_notification_title), content, currProgress, 100,isCancelDownload);
}
}
}
if(callback!=null){
if(callback != null){
callback.onProgress(progress,total,isChange);
}
}
@Override
public void onFinish(File file) {
Log.d(Constants.TAG,"onFinish:" + 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);
LogUtils.d("File:" + file);
downloadService.isDownloading = false;
if(isShowNotification && notification != null){
notification.onFinish(context,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);
AppUtils.installApk(context,file,authority);
}
if(callback!=null){
if(callback != null){
callback.onFinish(file);
}
stopService();
downloadService.stopService();
}
@Override
public void onError(Exception e) {
Log.w(Constants.TAG,e);
isDownloading = false;
//支持下载失败重新并最多支持失败下载3次
boolean isReDownload = this.isReDownload && mCount < 3;
LogUtils.w(e.getMessage());
downloadService.isDownloading = false;
if(isShowNotification && notification != null){
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);
notification.onError(context,notifyId,channelId,notificationIcon,getString(R.string.app_updater_error_notification_title),content,isReDownload,config);
}
if(callback!=null){
if(callback != null){
callback.onError(e);
}
if(!isReDownload){
stopService();
downloadService.stopService();
}
}
@Override
public void onCancel() {
Log.d(Constants.TAG,"onCancel");
isDownloading = false;
cancelNotification(notifyId);
if(callback!=null){
LogUtils.d("Cancel download.");
downloadService.isDownloading = false;
if(isShowNotification && notification != null){
notification.onCancel(context,notifyId);
}
if(callback != null){
callback.onCancel();
}
stopService();
if(isDeleteCancelFile && apkFile != null){
apkFile.delete();
}
downloadService.stopService();
}
private String getString(@StringRes int resId){
return context.getString(resId);
}
}
@Override
public void onDestroy() {
isDownloading = false;
mHttpManager = null;
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,boolean isVibrate,boolean isSound){
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O){
createNotificationChannel(channelId,channelName,isVibrate,isSound);
}
NotificationCompat.Builder builder = buildNotification(channelId,icon,title,content);
if(isVibrate && isSound){
builder.setDefaults(Notification.DEFAULT_VIBRATE | Notification.DEFAULT_SOUND);
}else if(isVibrate){
builder.setDefaults(Notification.DEFAULT_VIBRATE);
}else if(isSound){
builder.setDefaults(Notification.DEFAULT_SOUND);
}
Notification notification = builder.build();
notification.flags = Notification.FLAG_NO_CLEAR | Notification.FLAG_ONLY_ALERT_ONCE;
notifyNotification(notifyId,notification);
}
/**
* 显示下载中的通知更新进度
* @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);
Notification notification = builder.build();
notification.flags = Notification.FLAG_NO_CLEAR | 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);
}
//---------------------------------------- Binder
/**
* 获取通知管理器
* @return
*/
private NotificationManager getNotificationManager(){
return (NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE);
@Nullable
@Override
public IBinder onBind(Intent intent) {
return mDownloadBinder;
}
/**
* 创建一个通知渠道兼容0以上版本
* @param channelId
* @param channelName
* 提供绑定服务的方式进行下载
*/
@RequiresApi(api = Build.VERSION_CODES.O)
private void createNotificationChannel(String channelId, String channelName,boolean isVibrate,boolean isSound){
NotificationChannel channel = new NotificationChannel(channelId,channelName, NotificationManager.IMPORTANCE_DEFAULT);
channel.enableVibration(isVibrate);
if(!isSound){
channel.setSound(null,null);
}
getNotificationManager().createNotificationChannel(channel);
}
public class DownloadBinder extends Binder {
/**
* 构建一个通知构建器
* @param channelId
* @param icon
* @param title
* @param content
* @return
* 开始下载
* @param config {@link UpdateConfig}
*/
private NotificationCompat.Builder buildNotification(String channelId, @DrawableRes int icon,CharSequence title,CharSequence content){
return buildNotification(channelId,icon,title,content,Constants.NONE,Constants.NONE);
public void start(@NonNull UpdateConfig config){
start(config,null);
}
/**
* 构建一个通知构建器
* @param channelId
* @param icon
* @param title
* @param content
* @param progress
* @param size
* @return
* 开始下载
* @param config {@link UpdateConfig}
* @param callback {@link UpdateCallback}
*/
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;
public void start(@NonNull UpdateConfig config, @Nullable UpdateCallback callback){
start(config,null, callback);
}
/**
* 更新通知栏
* @param id
* @param notification
* 开始下载
* @param config {@link UpdateConfig}
* @param httpManager {@link IHttpManager}
* @param callback {@link UpdateCallback}
*/
private void notifyNotification(int id, Notification notification){
getNotificationManager().notify(id,notification);
}
//---------------------------------------- Binder
@Nullable
@Override
public IBinder onBind(Intent intent) {
return mDownloadBinder;
public void start(@NonNull UpdateConfig config, @Nullable IHttpManager httpManager, @Nullable UpdateCallback callback){
start(config, httpManager, callback, new NotificationImpl());
}
/**
* 提供绑定服务的方式下载
* 开始下载
* @param config {@link UpdateConfig}
* @param httpManager {@link IHttpManager}
* @param callback {@link UpdateCallback}
* @param notification {@link INotification}
*/
public class DownloadBinder extends Binder {
public void start(UpdateConfig config){
start(config,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);
public void start(@NonNull UpdateConfig config, @Nullable IHttpManager httpManager, @Nullable UpdateCallback callback,@NonNull INotification notification){
startDownload(config, httpManager, callback, notification);
}
}

@ -1,40 +1,59 @@
package com.king.app.updater.util;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.res.AssetFileDescriptor;
import android.net.Uri;
import android.os.Build;
import android.support.v4.content.FileProvider;
import android.text.TextUtils;
import android.util.Log;
import com.king.app.updater.constant.Constants;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.math.BigInteger;
import java.security.MessageDigest;
import androidx.core.content.FileProvider;
/**
* @author Jenly <a href="mailto:jenly1314@gmail.com">Jenly</a>
*/
public enum AppUtils {
public final class AppUtils {
INSTANCE;
private AppUtils(){
throw new AssertionError();
}
/**
* 通过url获取App的全名称
* @param context
* @return AppName.apk
*/
public String getAppFullName(Context context,String url,String defaultName){
public static String getAppFullName(Context context,String url,String defaultName){
if(url.endsWith(".apk")){
return url.substring(url.lastIndexOf("/") + 1);
String apkName = url.substring(url.lastIndexOf("/") + 1);
if(apkName.length() <= 64){
return apkName;
}
}
String filename = getAppName(context);
Log.d(Constants.TAG, "AppName:" + filename);
if(TextUtils.isEmpty(filename)){
filename = defaultName;
}
if(filename.endsWith(".apk")){
return filename;
}
return String.format("%s.apk",filename);
}
@ -44,9 +63,9 @@ public enum AppUtils {
* @return
* @throws PackageManager.NameNotFoundException
*/
public PackageInfo getPackageInfo(Context context) throws PackageManager.NameNotFoundException {
public static PackageInfo getPackageInfo(Context context) throws PackageManager.NameNotFoundException {
PackageManager packageManager = context.getPackageManager();
PackageInfo packageInfo = packageManager.getPackageInfo( context.getPackageName(), 0);
PackageInfo packageInfo = packageManager.getPackageInfo(context.getPackageName(), 0);
return packageInfo;
}
@ -56,7 +75,7 @@ public enum AppUtils {
* @param archiveFilePath
* @return
*/
public static PackageInfo getPackageInfo(Context context, String archiveFilePath) throws Exception {
public static PackageInfo getPackageInfo(Context context, String archiveFilePath) {
PackageManager packageManager = context.getPackageManager();
PackageInfo packageInfo = packageManager.getPackageArchiveInfo(archiveFilePath, PackageManager.GET_ACTIVITIES);
return packageInfo;
@ -66,9 +85,8 @@ public enum AppUtils {
/**
* 获取App的名称
*/
public String getAppName(Context context) {
public static String getAppName(Context context) {
try{
int labelRes = getPackageInfo(context).applicationInfo.labelRes;
return context.getResources().getString(labelRes);
} catch (Exception e) {
@ -82,13 +100,12 @@ public enum AppUtils {
* @param context
* @return
*/
public int getAppIcon(Context context){
public static int getAppIcon(Context context){
try{
return getPackageInfo(context).applicationInfo.icon;
} catch (Exception e) {
e.printStackTrace();
}
return 0;
}
@ -97,12 +114,23 @@ public enum AppUtils {
* @param context
* @param file
*/
public void installApk(Context context,File file,String authority){
public static void installApk(Context context,File file,String authority){
Intent intent = getInstallIntent(context,file,authority);
context.startActivity(intent);
}
/**
* 获取安装Intent
* @param context
* @param file
* @param authority
* @return
*/
public static Intent getInstallIntent(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;
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);
@ -111,7 +139,7 @@ public enum AppUtils {
uriData = Uri.fromFile(file);
}
intent.setDataAndType(uriData, type);
context.startActivity(intent);
return intent;
}
/**
@ -122,17 +150,114 @@ public enum AppUtils {
* @return
* @throws Exception
*/
public boolean apkExists(Context context,int versionCode,File file) throws Exception{
if(file!=null && file.exists()){
public static boolean apkExists(Context context,int versionCode,File file){
if(file != null && file.exists()){
String packageName = context.getPackageName();
PackageInfo packageInfo = AppUtils.getPackageInfo(context,file.getAbsolutePath());
if(packageInfo!=null && versionCode == packageInfo.versionCode){//比对versionCode
if(packageInfo != null){// 比对versionCode
Log.d(Constants.TAG,String.format("ApkVersionCode:%d",packageInfo.versionCode));
if(versionCode == packageInfo.versionCode){
ApplicationInfo applicationInfo = packageInfo.applicationInfo;
if(applicationInfo!=null && packageName.equals(applicationInfo.packageName)){//比对packageName
if(applicationInfo != null && packageName.equals(applicationInfo.packageName)){//比对packageName
return true;
}
}
}
}
return false;
}
/**
* 判断文件是否存在
* @param context
* @param path
* @return
*/
public static boolean isAndroidQFileExists(Context context,String path){
return isAndroidQFileExists(context,new File(path));
}
/**
* 判断文件是否存在
* @param context
* @param file
* @return
*/
public static boolean isAndroidQFileExists(Context context,File file){
AssetFileDescriptor descriptor = null;
ContentResolver contentResolver = context.getContentResolver();
try {
Uri uri = Uri.fromFile(file);
descriptor = contentResolver.openAssetFileDescriptor(uri, "r");
if (descriptor == null) {
return false;
} else {
close(descriptor);
}
return true;
} catch (FileNotFoundException e) {
}finally {
close(descriptor);
}
return false;
}
/**
* 校验文件MD5
* @param file
* @param md5
* @return
*/
public static boolean checkFileMD5(File file,String md5){
String fileMD5 = getFileMD5(file);
Log.d(Constants.TAG,"FileMD5:"+ fileMD5);
if(!TextUtils.isEmpty(md5)){
return md5.equalsIgnoreCase(fileMD5);
}
return false;
}
/**
* 获取文件MD5
* @param file
* @return
*/
public static String getFileMD5(File file){
try {
FileInputStream fis = new FileInputStream(file);
MessageDigest messageDigest = MessageDigest.getInstance("MD5");
byte[] buffer = new byte[1024];
int length;
while ((length = fis.read(buffer)) != -1){
messageDigest.update(buffer,0,length);
}
BigInteger bigInteger = new BigInteger(1,messageDigest.digest());
return bigInteger.toString(16);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public static String getFileProviderAuthority(Context context){
return context.getPackageName() + Constants.DEFAULT_FILE_PROVIDER;
}
/**
* 关闭
* @param descriptor
*/
private static void close(AssetFileDescriptor descriptor){
if(descriptor != null){
try {
descriptor.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}

@ -0,0 +1,316 @@
/*
Copyright © 2015, 2016 Jenly Yu <a href="mailto:jenly1314@gmail.com">Jenly</a>
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package com.king.app.updater.util;
import android.util.Log;
import java.util.Locale;
/**
* @author Jenly <a href="mailto:jenly1314@gmail.com">Jenly</a>
*/
public class LogUtils {
public static final String TAG = "AppUpdater";
public static final String VERTICAL = "|";
/** 是否显示Log日志 */
private static boolean isShowLog = true;
/** Log日志优先权 */
private static int priority = 1;
/**
* Priority constant for the println method;use System.out.println
*/
public static final int PRINTLN = 1;
/**
* Priority constant for the println method; use Log.v.
*/
public static final int VERBOSE = 2;
/**
* Priority constant for the println method; use Log.d.
*/
public static final int DEBUG = 3;
/**
* Priority constant for the println method; use Log.i.
*/
public static final int INFO = 4;
/**
* Priority constant for the println method; use Log.w.
*/
public static final int WARN = 5;
/**
* Priority constant for the println method; use Log.e.
*/
public static final int ERROR = 6;
/**
* Priority constant for the println method.use Log.wtf.
*/
public static final int ASSERT = 7;
public static final String TAG_FORMAT = "%s.%s(%s:%d)";
private LogUtils(){
throw new AssertionError();
}
public static void setShowLog(boolean isShowLog) {
LogUtils.isShowLog = isShowLog;
}
public static boolean isShowLog() {
return isShowLog;
}
public static int getPriority() {
return priority;
}
public static void setPriority(int priority) {
LogUtils.priority = priority;
}
/**
* 根据堆栈生成TAG
* @return TAG|className.methodName(fileName:lineNumber)
*/
private static String generateTag(StackTraceElement caller) {
String tag = TAG_FORMAT;
String callerClazzName = caller.getClassName();
callerClazzName = callerClazzName.substring(callerClazzName.lastIndexOf(".") + 1);
tag = String.format(Locale.getDefault(),tag,callerClazzName, caller.getMethodName(),caller.getFileName(),caller.getLineNumber());
return new StringBuilder().append(TAG).append(VERTICAL).append(tag).toString();
}
/**
* 获取堆栈
* @param n
* n=0 VMStack
* n=1 Thread
* n=3 CurrentStack
* n=4 CallerStack
* ...
* @return
*/
public static StackTraceElement getStackTraceElement(int n) {
return Thread.currentThread().getStackTrace()[n];
}
/**
* 获取调用方的堆栈TAG
* @return
*/
private static String getCallerStackLogTag(){
return generateTag(getStackTraceElement(5));
}
/**
*
* @param t
* @return
*/
private static String getStackTraceString(Throwable t){
return Log.getStackTraceString(t);
}
// -----------------------------------Log.v
/**
* Log.v
* @param msg
*/
public static void v(String msg) {
if (isShowLog && priority <= VERBOSE)
Log.v(getCallerStackLogTag(), String.valueOf(msg));
}
public static void v(Throwable t) {
if (isShowLog && priority <= VERBOSE)
Log.v(getCallerStackLogTag(), getStackTraceString(t));
}
public static void v(String msg,Throwable t) {
if (isShowLog && priority <= VERBOSE)
Log.v(getCallerStackLogTag(), String.valueOf(msg), t);
}
// -----------------------------------Log.d
/**
* Log.d
* @param msg
*/
public static void d(String msg) {
if (isShowLog && priority <= DEBUG)
Log.d(getCallerStackLogTag(), String.valueOf(msg));
}
public static void d(Throwable t) {
if (isShowLog && priority <= DEBUG)
Log.d(getCallerStackLogTag(), getStackTraceString(t));
}
public static void d(String msg,Throwable t) {
if (isShowLog && priority <= DEBUG)
Log.d(getCallerStackLogTag(), String.valueOf(msg), t);
}
// -----------------------------------Log.i
/**
* Log.i
* @param msg
*/
public static void i(String msg) {
if (isShowLog && priority <= INFO)
Log.i(getCallerStackLogTag(), String.valueOf(msg));
}
public static void i(Throwable t) {
if (isShowLog && priority <= INFO)
Log.i(getCallerStackLogTag(), getStackTraceString(t));
}
public static void i(String msg,Throwable t) {
if (isShowLog && priority <= INFO)
Log.i(getCallerStackLogTag(), String.valueOf(msg), t);
}
// -----------------------------------Log.w
/**
* Log.w
* @param msg
*/
public static void w(String msg) {
if (isShowLog && priority <= WARN)
Log.w(getCallerStackLogTag(), String.valueOf(msg));
}
public static void w(Throwable t) {
if (isShowLog && priority <= WARN)
Log.w(getCallerStackLogTag(), getStackTraceString(t));
}
public static void w(String msg,Throwable t) {
if (isShowLog && priority <= WARN)
Log.w(getCallerStackLogTag(), String.valueOf(msg), t);
}
// -----------------------------------Log.e
/**
* Log.e
* @param msg
*/
public static void e(String msg) {
if (isShowLog && priority <= ERROR)
Log.e(getCallerStackLogTag(), String.valueOf(msg));
}
public static void e(Throwable t) {
if (isShowLog && priority <= ERROR)
Log.e(getCallerStackLogTag(), getStackTraceString(t));
}
public static void e(String msg,Throwable t) {
if (isShowLog && priority <= ERROR)
Log.e(getCallerStackLogTag(), String.valueOf(msg), t);
}
// -----------------------------------Log.wtf
/**
* Log.wtf
* @param msg
*/
public static void wtf(String msg) {
if (isShowLog && priority <= ASSERT)
Log.wtf(getCallerStackLogTag(), String.valueOf(msg));
}
public static void wtf(Throwable t) {
if (isShowLog && priority <= ASSERT)
Log.wtf(getCallerStackLogTag(), getStackTraceString(t));
}
public static void wtf(String msg,Throwable t) {
if (isShowLog && priority <= ASSERT)
Log.wtf(getCallerStackLogTag(), String.valueOf(msg), t);
}
// -----------------------------------System.out.print
/**
* System.out.print
*
* @param msg
*/
public static void print(String msg) {
if (isShowLog && priority <= PRINTLN)
System.out.print(msg);
}
public static void print(Object obj) {
if (isShowLog && priority <= PRINTLN)
System.out.print(obj);
}
// -----------------------------------System.out.printf
/**
* System.out.printf
*
* @param msg
*/
public static void printf(String msg) {
if (isShowLog && priority <= PRINTLN)
System.out.printf(msg);
}
// -----------------------------------System.out.println
/**
* System.out.println
*
* @param msg
*/
public static void println(String msg) {
if (isShowLog && priority <= PRINTLN)
System.out.println(msg);
}
public static void println(Object obj) {
if (isShowLog && priority <= PRINTLN)
System.out.println(obj);
}
}

@ -0,0 +1,260 @@
package com.king.app.updater.util;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import com.king.app.updater.UpdateConfig;
import com.king.app.updater.constant.Constants;
import com.king.app.updater.service.DownloadService;
import java.io.File;
import androidx.annotation.DrawableRes;
import androidx.annotation.RequiresApi;
import androidx.core.app.NotificationCompat;
/**
* @author <a href="mailto:jenly1314@gmail.com">Jenly</a>
*/
public class NotificationUtils {
private NotificationUtils(){
throw new AssertionError();
}
/**
* 显示开始下载时的通知
* @param notifyId
* @param channelId
* @param channelName
* @param icon
* @param title
* @param content
*/
public static void showStartNotification(Context context, int notifyId,String channelId, String channelName,@DrawableRes int icon,CharSequence title,CharSequence content,boolean isVibrate,boolean isSound, boolean isCancelDownload){
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O){
createNotificationChannel(context,channelId,channelName,isVibrate,isSound);
}
NotificationCompat.Builder builder = buildNotification(context,channelId,icon,title,content);
builder.setPriority(NotificationCompat.PRIORITY_DEFAULT);
if(isVibrate && isSound){
builder.setDefaults(Notification.DEFAULT_VIBRATE | Notification.DEFAULT_SOUND);
}else if(isVibrate){
builder.setDefaults(Notification.DEFAULT_VIBRATE);
}else if(isSound){
builder.setDefaults(Notification.DEFAULT_SOUND);
}
if(isCancelDownload){
Intent intent = new Intent(context, DownloadService.class);
intent.putExtra(Constants.KEY_STOP_DOWNLOAD_SERVICE,true);
PendingIntent deleteIntent = PendingIntent.getService(context, notifyId,intent, getPendingIntentFlags(PendingIntent.FLAG_CANCEL_CURRENT));
builder.setDeleteIntent(deleteIntent);
}
Notification notification = builder.build();
if(isCancelDownload){
notification.flags = Notification.FLAG_ONLY_ALERT_ONCE;
}else{
notification.flags = Notification.FLAG_NO_CLEAR | Notification.FLAG_ONLY_ALERT_ONCE;
}
notifyNotification(context,notifyId,notification);
}
/**
* 显示下载中的通知更新进度
* @param notifyId
* @param channelId
* @param icon
* @param title
* @param content
* @param progress
* @param size
*/
public static void showProgressNotification(Context context, int notifyId,String channelId,@DrawableRes int icon,CharSequence title,CharSequence content,int progress,int size, boolean isCancelDownload){
NotificationCompat.Builder builder = buildNotification(context,channelId,icon,title,content,progress,size);
if(isCancelDownload){
Intent intent = new Intent(context, DownloadService.class);
intent.putExtra(Constants.KEY_STOP_DOWNLOAD_SERVICE,true);
PendingIntent deleteIntent = PendingIntent.getService(context, notifyId, intent, getPendingIntentFlags(PendingIntent.FLAG_CANCEL_CURRENT));
builder.setDeleteIntent(deleteIntent);
}
Notification notification = builder.build();
if(isCancelDownload){
notification.flags = Notification.FLAG_ONLY_ALERT_ONCE;
}else{
notification.flags = Notification.FLAG_NO_CLEAR | Notification.FLAG_ONLY_ALERT_ONCE;
}
notifyNotification(context,notifyId,notification);
}
/**
* 显示下载完成时的通知点击安装
* @param notifyId
* @param channelId
* @param icon
* @param title
* @param content
* @param file
*/
public static void showFinishNotification(Context context, int notifyId, String channelId, @DrawableRes int icon, CharSequence title, CharSequence content, File file, String authority){
cancelNotification(context,notifyId);
NotificationCompat.Builder builder = buildNotification(context,channelId,icon,title,content);
builder.setAutoCancel(true);
Intent intent = AppUtils.getInstallIntent(context,file,authority);
PendingIntent clickIntent = PendingIntent.getActivity(context, notifyId, intent, getPendingIntentFlags(PendingIntent.FLAG_UPDATE_CURRENT));
builder.setContentIntent(clickIntent);
Notification notification = builder.build();
notification.flags = Notification.FLAG_AUTO_CANCEL;
notifyNotification(context,notifyId,notification);
}
/**
* 现在下载失败通知
* @param context
* @param notifyId
* @param channelId
* @param icon
* @param title
* @param content
* @param isReDownload
* @param config
*/
public static void showErrorNotification(Context context, int notifyId, String channelId, @DrawableRes int icon, CharSequence title, CharSequence content, boolean isReDownload, UpdateConfig config){
NotificationCompat.Builder builder = buildNotification(context,channelId,icon,title,content);
builder.setAutoCancel(true);
int flag = getPendingIntentFlags(PendingIntent.FLAG_UPDATE_CURRENT);
if(isReDownload){// 重新下载
Intent intent = new Intent(context, DownloadService.class);
intent.putExtra(Constants.KEY_RE_DOWNLOAD,true);
intent.putExtra(Constants.KEY_UPDATE_CONFIG,config);
PendingIntent clickIntent = PendingIntent.getService(context, notifyId,intent, flag);
builder.setContentIntent(clickIntent);
}else{
PendingIntent clickIntent = PendingIntent.getService(context, notifyId, new Intent(), flag);
builder.setContentIntent(clickIntent);
}
Notification notification = builder.build();
notification.flags = Notification.FLAG_AUTO_CANCEL;
notifyNotification(context,notifyId,notification);
}
/**
* 显示通知信息非第一次
* @param notifyId
* @param channelId
* @param icon
* @param title
* @param content
*/
public static void showNotification(Context context, int notifyId,String channelId,@DrawableRes int icon,CharSequence title,CharSequence content,boolean isAutoCancel){
NotificationCompat.Builder builder = buildNotification(context,channelId,icon,title,content);
builder.setAutoCancel(isAutoCancel);
Notification notification = builder.build();
notification.flags = Notification.FLAG_AUTO_CANCEL;
notifyNotification(context,notifyId,notification);
}
/**
* 取消通知
* @param notifyId
*/
public static void cancelNotification(Context context, int notifyId){
getNotificationManager(context).cancel(notifyId);
}
/**
* 获取通知管理器
* @return
*/
public static NotificationManager getNotificationManager(Context context){
return (NotificationManager)context.getSystemService(Context.NOTIFICATION_SERVICE);
}
/**
* 创建一个通知渠道兼容0以上版本
* @param channelId
* @param channelName
*/
@RequiresApi(api = Build.VERSION_CODES.O)
public static void createNotificationChannel(Context context, String channelId, String channelName,boolean isVibrate,boolean isSound){
NotificationChannel channel = new NotificationChannel(channelId,channelName, NotificationManager.IMPORTANCE_HIGH);
channel.enableVibration(isVibrate);
if(!isSound){
channel.setSound(null,null);
}
getNotificationManager(context).createNotificationChannel(channel);
}
/**
* 构建一个通知构建器
* @param channelId
* @param icon
* @param title
* @param content
* @return
*/
private static NotificationCompat.Builder buildNotification(Context context, String channelId, @DrawableRes int icon,CharSequence title,CharSequence content){
return buildNotification(context,channelId,icon,title,content,Constants.NONE,Constants.NONE);
}
/**
* 构建一个通知构建器
* @param channelId
* @param icon
* @param title
* @param content
* @param progress
* @param size
* @return
*/
private static NotificationCompat.Builder buildNotification(Context context, String channelId, @DrawableRes int icon, CharSequence title, CharSequence content, int progress, int size){
NotificationCompat.Builder builder = new NotificationCompat.Builder(context,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 static void notifyNotification(Context context, int id, Notification notification){
getNotificationManager(context).notify(id,notification);
}
/**
* 获取 PendingIntent flags
* @param flag
* @return
*/
private static int getPendingIntentFlags(int flag){
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
return flag | PendingIntent.FLAG_IMMUTABLE;
}
return flag;
}
}

@ -1,7 +1,6 @@
package com.king.app.updater.util;
import android.Manifest;
import android.annotation.TargetApi;
import android.app.Activity;
import android.app.AppOpsManager;
import android.app.NotificationManager;
@ -9,60 +8,61 @@ import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.os.Build;
import android.support.annotation.NonNull;
import android.support.v4.app.ActivityCompat;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import androidx.annotation.NonNull;
import androidx.core.app.ActivityCompat;
/**
* @author Jenly <a href="mailto:jenly1314@gmail.com">Jenly</a>
*/
public enum PermissionUtils {
INSTANCE;
public final class PermissionUtils {
private String[] PERMISSIONS_STORAGE = {
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.WRITE_EXTERNAL_STORAGE,
Manifest.permission.READ_PHONE_STATE};
private PermissionUtils() {
throw new AssertionError();
}
/**
* 校验权限
*
* @param activity
* @param requestCode
* @return
*/
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);
public static boolean verifyReadAndWritePermissions(@NonNull Activity activity, int requestCode) {
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M){
int readResult = checkPermission(activity, Manifest.permission.READ_EXTERNAL_STORAGE);
int writeResult = checkPermission(activity, Manifest.permission.WRITE_EXTERNAL_STORAGE);
if (readResult != PackageManager.PERMISSION_GRANTED || writeResult != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(activity, new String[]{
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.WRITE_EXTERNAL_STORAGE}, requestCode);
return false;
}
}
return true;
}
public int checkPermission(@NonNull Activity activity,@NonNull String permission){
return ActivityCompat.checkSelfPermission(activity,permission);
public static int checkPermission(@NonNull Activity activity, @NonNull String permission) {
return ActivityCompat.checkSelfPermission(activity, permission);
}
/**
* 获取通知权限
*
* @param context
*/
public boolean isNotificationEnabled(Context context) {
public static boolean isNotificationEnabled(Context context) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationManager notificationManager = (NotificationManager)context.getSystemService(Context.NOTIFICATION_SERVICE);
NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
if (notificationManager.getImportance() == NotificationManager.IMPORTANCE_NONE) {
return false;
}
}
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT){
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
String CHECK_OP_NO_THROW = "checkOpNoThrow";
String OP_POST_NOTIFICATION = "OP_POST_NOTIFICATION";

@ -1,17 +1,18 @@
package com.king.app.updater.util;
import android.content.Context;
import android.support.annotation.RawRes;
import android.text.TextUtils;
import androidx.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 java.util.Arrays;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
@ -21,20 +22,23 @@ 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 {
public final class SSLSocketFactoryUtils {
private SSLSocketFactoryUtils(){
private static final String[] VERIFY_HOST_NAME = new String[]{};
private SSLSocketFactoryUtils() {
throw new AssertionError();
}
public static SSLSocketFactory createSSLSocketFactory() {
SSLSocketFactory sslSocketFactory = null;
try {
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, new TrustManager[]{createTrustAllManager()}, new SecureRandom());
sslContext.init(null, getTrustAllManager(), new SecureRandom());
sslSocketFactory = sslContext.getSocketFactory();
} catch (Exception e) {
@ -66,74 +70,63 @@ public class SSLSocketFactoryUtils {
return tm;
}
public static TrustAllHostnameVerifier createTrustAllHostnameVerifier(){
public static TrustAllHostnameVerifier createTrustAllHostnameVerifier() {
return new TrustAllHostnameVerifier();
}
public static class TrustAllHostnameVerifier implements HostnameVerifier {
@Override
public boolean verify(String hostname, SSLSession session) {
return true;
if (TextUtils.isEmpty(hostname)) {
return false;
}
return !Arrays.asList(VERIFY_HOST_NAME).contains(hostname);
}
}
/**
*
* @param context
* @param keyServerStoreID
* @return
*/
public static SSLSocketFactory createSSLSocketFactory(Context context,@RawRes int keyServerStoreID){
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){
SSLSocketFactory sSLSocketFactory = null;
if (sSLSocketFactory == null) {
synchronized (SSLSocketFactoryUtils.class) {
if(mSSLSocketFactory==null){
SSLContext sslContext = null;
if (sSLSocketFactory == 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) {
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, getTrustManager(certificates), new SecureRandom());
sSLSocketFactory = sslContext.getSocketFactory();
} catch (Exception e) {
e.printStackTrace();
}
//获得sslSocketFactory
mSSLSocketFactory = sslContext.getSocketFactory();
}
}
}
return mSSLSocketFactory;
return sSLSocketFactory;
}
/**
* 获得指定流中的服务器端证书库
*
* @param certificates
* @return
*/
public static TrustManager[] getTurstManager(InputStream... certificates) {
public static TrustManager[] getTrustManager(InputStream... certificates) {
try {
CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(null,null);
keyStore.load(null, null);
int index = 0;
for (InputStream certificate : certificates) {
if (certificate == null) {
@ -142,12 +135,12 @@ public class SSLSocketFactoryUtils {
Certificate certificate1;
try {
certificate1 = certificateFactory.generateCertificate(certificate);
}finally {
} finally {
certificate.close();
}
String certificateAlias = Integer.toString(index++);
keyStore.setCertificateEntry(certificateAlias,certificate1);
keyStore.setCertificateEntry(certificateAlias, certificate1);
}
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory
@ -161,14 +154,14 @@ public class SSLSocketFactoryUtils {
}
return getTurstAllManager();
return getTrustAllManager();
}
/**
* 获得信任所有服务器端证书库
* */
public static TrustManager[] getTurstAllManager() {
return new X509TrustManager[] {createTrustAllManager()};
*/
public static TrustManager[] getTrustAllManager() {
return new TrustManager[]{createTrustAllManager()};
}

@ -5,7 +5,7 @@
<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_progress_notification_content">正在下载…</string>
<string name="app_updater_finish_notification_title">下载完成</string>
<string name="app_updater_finish_notification_content">点击安装</string>

@ -2,7 +2,6 @@
<paths>
<root-path name="app_root_path" path="/"/>
<external-path name="app_external_path" path="/"/>
<external-path name="app_updater_path" path=".AppUpdater/"/>
<external-cache-path name="app_external_cache_path" path="/"/>
<external-files-path name="app_external_files_path" path="/"/>
<files-path name="app_files_path" path="/"/>

@ -10,7 +10,7 @@ android {
targetSdkVersion build_versions.targetSdk
versionCode app_version.versionCode
versionName app_version.versionName
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
@ -24,12 +24,14 @@ android {
dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
testImplementation deps.test.junit
androidTestImplementation deps.test.runner
androidTestImplementation deps.test.espresso
//support
implementation deps.support.appcompat
implementation deps.support.constraintlayout
testImplementation "junit:junit:$versions.junit"
androidTestImplementation "androidx.test.ext:junit:$versions.androidExtJunit"
androidTestImplementation "androidx.test.espresso:espresso-core:$versions.espressoCore"
implementation "androidx.appcompat:appcompat:$versions.appcompat"
implementation "androidx.constraintlayout:constraintlayout:$versions.constraintLayout"
implementation "com.squareup.okhttp3:okhttp:$versions.okhttp"
implementation project(':app-updater')
implementation project(':app-dialog')

Binary file not shown.

@ -0,0 +1,18 @@
{
"version": 2,
"artifactType": {
"type": "APK",
"kind": "Directory"
},
"applicationId": "com.king.appupdater",
"variantName": "processReleaseResources",
"elements": [
{
"type": "SINGLE",
"filters": [],
"versionCode": 22,
"versionName": "1.1.3",
"outputFile": "app-release.apk"
}
]
}

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

@ -1,12 +1,13 @@
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 androidx.test.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
import static org.junit.Assert.*;
/**

@ -2,6 +2,9 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.king.appupdater">
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"

@ -3,12 +3,10 @@ package com.king.appupdater;
import android.content.Context;
import android.content.DialogInterface;
import android.os.Bundle;
import android.os.Environment;
import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatActivity;
import android.os.CountDownTimer;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.Button;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;
@ -19,29 +17,40 @@ 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 com.king.app.updater.constant.Constants;
import com.king.app.updater.util.PermissionUtils;
import com.king.app.updater.http.OkHttpManager;
import java.io.File;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
/**
* @author <a href="mailto:jenly1314@gmail.com">Jenly</a>
*/
public class MainActivity extends AppCompatActivity {
private final Object mLock = new Object();
private String mUrl = "https://raw.githubusercontent.com/jenly1314/AppUpdater/master/app/release/app-release.apk";
// 下载出现Failed to connect to raw.githubusercontent.com时,可以换个下载链接测试,github的raw.githubusercontent.com目前不太稳定。
// private String mUrl = "https://raw.githubusercontent.com/jenly1314/AppUpdater/master/app/release/app-release.apk";
private String mUrl = "https://gitlab.com/jenly1314/AppUpdater/-/raw/master/app/release/app-release.apk";
private TextView tvProgress;
private ProgressBar progressBar;
private Toast toast;
private AppUpdater mAppUpdater;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
progressBar = findViewById(R.id.progressBar);
progressBar.setVisibility(View.INVISIBLE);
// progressBar = findViewById(R.id.progressBar);
// progressBar.setVisibility(View.INVISIBLE);
// progressBar.setMax(100);
PermissionUtils.INSTANCE.verifyReadAndWritePermissions(this,Constants.RE_CODE_STORAGE_PERMISSION);
// PermissionUtils.verifyReadAndWritePermissions(this,Constants.RE_CODE_STORAGE_PERMISSION);
}
public Context getContext(){
@ -64,7 +73,8 @@ public class MainActivity extends AppCompatActivity {
* 简单一键后台升级
*/
private void clickBtn1(){
new AppUpdater(getContext(),mUrl).start();
mAppUpdater = new AppUpdater(getContext(),mUrl);
mAppUpdater.start();
}
/**
@ -74,46 +84,68 @@ public class MainActivity extends AppCompatActivity {
UpdateConfig config = new UpdateConfig();
config.setUrl(mUrl);
config.addHeader("token","xxxxxx");
new AppUpdater(getContext(),config)
mAppUpdater = new AppUpdater(getContext(),config)
.setHttpManager(OkHttpManager.getInstance())
.setUpdateCallback(new UpdateCallback() {
@Override
public void onDownloading(boolean isDownloading) {
if(isDownloading){
showToast("已经在下载中,请勿重复下载。");
}else{
// showToast("开始下载…");
View view = LayoutInflater.from(getContext()).inflate(R.layout.dialog_progress,null);
tvProgress = view.findViewById(R.id.tvProgress);
progressBar = view.findViewById(R.id.progressBar);
AppDialog.INSTANCE.showDialog(getContext(),view,false);
}
}
@Override
public void onStart(String url) {
progressBar.setProgress(0);
progressBar.setVisibility(View.VISIBLE);
updateProgress(0,100);
}
@Override
public void onProgress(int progress, int total, boolean isChange) {
public void onProgress(long progress, long total, boolean isChange) {
if(isChange){
progressBar.setMax(total);
progressBar.setProgress(progress);
updateProgress(progress,total);
}
}
@Override
public void onFinish(File file) {
progressBar.setVisibility(View.INVISIBLE);
AppDialog.INSTANCE.dismissDialog();
showToast("下载完成");
}
@Override
public void onError(Exception e) {
progressBar.setVisibility(View.INVISIBLE);
AppDialog.INSTANCE.dismissDialog();
showToast("下载失败");
}
@Override
public void onCancel() {
progressBar.setVisibility(View.INVISIBLE);
AppDialog.INSTANCE.dismissDialog();
showToast("取消下载");
}
});
mAppUpdater.start();
}
})
.start();
private void updateProgress(long progress, long total){
if(tvProgress == null || progressBar == null){
return;
}
if(progress > 0){
int currProgress = (int)(progress * 1.0f / total * 100.0f);
tvProgress.setText(getString(R.string.app_updater_progress_notification_content) + currProgress + "%");
progressBar.setProgress(currProgress);
}else{
tvProgress.setText(getString(R.string.app_updater_start_notification_content));
}
}
/**
@ -126,12 +158,35 @@ public class MainActivity extends AppCompatActivity {
.setPositiveButton("升级", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
new AppUpdater.Builder()
.serUrl(mUrl)
mAppUpdater = new AppUpdater.Builder()
.setUrl(mUrl)
.build(getContext())
.setUpdateCallback(new AppUpdateCallback() {
@Override
public void onProgress(int progress, int total, boolean isChange) {
public void onStart(String url) {
super.onStart(url);
// 模仿系统自带的横幅通知效果
AppDialogConfig config = new AppDialogConfig(getContext(),R.layout.dialog_heads_up);
config.setStyleId(R.style.app_dialog_heads_up)
.setWidthRatio(.95f)
.setGravity(Gravity.TOP);
AppDialog.INSTANCE.showDialog(getContext(),config);
new CountDownTimer(2000,500){
@Override
public void onTick(long millisUntilFinished) {
}
@Override
public void onFinish() {
AppDialog.INSTANCE.dismissDialog();
}
}.start();
}
@Override
public void onProgress(long progress, long total, boolean isChange) {
}
@ -139,8 +194,8 @@ public class MainActivity extends AppCompatActivity {
public void onFinish(File file) {
showToast("下载完成");
}
})
.start();
});
mAppUpdater.start();
}
}).show();
}
@ -149,41 +204,41 @@ public class MainActivity extends AppCompatActivity {
* 简单弹框升级
*/
private void clickBtn4(){
AppDialogConfig config = new AppDialogConfig();
AppDialogConfig config = new AppDialogConfig(getContext());
config.setTitle("简单弹框升级")
.setOk("升级")
.setConfirm("升级")
.setContent("1、新增某某功能、\n2、修改某某问题、\n3、优化某某BUG、")
.setOnClickOk(new View.OnClickListener() {
.setOnClickConfirm(new View.OnClickListener() {
@Override
public void onClick(View v) {
new AppUpdater(getContext(),mUrl).start();
mAppUpdater = new AppUpdater(getContext(),mUrl);
mAppUpdater.start();
AppDialog.INSTANCE.dismissDialog();
}
});
AppDialog.INSTANCE.showDialog(getContext(),config);
AppDialog.INSTANCE.showDialog(config);
}
/**
* 简单自定义弹框升级
*/
private void clickBtn5(){
AppDialogConfig config = new AppDialogConfig();
config.setLayoutId(R.layout.dialog)
.setOk("升级")
AppDialogConfig config = new AppDialogConfig(getContext(),R.layout.dialog);
config.setConfirm("升级")
.setHideCancel(true)
.setTitle("简单自定义弹框升级")
.setContent("1、新增某某功能、\n2、修改某某问题、\n3、优化某某BUG、")
.setOnClickOk(new View.OnClickListener() {
.setOnClickConfirm(new View.OnClickListener() {
@Override
public void onClick(View v) {
new AppUpdater.Builder()
.serUrl(mUrl)
.build(getContext())
.start();
mAppUpdater = new AppUpdater.Builder()
.setUrl(mUrl)
.build(getContext());
mAppUpdater.start();
AppDialog.INSTANCE.dismissDialog();
}
});
AppDialog.INSTANCE.showDialog(getContext(),AppDialog.INSTANCE.createAppDialogView(getContext(),config),true);
AppDialog.INSTANCE.showDialog(config);
}
/**
@ -197,25 +252,25 @@ public class MainActivity extends AppCompatActivity {
TextView tvContent = view.findViewById(R.id.tvContent);
tvContent.setText("1、新增某某功能、\n2、修改某某问题、\n3、优化某某BUG、");
Button btnCancel = view.findViewById(R.id.btnCancel);
View 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() {
View btnConfirm = view.findViewById(R.id.btnConfirm);
btnConfirm.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
new AppUpdater.Builder()
.serUrl(mUrl)
.setPath(Environment.getExternalStorageDirectory() + "/.AppUpdater")
.setVersionCode(BuildConfig.VERSION_CODE)//设置versionCode之后,新版本相同的apk只下载一次,优先取本地缓存。
.setFilename("AppUpdater1.apk")
mAppUpdater = new AppUpdater.Builder()
.setUrl(mUrl)
// .setApkMD5("3df5b1c1d2bbd01b4a7ddb3f2722ccca")// 支持MD5校验,如果缓存APK的MD5与此MD5相同,则直接取本地缓存安装,推荐使用MD5校验的方式
.setVersionCode(BuildConfig.VERSION_CODE)// 支持versionCode校验,设置versionCode之后,新版本versionCode相同的apk只下载一次,优先取本地缓存,推荐使用MD5校验的方式
.setFilename("AppUpdater.apk")
.setVibrate(true)
.build(getContext())
.start();
.build(getContext());
mAppUpdater.setHttpManager(OkHttpManager.getInstance()).start();
AppDialog.INSTANCE.dismissDialog();
}
});
@ -227,19 +282,19 @@ public class MainActivity extends AppCompatActivity {
* 简单DialogFragment升级
*/
private void clickBtn7(){
AppDialogConfig config = new AppDialogConfig();
AppDialogConfig config = new AppDialogConfig(getContext());
config.setTitle("简单DialogFragment升级")
.setOk("升级")
.setConfirm("升级")
.setContent("1、新增某某功能、\n2、修改某某问题、\n3、优化某某BUG、")
.setOnClickOk(new View.OnClickListener() {
.setOnClickConfirm(new View.OnClickListener() {
@Override
public void onClick(View v) {
new AppUpdater.Builder()
.serUrl(mUrl)
mAppUpdater = new AppUpdater.Builder()
.setUrl(mUrl)
.setVibrate(true)
.setSound(true)
.build(getContext())
.start();
.build(getContext());
mAppUpdater.setHttpManager(OkHttpManager.getInstance()).start();
AppDialog.INSTANCE.dismissDialogFragment(getSupportFragmentManager());
}
});
@ -247,7 +302,14 @@ public class MainActivity extends AppCompatActivity {
}
public void OnClick(View v){
private void clickCancel(){
if(mAppUpdater != null){
mAppUpdater.stop();
}
}
public void onClick(View v){
switch (v.getId()){
case R.id.btn1:
clickBtn1();
@ -270,6 +332,9 @@ public class MainActivity extends AppCompatActivity {
case R.id.btn7:
clickBtn7();
break;
case R.id.btnCancel:
clickCancel();
break;
}
}
}

@ -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/text_666"/>
<item android:state_pressed="false" android:color="@color/text_999"/>
</selector>

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="false">
<shape>
<solid android:color="@color/colorAccent"/>
<corners android:radius="20dp"/>
</shape>
</item>
<item android:state_pressed="true">
<shape>
<solid android:color="@color/colorPrimaryDark"/>
<corners android:radius="20dp"/>
</shape>
</item>
</selector>

@ -1,21 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
<androidx.constraintlayout.widget.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"
@ -24,7 +14,6 @@
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"
@ -36,7 +25,7 @@
android:layout_marginLeft="20dp"
android:layout_marginRight="20dp"
android:layout_marginTop="10dp"
android:text="一键监听进度后台升级"
android:text="一键监听进度升级"
app:layout_constraintTop_toBottomOf="@+id/btn1"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
@ -104,4 +93,17 @@
app:layout_constraintRight_toRightOf="parent"
style="@style/OnClick"/>
</android.support.constraint.ConstraintLayout>
<Button
android:id="@+id/btnCancel"
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/btn7"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
style="@style/OnClick"/>
</androidx.constraintlayout.widget.ConstraintLayout>

@ -11,8 +11,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:minHeight="40dp"
android:padding="6dp"
android:padding="10dp"
android:lines="1"
android:textSize="16sp"
android:background="@drawable/dialog_title_bg"
@ -21,9 +20,11 @@
android:id="@+id/tvDialogContent"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="10dp"
android:layout_marginBottom="6dp"
android:lineSpacingMultiplier="1" />
android:padding="16dp"
android:textSize="@dimen/app_dialog_content_text_size"
android:textColor="@color/app_dialog_content_color"
android:layout_marginBottom="16dp"
android:lineSpacingMultiplier="1.2" />
<include layout="@layout/app_dialog_line_h"/>
<LinearLayout
android:orientation="horizontal"
@ -45,13 +46,13 @@
android:id="@+id/line"
layout="@layout/app_dialog_line_v"/>
<Button
android:id="@+id/btnDialogOK"
android:id="@+id/btnDialogConfirm"
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:text="@string/app_dialog_confirm"
android:textSize="18sp"
android:textColor="@color/app_dialog_button_color_selector"
android:background="?android:attr/selectableItemBackground"/>

@ -4,53 +4,57 @@
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_margin="30dp"
android:background="@android:color/white"
android:background="@drawable/app_dialog_bg"
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" />
android:textSize="16sp" />
<TextView
android:id="@+id/tvContent"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="10dp"
android:layout_marginBottom="6dp"
android:padding="16dp"
android:lineSpacingMultiplier="1" />
<include layout="@layout/app_dialog_line_h"/>
<LinearLayout
android:orientation="horizontal"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="20dp"
android:layout_marginTop="10dp"
android:layout_marginRight="20dp"
android:layout_marginBottom="10dp"
android:gravity="center_horizontal">
<Button
android:id="@+id/btnCancel"
android:layout_width="0dp"
<TextView
android:id="@+id/btnConfirm"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="10dp"
android:paddingBottom="10dp"
android:gravity="center"
android:layout_weight="1"
android:text="@string/app_dialog_cancel"
android:text="升级"
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:textColor="@color/white"
android:background="@drawable/btn_confirm_selector"/>
<TextView
android:id="@+id/btnCancel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="2dp"
android:paddingTop="10dp"
android:paddingBottom="10dp"
android:paddingLeft="16dp"
android:paddingRight="16dp"
android:gravity="center"
android:layout_weight="1"
android:text="升级"
android:text="@string/ignore"
android:textSize="16sp"
android:textColor="@color/app_dialog_button_color_selector"
android:background="?android:attr/selectableItemBackground"/>
android:textColor="@color/btn_cancel_color_selector" />
</LinearLayout>
</LinearLayout>

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="16dp"
android:gravity="center_vertical"
android:background="@drawable/app_dialog_bg">
<ImageView
android:id="@+id/ivIcon"
android:layout_width="30dp"
android:layout_height="30dp"
android:src="@mipmap/ic_launcher"
android:layout_marginRight="4dp"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_toRightOf="@+id/ivIcon"
android:textSize="14sp"
android:textColor="@color/app_dialog_title_color"
android:text="@string/app_updater_start_notification_content"/>
</RelativeLayout>

@ -0,0 +1,43 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="16dp"
android:gravity="center_vertical"
android:background="@drawable/app_dialog_bg">
<ImageView
android:id="@+id/ivIcon"
android:layout_width="20dp"
android:layout_height="20dp"
android:src="@mipmap/ic_launcher"
android:layout_marginRight="4dp"/>
<TextView
android:id="@+id/tvDialogTitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_toRightOf="@+id/ivIcon"
android:textSize="14sp"
android:textColor="@color/app_dialog_title_color"
android:text="@string/app_updater_progress_notification_title"/>
<TextView
android:id="@+id/tvProgress"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/tvDialogTitle"
android:layout_marginTop="10dp"
android:text="@string/app_updater_start_notification_content"
android:textSize="14sp"
android:textColor="@color/app_dialog_content_color"
android:lineSpacingMultiplier="1.2" />
<ProgressBar
android:id="@+id/progressBar"
android:layout_width="match_parent"
android:layout_height="6dp"
android:layout_marginTop="10dp"
android:layout_marginBottom="10dp"
android:max="100"
android:layout_below="@+id/tvProgress"
style="@style/Widget.AppCompat.ProgressBar.Horizontal"/>
</RelativeLayout>

@ -1,10 +1,15 @@
<?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="colorPrimary">#5588FF</color>
<color name="colorPrimaryDark">#4B7CFD</color>
<color name="colorAccent">#5588FF</color>
<color name="ic_launcher_background">#FFFFFF</color>
<color name="white">#FFFFFF</color>
<color name="text_666">#666666</color>
<color name="text_999">#999999</color>
</resources>

@ -1,4 +1,6 @@
<resources>
<string name="app_name">AppUpdater</string>
<string name="ignore">忽略</string>
</resources>

@ -9,8 +9,19 @@
<item name="android:textAllCaps">false</item>
</style>
<style name="app_dialog_heads_up" 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">false</item>
<item name="android:windowAnimationStyle">@style/app_dialog_top_animation</item>
</style>
<style name="OnClick">
<item name="android:clickable">true</item>
<item name="android:onClick">OnClick</item>
<item name="android:onClick">onClick</item>
</style>
</resources>

@ -1,21 +1,26 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
apply from: 'versions.gradle'
addRepos(repositories)
repositories {
google()
mavenCentral()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.2.1'
classpath "com.android.tools.build:gradle:$versions.gradle"
classpath "com.vanniktech:gradle-maven-publish-plugin:$versions.mavenPublish"
classpath 'com.novoda:bintray-release:0.9'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects {
addRepos(repositories)
repositories {
google()
mavenCentral()
jcenter() // Warning: this repository is going to shut down soon
}
}
task clean(type: Delete) {
@ -29,8 +34,8 @@ allprojects {
charSet 'UTF-8'
links "http://docs.oracle.com/javase/8/docs/api"
}
options.addStringOption('Xdoclint:none', '-quiet')
// options.compilerArgs << "-Xlint:unchecked" << "- Xlint:deprecation"
failOnError false
}
}

@ -11,3 +11,26 @@ org.gradle.jvmargs=-Xmx1536m
# 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
android.useAndroidX=true
android.enableJetifier=true
VERSION_NAME=1.1.3
VERSION_CODE=22
GROUP=com.github.jenly1314.AppUpdater
POM_DESCRIPTION=AppUpdater for Android
POM_URL=https://github.com/jenly1314/AppUpdater
POM_SCM_URL=https://github.com/jenly1314/AppUpdater
POM_SCM_CONNECTION=scm:git@github.com:jenly1314/AppUpdater.git
POM_SCM_DEV_CONNECTION=scm:git@github.com:jenly1314/AppUpdater.git
POM_LICENCE_NAME=The MIT License
POM_LICENCE_URL=https://opensource.org/licenses/mit-license.php
#POM_LICENCE_NAME=The Apache Software License, Version 2.0
#POM_LICENCE_URL=http://www.apache.org/licenses/LICENSE-2.0.txt
POM_DEVELOPER_ID=jenly
POM_DEVELOPER_NAME=Jenly Yu
RELEASE_REPOSITORY_URL=https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/
SNAPSHOT_REPOSITORY_URL=https://s01.oss.sonatype.org/content/repositories/snapshots/
RELEASE_SIGNING_ENABLED=false

@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-bin.zip

@ -1,49 +1,34 @@
//App
def app_version = [:]
app_version.versionCode = 5 //androidx 6
app_version.versionName = "1.0.4"
app_version.versionCode = 22
app_version.versionName = "1.1.3"
ext.app_version = app_version
//build version
def build_versions = [:]
build_versions.minSdk = 15
build_versions.targetSdk = 28
build_versions.compileSdk = 28
build_versions.buildTools = "28.0.3"
build_versions.targetSdk = 29
build_versions.compileSdk = 29
build_versions.buildTools = "29.0.3"
ext.build_versions = build_versions
// App dependencies
def versions = [:]
//support
versions.supportLibrary = "28.0.0"
versions.constraintLayout = "1.1.3"
//test
versions.junit = "4.12"
versions.runner = "1.0.2"
versions.espresso = "3.0.2"
ext.versions = versions
versions.mavenPublish = '0.13.0'
versions.gradle = "4.1.1"
ext.deps = [:]
//support
def support = [:]
support.appcompat = "com.android.support:appcompat-v7:$versions.supportLibrary"
support.constraintlayout = "com.android.support.constraint:constraint-layout:$versions.constraintLayout"
deps.support = support
//androidx
versions.appcompat = "1.1.0"
versions.constraintLayout = "2.0.4"
//test
def test = [:]
test.junit = "junit:junit:$versions.junit"
test.runner = "com.android.support.test:runner:$versions.runner"
test.espresso = "com.android.support.test.espresso:espresso-core:$versions.espresso"
deps.test = test
versions.junit = "1.2.0"
versions.junit = "4.13.1"
versions.androidExtJunit = "1.1.2"
versions.espressoCore = "3.2.0"
versions.okhttp = "4.9.1"
ext.deps = deps
ext.versions = versions
def addRepos(RepositoryHandler handler) {
handler.google()
handler.jcenter()
}
ext.addRepos = this.&addRepos
Loading…
Cancel
Save