<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> |
<?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> |
<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"> |
<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> |
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>) |
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> |
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}", | |||||
// | |||||
// ] | |||||
} | } |
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" | |||||
} | } |
<?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> |
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 = |
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()") | |||||
} | |||||
} |
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() | |||||
} | } |
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 | |||||
} | } |
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()") | |||||
} | |||||
} |
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() | |||||
} |
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)) | |||||
} | |||||
} |
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" | |||||
} | |||||
} | |||||
} |
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() { | |||||
} | |||||
} |
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() | |||||
} |
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() | |||||
} | |||||
} |
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) | |||||
// } | |||||
} |
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 | |||||
} |
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() | |||||
} |
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) | |||||
} |
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() | |||||
} | |||||
} |
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}") | |||||
} | |||||
} | |||||
} | |||||
} |
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) | |||||
} | |||||
} | |||||
} |
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) | |||||
// } | |||||
} |
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() | |||||
} | |||||
} |
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 | |||||
} | |||||
} |
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) | |||||
} | |||||
} |
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 | |||||
} | |||||
} |
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"); | |||||
} | |||||
}); | |||||
} | |||||
*/ | |||||
} |
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) | |||||
} | |||||
} | |||||
} |
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 | |||||
} | |||||
} | |||||
} |
<?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> |