Browse Source

common

master
suliang 2 years ago
parent
commit
d9bcd12263
36 changed files with 2930 additions and 114 deletions
  1. 55
    0
      .idea/codeStyles/Project.xml
  2. 461
    0
      .idea/dbnavigator.xml
  3. 2
    1
      .idea/misc.xml
  4. 1
    0
      app/src/main/AndroidManifest.xml
  5. 56
    23
      app/src/main/java/com/xkl/cdl/module/splash/SplashActivity.kt
  6. 81
    15
      app/src/main/res/layout/activity_splash.xml
  7. 5
    6
      build.gradle
  8. 11
    33
      lib/common/build.gradle
  9. 7
    0
      lib/common/src/main/AndroidManifest.xml
  10. 29
    23
      lib/common/src/main/java/com/suliang/common/base/activity/BaseActivity.kt
  11. 57
    0
      lib/common/src/main/java/com/suliang/common/base/activity/LifecycleLogActivity.kt
  12. 45
    5
      lib/common/src/main/java/com/suliang/common/base/fragment/BaseFragment.kt
  13. 5
    3
      lib/common/src/main/java/com/suliang/common/base/fragment/BaseFragmentVM.kt
  14. 96
    0
      lib/common/src/main/java/com/suliang/common/base/fragment/LifecycleLogFragment.kt
  15. 117
    5
      lib/common/src/main/java/com/suliang/common/extension/ActivityFragmentExtension.kt
  16. 58
    0
      lib/common/src/main/java/com/suliang/common/util/LogUtil.kt
  17. 464
    0
      lib/common/src/main/java/com/suliang/common/util/file/FileUtil.kt
  18. 30
    0
      lib/common/src/main/java/com/suliang/common/util/image/GlideLoader.kt
  19. 32
    0
      lib/common/src/main/java/com/suliang/common/util/image/ILoaderStrategy.kt
  20. 63
    0
      lib/common/src/main/java/com/suliang/common/util/image/ImageLoader.kt
  21. 61
    0
      lib/common/src/main/java/com/suliang/common/util/image/LoaderOptions.kt
  22. 28
    0
      lib/common/src/main/java/com/suliang/common/util/media/EMediaState.kt
  23. 76
    0
      lib/common/src/main/java/com/suliang/common/util/media/IMP.kt
  24. 14
    0
      lib/common/src/main/java/com/suliang/common/util/media/IMPListener.kt
  25. 71
    0
      lib/common/src/main/java/com/suliang/common/util/media/MPManager.kt
  26. 338
    0
      lib/common/src/main/java/com/suliang/common/util/media/MPUtil.kt
  27. 57
    0
      lib/common/src/main/java/com/suliang/common/util/os/FloatingWindowUtil.kt
  28. 70
    0
      lib/common/src/main/java/com/suliang/common/util/os/IntentUtils.kt
  29. 32
    0
      lib/common/src/main/java/com/suliang/common/util/os/KeyboardUtil.kt
  30. 142
    0
      lib/common/src/main/java/com/suliang/common/util/os/ScreenUtil.kt
  31. 122
    0
      lib/common/src/main/java/com/suliang/common/util/os/SystemUtil.kt
  32. 107
    0
      lib/common/src/main/java/com/suliang/common/util/os/VersionUtil.kt
  33. 86
    0
      lib/common/src/main/java/com/suliang/common/util/thread/AppExecutors.kt
  34. 23
    0
      lib/common/src/main/java/com/suliang/common/util/thread/MainThreadExecutor.kt
  35. 22
    0
      lib/common/src/main/java/com/suliang/common/util/thread/ThreadFactoryImp.kt
  36. 6
    0
      lib/common/src/main/res/xml/network_security_config.xml

+ 55
- 0
.idea/codeStyles/Project.xml View File

@@ -1,8 +1,57 @@
<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>
<JetCodeStyleSettings>
<option name="CONTINUATION_INDENT_FOR_CHAINED_CALLS" value="true" />
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
</JetCodeStyleSettings>
<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>
<codeStyleSettings language="XML">
<indentOptions>
<option name="CONTINUATION_INDENT_SIZE" value="4" />
@@ -117,6 +166,12 @@
</codeStyleSettings>
<codeStyleSettings language="kotlin">
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
<option name="RIGHT_MARGIN" value="120" />
<option name="KEEP_LINE_BREAKS" value="false" />
<option name="CALL_PARAMETERS_WRAP" value="0" />
<option name="METHOD_PARAMETERS_WRAP" value="1" />
<option name="METHOD_PARAMETERS_LPAREN_ON_NEXT_LINE" value="false" />
<option name="METHOD_PARAMETERS_RPAREN_ON_NEXT_LINE" value="false" />
</codeStyleSettings>
</code_scheme>
</component>

+ 461
- 0
.idea/dbnavigator.xml View File

@@ -0,0 +1,461 @@
<?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" />
<friendly-headers value="false" />
<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="UTF-8" />
</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.ExecutionManager">
<retain-sticky-names value="false" />
</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.ParserDiagnosticsManager">
<diagnostics-history />
</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="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="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="JSON" enabled="true" />
<content-type name="JSON5" 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>

+ 2
- 1
.idea/misc.xml View File

@@ -3,6 +3,7 @@
<component name="DesignSurface">
<option name="filePathToZoomLevelMap">
<map>
<entry key="..\:/Work/XKL/XKL/XklLocal/app/src/main/res/layout/activity_splash.xml" value="0.4921875" />
<entry key="..\:/Work/XKL/XklLocal/app/src/main/res/drawable/theme_splash_bg.xml" value="0.22" />
<entry key="..\:/Work/XKL/XklLocal/app/src/main/res/layout/activity_fullscreen.xml" value="0.10144927536231885" />
<entry key="..\:/Work/XKL/XklLocal/app/src/main/res/layout/activity_main.xml" value="0.1" />
@@ -10,7 +11,7 @@
</map>
</option>
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="true" project-jdk-name="JDK" project-jdk-type="JavaSDK">
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="true" project-jdk-name="11" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" />
</component>
<component name="ProjectType">

+ 1
- 0
app/src/main/AndroidManifest.xml View File

@@ -12,6 +12,7 @@
<activity
android:name=".module.splash.SplashActivity"
android:configChanges="orientation|keyboardHidden|screenSize"
android:screenOrientation="portrait"
android:exported="true"
android:theme="@style/Theme.Splash">
<intent-filter>

+ 56
- 23
app/src/main/java/com/xkl/cdl/module/splash/SplashActivity.kt View File

@@ -1,46 +1,79 @@
package com.xkl.cdl.module.splash

import android.annotation.SuppressLint
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.os.Message
import androidx.databinding.ObservableField
import android.view.View
import com.suliang.common.base.activity.BaseActivity
import com.suliang.common.util.LogUtil
import com.suliang.common.util.image.ImageLoader
import com.suliang.common.util.media.*
import com.suliang.common.util.media.MPManager.addPlayListener
import com.suliang.common.util.thread.AppExecutors
import com.xkl.cdl.R
import com.xkl.cdl.databinding.ActivitySplashBinding

@SuppressLint("CustomSplashScreen")
class SplashActivity : BaseActivity<ActivitySplashBinding>() {

val textvalue = ObservableField<String>()

private val handler = object : Handler(Looper.getMainLooper()){
override fun handleMessage(msg: Message) {
super.handleMessage(msg)
binding.text.text = textvalue.get()
}
}

override fun onCreate(savedInstanceState: Bundle?) {
if(!isTaskRoot){
if (!isTaskRoot) {
finish()
return
}
super.onCreate(savedInstanceState)
}
var count = 1

override fun initActivity(savedInstanceState: Bundle?) {

val x = Tvalue(textvalue)
binding.name = x
}

handler.postDelayed(object : Runnable{
override fun run() {
count ++
textvalue.set("$count")
handler.postDelayed(this,1000)

override fun loadData() {
ImageLoader.loadImage(R.mipmap.ic_launcher)
}

val util:IMP = MPManager.apply {
addPlayListener(object :IMPListener{
override fun onMpState(state: EMediaState) {
LogUtil.e("SplashActivity 接收 ${Thread.currentThread() == Looper.getMainLooper().thread} ${Thread.currentThread()} --${state.name}")
}
},1000)
})
}
var position = 0
var seekToPosition = 1000
val source = listOf("http://ws.stream.qqmusic.qq.com/C4000017AcyB00D6Cr.m4a?guid=395745734&vkey=9CF75D8FDE45B1A7755C15F93C5F74CC18E444873F498DF3F5B9F0B41889778BE0EAC45305BD444F90D777C612BC5865DB0AF55A318854E1&uin=&fromtag=66"
,"http://ws.stream.qqmusic.qq.com/C400001lePVO36SNSo.m4a?guid=748677131&vkey=A6077E31CB4A1C8A4F3D5D2BEDE8D3E16AB69FF2FEE95D9D48288EAD048AB89DC53B6846D184F842257617AF9714D97C864FB183BE05CC66&uin=&fromtag=66"
,"http://ws.stream.qqmusic.qq.com/C400000ZFBf22vBvrf.m4a?guid=921100582&vkey=C9EA33972404E2AC1C80A479614AD44A9BF132A16F9B307D9515F595B41901AE53F6681E28BDA799FC047F30D624F8A19AE9C4856ADBEF4B&uin=&fromtag=66")


fun play(view: View) {
seekToPosition = 1000
util.play(source[0])
}
fun continuePlay(view: View) {
util.continuePlay()
}
fun stop(view: View) {
util.stopPlay()
}
fun pause(view: View) {
util.pausePlay()
}
fun seekTo(view: View) {
util.seekTo(seekToPosition)
seekToPosition += 30000
}
fun release(view: View) {
util.destroyPlay()
}

fun nextPlay(view: View) {
var i = position + 1
if (i > 3){
i = 0
}
position = i
util.play(source[position])
}
}
data class Tvalue(val value : ObservableField<String>)

+ 81
- 15
app/src/main/res/layout/activity_splash.xml View File

@@ -4,9 +4,7 @@
xmlns:tools="http://schemas.android.com/tools">

<data>
<variable
name="name"
type="com.xkl.cdl.module.splash.Tvalue" />

</data>

<androidx.constraintlayout.widget.ConstraintLayout
@@ -18,23 +16,91 @@
android:id="@+id/img"
android:layout_width="match_parent"
android:layout_height="0dp"
android:src="@drawable/illustration_splash"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintDimensionRatio="1125:813"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintVertical_bias="0.6"
app:layout_constraintDimensionRatio="1125:813"
android:src="@drawable/illustration_splash"
/>
<TextView
android:id="@+id/text"
app:layout_constraintVertical_bias="0.6" />

<Button
android:id="@+id/button5"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@={name.value}"
android:textSize="18dp"
app:layout_constraintBottom_toBottomOf="parent"
android:layout_marginTop="8dp"
android:text="下一曲"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/button"
android:onClick="nextPlay"/>

<Button
android:id="@+id/button6"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="104dp"
android:layout_marginTop="8dp"
android:text="拖动"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"/>
app:layout_constraintTop_toBottomOf="@+id/button"
android:onClick="seekTo"/>

<Button
android:id="@+id/button7"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="204dp"
android:layout_marginTop="8dp"
android:onClick="release"
android:text="释放"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/button" />

<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="24dp"
android:text="播放"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/img"
android:onClick="play"/>

<Button
android:id="@+id/button2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:layout_marginTop="24dp"
android:text="暂停"
app:layout_constraintStart_toEndOf="@+id/button"
app:layout_constraintTop_toBottomOf="@+id/img"
android:onClick="pause"/>

<Button
android:id="@+id/button3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="24dp"
android:text="停止"
app:layout_constraintStart_toEndOf="@+id/button2"
app:layout_constraintTop_toBottomOf="@+id/img"
android:onClick="stop"/>

<Button
android:id="@+id/button4"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:text="继续"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.555"
app:layout_constraintStart_toEndOf="@+id/button3"
app:layout_constraintTop_toBottomOf="@+id/img"
android:onClick="continuePlay"/>


</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>

+ 5
- 6
build.gradle View File

@@ -34,7 +34,8 @@ ext {
core_ktx_version : "1.3.2",
appcompat_version: "1.4.1",
material_version : "1.3.0",
lifecycle_version: "2.5.0-alpha02"
lifecycle_version: "2.5.0-alpha02",
glide_version : "4.1.0",
]
//必须依赖
dependencies_required = [
@@ -68,12 +69,10 @@ ext {
dependencies_custom = [
//设置状态栏和导航栏的框架 https://github.com/Zackratos/UltimateBarX
UltimateBarX: "com.gitee.zackratos:UltimateBarX:0.8.0",
//Glide 依赖和配置
Glide : "com.github.bumptech.glide:glide:${versions.glide_version}",
GlideCompiler : "com.github.bumptech.glide:compiler:${versions.glide_version}",
]
//注解
// dependencies_kapt = [
// lifecycle_compiler: "androidx.lifecycle:lifecycle-compiler:${versions.lifecycle_version}",
//
// ]


}

+ 11
- 33
lib/common/build.gradle View File

@@ -19,6 +19,12 @@ android {

buildTypes {
release {
buildConfigField("Boolean","LOG_ENABLE","false")
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
debug {
buildConfigField("Boolean","LOG_ENABLE","true")
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
@@ -40,39 +46,11 @@ dependencies {
rootProject.ext.dependencies_required.each{ k,v -> implementation v}
testImplementation rootProject.ext.dependencies_testImplementation.junit
rootProject.ext.dependencies_androidTestImplementation.each{ k,v -> androidTestImplementation v}
// api rootProject.ext.dependencies_custom.UltimateBarX

// kapt rootProject.ext.dependencies_kapt.lifecycle_compiler

def lifecycle_version = "2.5.0-alpha02"
def arch_version = "2.1.0"
//状态栏
api rootProject.ext.dependencies_custom.UltimateBarX
//glide
api rootProject.ext.dependencies_custom.Glide
kapt rootProject.ext.dependencies_custom.GlideCompiler

// // ViewModel
// implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
// // ViewModel utilities for Compose
// implementation "androidx.lifecycle:lifecycle-viewmodel-compose:$lifecycle_version"
// // LiveData
// implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"
// // Lifecycles only (without ViewModel or LiveData)
// implementation "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycle_version"
//
// // Saved state module for ViewModel
// implementation "androidx.lifecycle:lifecycle-viewmodel-savedstate:$lifecycle_version"
//
// // Annotation processor
//// kapt "androidx.lifecycle:lifecycle-compiler:$lifecycle_version"
// // alternately - if using Java8, use the following instead of lifecycle-compiler
// implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version"
// // optional - helpers for implementing LifecycleOwner in a Service
// implementation "androidx.lifecycle:lifecycle-service:$lifecycle_version"
//
// // optional - ProcessLifecycleOwner provides a lifecycle for the whole application process
// implementation "androidx.lifecycle:lifecycle-process:$lifecycle_version"
//
// // optional - ReactiveStreams support for LiveData
// implementation "androidx.lifecycle:lifecycle-reactivestreams-ktx:$lifecycle_version"
//
// // optional - Test helpers for LiveData
// testImplementation "androidx.arch.core:core-testing:$arch_version"

}

+ 7
- 0
lib/common/src/main/AndroidManifest.xml View File

@@ -1,5 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.suliang.common">
<uses-permission android:name="android.permission.INTERNET"/>
<!--networkSecurityConfig,usesCleartextTraffic 二选一都可以9.0-P以上高版本的http连接,否则连接失败
android:networkSecurityConfig="@xml/network_security_config"
android:usesCleartextTraffic="true"
-->
<application
android:networkSecurityConfig="@xml/network_security_config"/>

</manifest>

+ 29
- 23
lib/common/src/main/java/com/suliang/common/base/activity/BaseActivity.kt View File

@@ -1,76 +1,82 @@
package com.suliang.common.base.activity

import android.graphics.Color
import android.os.Bundle
import android.view.LayoutInflater
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.ViewModelProviders
import androidx.viewbinding.ViewBinding
import com.suliang.common.extension.initBinding
import com.suliang.common.util.ActivityStackManager
import java.lang.reflect.ParameterizedType
import com.zackratos.ultimatebarx.ultimatebarx.statusBarOnly

/**
* 基类Activity
*/
abstract class BaseActivity<VB : ViewBinding> : AppCompatActivity() {
abstract class BaseActivity<VB : ViewBinding> : LifecycleLogActivity() {

lateinit var binding: VB
private var _binding: VB? = null

val binding : VB
get() = _binding!!

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
addStackManager()
//入栈
ActivityStackManager.addActivity(this)
initFirst()
initActivity(savedInstanceState) }
initActivity(savedInstanceState)
loadData()
}

/***
* 实例化binding,
*/
protected open fun initFirst() {
setLayoutBefore()
binding = initBinding(layoutInflater)
_binding = initBinding(layoutInflater)
setContentView(binding.root)
initStatusBar()
setLayoutAfter()
}


/**
* 状态栏初始化方法
*/
open fun initStatusBar() {
// statusBarOnly {
// fitWindow = true
// color = Color.WHITE
// light = true
// }
statusBarOnly {
fitWindow = true
color = Color.WHITE
light = true
}
}

override fun onDestroy() {
super.onDestroy()
ActivityStackManager.removeActivity(this)
}


private fun addStackManager() {
ActivityStackManager.addActivity(this)
_binding = null
}

/** 布局前操作 : 空实现 */
public open fun setLayoutBefore() {
open fun setLayoutBefore() {

}

/**
* 布局后操作:空实现
*/
public open fun setLayoutAfter() {
open fun setLayoutAfter() {

}

/**
* onCreate中给与app中用户的具体实现
* binding 与 ViewModel都已实现好
* @param savedInstanceState Bundle?
*/
abstract fun initActivity(savedInstanceState: Bundle?)

/** onCreate()最后实现的数据加载调用 */
abstract fun loadData()

// 反射实现 ViewBinding
// @SuppressWarnings("unchecked")
// fun initBinding(inflater: LayoutInflater): VB {
// val vbClass =

+ 57
- 0
lib/common/src/main/java/com/suliang/common/base/activity/LifecycleLogActivity.kt View File

@@ -0,0 +1,57 @@
package com.suliang.common.base.activity

import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import com.suliang.common.util.LogUtil

/**
* author suliang
* create 2022/3/7 10:47
* Describe: 打印生命周期的Activity
*/
open class LifecycleLogActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
LogUtil.d("onCreate()")
}

override fun onRestart() {
super.onRestart()
LogUtil.d("onRestart()")
}

override fun onStart() {
super.onStart()
LogUtil.d("onStart()")
}

override fun onResume() {
super.onResume()
LogUtil.d("onResume()")
}

override fun onPause() {
super.onPause()
LogUtil.d("onPause()")
}

override fun onStop() {
super.onStop()
LogUtil.d("onStop()")
}

override fun onDestroy() {
super.onDestroy()
LogUtil.d("onDestroy()")
}

override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
LogUtil.d("onSaveInstanceState()")
}

override fun onRestoreInstanceState(savedInstanceState: Bundle) {
super.onRestoreInstanceState(savedInstanceState)
LogUtil.d("onRestoreInstanceState()")
}
}

+ 45
- 5
lib/common/src/main/java/com/suliang/common/base/fragment/BaseFragment.kt View File

@@ -7,20 +7,35 @@ import android.view.ViewGroup
import androidx.databinding.ViewDataBinding
import androidx.fragment.app.Fragment
import com.suliang.common.extension.initBinding

/**
* Fragment ViewBinding基类,封装ViewDataBinding
* Fragment ViewBinding基类,封装ViewDataBinding 自带懒加载
*
* TabLayout+ViewPager懒加载: 关联使用 tabLayout.setupWithViewPager(ViewPager),
* 适配器使用两参构造,第二参数使用:BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT,否则会走setUserVisibleHint
*
* FragmentStatePagerAdapter会调用onSaveInstanceState方法来保存Fragment的状态
*
* 通过add + show + hide 模式加载Fragment
*
* @property VB : ViewDataBinding
* @property VM: ViewModel
*/
abstract class BaseFragment<VB: ViewDataBinding>: Fragment(){
lateinit var binding: VB
abstract class BaseFragment<VB : ViewDataBinding> : Fragment() {

private var _binding: VB? = null

val binding : VB
get() = _binding!!

//是否加载数据
private var isLoaded = false

override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
binding = initBinding(inflater,container)
_binding = initBinding(inflater, container)
return super.onCreateView(inflater, container, savedInstanceState)
}

@@ -30,7 +45,32 @@ abstract class BaseFragment<VB: ViewDataBinding>: Fragment(){
initFragment()
}

override fun onResume() {
super.onResume()
if (!isLoaded && !isHidden) {
loadData()
isLoaded = true
}
}

override fun onDestroy() {
super.onDestroy()
_binding = null
}

/**
* onViewCreated中进行的初始:多数时候可以是空实现,主要在使用VM时进行vm的初始
*/
abstract fun initFirst()

/**
* onViewCreated() 后进行的Fragment初始设置
*/
abstract fun initFragment()

/**
* 界面显示出来后再加载数据
*/
abstract fun loadData()

}

+ 5
- 3
lib/common/src/main/java/com/suliang/common/base/fragment/BaseFragmentVM.kt View File

@@ -2,16 +2,18 @@ package com.suliang.common.base.fragment

import androidx.databinding.ViewDataBinding
import androidx.lifecycle.ViewModel

/**
* Fragment DataBinding 与 ViewModel基类,封装DataBinding和ViewModel
* Fragment DataBinding 与 ViewModel基类,封装DataBinding和ViewModel 自带懒加载
* @property VB : ViewDataBinding
* @property VM: ViewModel
*/
abstract class BaseFragmentVM<VB:ViewDataBinding,VM:ViewModel>:BaseFragment<VB>() {
abstract class BaseFragmentVM<VB : ViewDataBinding, VM : ViewModel> : BaseFragment<VB>() {
lateinit var viewModel: VM

override fun initFirst() {
viewModel = initViewModel()
}

abstract fun initViewModel():VM
abstract fun initViewModel(): VM
}

+ 96
- 0
lib/common/src/main/java/com/suliang/common/base/fragment/LifecycleLogFragment.kt View File

@@ -0,0 +1,96 @@
package com.suliang.common.base.fragment

import android.content.Context
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import com.suliang.common.util.LogUtil

/**
* author suliang
* create 2022/3/7 14:14
* Describe: Fragment生命周期日志打印
*/
class LifecycleLogFragment : Fragment() {
override fun onAttach(context: Context) {
super.onAttach(context)
LogUtil.d("onAttach")
}

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
LogUtil.d("onCreate")
}

override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
LogUtil.d("onCreateView")
return super.onCreateView(inflater, container, savedInstanceState)
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
LogUtil.d("onViewCreated")
}

override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
LogUtil.d("onActivityCreated")
}

override fun onStart() {
super.onStart()
LogUtil.d("onStart")
}

override fun onResume() {
super.onResume()
LogUtil.d("onResume")
}

override fun onStop() {
super.onStop()
LogUtil.d("onStop")
}

override fun onDestroyView() {
super.onDestroyView()
LogUtil.d("onDestroyView")
}

override fun onDetach() {
super.onDetach()
LogUtil.d("onDetach")
}

override fun onDestroy() {
super.onDestroy()
LogUtil.d("onDestroy")
}

override fun onHiddenChanged(hidden: Boolean) {
super.onHiddenChanged(hidden)
LogUtil.d("onHiddenChanged : hidden -> : $hidden" )
}

override fun setUserVisibleHint(isVisibleToUser: Boolean) {
super.setUserVisibleHint(isVisibleToUser)
LogUtil.d("setUserVisibleHint: isVisibleToUser -> $isVisibleToUser ")
}

override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
LogUtil.d(javaClass.simpleName + " onSaveInstanceState()")
}

override fun onViewStateRestored(savedInstanceState: Bundle?) {
super.onViewStateRestored(savedInstanceState)
LogUtil.d(javaClass.simpleName + " onViewStateRestored()")
}
}

+ 117
- 5
lib/common/src/main/java/com/suliang/common/extension/ActivityFragmentExtension.kt View File

@@ -1,10 +1,122 @@
package com.suliang.common.extension

import androidx.annotation.IdRes
import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import androidx.fragment.app.FragmentManager
import androidx.lifecycle.Lifecycle

//inline fun <reified T : Fragment> AppCompatActivity.addFragment(@IdRes containerViewId:Int){
// supportFragmentManager.beginTransaction()
// .add(containerViewId,newInstanceFragment<T>()).commit()
//}
/**
* author suliang
* create 2022/3/7 14:30
* Describe: 扩展: fragment的显示 隐藏
*/

/*----------------------------------Activity Start-------------------------------------*/
/**
* Activity 级别 加载Fragment集合进入容器,并显示
* @receiver FragmentActivity
* @param containerId Int
* @param showPosition Int
* @param fragment Array<out Fragment>
*/
fun FragmentActivity.loadFragment(@IdRes containerId: Int,showPosition: Int = 0,vararg fragment: Fragment){
loadFragmentTransaction(containerId,showPosition,supportFragmentManager,*fragment)
}

/**
* Activity 显示指定Fragment并隐藏其他Fragment
* @receiver FragmentActivity
* @param showFragment Fragment
*/
fun FragmentActivity.showHideFragment(showFragment: Fragment){
showHideFragmentTransaction(supportFragmentManager,showFragment)
}


/*----------------------------------Activity End-------------------------------------*/


/*----------------------------------Fragment Start-------------------------------------*/

/**
* Fragment 添加 子Fragment进入容器,并显示[showPosition]位置的fragment
* @param containerId Int
* @param showPosition Int
* @param fragments Array<out Fragment>
*/
fun Fragment.loadFragment(@IdRes containerId: Int,showPosition: Int,vararg fragments: Fragment){
loadFragmentTransaction(containerId,showPosition,childFragmentManager,*fragments)
}

/**
* Fragment 显示指定的子Fragment
* @receiver Fragment
* @param showFragment Fragment
*/
fun Fragment.showHideFragment(showFragment: Fragment){
showHideFragmentTransaction(childFragmentManager,showFragment)
}


/*----------------------------------Fragment End-------------------------------------*/


/**
* 使用 add + show + hide 实现懒加载
* 默认显示[showPosition]位置的fragment,最大[Lifecycle]为[Lifecycle.State.RESUMED]
* @param containerId Int 容器
* @param showPosition Int 显示位置
* @param supportFragmentManager FragmentManager
* @param fragment Array<out Fragment> 集合
*/
private fun loadFragmentTransaction(
@IdRes containerId: Int,
showPosition: Int,
fragmentManager: FragmentManager,
vararg fragment : Fragment
) {
when(fragment.isNotEmpty()){
true -> {
fragmentManager.beginTransaction().apply {
fragment.forEachIndexed{ index, fragment ->
//添加fragment,并设置有tag
add(containerId,fragment,fragment::javaClass.name)
//设置什么周期
when(index){
showPosition -> {
setMaxLifecycle(fragment,Lifecycle.State.RESUMED)
}
else -> {
hide(fragment)
setMaxLifecycle(fragment,Lifecycle.State.STARTED)
}
}
}
}.commit()
}
else -> throw IllegalStateException("fragments must not empty!")
}
}

/**
* 显示指定Fragment且隐藏其他Fragment
* @param fragmentManager FragmentManager
* @param showFragment Fragment
*/
private fun showHideFragmentTransaction(fragmentManager: FragmentManager, showFragment: Fragment) {
fragmentManager.beginTransaction().apply {
fragmentManager.fragments.forEach{
when(it){
showFragment -> {
show(it)
setMaxLifecycle(it,Lifecycle.State.RESUMED)
}
else -> {
hide(it)
setMaxLifecycle(it,Lifecycle.State.STARTED)
}
}
}
}.commit()
}

+ 58
- 0
lib/common/src/main/java/com/suliang/common/util/LogUtil.kt View File

@@ -0,0 +1,58 @@
package com.suliang.common.util

import android.util.Log
import com.suliang.common.BuildConfig

/**
* author suliang
* create 2022/3/7 10:55
* Describe: Log日志打印框架
*/
object LogUtil {

@JvmStatic
private val LogEnable =
BuildConfig.LOG_ENABLE // 根据BuildConfig中的field LOG_ENABLE配置的值,确定是否打印

/**
* 创建输出内容
* @param element StackTraceElement
* @param message String
* @return String
*/
private fun createLog(element:StackTraceElement ,message : String) : String{
return StringBuffer().apply {
append("====${element.methodName}() : ${element.lineNumber} ==== $message ====" )
}.toString()
}

fun v(message: String) {
if (!LogEnable) return
val stackTrace = Throwable().stackTrace[1]
Log.v(stackTrace.fileName,createLog(stackTrace,message))
}

fun d(message: String) {
if (!LogEnable) return
val stackTrace = Throwable().stackTrace[1]
Log.d(stackTrace.fileName,createLog(stackTrace,message))
}

fun e(message: String) {
if (!LogEnable) return
val stackTrace = Throwable().stackTrace[1]
Log.e(stackTrace.fileName,createLog(stackTrace,message))
}

fun i(message: String) {
if (!LogEnable) return
val stackTrace = Throwable().stackTrace[1]
Log.i(stackTrace.fileName,createLog(stackTrace,message))
}

fun w(message: String) {
if (!LogEnable) return
val stackTrace = Throwable().stackTrace[1]
Log.w(stackTrace.fileName,createLog(stackTrace,message))
}
}

+ 464
- 0
lib/common/src/main/java/com/suliang/common/util/file/FileUtil.kt View File

@@ -0,0 +1,464 @@
package com.suliang.common.util.file

import android.os.Environment
import com.suliang.common.util.AppGlobals
import com.suliang.common.util.LogUtil
import java.io.*
import java.lang.Exception
import java.text.DecimalFormat
import java.util.zip.GZIPInputStream
import java.util.zip.GZIPOutputStream

/**
* author suliang
* create 2022/3/7 17:06
* Describe: File工具类
*/
object FileUtil {

const val SIZETYPE_B = 1 // 获取文件大小单位为B的double值

const val SIZETYPE_KB = 2 // 获取文件大小单位为KB的double值

const val SIZETYPE_MB = 3 // 获取文件大小单位为MB的double值

const val SIZETYPE_GB = 4 // 获取文件大小单位为GB的double值

/**
* 获取应用内置存储目录文件
*/
private fun getAppExternalFile(): File {
val file = if (Environment.MEDIA_MOUNTED == Environment.getExternalStorageState()) {
AppGlobals.application.getExternalFilesDir(null)
} else {
AppGlobals.application.filesDir
}
file?.let {
if (it.exists()) file.mkdirs()
}
LogUtil.d("应用内置目录文件: ${file?.path}")
return file ?: throw NullPointerException("Expression 'file' must not be null")
}

/**
* 获取定义的用于保存的目录文件 [getAppExternalFile]/[saveDirName]/
* @param saveDirName String 保存的文件目录名称
* @return File
*/
fun getSaveDirFile(saveDirName: String): File {
return File(getAppExternalFile(), saveDirName).apply {
if (!this.exists()) mkdirs()
}
}

/**
* 获取定义的用于保存的目录路径: [getAppExternalFile]/[saveDirName]/
* @param saveDirName String 保存的文件目录名称
* @return String
*/
fun getSaveDirPath(saveDirName: String): String {
return getSaveDirFile(saveDirName).path
}

/**
* 创建一个新文件
* @param parentFile File 父文件
* @param fileName String 文件名
* @return File 创建的文件
*/
fun createNewFile(parentFile: File, fileName: String): File {
if (!parentFile.exists()) parentFile.mkdirs()
val newFile = File(parentFile, fileName)
var createNewFile = false
try {
createNewFile = newFile.createNewFile()
} catch (e: IOException) {
e.printStackTrace()
}
LogUtil.d("创建的新文件结果:$createNewFile , 文件路径: ${newFile.path}")
return newFile
}

/**
* 创建一个新文件
* @param parentFilePath File 父文件路径
* @param fileName String 文件名
* @return File 创建的文件
*/
fun createNewFile(parentFilePath: String, fileName: String): File {
return createNewFile(File(parentFilePath), fileName)
}

/**
* 删除文件/文件夹
* @param filePath String 文件路径
*/
fun deleteFile(filePath: String) {
deleteFile(File(filePath))
}

/**
* 删除文件/文件夹
* @param file File 被删除的文件
*/
fun deleteFile(file: File) {
if (file.exists()) {
synchronized(file) {
if (file.isDirectory) {
file.listFiles()?.forEach {
if (file.isDirectory) {
deleteFile(it)
} else {
it.delete()
}
}
}
file.delete()
}
}
}

/**
* 将字节数组写入文件
* @param file File 被写入的文件
* @param content ByteArray 被写入的内容
*/
fun writeBytesToFile(file: File, content: ByteArray) {
try {
file.parentFile?.let {
if (!it.exists()) {
it.mkdirs()
}
}
val inputStream = ByteArrayInputStream(content)
val outputStream = FileOutputStream(file)
writeStream(inputStream, outputStream)
} catch (e: Exception) {
LogUtil.e("文件写入失败: ${file.path}")
}
}

/**
* 将字节数组写入文件
* @param parentFile File 父文件
* @param fileName String 写入文件名称
* @param content ByteArray 内容
*/
fun writeBytesToFile(parentFile: File, fileName: String, content: ByteArray) {
val file = File(parentFile, fileName)
writeBytesToFile(file, content)
}

/**
* 将字节数组写入文件
* @param parentFilePath String 父文件路径
* @param fileName String 写入文件名称
* @param content ByteArray 内容
*/
fun writeBytesToFile(parentFilePath: String, fileName: String, content: ByteArray) {
val file = File(parentFilePath, fileName)
writeBytesToFile(file, content)
}

/**
* 写入方法
* @param inputStream InputStream 输入流
* @param outputStream OutputStream 输出流
*/
private fun writeStream(inputStream: InputStream, outputStream: OutputStream) {
try {
var len = 0
val buffer: ByteArray = ByteArray(1024)
while ((inputStream.read(buffer).also { len = it }) != -1) {
outputStream.write(buffer, 0, len)
}
} catch (e: Exception) {
LogUtil.e("写入文件失败:${e.message}")
e.printStackTrace()
} finally {
closeStream(inputStream)
closeStream(outputStream)
}
}

/**
* 复制文件
* @param srcFile File 源文件
* @param destFile File 目标文件
* @param deleteSrcFile Boolean 是否需要删除源文件
*/
@Synchronized
fun copyFile(srcFile: File, destFile: File, deleteSrcFile: Boolean): Boolean {
//源文件部存在,直接返回false
if (!srcFile.exists()) return false
//目标文件父文件判断
destFile.parentFile?.let {
if (!it.exists()) it.mkdirs()
}
try {
val inputStream = FileInputStream(srcFile)
val outputStream = FileOutputStream(destFile)
writeStream(inputStream, outputStream)
if (deleteSrcFile) srcFile.delete()
return true
} catch (e: Exception) {
LogUtil.e("复制文件失败")
e.printStackTrace()
}
return false
}

/**
* 复制文件
* @param srcPath File 源文件路径
* @param destPath File 目标文件路径
* @param deleteSrcFile Boolean 是否需要删除源文件
*/
fun copyFile(srcPath: String, destPath: String, deleteSrcFile: Boolean): Boolean {
return copyFile(File(srcPath), File(destPath), deleteSrcFile)
}

/**
* 复制raw文件到本地
* @param rawId Int raw文件id
* @param parentDirFile File 父文件
* @param copyFileName String 新文件名称
* @return Boolean 结果
*/
fun copyRaw(rawId: Int, parentDirFile: File, copyFileName: String): Boolean {
try {
if (!parentDirFile.exists()) parentDirFile.mkdirs()
val inputStream = AppGlobals.application.resources.openRawResource(rawId)
val outputStream = FileOutputStream(File(parentDirFile, copyFileName))
writeStream(inputStream, outputStream)
} catch (e: Exception) {
e.printStackTrace()
return false
}
return true
}

/**
* 复制asset文件进入本地
* @param assetName String
* @param saveFile File
* @return Boolean 结果
*/
fun copyAsset(assetName: String, saveFile: File): Boolean {
saveFile.parentFile?.let {
if (!it.exists()) it.mkdirs()
}
try {
val inputStream = AppGlobals.application.assets.open(assetName)
val outputStream = FileOutputStream(saveFile)
writeStream(inputStream, outputStream)
} catch (e: Exception) {
e.printStackTrace()
LogUtil.e("复制asset失败: $assetName")
}
return true
}

/**
* 复制asset到本地
* @param assetName String asset名字
* @param parentFilePath String 父文件夹路径
* @param saveFileName String 复制的名称 默认为assetName
* @return Boolean
*/
fun copyAsset(assetName: String, parentFilePath: String, saveFileName: String = assetName): Boolean {
return copyAsset(assetName, File(parentFilePath, saveFileName))
}


/**
* 读取文件
* @param file File 读的文件
* @return ByteArray 返回字节数组
*/
fun readFile(file: File): ByteArray {
if (!file.exists()) return ByteArray(0)

try {
val fileInputStream = FileInputStream(file)
val outputStream = ByteArrayOutputStream()
var len = 0
var byteArray = ByteArray(1024)
while (fileInputStream.read(byteArray).also { len = it } != -1) {
outputStream.write(byteArray, 0, len)
}
byteArray = outputStream.toByteArray()
closeStream(fileInputStream)
closeStream(outputStream)

return byteArray
} catch (e: Exception) {
LogUtil.e("读取文件是失败 : ${e.message}")
e.printStackTrace()
}
return ByteArray(0)
}


/**
* 关闭流
* @param io Closeable?
*/
private fun closeStream(io: Closeable?) {
io?.run {
try {
io.close()
} catch (e: Exception) {
e.printStackTrace()
}
}
}


// /**
// TODO: 2022/3/8 此功能需要重新定义 进行版本适配
// * 保存图片到相册
// * @param context
// * @param bitmap 图片
// * @param name 相册下保存的文件加名称
// * @return
// */
// fun saveBitmapToDicm(context: Context, bitmap: Bitmap, name: String): String? {
// //相册路径
// val parentfile = File(
// Environment.getExternalStorageDirectory()
// .toString() + File.separator + Environment.DIRECTORY_DCIM + File.separator + name
// )
// if (!com.sl.lib_common.filecache.LibFileUtils.isExits(parentfile)) {
// val mkdirs = parentfile.mkdirs()
// }
// var file: File? = null
// var outputStream: FileOutputStream? = null
// try {
// file = File(parentfile, "IMG_" + System.currentTimeMillis() + ".jpg")
// outputStream = FileOutputStream(file)
// bitmap.compress(Bitmap.CompressFormat.JPEG, 100, outputStream)
// } catch (e: Exception) {
// e.printStackTrace()
// return null
// } finally {
// com.sl.lib_common.filecache.LibFileUtils.close(outputStream)
// }
// MediaStore.Images.Media.insertImage(context.contentResolver, bitmap, file!!.path, null)
// val intent = Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE)
// val uri = Uri.fromFile(file)
// intent.data = uri
// context.sendBroadcast(intent)
// return file.path
// }
/**
* 字符串的GZip压缩
* @param str 待压缩的字符串
* @return 返回压缩后的字符串
* @throws IOException
*/
@Throws(IOException::class)
fun compressGZip(str: String): String {
if (str.isEmpty()) return str
// 创建一个新的输出流
val out = ByteArrayOutputStream()
// 使用默认缓冲区大小创建新的输出流
val gzip = GZIPOutputStream(out)
// 将字节写入此输出流
gzip.write(str.toByteArray(charset("utf-8"))) // 因为后台默认字符集有可能是GBK字符集,所以此处需指定一个字符集
// 使用指定的 charsetName,通过解码字节将缓冲区内容转换为字符串
val result = out.toString("ISO-8859-1")
closeStream(gzip)
closeStream(out)
return result
}

/**
* 字符串的GZip解压
* @param str 对字符串解压
* @return 返回解压缩后的字符串
* @throws IOException
*/
@Throws(IOException::class)
fun unCompressGZip(str: String): String {
if (str.isEmpty()) return str
// 创建一个新的输出流
val out = ByteArrayOutputStream()
// 创建一个 ByteArrayInputStream,使用 buf 作为其缓冲区数组
val inputStream = ByteArrayInputStream(str.toByteArray(charset("ISO-8859-1")))
// 使用默认缓冲区大小创建新的输入流
val gzip = GZIPInputStream(inputStream)
val buffer = ByteArray(256)
var n = 0
// 将未压缩数据读入字节数组
while (gzip.read(buffer).also { n = it } >= 0) {
out.write(buffer, 0, n)
}
// 使用指定的 charsetName,通过解码字节将缓冲区内容转换为字符串
val result = out.toString("utf-8")
closeStream(inputStream)
closeStream(out)
return result
}

@Throws(Exception::class)
fun objectToBytes(data: Any): ByteArray {
var oos: ObjectOutputStream? = null
return try {
val bos = ByteArrayOutputStream()
oos = ObjectOutputStream(bos)
oos.writeObject(data)
bos.toByteArray()
} finally {
oos?.close()
}
}

/**
* 获取文件大小
* @param file File 需要获取大小的文件
* @return Long
*/
fun getFileSize(file: File): Long {
var result = 0L
if (!file.exists()) return 0
if (file.isDirectory) {
file.listFiles()?.forEach {
result += getFileSize(it)
}
}else{
result += file.length()
}
return result
}

/* private fun getFileSize(file: File) : Int{
if (file.exists()){
LogUtil.e("文件不存在: $file")
return 0
}
val fileInputStream = FileInputStream(file)
val size = fileInputStream.available()
closeStream(fileInputStream)
return size
}*/

/**
* 获取文件大小,默认保留两位小数,没有两位自动补0,为四舍五入模式
* @param file File 文件
* @param sizeType String 格式 B、KB、MB、GB
* @return String 结果
*/
fun getFileSize(file: File, sizeType:String) : String {
val fileSize = getFileSize(file)
return when(sizeType.uppercase()){
"B" -> DecimalFormat("#.00").format(fileSize)
"KB" -> DecimalFormat("#.00").format(fileSize/1024.0)
"MB" -> DecimalFormat("#.00").format(fileSize/1048576.0)
"GB" -> DecimalFormat("#.00").format(fileSize/1073741824.0)
else -> "too large"
}
}


}

+ 30
- 0
lib/common/src/main/java/com/suliang/common/util/image/GlideLoader.kt View File

@@ -0,0 +1,30 @@
package com.suliang.common.util.image

import java.lang.Exception

/**
* author suliang
* create 2022/3/11 16:14
* Describe: Glide 加载的具体实现
* https://juejin.cn/post/6844903463134953479#heading-3
*/
internal class GlideLoader: ILoaderStrategy {

override fun loadImage(option: LoaderOptions) {
when{
option.targetView == null -> throw Exception("Glide Option targetView is not empty")

}
}




override fun clearMemoryCache() {

}

override fun clearDiskCache() {

}
}

+ 32
- 0
lib/common/src/main/java/com/suliang/common/util/image/ILoaderStrategy.kt View File

@@ -0,0 +1,32 @@
package com.suliang.common.util.image

import android.net.Uri
import android.widget.ImageView
import java.io.File

/**
* author suliang
* create 2022/3/11 15:45
* Describe: 策略接口
*/
interface ILoaderStrategy {

fun loadImage(option : LoaderOptions)

/**
* 清理内存缓存
*/
fun clearMemoryCache()

/**
* 清理磁盘缓存
*/
fun clearDiskCache()


// fun getMemoryCacheSize()
//
// fun getDiskCacheSize()


}

+ 63
- 0
lib/common/src/main/java/com/suliang/common/util/image/ImageLoader.kt View File

@@ -0,0 +1,63 @@
package com.suliang.common.util.image

import android.content.Context
import android.net.Uri
import android.widget.ImageView
import androidx.annotation.DrawableRes
import java.io.File
import java.net.URL

/**
* author suliang
* create 2022/3/11 16:10
* Describe: 外部调用,图片加载库
*
* 使用: ImageLoader.load(url).into(View)
*
* 顺序判断,自动限制,前面参数有值了,则后面的值无效,如果都没有值,则抛异常
* url > file > drawableRes > uri
*
* https://blog.csdn.net/Coo123_/article/details/87950232
* https://juejin.cn/post/6844903463134953479#heading-3
* https://www.jianshu.com/p/19420b151507
* https://blog.csdn.net/EthanCo/article/details/102587193
*/
object ImageLoader : ILoaderStrategy {

//具体实现的策略,外部可设置
private var mStrategy: ILoaderStrategy = GlideLoader()

public fun setMStrategy(strategy: ILoaderStrategy) {
this.mStrategy = mStrategy
}


fun loadImage(imageView: ImageView, url: String) {
loadImage(LoaderOptions(url).apply { targetView = imageView })
}

fun loadImage(imageView: ImageView, file: File) {
loadImage( LoaderOptions(file = file).apply { targetView = imageView })
}

fun loadImage(imageView: ImageView, drawableRes: Int) {
loadImage(LoaderOptions(drawableResId = drawableRes).apply { targetView = imageView })
}

fun loadImage(imageView: ImageView, uri: Uri) {
loadImage(LoaderOptions(uri = uri).apply { targetView = imageView })
}

override fun loadImage(option: LoaderOptions) {
mStrategy.loadImage(option)
}


override fun clearMemoryCache() {
mStrategy.clearMemoryCache()
}

override fun clearDiskCache() {
mStrategy.clearDiskCache()
}
}

+ 61
- 0
lib/common/src/main/java/com/suliang/common/util/image/LoaderOptions.kt View File

@@ -0,0 +1,61 @@
package com.suliang.common.util.image

import android.graphics.drawable.Drawable
import android.net.Uri
import android.widget.ImageView
import androidx.annotation.IdRes
import java.io.File
import java.net.URL

/**
* author suliang
* create 2022/3/11 15:45
* Describe: 加载配置
* 提取通用的View、加载路径等多参数
*/
class LoaderOptions @JvmOverloads constructor(var url: String? = null, var file: File? = null,
var drawableResId: Int = -1, var uri: Uri? = null) {

/** 预加载图 */
var placeholderResId: Int = -1
var placeHolderDrawable: Drawable? = null

/** 错误图 */
var errorResId: Int = -1

/**是否居中裁剪*/
var centerCropEnable: Boolean = false

/** 居中全显 */
var centerInsideEnable: Boolean = false

/**是否缓存到本地*/
var skipLocalCache: Boolean = false

var skipNetCache: Boolean = false

var targetWidth: Int = -1
var targetHeight: Int = -1

//圆角角度
var bitmapAngle: Float = -1f

//图片
var targetView: ImageView? = null

//回调
// var callBack: BitmapCallBack? = null


// fun into(view: ImageView) {
// this.targetView = view
// ImageLoader.loadImage(this)
// }

// fun bitmap(callBack: BitmapCallBack) {
// this.callBack = callBack
// ImageLoader.loadOption(this)
// }


}

+ 28
- 0
lib/common/src/main/java/com/suliang/common/util/media/EMediaState.kt View File

@@ -0,0 +1,28 @@
package com.suliang.common.util.media

/**
* author suliang
* create 2022/3/8 16:41
* Describe: MediaPlayer定义的状态
*/
enum class EMediaState {
/**空置状态:设置了监听挺,mediaPlayer进行了初始化,但没有设置数据 */
IDLE,
/** 初始状态: 在idle后,设置了数据源的状态 */
INITIALIZED ,
/** 准备状态:设置数据源后,调用了 prepareAsync()方法 */
PREPARING,
/** 准备状态:可以开始播放了 */
PREPARED ,
/** 调用了播放,进入运行状态 */
RUNNING,
/** 暂停 */
PAUSE,
/** 停止 */
STOP,
/** 播放完成状态 */
COMPLETE,
/** 错误状态 */
ERROR

}

+ 76
- 0
lib/common/src/main/java/com/suliang/common/util/media/IMP.kt View File

@@ -0,0 +1,76 @@
package com.suliang.common.util.media

/**
* author suliang
* create 2022/3/8 16:21
* Describe: MediaPlayer操作接口
*/
interface IMP {

/**
* 开始播放,从指定位置开始
* @param url String 本地路径或网络url
* @param position Int 指定位置 , 默认为0
* @param listener 默认为空
*/
fun play(url: String, position: Int = 0,listener: IMPListener? = null)

/** 暂停播放 ,如果没有播放文件则会忽略*/
fun pausePlay()

/** 继续播放,如果没有播放文件则会忽略 */
fun continuePlay()

/** 停止播放,清空当前播放信息,如果没有播放文件信息,则忽略 */
fun stopPlay()

/** 释放销毁*/
fun destroyPlay()

/**
* 拖动到指定位置播放
* @param targetPosition Int
*/
fun seekTo(targetPosition : Int)





/**是否支持seekTo */
fun seekToEnable() : Boolean

/**
* 获取播放路径
* @return String
*/
fun getPlayPath(): String

/**
* 获取总时间
* @return Long
*/
fun getDuration(): Int

/**
* 获取当前播放位置
* @return Long
*/
fun getCurrentPosition(): Int

/** 是否播放中 */
fun isPlaying(): Boolean

/**
* 获取当前播放状态
* @return MediaPlayerStatus
*/
fun getMediaState(): EMediaState

/** 添加监听 */
fun addPlayListener(listener: IMPListener)

/** 移除监听 */
fun removePlayListener()

}

+ 14
- 0
lib/common/src/main/java/com/suliang/common/util/media/IMPListener.kt View File

@@ -0,0 +1,14 @@
package com.suliang.common.util.media

/**
* author suliang
* create 2022/3/8 16:53
* Describe: 播放监听
*/
interface IMPListener {
/**
* MediaPlayer的状态监听
* @param state EMediaStatu
*/
fun onMpState(state : EMediaState)
}

+ 71
- 0
lib/common/src/main/java/com/suliang/common/util/media/MPManager.kt View File

@@ -0,0 +1,71 @@
package com.suliang.common.util.media

/**
* author suliang
* create 2022/3/10 17:20
* Describe: MediaPlayer管理类
* 不要直接调用MPUtil
*
*/
object MPManager:IMP {

private val mpUtil : IMP = MPUtil()

override fun play(url: String, position: Int, listener: IMPListener?) {
mpUtil.play(url,position,listener)
}

override fun pausePlay() {
mpUtil.pausePlay()
}

override fun continuePlay() {
mpUtil.continuePlay()
}

override fun stopPlay() {
mpUtil.stopPlay()
}

override fun destroyPlay() {
mpUtil.destroyPlay()
}

override fun seekTo(targetPosition: Int) {
mpUtil.seekTo(targetPosition)
}

override fun seekToEnable(): Boolean {
return mpUtil.seekToEnable()
}

override fun getPlayPath(): String {
return mpUtil.getPlayPath()
}

override fun getDuration(): Int {
return mpUtil.getDuration()
}

override fun getCurrentPosition(): Int {
return mpUtil.getCurrentPosition()
}

override fun isPlaying(): Boolean {
return mpUtil.isPlaying()
}

override fun getMediaState(): EMediaState {
return mpUtil.getMediaState()
}

override fun addPlayListener(listener: IMPListener) {
mpUtil.addPlayListener(listener)
}

override fun removePlayListener() {
mpUtil.removePlayListener()
}


}

+ 338
- 0
lib/common/src/main/java/com/suliang/common/util/media/MPUtil.kt View File

@@ -0,0 +1,338 @@
package com.suliang.common.util.media

import android.media.MediaPlayer
import android.os.Handler
import android.os.Looper
import com.suliang.common.util.LogUtil
import kotlin.concurrent.thread

/**
* author suliang
* create 2022/3/8 17:04
* Describe: MediaPlayer播放器
* 正常使用:先调用play 退出时需释放
* seekTo 播放时,可直接使用, 暂停时,需在回调后调用start
* internal 模块内可见
*
*/
internal class MPUtil : IMP,MediaPlayer.OnPreparedListener,MediaPlayer.OnErrorListener,MediaPlayer.OnCompletionListener,MediaPlayer.OnInfoListener,MediaPlayer.OnSeekCompleteListener{


private var mediaPlayer: MediaPlayer? = null //播放器
private var urlPath: String? = null //播放路径
private var listener: IMPListener? = null //播放监听器
private var currentState: EMediaState = EMediaState.ERROR //播放状态
private var mTargetPosition = -1 //拖动位置

private fun MediaPlayer.resetPlayer() {
setOnPreparedListener(null)
setOnErrorListener(null)
setOnCompletionListener(null)
setOnInfoListener(null)
setOnSeekCompleteListener(null)
mTargetPosition = -1
try {
reset()
} catch (e: Exception) {
LogUtil.e("MediaPlayer reset释放异常 $currentState")
e.printStackTrace()
}
}

override fun play(url: String, position: Int,listener: IMPListener?) {
listener?.let {
this.listener = it
}
if (url.isEmpty()) {
handleError("url路径为空")
return
}
//初始化MediaPlayer
initMedia()
urlPath = url
//拖动到开始进行播放
seekTo(0)
}

/**
* MediaPlayer初始化,设置监听
*/
private fun initMedia() {
mediaPlayer?.resetPlayer() ?: let {
it.mediaPlayer = MediaPlayer()
}
mediaPlayer?.apply {
setOnPreparedListener(mOnPreparedLister)
setOnErrorListener(this@MPUtil)
setOnCompletionListener(this@MPUtil)
setOnInfoListener(this@MPUtil)
setOnSeekCompleteListener(this@MPUtil)
}
currentState = EMediaState.IDLE
handleListener()
urlPath = null
}



/** mediaPlayer的seekTo 完成后有监听回调,在播放中会跳到指定地方,暂停时跳到指定位置后需手动调用播放
* 1、initMedia后调用该方法
* 2、在外部直接调用该方法(必须是播放或暂停状态才有效果)
* */
override fun seekTo(targetPosition: Int) {
//路径错误,返回报错
if (urlPath.isNullOrEmpty()) {
handleError("seek url路径为空")
return
}
//修改定位标志
mTargetPosition = if (targetPosition < 0) 0 else targetPosition

//根据状态开始操作
if (currentState == EMediaState.IDLE) { //空闲状态,开始设置资源
try {
mediaPlayer?.setDataSource(urlPath) //设置资源
currentState = EMediaState.INITIALIZED //idle -> init
handleListener()
} catch (e: Exception) {
handleError("MediaPlayer设置资源失败")
e.printStackTrace()
return
}
}
when (currentState) {
EMediaState.INITIALIZED -> { // 设置好资源了,开始播放准备
currentState = EMediaState.PREPARING
handleListener()
mediaPlayer?.prepareAsync()
}
EMediaState.PREPARED -> { //准备好了,进行操作
handlePrepared()
}
EMediaState.RUNNING, EMediaState.PAUSE -> { //播放或暂停时,直接跳到指定位置
mediaPlayer?.seekTo(mTargetPosition) // 拖动播放(播放或暂停时重新检查状态处理)
}
else -> {
handleError("seekTo时状态异常" )
}
}
}

private val mOnPreparedLister: MediaPlayer.OnPreparedListener = MediaPlayer.OnPreparedListener {

}

/** 异步准备完毕 ,开始播放*/
private fun handlePrepared() {
if (currentState == EMediaState.PREPARED) { //修改为播放状态,调用播放
LogUtil.i("准备完成,开始播放")
mediaPlayer?.start()
currentState = EMediaState.RUNNING
handleListener()
checkSeekPlay()
} else {
handleError("状态异常,这里只能是 PREPARED 而现在是:$currentState")
}
}

// private val mOnSeekCompleteListener: MediaPlayer.OnSeekCompleteListener = MediaPlayer.OnSeekCompleteListener {
// LogUtil.i("mOnSeekCompleteListener 回调")
// handleSeekComplete()
// }
//
//
// private val mOnCompleteListener: MediaPlayer.OnCompletionListener = MediaPlayer.OnCompletionListener {
// handleComplete()
// }
//
//
// private val mOnErrorListener: MediaPlayer.OnErrorListener = MediaPlayer.OnErrorListener { _, what, extra ->
// handleError("mOnErrorListener 异常调用 $extra")
// true
// }
//
// private val mOnInfoListener: MediaPlayer.OnInfoListener = MediaPlayer.OnInfoListener { mp, what, extra ->
// false
// }

private fun handleSeekComplete() {
when (currentState) {
EMediaState.PREPARED -> handlePrepared()
EMediaState.RUNNING,EMediaState.PAUSE -> checkSeekPlay()
else -> {
handleError("seekComplete state error : ${currentState.name}")
}
}
}

private fun checkSeekPlay() {
mediaPlayer?.let {
//拖动只有播放中才有效
if (currentState == EMediaState.RUNNING || currentState == EMediaState.PAUSE) {
if (mTargetPosition >= 0) { //如果拖动大于等于0
it.seekTo(mTargetPosition)
mTargetPosition = -1
}
if (!it.isPlaying) { //如果暂停,则开始播放
it.start()
}
} else {
handleError("checkSeekPlay 非播放状态")
}
}
}

private fun handleError(errorMessage: String) {
LogUtil.e(errorMessage)
currentState = EMediaState.ERROR
handleListener()

}

override fun pausePlay() {
mediaPlayer?.let {
if (currentState == EMediaState.RUNNING) {
try {
it.pause()
currentState = EMediaState.PAUSE
handleListener()
} catch (e: Exception) {
e.printStackTrace()
handleError("暂停播放异常调用")
}
}
}
}

override fun continuePlay() {
mediaPlayer?.let {
when (currentState) {
EMediaState.PAUSE -> {
try {
it.start()
currentState = EMediaState.RUNNING
handleListener()
} catch (e: Exception) {
handleError("mp.start()异常 ${e.message}")
e.printStackTrace()
}
}
else ->{
handleError("继续播放异常调用")
}
}
}
}

override fun stopPlay() {
mediaPlayer?.let {
if (currentState != EMediaState.STOP) {
try {
it.stop()
currentState = EMediaState.STOP
handleListener()
} catch (e: Exception) {
handleError("mp.stop()异常 ${e.message}")
e.printStackTrace()
}
} else {
handleError("停止播放状态异常 ${currentState.name}")
}
}
}

override fun destroyPlay() {
mediaPlayer?.let {
when(currentState){
EMediaState.RUNNING,EMediaState.PAUSE,EMediaState.COMPLETE -> it.stop()
else -> {}
}
it.release()
}
removePlayListener()

mediaPlayer = null
}


override fun seekToEnable(): Boolean {
return true
}

override fun getPlayPath(): String {
return urlPath ?: ""
}

override fun getDuration(): Int {
return mediaPlayer?.duration ?: -1
}

override fun getCurrentPosition(): Int {
return mediaPlayer?.currentPosition ?: -1
}

override fun isPlaying(): Boolean {
return mediaPlayer?.isPlaying ?: false
}

override fun getMediaState(): EMediaState {
return currentState
}

override fun addPlayListener(listener: IMPListener) {
this.listener = listener
}

override fun removePlayListener() {
listener = null
}

/** 状态回调 默认状态回调是在调用MediaPlayer的线程
* */
private fun handleListener() {
val temp = currentState
synchronized(temp) {
listener?.let {
LogUtil.e("MPUtil发送 --》 ${Thread.currentThread()}")
Handler(Looper.getMainLooper()).post {
LogUtil.e("MPUtil发送 --》 ${temp.name}")
it.onMpState(temp)
}
}
}
}

override fun onPrepared(mp: MediaPlayer?) {
LogUtil.i("mOnPreparedLister 回调")
if (currentState == EMediaState.INITIALIZED || currentState == EMediaState.PREPARING) {
currentState = EMediaState.PREPARED
handlePrepared()
} else {
handleError("mOnPreparedLister 状态异常")
}
}

override fun onError(mp: MediaPlayer?, what: Int, extra: Int): Boolean {
handleError("mOnErrorListener 异常调用 $extra")
return true
}

override fun onCompletion(mp: MediaPlayer?) {
currentState = EMediaState.COMPLETE
handleListener()
}

override fun onInfo(mp: MediaPlayer?, what: Int, extra: Int): Boolean {
return false
}

override fun onSeekComplete(mp: MediaPlayer?) {
when (currentState) {
EMediaState.PREPARED -> handlePrepared()
EMediaState.RUNNING,EMediaState.PAUSE -> checkSeekPlay()
else -> {
handleError("seekComplete state error : ${currentState.name}")
}
}
}
}

+ 57
- 0
lib/common/src/main/java/com/suliang/common/util/os/FloatingWindowUtil.kt View File

@@ -0,0 +1,57 @@
package com.suliang.common.util.os

import android.app.Activity
import android.app.AppOpsManager
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Binder
import android.provider.Settings
import java.lang.Exception

/**
* author suliang
* create 2022/3/8 15:52
* Describe: 悬浮框工具
*/
class FloatingWindowUtil {
/**
* 检查悬浮框权限
* @param context Context 上下文
* @return Boolean 是否开启了悬浮框权限
*/
fun checkOverLayerPermission(context: Context): Boolean {
return if (VersionUtil.versionMorLater()) Settings.canDrawOverlays(context) else checkOp(context, 24)
}

private fun checkOp(context: Context, op: Int): Boolean {
val manager = context.getSystemService(Context.APP_OPS_SERVICE) as AppOpsManager
try {
val method =
AppOpsManager::class.java.getDeclaredMethod("checkOp", Int::class.javaPrimitiveType, Int::class.javaPrimitiveType, String::class.java)
return AppOpsManager.MODE_ALLOWED == method.invoke(manager, op, Binder.getCallingUid(), context.packageName) as Int
} catch (e: Exception) {
e.printStackTrace()
}
return false
}

/**
* 跳转打开悬浮窗权限设置界面
* @param activity Activity
*/
fun applyOverLayerPermission(activity: Activity) {
if (VersionUtil.versionMorLater()) {
val intent = Intent()
intent.action = Settings.ACTION_MANAGE_OVERLAY_PERMISSION
intent.data = Uri.parse("package:" + activity.packageName)
activity.startActivityForResult(intent, 0)
} else {
val intent = Intent()
intent.action = Settings.ACTION_MANAGE_OVERLAY_PERMISSION
intent.data = Uri.fromParts("package", activity.packageName, null)
activity.startActivityForResult(intent, 0)
}
}

}

+ 70
- 0
lib/common/src/main/java/com/suliang/common/util/os/IntentUtils.kt View File

@@ -0,0 +1,70 @@
package com.suliang.common.util.os

import android.content.Context
import android.content.Intent
import android.net.Uri

/**
* Created by 苏亮 on 2017/11/28.
* Intent启动封装工具
*/
object IntentUtils {
/**
* 打开系统浏览器
*/
fun startOsWebView(context: Context, url: String?) {
val uri = Uri.parse(url)
val intent = Intent(Intent.ACTION_VIEW, uri)
context.startActivity(intent)
}

// /**
// * 发送广播让相册扫描文件进入相册
// */
// fun mediaScannerFile(file: File?, mContext: Context) {
// val intent = Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE)
// val uri = Uri.fromFile(file)
// intent.setData(uri)
// mContext.sendBroadcast(intent)
// }
//
// fun cropImageUri(activity: Activity, path: String?, outputX: Int, outputY: Int, aspectX: Int, aspectY: Int,
// requestCode: Int): File {
// val intent = Intent("com.android.camera.action.CROP")
// val file = File(path)
// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
// val dataUri: Uri = LibFileUtils.getImageContentUri(file, activity.getApplicationContext())
// intent.setDataAndType(dataUri, "image/*")
// } else {
// intent.setDataAndType(Uri.fromFile(file), "image/*")
// }
// intent.putExtra("crop", "true")
// intent.putExtra("aspectX", aspectX)
// intent.putExtra("aspectY", aspectY)
// intent.putExtra("outputX", outputX)
// intent.putExtra("outputY", outputY)
// intent.putExtra("scale", true)
// val outFile: File = File(LibFileUtils.getSaveDir(activity.getApplicationContext(), "crop"), "crop.jpg")
// outFile.delete()
// val outUri = Uri.fromFile(outFile)
// intent.putExtra(MediaStore.EXTRA_OUTPUT, outUri)
// intent.putExtra("return-data", false)
// intent.putExtra("outputFormat", Bitmap.CompressFormat.JPEG.toString())
// intent.putExtra("noFaceDetection", true)
// activity.startActivityForResult(intent, requestCode)
//
///*
// UCrop.Options options = new UCrop.Options();
// options.setStatusBarColor(Color.BLACK);
//
// UCrop.of(Uri.fromFile(file), Uri.fromFile(outFile))
// .withOptions()
// .withAspectRatio(aspectX, aspectY)
// .withMaxResultSize(outputX, outputY)
// .start(activity,requestCode);*/return outFile
// }
//
// fun cropImageUri(activity: Activity, path: String?, outputX: Int, outputY: Int, requestCode: Int): File {
// return cropImageUri(activity, path, outputX, outputY, 1, 1, requestCode)
// }
}

+ 32
- 0
lib/common/src/main/java/com/suliang/common/util/os/KeyboardUtil.kt View File

@@ -0,0 +1,32 @@
package com.suliang.common.util.os

import android.content.Context
import android.view.View
import android.view.inputmethod.InputMethodManager

/**
* author suliang
* create 2022/3/8 15:26
* Describe: 键盘工具
*/
object KeyboardUtil {
/**
* 显示软键盘
* @param view View
*/
fun showKeyboard(view:View){
val inputMethodManager = view.context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
view.requestFocus()
inputMethodManager.showSoftInput(view,0)
}

/**
* 隐藏软键盘
* @param view View
*/
fun hideKeyboard(view: View){
val inputMethodManager = view.context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
inputMethodManager.hideSoftInputFromWindow(view.windowToken,0)
view.clearFocus()
}
}

+ 142
- 0
lib/common/src/main/java/com/suliang/common/util/os/ScreenUtil.kt View File

@@ -0,0 +1,142 @@
package com.suliang.common.util.os

import android.annotation.TargetApi
import android.app.Activity
import android.content.Context
import android.content.res.Resources
import android.os.Build
import android.util.DisplayMetrics
import android.view.ViewConfiguration
import android.view.WindowManager
import com.suliang.common.util.AppGlobals
import com.suliang.common.util.LogUtil
import java.lang.Exception

/**
* author suliang
* create 2022/3/8 15:41
* Describe: 屏幕工具
*/
object ScreenUtil {

private var screenWidth = 0
private var screenHeight = 0
/**
* 获取屏幕宽度
* @return
*/
fun getScreenWidth(): Int {
if (screenWidth == 0) {
screenWidth = AppGlobals.application.resources.displayMetrics.widthPixels
}
return screenWidth
}

/**
* 获取屏幕高度
* @return
*/
fun getScreenHeight(): Int {
if (screenHeight == 0) {
screenHeight = AppGlobals.application.resources.displayMetrics.heightPixels
}
return screenHeight
}

/** 获取状态栏高度 */
fun getStatusBarHeight(): Int {
var statusBarHeight = -1
val resources: Resources = AppGlobals.application.resources
//获取status_bar_height的资源Id
val resourceId = resources.getIdentifier("status_bar_height", "dimen", "android")
if (resourceId > 0) {
//根据资源Id获取相应的尺寸
statusBarHeight = resources.getDimensionPixelSize(resourceId)
}
LogUtil.d(" 状态栏高度 navigationBarHeight = $statusBarHeight")
return statusBarHeight
}

/**
* 获取虚拟按键的高度
*/
fun getNavigationBarHeight(): Int {
var result = 0
if (hasNavBar(AppGlobals.application)) {
val res: Resources = AppGlobals.application.resources
val resourceId = res.getIdentifier("navigation_bar_height", "dimen", "android")
if (resourceId > 0) {
result = res.getDimensionPixelSize(resourceId)
}
}
LogUtil.d(" 虚拟按键高度 navigationBarHeight = $result")
return result
}

/**
* 检查是否存在虚拟按键栏
*/
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
private fun hasNavBar(context: Context): Boolean {
val res = context.resources
val resourceId = res.getIdentifier("config_showNavigationBar", "bool", "android")
return if (resourceId != 0) {
var hasNav = res.getBoolean(resourceId)
val sNavBarOverride = getNavBarOverride()
if ("1" == sNavBarOverride) {
hasNav = false
} else if ("0" == sNavBarOverride) {
hasNav = true
}
hasNav
} else {
!ViewConfiguration.get(context).hasPermanentMenuKey()
}
}

/**
* 判断虚拟按键栏是否重写
*/
private fun getNavBarOverride(): String? {
var sNavBarOverride: String? = null
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
try {
val c = Class.forName("android.os.SystemProperties")
val m = c.getDeclaredMethod("get", String::class.java)
m.isAccessible = true
sNavBarOverride = m.invoke(null, "qemu.hw.mainkeys") as String
} catch (e: Exception) {
e.printStackTrace()
}
}
return sNavBarOverride
}

/**
* dp2px
*/
fun dp2px(dpValue: Float): Int {
val scale = AppGlobals.application.resources.displayMetrics.density
return (dpValue * scale + 0.5f).toInt()
}

/**
* 屏幕变暗 alpha 0.3
* @param activity Activity
*/
fun lightOff(activity: Activity) {
val lp = activity.window.attributes
lp.alpha = 0.3f
activity.window.attributes = lp
}

/**
* 屏幕变亮 alpha 1
* @param activity Activity
*/
fun lightOn(activity: Activity) {
val lp = activity.window.attributes
lp.alpha = 1f
activity.window.attributes = lp
}
}

+ 122
- 0
lib/common/src/main/java/com/suliang/common/util/os/SystemUtil.kt View File

@@ -0,0 +1,122 @@
package com.suliang.common.util.os

import android.app.Activity
import android.content.Context
import android.content.Intent
import android.os.Build
import android.content.res.Configuration
import android.net.Uri
import android.provider.Settings
import androidx.annotation.RequiresApi
import androidx.core.content.FileProvider
import com.suliang.common.util.AppGlobals
import com.suliang.common.util.LogUtil
import java.io.File

/**
* author suliang
* create 2022/3/7 15:06
* Describe: 系统工具
*/
object SystemUtil {


/**
* 判断当前设备时手机还是平板
* @param context Context 上下文
* @return Boolean pad: true , phone: false
*/
fun belongToPadOrPhone(context: Context): Boolean {
val b =
context.resources.configuration.screenLayout.and(Configuration.SCREENLAYOUT_SIZE_MASK) >= Configuration.SCREENLAYOUT_SIZE_LARGE
LogUtil.d("是否是平板设备: $b")
return b
}


/** 安装 */
fun install(context: Context, mFilePath: String?) {
val intent = Intent(Intent.ACTION_VIEW)
if (VersionUtil.versionOorLater()) {
intent.flags = Intent.FLAG_GRANT_READ_URI_PERMISSION //添加这一句表示对目标应用临时授权该Uri所代表的文件
val apkUri = FileProvider.getUriForFile(
context,
"com.xuekaole.education.camera",
File(mFilePath)
)
intent.setDataAndType(apkUri, "application/vnd.android.package-archive")
} else {
intent.setDataAndType(
Uri.fromFile(File(mFilePath)),
"application/vnd.android.package-archive"
)
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
}
context.startActivity(intent)
}

@RequiresApi(api = Build.VERSION_CODES.O)
fun isHasInstallPermissionWithO(context: Context?): Boolean {
return context?.packageManager?.canRequestPackageInstalls() ?: false
}

/**
* 开启设置安装未知来源应用权限界面
*
* @param context
*/
@RequiresApi(api = Build.VERSION_CODES.O)
fun startInstallPermissionSettingActivity(context: Context?) {
if (context == null) {
return
}
val intent = Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES)
(context as Activity).startActivityForResult(intent, 100)
}

/**
* 安装Apk 注8.0以下,8.0以上需要获取权限
*/
fun installApk(mContext: Context, path: String) {
val install = Intent(Intent.ACTION_VIEW)
val file = File(path)
if (VersionUtil.versionOorLater()) {
val apkUri =
FileProvider.getUriForFile(mContext, "com.xuekaole.education.download", file)
install.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) //添加这一句表示对目标应用临时授权该Uri所代表的文件
install.setDataAndType(apkUri, "application/vnd.android.package-archive")
} else {
install.setDataAndType(Uri.fromFile(file), "application/vnd.android.package-archive")
install.flags = Intent.FLAG_ACTIVITY_NEW_TASK
}
mContext.startActivity(install)
}


/** 打开系统通知设置权限 */
fun skipToNotifySetting(activity: Activity) {
val intent = Intent()
if (VersionUtil.versionOorLater()) {
intent.action = "android.settings.APP_NOTIFICATION_SETTINGS"
intent.putExtra(
"android.provider.extra.APP_PACKAGE",
VersionUtil.getPackageName()
)
} else if (VersionUtil.versionLorLater()) {
intent.action = "android.settings.APP_NOTIFICATION_SETTINGS"
intent.putExtra("app_package", VersionUtil.getPackageName())
intent.putExtra("app_uid", activity.applicationInfo.uid)
} else if (Build.VERSION.SDK_INT == Build.VERSION_CODES.KITKAT) {
intent.action = Settings.ACTION_APPLICATION_DETAILS_SETTINGS
intent.addCategory(Intent.CATEGORY_DEFAULT)
intent.data = Uri.parse("package:" + VersionUtil.getPackageName())
} else if (Build.VERSION.SDK_INT >= 15) {
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
intent.action = "android.settings.APPLICATION_DETAILS_SETTINGS"
intent.data =
Uri.fromParts("package", VersionUtil.getPackageName(), null)
}
activity.startActivityForResult(intent, 10000)
}

}

+ 107
- 0
lib/common/src/main/java/com/suliang/common/util/os/VersionUtil.kt View File

@@ -0,0 +1,107 @@
package com.suliang.common.util.os

import android.os.Build
import androidx.annotation.RequiresApi
import com.suliang.common.util.AppGlobals

/**
* author suliang
* create 2022/3/7 16:03
* Describe:
*/
object VersionUtil {

/**
* Android12 S 31 和 以后的版本
* @receiver Build
* @return Boolean
*/
fun versionSorLater(): Boolean = Build.VERSION.SDK_INT >= Build.VERSION_CODES.S

/**
* Android11 R 30 和 以后的版本
* @receiver Build
* @return Boolean
*/
fun versionRorLater(): Boolean = Build.VERSION.SDK_INT >= Build.VERSION_CODES.R

/**
* Android10 Q 29和 以后的版本
* @receiver Build
* @return Boolean
*/
fun versionQorLater(): Boolean = Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q

/**
* Android9 P 28和 以后的版本
* @receiver Build
* @return Boolean
*/
fun versionPorLater(): Boolean = Build.VERSION.SDK_INT >= Build.VERSION_CODES.P

/**
* Android8 O 26 27 和 以后的版本
* @receiver Build
* @return Boolean
*/
fun versionOorLater(): Boolean = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O

/**
* Android7.0 N 24 和 以后的版本
* @receiver Build
* @return Boolean
*/
fun versionNorLater(): Boolean = Build.VERSION.SDK_INT >= Build.VERSION_CODES.N

/**
* Android6.0 M 23 和 以后的版本
* @receiver Build
* @return Boolean
*/
fun versionMorLater(): Boolean = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M

/**
* Android5.0 21和 以后的版本
* @receiver Build
* @return Boolean
*/
fun versionLorLater(): Boolean = Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP


/**
* 获取versionName
* @return String
*/
fun getVersionName(): String {
return AppGlobals.application.packageManager.getPackageInfo(
AppGlobals.application.packageName,
0
).versionName
}

/**
* 获取versionCode
* @return Long
*/
fun getVersionCode(): Long {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
AppGlobals.application.packageManager.getPackageInfo(
AppGlobals.application.packageName,
0
).longVersionCode
} else {
AppGlobals.application.packageManager.getPackageInfo(
AppGlobals.application.packageName,
0
).versionCode.toLong()
}
}

fun getPackageName(): String {
return AppGlobals.application.packageManager.getPackageInfo(
AppGlobals.application.packageName,
0
).packageName
}

}

+ 86
- 0
lib/common/src/main/java/com/suliang/common/util/thread/AppExecutors.kt View File

@@ -0,0 +1,86 @@
package com.suliang.common.util.thread

import java.util.concurrent.*

/**
* author suliang
* create 2022/3/11 9:56
* Describe: 线程池
* 参考url : https://blog.csdn.net/weixin_43115440/article/details/90479752
* newFixedThreadPool(int) : 创建固定数目线程的线程池
* newCacheThreadPool() : 创建一个可缓存的线程池,调用execute 将重用以前构造的线程(如果线程可用)。如果没有可用的线程,则创建一个新线程并添加到池中。终止并从缓存中移除那些已有 60 秒钟未被使用的线程。
* newSingleThreadExecutor():创建一个单线程化的Executor。
* newScheduledThreadPool(int corePoolSize):创建一个支持定时及周期性的任务执行的线程池,多数情况下可用来替代Timer类。
*
* FixedThreadPool 和 SingleThreadPool: 允许的请求队列长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM
* CachedThreadPool 和 ScheduledThreadPool:允许的创建线程数量为 Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM。
*
*/
object AppExecutors {

/* 磁盘IO线程池 和磁盘操作有关的进行使用此线程(如独写数据库,独写文件) 禁止延迟,避免等待,此线程池不用考虑同步问题 */
val diskIO: ExecutorService = Executors.newSingleThreadExecutor()

/** 网络线程池 网络请求、异步任务等适用此线程 不建议在此线程 sleep 或者 wait */
val netWork: ExecutorService = Executors.newCachedThreadPool()

/**主线程*/
val mainThread: Executor = MainThreadExecutor()

/**定时任务线程池 替代Timer,执行定时任务、延时任务 * */
val scheduledExecutor: ScheduledExecutorService =
ScheduledThreadPoolExecutor(5, ThreadFactoryImp("executors_scheduled"), ThreadPoolExecutor.AbortPolicy())


/* private static ScheduledExecutorService scheduledThreadPoolExecutor(){

1-- new ScheduledThreadPoolExecutor(5, new MyThreadFactory("scheduled"), new ThreadPoolExecutor.AbortPolicy()) ;

2-- return new ScheduledThreadPoolExecutor(16, new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
return new Thread(r, "scheduled_executor");
}
}, new RejectedExecutionHandler() {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
CommonLog.e(TAG,"rejectedExecution: scheduled executor queue overflow");
}
});
}*/

/*
private static ExecutorService diskIoExecutor(){


Executors.newSingleThreadExecutor(new MyThreadFactory("single")) ;
return new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingDeque<Runnable>(1024), new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
return new Thread(r, "disk_executor");
}
}, new RejectedExecutionHandler() {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
CommonLog.e(TAG,"rejectedExecution: diskIo executor queue overflow");
}
});
}

private static ExecutorService netWorkExecutor(){
Executors.newFixedThreadPool(3, new MyThreadFactory("fixed"));

return new ThreadPoolExecutor(3, 6, 1000, TimeUnit.MILLISECONDS, new LinkedBlockingDeque<>(6), new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
return new Thread(r, "network_executor");
}
}, new RejectedExecutionHandler() {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
CommonLog.e(TAG,"rejectedExecution: network executor queue overflow");
}
});
}
*/
}

+ 23
- 0
lib/common/src/main/java/com/suliang/common/util/thread/MainThreadExecutor.kt View File

@@ -0,0 +1,23 @@
package com.suliang.common.util.thread

import android.os.Handler
import android.os.Looper
import java.util.concurrent.Executor

/**
* author suliang
* create 2022/3/11 11:31
* Describe:
*/
internal class MainThreadExecutor : Executor {

companion object{
private val mainThreadHandler = Handler(Looper.getMainLooper())
}

override fun execute(command: Runnable?) {
command?.let {
mainThreadHandler.post(command)
}
}
}

+ 22
- 0
lib/common/src/main/java/com/suliang/common/util/thread/ThreadFactoryImp.kt View File

@@ -0,0 +1,22 @@
package com.suliang.common.util.thread

import java.util.concurrent.ThreadFactory
import java.util.concurrent.atomic.AtomicLong

/**
* author suliang
* create 2022/3/11 10:41
* Describe: 创建线程工具类
*/
internal class ThreadFactoryImp @JvmOverloads constructor(val threadNamePrefix:String, var daemon:Boolean = false): ThreadFactory {

companion object{
private val threadIndex : AtomicLong = AtomicLong(0)
}

override fun newThread(r: Runnable?): Thread {
return Thread(r,"$threadNamePrefix-thread-${threadIndex.incrementAndGet()}").apply {
isDaemon = daemon
}
}
}

+ 6
- 0
lib/common/src/main/res/xml/network_security_config.xml View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<network-security-config xmlns:tools="http://schemas.android.com/tools"
xmlns:android="http://schemas.android.com/apk/res/android">
<base-config cleartextTrafficPermitted = "true"
tools:ignore="InsecureBaseConfiguration" />
</network-security-config>

Loading…
Cancel
Save