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

<component name="ProjectCodeStyleConfiguration"> <component name="ProjectCodeStyleConfiguration">
<code_scheme name="Project" version="173"> <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> <JetCodeStyleSettings>
<option name="CONTINUATION_INDENT_FOR_CHAINED_CALLS" value="true" />
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" /> <option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
</JetCodeStyleSettings> </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"> <codeStyleSettings language="XML">
<indentOptions> <indentOptions>
<option name="CONTINUATION_INDENT_SIZE" value="4" /> <option name="CONTINUATION_INDENT_SIZE" value="4" />
</codeStyleSettings> </codeStyleSettings>
<codeStyleSettings language="kotlin"> <codeStyleSettings language="kotlin">
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" /> <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> </codeStyleSettings>
</code_scheme> </code_scheme>
</component> </component>

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

<?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

<component name="DesignSurface"> <component name="DesignSurface">
<option name="filePathToZoomLevelMap"> <option name="filePathToZoomLevelMap">
<map> <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/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_fullscreen.xml" value="0.10144927536231885" />
<entry key="..\:/Work/XKL/XklLocal/app/src/main/res/layout/activity_main.xml" value="0.1" /> <entry key="..\:/Work/XKL/XklLocal/app/src/main/res/layout/activity_main.xml" value="0.1" />
</map> </map>
</option> </option>
</component> </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" /> <output url="file://$PROJECT_DIR$/build/classes" />
</component> </component>
<component name="ProjectType"> <component name="ProjectType">

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

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

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

package com.xkl.cdl.module.splash package com.xkl.cdl.module.splash


import android.annotation.SuppressLint
import android.os.Bundle import android.os.Bundle
import android.os.Handler
import android.os.Looper 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.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 import com.xkl.cdl.databinding.ActivitySplashBinding


@SuppressLint("CustomSplashScreen")
class SplashActivity : BaseActivity<ActivitySplashBinding>() { 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?) { override fun onCreate(savedInstanceState: Bundle?) {
if(!isTaskRoot){
if (!isTaskRoot) {
finish() finish()
return return
} }
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
} }
var count = 1


override fun initActivity(savedInstanceState: Bundle?) { 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

xmlns:tools="http://schemas.android.com/tools"> xmlns:tools="http://schemas.android.com/tools">


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

</data> </data>


<androidx.constraintlayout.widget.ConstraintLayout <androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/img" android:id="@+id/img"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="0dp" 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_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent" app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="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_width="wrap_content"
android:layout_height="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_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> </layout>

+ 5
- 6
build.gradle View File

core_ktx_version : "1.3.2", core_ktx_version : "1.3.2",
appcompat_version: "1.4.1", appcompat_version: "1.4.1",
material_version : "1.3.0", material_version : "1.3.0",
lifecycle_version: "2.5.0-alpha02"
lifecycle_version: "2.5.0-alpha02",
glide_version : "4.1.0",
] ]
//必须依赖 //必须依赖
dependencies_required = [ dependencies_required = [
dependencies_custom = [ dependencies_custom = [
//设置状态栏和导航栏的框架 https://github.com/Zackratos/UltimateBarX //设置状态栏和导航栏的框架 https://github.com/Zackratos/UltimateBarX
UltimateBarX: "com.gitee.zackratos:UltimateBarX:0.8.0", 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



buildTypes { buildTypes {
release { 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 minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
} }
rootProject.ext.dependencies_required.each{ k,v -> implementation v} rootProject.ext.dependencies_required.each{ k,v -> implementation v}
testImplementation rootProject.ext.dependencies_testImplementation.junit testImplementation rootProject.ext.dependencies_testImplementation.junit
rootProject.ext.dependencies_androidTestImplementation.each{ k,v -> androidTestImplementation v} 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

<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.suliang.common"> 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> </manifest>

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

package com.suliang.common.base.activity package com.suliang.common.base.activity


import android.graphics.Color
import android.os.Bundle 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 androidx.viewbinding.ViewBinding
import com.suliang.common.extension.initBinding import com.suliang.common.extension.initBinding
import com.suliang.common.util.ActivityStackManager import com.suliang.common.util.ActivityStackManager
import java.lang.reflect.ParameterizedType
import com.zackratos.ultimatebarx.ultimatebarx.statusBarOnly


/** /**
* 基类Activity * 基类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?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
addStackManager()
//入栈
ActivityStackManager.addActivity(this)
initFirst() initFirst()
initActivity(savedInstanceState) }
initActivity(savedInstanceState)
loadData()
}


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



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


override fun onDestroy() { override fun onDestroy() {
super.onDestroy() super.onDestroy()
ActivityStackManager.removeActivity(this) 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中用户的具体实现 * onCreate中给与app中用户的具体实现
* binding 与 ViewModel都已实现好
* @param savedInstanceState Bundle? * @param savedInstanceState Bundle?
*/ */
abstract fun initActivity(savedInstanceState: Bundle?) abstract fun initActivity(savedInstanceState: Bundle?)


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

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

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

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

import androidx.databinding.ViewDataBinding import androidx.databinding.ViewDataBinding
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import com.suliang.common.extension.initBinding 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 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( override fun onCreateView(
inflater: LayoutInflater, inflater: LayoutInflater,
container: ViewGroup?, container: ViewGroup?,
savedInstanceState: Bundle? savedInstanceState: Bundle?
): View? { ): View? {
binding = initBinding(inflater,container)
_binding = initBinding(inflater, container)
return super.onCreateView(inflater, container, savedInstanceState) return super.onCreateView(inflater, container, savedInstanceState)
} }


initFragment() 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() abstract fun initFirst()

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


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

} }

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



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

/** /**
* Fragment DataBinding 与 ViewModel基类,封装DataBinding和ViewModel
* Fragment DataBinding 与 ViewModel基类,封装DataBinding和ViewModel 自带懒加载
* @property VB : ViewDataBinding * @property VB : ViewDataBinding
* @property VM: ViewModel * @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 lateinit var viewModel: VM

override fun initFirst() { override fun initFirst() {
viewModel = initViewModel() 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

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

package com.suliang.common.extension package com.suliang.common.extension


import androidx.annotation.IdRes import androidx.annotation.IdRes
import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.Fragment 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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

<?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