├── .github ├── ISSUE_TEMPLATE │ └── ----.md └── workflows │ └── main.yml ├── .gitignore ├── LICENSE ├── README.md ├── README_EN.md ├── build.gradle ├── docs ├── api │ ├── -net │ │ ├── androidx.lifecycle │ │ │ ├── index.html │ │ │ ├── life.html │ │ │ ├── scope-life.html │ │ │ └── scope-net-life.html │ │ ├── com.drake.net.body │ │ │ ├── -net-request-body │ │ │ │ ├── -net-request-body.html │ │ │ │ ├── content-length.html │ │ │ │ ├── content-type.html │ │ │ │ ├── index.html │ │ │ │ └── write-to.html │ │ │ ├── -net-response-body │ │ │ │ ├── -net-response-body.html │ │ │ │ ├── content-length.html │ │ │ │ ├── content-type.html │ │ │ │ ├── index.html │ │ │ │ └── source.html │ │ │ ├── file-name.html │ │ │ ├── index.html │ │ │ ├── name.html │ │ │ ├── peek-bytes.html │ │ │ ├── to-net-request-body.html │ │ │ ├── to-net-response-body.html │ │ │ └── value.html │ │ ├── com.drake.net.cache │ │ │ ├── -cache-mode │ │ │ │ ├── -r-e-a-d │ │ │ │ │ └── index.html │ │ │ │ ├── -r-e-a-d_-t-h-e-n_-r-e-q-u-e-s-t │ │ │ │ │ └── index.html │ │ │ │ ├── -r-e-q-u-e-s-t_-t-h-e-n_-r-e-a-d │ │ │ │ │ └── index.html │ │ │ │ ├── -w-r-i-t-e │ │ │ │ │ └── index.html │ │ │ │ └── index.html │ │ │ ├── -force-cache │ │ │ │ ├── -companion │ │ │ │ │ ├── has-vary-all.html │ │ │ │ │ ├── index.html │ │ │ │ │ ├── key.html │ │ │ │ │ ├── vary-headers.html │ │ │ │ │ └── vary-matches.html │ │ │ │ ├── cache.html │ │ │ │ ├── close.html │ │ │ │ ├── delete.html │ │ │ │ ├── directory.html │ │ │ │ ├── evict-all.html │ │ │ │ ├── flush.html │ │ │ │ ├── index.html │ │ │ │ ├── initialize.html │ │ │ │ ├── is-closed.html │ │ │ │ ├── max-size.html │ │ │ │ ├── size.html │ │ │ │ ├── urls.html │ │ │ │ ├── write-abort-count.html │ │ │ │ └── write-success-count.html │ │ │ └── index.html │ │ ├── com.drake.net.component │ │ │ ├── -progress │ │ │ │ ├── -progress.html │ │ │ │ ├── current-byte-count.html │ │ │ │ ├── current-size.html │ │ │ │ ├── finish.html │ │ │ │ ├── index.html │ │ │ │ ├── interval-byte-count.html │ │ │ │ ├── interval-time.html │ │ │ │ ├── progress.html │ │ │ │ ├── remain-size.html │ │ │ │ ├── remain-time-seconds.html │ │ │ │ ├── remain-time.html │ │ │ │ ├── speed-bytes.html │ │ │ │ ├── speed-size.html │ │ │ │ ├── start-elapsed-realtime.html │ │ │ │ ├── to-string.html │ │ │ │ ├── total-byte-count.html │ │ │ │ ├── total-size.html │ │ │ │ ├── use-time-seconds.html │ │ │ │ └── use-time.html │ │ │ └── index.html │ │ ├── com.drake.net.convert │ │ │ ├── -j-s-o-n-convert │ │ │ │ ├── -j-s-o-n-convert.html │ │ │ │ ├── code.html │ │ │ │ ├── index.html │ │ │ │ ├── message.html │ │ │ │ ├── on-convert.html │ │ │ │ ├── parse-body.html │ │ │ │ └── success.html │ │ │ ├── -net-converter │ │ │ │ ├── -d-e-f-a-u-l-t │ │ │ │ │ ├── index.html │ │ │ │ │ └── on-convert.html │ │ │ │ ├── index.html │ │ │ │ └── on-convert.html │ │ │ └── index.html │ │ ├── com.drake.net.cookie │ │ │ ├── -persistent-cookie-jar │ │ │ │ ├── -persistent-cookie-jar.html │ │ │ │ ├── add-all.html │ │ │ │ ├── clear.html │ │ │ │ ├── context.html │ │ │ │ ├── db-name.html │ │ │ │ ├── get-all.html │ │ │ │ ├── index.html │ │ │ │ ├── load-for-request.html │ │ │ │ ├── remove.html │ │ │ │ └── save-from-response.html │ │ │ └── index.html │ │ ├── com.drake.net.exception │ │ │ ├── -convert-exception │ │ │ │ ├── -convert-exception.html │ │ │ │ ├── index.html │ │ │ │ └── tag.html │ │ │ ├── -download-file-exception │ │ │ │ ├── -download-file-exception.html │ │ │ │ ├── index.html │ │ │ │ └── tag.html │ │ │ ├── -http-failure-exception │ │ │ │ ├── -http-failure-exception.html │ │ │ │ └── index.html │ │ │ ├── -http-response-exception │ │ │ │ ├── -http-response-exception.html │ │ │ │ ├── index.html │ │ │ │ └── response.html │ │ │ ├── -net-cancellation-exception.html │ │ │ ├── -net-cancellation-exception │ │ │ │ ├── -net-cancellation-exception.html │ │ │ │ └── index.html │ │ │ ├── -net-connect-exception │ │ │ │ ├── -net-connect-exception.html │ │ │ │ └── index.html │ │ │ ├── -net-exception │ │ │ │ ├── -net-exception.html │ │ │ │ ├── get-localized-message.html │ │ │ │ ├── index.html │ │ │ │ ├── occurred.html │ │ │ │ └── request.html │ │ │ ├── -net-socket-timeout-exception │ │ │ │ ├── -net-socket-timeout-exception.html │ │ │ │ └── index.html │ │ │ ├── -net-unknown-host-exception │ │ │ │ ├── -net-unknown-host-exception.html │ │ │ │ └── index.html │ │ │ ├── -networking-exception │ │ │ │ ├── -networking-exception.html │ │ │ │ └── index.html │ │ │ ├── -no-cache-exception │ │ │ │ ├── -no-cache-exception.html │ │ │ │ ├── get-localized-message.html │ │ │ │ └── index.html │ │ │ ├── -request-params-exception │ │ │ │ ├── -request-params-exception.html │ │ │ │ ├── index.html │ │ │ │ └── tag.html │ │ │ ├── -response-exception │ │ │ │ ├── -response-exception.html │ │ │ │ ├── index.html │ │ │ │ └── tag.html │ │ │ ├── -server-response-exception │ │ │ │ ├── -server-response-exception.html │ │ │ │ ├── index.html │ │ │ │ └── tag.html │ │ │ ├── -u-r-l-parse-exception │ │ │ │ ├── -u-r-l-parse-exception.html │ │ │ │ ├── get-localized-message.html │ │ │ │ ├── index.html │ │ │ │ └── occurred.html │ │ │ └── index.html │ │ ├── com.drake.net.interceptor │ │ │ ├── -log-record-interceptor │ │ │ │ ├── -log-record-interceptor.html │ │ │ │ ├── enabled.html │ │ │ │ ├── index.html │ │ │ │ ├── intercept.html │ │ │ │ ├── request-byte-count.html │ │ │ │ └── response-byte-count.html │ │ │ ├── -net-ok-http-interceptor │ │ │ │ ├── index.html │ │ │ │ └── intercept.html │ │ │ ├── -request-interceptor │ │ │ │ ├── index.html │ │ │ │ └── interceptor.html │ │ │ ├── -retry-interceptor │ │ │ │ ├── -retry-interceptor.html │ │ │ │ ├── index.html │ │ │ │ ├── intercept.html │ │ │ │ └── retry-count.html │ │ │ └── index.html │ │ ├── com.drake.net.interfaces │ │ │ ├── -net-dialog-factory │ │ │ │ ├── -d-e-f-a-u-l-t │ │ │ │ │ ├── index.html │ │ │ │ │ └── on-create.html │ │ │ │ ├── index.html │ │ │ │ └── on-create.html │ │ │ ├── -net-error-handler │ │ │ │ ├── -d-e-f-a-u-l-t │ │ │ │ │ └── index.html │ │ │ │ ├── index.html │ │ │ │ ├── on-error.html │ │ │ │ └── on-state-error.html │ │ │ ├── -progress-listener │ │ │ │ ├── -progress-listener.html │ │ │ │ ├── elapsed-time.html │ │ │ │ ├── index.html │ │ │ │ ├── interval-byte-count.html │ │ │ │ ├── interval.html │ │ │ │ └── on-progress.html │ │ │ └── index.html │ │ ├── com.drake.net.log │ │ │ ├── -log-recorder │ │ │ │ ├── enabled.html │ │ │ │ ├── generate-id.html │ │ │ │ ├── index.html │ │ │ │ ├── record-exception.html │ │ │ │ ├── record-request.html │ │ │ │ └── record-response.html │ │ │ ├── -message-type │ │ │ │ ├── -r-e-q-u-e-s-t_-b-o-d-y │ │ │ │ │ └── index.html │ │ │ │ ├── -r-e-q-u-e-s-t_-e-n-d │ │ │ │ │ └── index.html │ │ │ │ ├── -r-e-q-u-e-s-t_-h-e-a-d-e-r │ │ │ │ │ └── index.html │ │ │ │ ├── -r-e-q-u-e-s-t_-m-e-t-h-o-d │ │ │ │ │ └── index.html │ │ │ │ ├── -r-e-q-u-e-s-t_-t-i-m-e │ │ │ │ │ └── index.html │ │ │ │ ├── -r-e-q-u-e-s-t_-u-r-l │ │ │ │ │ └── index.html │ │ │ │ ├── -r-e-s-p-o-n-s-e_-b-o-d-y │ │ │ │ │ └── index.html │ │ │ │ ├── -r-e-s-p-o-n-s-e_-e-n-d │ │ │ │ │ └── index.html │ │ │ │ ├── -r-e-s-p-o-n-s-e_-e-r-r-o-r │ │ │ │ │ └── index.html │ │ │ │ ├── -r-e-s-p-o-n-s-e_-h-e-a-d-e-r │ │ │ │ │ └── index.html │ │ │ │ ├── -r-e-s-p-o-n-s-e_-s-t-a-t-u-s │ │ │ │ │ └── index.html │ │ │ │ ├── -r-e-s-p-o-n-s-e_-t-i-m-e │ │ │ │ │ └── index.html │ │ │ │ ├── -u-n-k-n-o-w-n │ │ │ │ │ └── index.html │ │ │ │ ├── index.html │ │ │ │ └── type.html │ │ │ └── index.html │ │ ├── com.drake.net.okhttp │ │ │ ├── index.html │ │ │ ├── set-converter.html │ │ │ ├── set-debug.html │ │ │ ├── set-dialog-factory.html │ │ │ ├── set-error-handler.html │ │ │ ├── set-request-interceptor.html │ │ │ ├── set-s-s-l-certificate.html │ │ │ ├── to-net-okhttp.html │ │ │ └── trust-s-s-l-certificate.html │ │ ├── com.drake.net.reflect │ │ │ ├── $-gson$-preconditions │ │ │ │ ├── check-argument.html │ │ │ │ ├── check-not-null.html │ │ │ │ └── index.html │ │ │ ├── $-gson$-types │ │ │ │ ├── array-of.html │ │ │ │ ├── canonicalize.html │ │ │ │ ├── equals.html │ │ │ │ ├── get-array-component-type.html │ │ │ │ ├── get-collection-element-type.html │ │ │ │ ├── get-map-key-and-value-types.html │ │ │ │ ├── get-raw-type.html │ │ │ │ ├── index.html │ │ │ │ ├── new-parameterized-type-with-owner.html │ │ │ │ ├── resolve.html │ │ │ │ ├── subtype-of.html │ │ │ │ ├── supertype-of.html │ │ │ │ └── type-to-string.html │ │ │ ├── -type-token │ │ │ │ ├── equals.html │ │ │ │ ├── get-array.html │ │ │ │ ├── get-parameterized.html │ │ │ │ ├── get.html │ │ │ │ ├── hash-code.html │ │ │ │ ├── index.html │ │ │ │ ├── raw-type.html │ │ │ │ ├── to-string.html │ │ │ │ └── type.html │ │ │ ├── index.html │ │ │ └── type-token-of.html │ │ ├── com.drake.net.request │ │ │ ├── -base-request │ │ │ │ ├── -base-request.html │ │ │ │ ├── add-download-listener.html │ │ │ │ ├── add-header.html │ │ │ │ ├── add-query.html │ │ │ │ ├── build-request.html │ │ │ │ ├── converter.html │ │ │ │ ├── enqueue.html │ │ │ │ ├── execute.html │ │ │ │ ├── headers.html │ │ │ │ ├── http-url.html │ │ │ │ ├── index.html │ │ │ │ ├── method.html │ │ │ │ ├── ok-http-client.html │ │ │ │ ├── ok-http-request.html │ │ │ │ ├── param.html │ │ │ │ ├── remove-header.html │ │ │ │ ├── set-cache-control.html │ │ │ │ ├── set-cache-key.html │ │ │ │ ├── set-cache-mode.html │ │ │ │ ├── set-cache-valid-time.html │ │ │ │ ├── set-client.html │ │ │ │ ├── set-download-dir.html │ │ │ │ ├── set-download-file-name-conflict.html │ │ │ │ ├── set-download-file-name-decode.html │ │ │ │ ├── set-download-file-name.html │ │ │ │ ├── set-download-md5-verify.html │ │ │ │ ├── set-download-temp-file.html │ │ │ │ ├── set-extra.html │ │ │ │ ├── set-group.html │ │ │ │ ├── set-header.html │ │ │ │ ├── set-headers.html │ │ │ │ ├── set-id.html │ │ │ │ ├── set-k-type.html │ │ │ │ ├── set-path.html │ │ │ │ ├── set-query.html │ │ │ │ ├── set-url.html │ │ │ │ ├── tag-of.html │ │ │ │ ├── tag.html │ │ │ │ └── to-result.html │ │ │ ├── -body-request │ │ │ │ ├── -body-request.html │ │ │ │ ├── add-upload-listener.html │ │ │ │ ├── body.html │ │ │ │ ├── build-request.html │ │ │ │ ├── form-body.html │ │ │ │ ├── index.html │ │ │ │ ├── json.html │ │ │ │ ├── media-type.html │ │ │ │ ├── method.html │ │ │ │ ├── param.html │ │ │ │ └── part-body.html │ │ │ ├── -media-const │ │ │ │ ├── -f-o-r-m.html │ │ │ │ ├── -g-i-f.html │ │ │ │ ├── -h-t-m-l.html │ │ │ │ ├── -i-m-g.html │ │ │ │ ├── -j-p-e-g.html │ │ │ │ ├── -j-s-o-n.html │ │ │ │ ├── -m-p4.html │ │ │ │ ├── -o-c-t-e-t_-s-t-r-e-a-m.html │ │ │ │ ├── -p-n-g.html │ │ │ │ ├── -t-x-t.html │ │ │ │ ├── -u-r-l-e-n-c-o-d-e-d.html │ │ │ │ ├── -x-m-l.html │ │ │ │ └── index.html │ │ │ ├── -method │ │ │ │ ├── -d-e-l-e-t-e │ │ │ │ │ └── index.html │ │ │ │ ├── -g-e-t │ │ │ │ │ └── index.html │ │ │ │ ├── -h-e-a-d │ │ │ │ │ └── index.html │ │ │ │ ├── -o-p-t-i-o-n-s │ │ │ │ │ └── index.html │ │ │ │ ├── -p-a-t-c-h │ │ │ │ │ └── index.html │ │ │ │ ├── -p-o-s-t │ │ │ │ │ └── index.html │ │ │ │ ├── -p-u-t │ │ │ │ │ └── index.html │ │ │ │ ├── -t-r-a-c-e │ │ │ │ │ └── index.html │ │ │ │ └── index.html │ │ │ ├── -url-request │ │ │ │ ├── -url-request.html │ │ │ │ ├── index.html │ │ │ │ └── param.html │ │ │ ├── converter.html │ │ │ ├── download-conflict-rename.html │ │ │ ├── download-file-dir.html │ │ │ ├── download-file-name-decode.html │ │ │ ├── download-file-name.html │ │ │ ├── download-listeners.html │ │ │ ├── download-md5-verify.html │ │ │ ├── download-temp-file.html │ │ │ ├── extra.html │ │ │ ├── extras.html │ │ │ ├── group.html │ │ │ ├── headers.html │ │ │ ├── id.html │ │ │ ├── index.html │ │ │ ├── k-type.html │ │ │ ├── set-converter.html │ │ │ ├── set-extra.html │ │ │ ├── tag-of.html │ │ │ ├── tags.html │ │ │ └── upload-listeners.html │ │ ├── com.drake.net.response │ │ │ ├── convert.html │ │ │ ├── file-name.html │ │ │ ├── file.html │ │ │ └── index.html │ │ ├── com.drake.net.scope │ │ │ ├── -android-scope │ │ │ │ ├── -android-scope.html │ │ │ │ ├── cancel.html │ │ │ │ ├── catch.html │ │ │ │ ├── close.html │ │ │ │ ├── coroutine-context.html │ │ │ │ ├── dispatcher.html │ │ │ │ ├── finally.html │ │ │ │ ├── handle-error.html │ │ │ │ ├── index.html │ │ │ │ ├── launch.html │ │ │ │ └── scope-group.html │ │ │ ├── -dialog-coroutine-scope │ │ │ │ ├── -dialog-coroutine-scope.html │ │ │ │ ├── activity.html │ │ │ │ ├── cancelable.html │ │ │ │ ├── dialog.html │ │ │ │ └── index.html │ │ │ ├── -net-coroutine-scope │ │ │ │ ├── -net-coroutine-scope.html │ │ │ │ ├── cancel.html │ │ │ │ ├── handle-error.html │ │ │ │ ├── index.html │ │ │ │ ├── launch.html │ │ │ │ └── preview.html │ │ │ ├── -page-coroutine-scope │ │ │ │ ├── --index--.html │ │ │ │ ├── -page-coroutine-scope.html │ │ │ │ ├── handle-error.html │ │ │ │ ├── index.html │ │ │ │ └── page.html │ │ │ ├── -state-coroutine-scope │ │ │ │ ├── -state-coroutine-scope.html │ │ │ │ ├── handle-error.html │ │ │ │ ├── index.html │ │ │ │ └── state.html │ │ │ ├── -view-coroutine-scope │ │ │ │ ├── -view-coroutine-scope.html │ │ │ │ ├── index.html │ │ │ │ └── view.html │ │ │ └── index.html │ │ ├── com.drake.net.tag │ │ │ ├── -net-tag │ │ │ │ ├── -cache-key │ │ │ │ │ ├── -cache-key.html │ │ │ │ │ ├── index.html │ │ │ │ │ └── value.html │ │ │ │ ├── -cache-valid-time │ │ │ │ │ ├── -cache-valid-time.html │ │ │ │ │ ├── index.html │ │ │ │ │ └── value.html │ │ │ │ ├── -download-file-conflict-rename │ │ │ │ │ ├── -download-file-conflict-rename.html │ │ │ │ │ ├── index.html │ │ │ │ │ └── value.html │ │ │ │ ├── -download-file-dir │ │ │ │ │ ├── -download-file-dir.html │ │ │ │ │ ├── index.html │ │ │ │ │ └── value.html │ │ │ │ ├── -download-file-m-d5-verify │ │ │ │ │ ├── -download-file-m-d5-verify.html │ │ │ │ │ ├── index.html │ │ │ │ │ └── value.html │ │ │ │ ├── -download-file-name-decode │ │ │ │ │ ├── -download-file-name-decode.html │ │ │ │ │ ├── index.html │ │ │ │ │ └── value.html │ │ │ │ ├── -download-file-name │ │ │ │ │ ├── -download-file-name.html │ │ │ │ │ ├── index.html │ │ │ │ │ └── value.html │ │ │ │ ├── -download-listeners │ │ │ │ │ ├── -download-listeners.html │ │ │ │ │ └── index.html │ │ │ │ ├── -download-temp-file │ │ │ │ │ ├── -download-temp-file.html │ │ │ │ │ ├── index.html │ │ │ │ │ └── value.html │ │ │ │ ├── -extras │ │ │ │ │ ├── -extras.html │ │ │ │ │ └── index.html │ │ │ │ ├── -request-group │ │ │ │ │ ├── -request-group.html │ │ │ │ │ ├── index.html │ │ │ │ │ └── value.html │ │ │ │ ├── -request-id │ │ │ │ │ ├── -request-id.html │ │ │ │ │ ├── index.html │ │ │ │ │ └── value.html │ │ │ │ ├── -request-k-type │ │ │ │ │ ├── -request-k-type.html │ │ │ │ │ ├── index.html │ │ │ │ │ └── value.html │ │ │ │ ├── -upload-listeners │ │ │ │ │ ├── -upload-listeners.html │ │ │ │ │ └── index.html │ │ │ │ └── index.html │ │ │ └── index.html │ │ ├── com.drake.net.time │ │ │ ├── -interval-status │ │ │ │ ├── -s-t-a-t-e_-a-c-t-i-v-e │ │ │ │ │ └── index.html │ │ │ │ ├── -s-t-a-t-e_-i-d-l-e │ │ │ │ │ └── index.html │ │ │ │ ├── -s-t-a-t-e_-p-a-u-s-e │ │ │ │ │ └── index.html │ │ │ │ └── index.html │ │ │ ├── -interval │ │ │ │ ├── -interval.html │ │ │ │ ├── cancel.html │ │ │ │ ├── close.html │ │ │ │ ├── count.html │ │ │ │ ├── end.html │ │ │ │ ├── finish.html │ │ │ │ ├── index.html │ │ │ │ ├── life.html │ │ │ │ ├── only-resumed.html │ │ │ │ ├── pause.html │ │ │ │ ├── reset.html │ │ │ │ ├── resume.html │ │ │ │ ├── start.html │ │ │ │ ├── state.html │ │ │ │ ├── stop.html │ │ │ │ ├── subscribe.html │ │ │ │ └── switch.html │ │ │ └── index.html │ │ ├── com.drake.net.transform │ │ │ ├── -deferred-transform │ │ │ │ ├── -deferred-transform.html │ │ │ │ ├── block.html │ │ │ │ ├── deferred.html │ │ │ │ └── index.html │ │ │ ├── index.html │ │ │ └── transform.html │ │ ├── com.drake.net.utils │ │ │ ├── -https │ │ │ │ ├── -un-safe-hostname-verifier.html │ │ │ │ ├── -un-safe-trust-manager.html │ │ │ │ └── index.html │ │ │ ├── -tip-utils │ │ │ │ ├── index.html │ │ │ │ └── toast.html │ │ │ ├── debounce.html │ │ │ ├── fastest.html │ │ │ ├── file-name.html │ │ │ ├── index.html │ │ │ ├── is-networking.html │ │ │ ├── launch-in.html │ │ │ ├── md5.html │ │ │ ├── media-type.html │ │ │ ├── run-main.html │ │ │ ├── scope-dialog.html │ │ │ ├── scope-life.html │ │ │ ├── scope-net-life.html │ │ │ ├── scope-net.html │ │ │ ├── scope.html │ │ │ ├── to-request-body.html │ │ │ ├── with-default.html │ │ │ ├── with-i-o.html │ │ │ ├── with-main.html │ │ │ └── with-unconfined.html │ │ ├── com.drake.net │ │ │ ├── -delete.html │ │ │ ├── -get.html │ │ │ ├── -head.html │ │ │ ├── -net-config │ │ │ │ ├── -t-a-g.html │ │ │ │ ├── app.html │ │ │ │ ├── converter.html │ │ │ │ ├── debug.html │ │ │ │ ├── dialog-factory.html │ │ │ │ ├── error-handler.html │ │ │ │ ├── host.html │ │ │ │ ├── index.html │ │ │ │ ├── initialize.html │ │ │ │ ├── ok-http-client.html │ │ │ │ ├── request-interceptor.html │ │ │ │ └── running-calls.html │ │ │ ├── -net │ │ │ │ ├── add-download-listener.html │ │ │ │ ├── add-upload-listener.html │ │ │ │ ├── cancel-all.html │ │ │ │ ├── cancel-group.html │ │ │ │ ├── cancel-id.html │ │ │ │ ├── debug.html │ │ │ │ ├── delete.html │ │ │ │ ├── get-request-by-group.html │ │ │ │ ├── get-request-by-id.html │ │ │ │ ├── get.html │ │ │ │ ├── head.html │ │ │ │ ├── index.html │ │ │ │ ├── options.html │ │ │ │ ├── patch.html │ │ │ │ ├── post.html │ │ │ │ ├── put.html │ │ │ │ ├── remove-download-listener.html │ │ │ │ ├── remove-upload-listener.html │ │ │ │ └── trace.html │ │ │ ├── -options.html │ │ │ ├── -patch.html │ │ │ ├── -post.html │ │ │ ├── -put.html │ │ │ ├── -trace.html │ │ │ └── index.html │ │ ├── okhttp3 │ │ │ ├── -ok-http-utils │ │ │ │ ├── add-lenient.html │ │ │ │ ├── disk-lru-cache.html │ │ │ │ ├── headers.html │ │ │ │ ├── index.html │ │ │ │ └── tags.html │ │ │ └── index.html │ │ └── package-list │ ├── images │ │ ├── anchor-copy-button.svg │ │ ├── arrow_down.svg │ │ ├── copy-icon.svg │ │ ├── copy-successful-icon.svg │ │ ├── docs_logo.svg │ │ ├── footer-go-to-link.svg │ │ ├── go-to-top-icon.svg │ │ └── logo-icon.svg │ ├── index.html │ ├── navigation.html │ ├── scripts │ │ ├── clipboard.js │ │ ├── main.js │ │ ├── navigation-loader.js │ │ ├── navigation-pane.json │ │ ├── pages.json │ │ ├── platform-content-handler.js │ │ └── sourceset_dependencies.js │ └── styles │ │ ├── jetbrains-mono.css │ │ ├── logo-styles.css │ │ ├── main.css │ │ └── style.css ├── auto-dialog.md ├── auto-pull.md ├── auto-refresh.md ├── auto-state.md ├── cache.md ├── callback.md ├── cancel.md ├── config.md ├── converter-customize.md ├── converter-struct.md ├── converter.md ├── cookie.md ├── coroutine-request.md ├── css │ └── extra.css ├── debounce.md ├── download-file.md ├── error-global.md ├── error-single.md ├── error-throws.md ├── error-tip.md ├── error.md ├── fastest.md ├── https.md ├── img │ ├── book-open.svg │ ├── code-preview.png │ ├── discussesions.svg │ ├── issues.svg │ ├── logo.gif │ └── preview.png ├── index.md ├── interceptor.md ├── interval.md ├── issues.md ├── kotlin-serialization.md ├── log-notice.md ├── log-recorder.md ├── material │ └── partials │ │ └── footer.html ├── model-generate.md ├── okhttp-client.md ├── progress.md ├── repeat-request.md ├── request.md ├── scope.md ├── sync-request.md ├── tag.md ├── thread.md ├── timing.md ├── track.md ├── updates.md ├── upload-file.md └── view-model.md ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── mkdocs.yml ├── net ├── .gitignore ├── build.gradle ├── consumer-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ ├── androidx │ │ └── lifecycle │ │ │ └── Scope.kt │ ├── com │ │ └── drake │ │ │ └── net │ │ │ ├── Net.kt │ │ │ ├── NetConfig.kt │ │ │ ├── NetCoroutine.kt │ │ │ ├── body │ │ │ ├── BodyExtension.kt │ │ │ ├── NetRequestBody.kt │ │ │ └── NetResponseBody.kt │ │ │ ├── cache │ │ │ ├── CacheMode.kt │ │ │ └── ForceCache.kt │ │ │ ├── component │ │ │ └── Progress.kt │ │ │ ├── convert │ │ │ ├── JSONConvert.kt │ │ │ └── NetConverter.kt │ │ │ ├── cookie │ │ │ └── PersistentCookieJar.kt │ │ │ ├── exception │ │ │ ├── ConvertException.kt │ │ │ ├── DownloadFileException.kt │ │ │ ├── HttpFailureException.kt │ │ │ ├── HttpResponseException.kt │ │ │ ├── NetCancellationException.kt │ │ │ ├── NetConnectException.kt │ │ │ ├── NetException.kt │ │ │ ├── NetSocketTimeoutException.kt │ │ │ ├── NetUnknownHostException.kt │ │ │ ├── NetworkingException.kt │ │ │ ├── NoCacheException.kt │ │ │ ├── RequestParamsException.kt │ │ │ ├── ResponseException.kt │ │ │ ├── ServerResponseException.kt │ │ │ └── URLParseException.kt │ │ │ ├── interceptor │ │ │ ├── LogRecordInterceptor.kt │ │ │ ├── NetOkHttpInterceptor.kt │ │ │ ├── RequestInterceptor.kt │ │ │ └── RetryInterceptor.kt │ │ │ ├── interfaces │ │ │ ├── NetDialogFactory.kt │ │ │ ├── NetErrorHandler.kt │ │ │ └── ProgressListener.kt │ │ │ ├── internal │ │ │ ├── NetDeferred.kt │ │ │ └── NetInitializer.kt │ │ │ ├── log │ │ │ ├── LogRecorder.kt │ │ │ └── MessageType.kt │ │ │ ├── okhttp │ │ │ ├── OkHttpBuilder.kt │ │ │ └── OkHttpExtension.kt │ │ │ ├── reflect │ │ │ └── TypeUtils.kt │ │ │ ├── request │ │ │ ├── BaseRequest.kt │ │ │ ├── BodyRequest.kt │ │ │ ├── MediaConst.kt │ │ │ ├── Method.kt │ │ │ ├── RequestBuilder.kt │ │ │ ├── RequestExtension.kt │ │ │ └── UrlRequest.kt │ │ │ ├── response │ │ │ └── ResponseExtension.kt │ │ │ ├── scope │ │ │ ├── AndroidScope.kt │ │ │ ├── DialogCoroutineScope.kt │ │ │ ├── NetCoroutineScope.kt │ │ │ ├── PageCoroutineScope.kt │ │ │ ├── StateCoroutineScope.kt │ │ │ └── ViewCoroutineScope.kt │ │ │ ├── tag │ │ │ └── NetTag.kt │ │ │ ├── time │ │ │ ├── Interval.kt │ │ │ └── IntervalStatus.kt │ │ │ ├── transform │ │ │ └── DeferredTransform.kt │ │ │ └── utils │ │ │ ├── Fastest.kt │ │ │ ├── FileUtils.kt │ │ │ ├── FlowUtils.kt │ │ │ ├── Https.kt │ │ │ ├── NetUtils.kt │ │ │ ├── Scope.kt │ │ │ ├── Suspend.kt │ │ │ ├── TipUtils.kt │ │ │ └── Uri.kt │ └── okhttp3 │ │ └── OkHttpUtils.java │ └── res │ ├── drawable │ ├── ic_limited_time.xml │ └── ic_timing.xml │ ├── values │ └── strings.xml │ └── xml │ └── network_security_config.xml ├── sample ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── drake │ │ └── net │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── drake │ │ │ └── net │ │ │ └── sample │ │ │ ├── base │ │ │ └── App.kt │ │ │ ├── constants │ │ │ ├── Api.kt │ │ │ └── UserConfig.kt │ │ │ ├── contract │ │ │ └── AlbumSelectContract.kt │ │ │ ├── converter │ │ │ ├── FastJsonConverter.kt │ │ │ ├── GsonConverter.kt │ │ │ ├── MoshiConverter.kt │ │ │ ├── ProtobufConverter.kt │ │ │ └── SerializationConverter.kt │ │ │ ├── interceptor │ │ │ ├── EncryptDataInterceptor.kt │ │ │ ├── GlobalHeaderInterceptor.kt │ │ │ └── RefreshTokenInterceptor.kt │ │ │ ├── mock │ │ │ └── MockDispatcher.kt │ │ │ ├── model │ │ │ ├── ArrayData.kt │ │ │ ├── BasicData.kt │ │ │ ├── ConfigModel.kt │ │ │ ├── GameModel.kt │ │ │ ├── SubData.kt │ │ │ ├── TokenModel.kt │ │ │ └── UserInfoModel.kt │ │ │ ├── ui │ │ │ ├── activity │ │ │ │ └── MainActivity.kt │ │ │ └── fragment │ │ │ │ ├── AsyncTaskFragment.kt │ │ │ │ ├── AutoDialogFragment.kt │ │ │ │ ├── CallbackRequestFragment.kt │ │ │ │ ├── CoroutineScopeFragment.kt │ │ │ │ ├── DownloadFileFragment.kt │ │ │ │ ├── EditDebounceFragment.kt │ │ │ │ ├── ErrorHandlerFragment.kt │ │ │ │ ├── ExceptionTraceFragment.kt │ │ │ │ ├── FastestFragment.kt │ │ │ │ ├── HttpsCertificateFragment.kt │ │ │ │ ├── InterceptorFragment.kt │ │ │ │ ├── LimitedTimeFragment.kt │ │ │ │ ├── ParallelNetworkFragment.kt │ │ │ │ ├── PreviewCacheFragment.kt │ │ │ │ ├── PullRefreshFragment.kt │ │ │ │ ├── PushRefreshFragment.kt │ │ │ │ ├── ReadCacheFragment.kt │ │ │ │ ├── SimpleRequestFragment.kt │ │ │ │ ├── StateLayoutFragment.kt │ │ │ │ ├── SuperIntervalFragment.kt │ │ │ │ ├── SwitchDispatcherFragment.kt │ │ │ │ ├── SyncRequestFragment.kt │ │ │ │ ├── TimingRequestFragment.kt │ │ │ │ ├── UniqueRequestFragment.kt │ │ │ │ ├── UploadFileFragment.kt │ │ │ │ ├── ViewModelRequestFragment.kt │ │ │ │ └── converter │ │ │ │ ├── BaseConvertFragment.kt │ │ │ │ ├── FastJsonConvertFragment.kt │ │ │ │ ├── GsonConvertFragment.kt │ │ │ │ ├── MoshiConvertFragment.kt │ │ │ │ └── SerializationConvertFragment.kt │ │ │ ├── utils │ │ │ ├── AESUtils.kt │ │ │ ├── HttpUtils.kt │ │ │ └── RandomFileUtils.kt │ │ │ └── vm │ │ │ └── UserViewModel.kt │ └── res │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable │ │ ├── bg_card.xml │ │ ├── bg_empty.webp │ │ ├── bg_error.webp │ │ ├── bg_input.xml │ │ ├── ic_async_task.xml │ │ ├── ic_callback_request.xml │ │ ├── ic_config_dialog.xml │ │ ├── ic_convert.xml │ │ ├── ic_debounce.xml │ │ ├── ic_download_file.xml │ │ ├── ic_error_handler.xml │ │ ├── ic_exception_trace.xml │ │ ├── ic_fastest.xml │ │ ├── ic_https.xml │ │ ├── ic_interceptor.xml │ │ ├── ic_interval.xml │ │ ├── ic_launcher_background.xml │ │ ├── ic_menu.xml │ │ ├── ic_parallel_network.xml │ │ ├── ic_preview_cache.xml │ │ ├── ic_pull_refresh.xml │ │ ├── ic_push_refresh.xml │ │ ├── ic_read_cache.xml │ │ ├── ic_scope.xml │ │ ├── ic_simple_request.xml │ │ ├── ic_state_layout.xml │ │ ├── ic_switch_dispatcher.xml │ │ ├── ic_sync_request.xml │ │ ├── ic_unique.xml │ │ ├── ic_upload_file.xml │ │ └── ic_view_model.xml │ │ ├── layout │ │ ├── activity_main.xml │ │ ├── fragment_async_task.xml │ │ ├── fragment_auto_dialog.xml │ │ ├── fragment_callback_request.xml │ │ ├── fragment_coroutine_scope.xml │ │ ├── fragment_custom_convert.xml │ │ ├── fragment_download_file.xml │ │ ├── fragment_edit_debounce.xml │ │ ├── fragment_error_handler.xml │ │ ├── fragment_exception_trace.xml │ │ ├── fragment_fastest.xml │ │ ├── fragment_https_certificate.xml │ │ ├── fragment_interceptor.xml │ │ ├── fragment_limited_time.xml │ │ ├── fragment_parallel_network.xml │ │ ├── fragment_pull_refresh.xml │ │ ├── fragment_push_refresh.xml │ │ ├── fragment_read_cache.xml │ │ ├── fragment_simple_request.xml │ │ ├── fragment_state_layout.xml │ │ ├── fragment_super_interval.xml │ │ ├── fragment_switch_dispatcher.xml │ │ ├── fragment_sync_request.xml │ │ ├── fragment_timing_request.xml │ │ ├── fragment_unique_request.xml │ │ ├── fragment_upload_file.xml │ │ ├── fragment_view_model_request.xml │ │ ├── item_game.xml │ │ ├── item_pull_list.xml │ │ ├── layout_drawer_nav_header.xml │ │ ├── layout_empty.xml │ │ ├── layout_error.xml │ │ └── layout_loading.xml │ │ ├── menu │ │ ├── menu_converter.xml │ │ ├── menu_download.xml │ │ ├── menu_interval.xml │ │ ├── menu_main.xml │ │ └── menu_request_method.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── navigation │ │ ├── nav_converter.xml │ │ └── nav_main.xml │ │ ├── raw │ │ ├── array.json │ │ ├── config.json │ │ ├── data.json │ │ ├── game.json │ │ └── user.json │ │ └── values │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── com │ └── drake │ └── net │ └── ExampleUnitTest.kt ├── settings.gradle └── signed /.github/ISSUE_TEMPLATE/----.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 提交问题 3 | about: 详细的描述可以快速解决问题 4 | title: '' 5 | labels: 寻求帮助 6 | assignees: '' 7 | 8 | --- 9 | 10 | ## 问题描述 11 | 12 | ### 期望行为 13 | 14 | ## 如何复现 15 | 16 | > fork仓库并复现问题可以快速解决, 猜测只会让问题晦涩难懂, 耽误所有人时间 17 | 18 | ## 截图 19 | 20 | 异常堆栈信息或者手机截图/视频(拖拽到输入框即可上传) 21 | 22 | ## 版本 23 | - Net: 24 | - OkHttp: 25 | - Android: 26 | - Gradle: 27 | - Android Studio: 28 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: ci 2 | on: 3 | push: 4 | branches: 5 | - master 6 | jobs: 7 | deploy: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v2 11 | - uses: actions/setup-python@v2 12 | with: 13 | python-version: 3.x 14 | - run: pip install mkdocs-material 15 | - run: mkdocs gh-deploy --force 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/navEditor.xml 5 | /.idea/assetWizardSettings.xml 6 | .DS_Store 7 | /build 8 | /captures 9 | .externalNativeBuild 10 | .cxx 11 | /.idea/ 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 劉強東 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | ext { 5 | kotlin_version = '1.8.10' 6 | brv_version = '1.5.2' 7 | coroutine_version = '1.6.1' 8 | okhttp_version = "4.10.0" 9 | } 10 | 11 | repositories { 12 | mavenCentral() 13 | google() 14 | jcenter() 15 | } 16 | dependencies { 17 | classpath 'com.android.tools.build:gradle:7.1.3' 18 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 19 | classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version" 20 | classpath 'org.jetbrains.dokka:dokka-gradle-plugin:1.4.32' 21 | } 22 | } 23 | 24 | allprojects { 25 | repositories { 26 | mavenCentral() 27 | google() 28 | maven { url 'https://jitpack.io' } 29 | jcenter() 30 | } 31 | } 32 | 33 | 34 | task clean(type: Delete) { 35 | delete rootProject.buildDir 36 | } 37 | -------------------------------------------------------------------------------- /docs/api/images/anchor-copy-button.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /docs/api/images/arrow_down.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /docs/api/images/copy-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /docs/api/images/copy-successful-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /docs/api/images/docs_logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /docs/api/images/footer-go-to-link.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /docs/api/images/go-to-top-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /docs/api/images/logo-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /docs/api/scripts/clipboard.js: -------------------------------------------------------------------------------- 1 | window.addEventListener('load', () => { 2 | document.querySelectorAll('span.copy-icon').forEach(element => { 3 | element.addEventListener('click', (el) => copyElementsContentToClipboard(element)); 4 | }) 5 | 6 | document.querySelectorAll('span.anchor-icon').forEach(element => { 7 | element.addEventListener('click', (el) => { 8 | if(element.hasAttribute('pointing-to')){ 9 | const location = hrefWithoutCurrentlyUsedAnchor() + '#' + element.getAttribute('pointing-to') 10 | copyTextToClipboard(element, location) 11 | } 12 | }); 13 | }) 14 | }) 15 | 16 | const copyElementsContentToClipboard = (element) => { 17 | const selection = window.getSelection(); 18 | const range = document.createRange(); 19 | range.selectNodeContents(element.parentNode.parentNode); 20 | selection.removeAllRanges(); 21 | selection.addRange(range); 22 | 23 | copyAndShowPopup(element, () => selection.removeAllRanges()) 24 | } 25 | 26 | const copyTextToClipboard = (element, text) => { 27 | var textarea = document.createElement("textarea"); 28 | textarea.textContent = text; 29 | textarea.style.position = "fixed"; 30 | document.body.appendChild(textarea); 31 | textarea.select(); 32 | 33 | copyAndShowPopup(element, () => document.body.removeChild(textarea)) 34 | } 35 | 36 | const copyAndShowPopup = (element, after) => { 37 | try { 38 | document.execCommand('copy'); 39 | element.nextElementSibling.classList.add('active-popup'); 40 | setTimeout(() => { 41 | element.nextElementSibling.classList.remove('active-popup'); 42 | }, 1200); 43 | } catch (e) { 44 | console.error('Failed to write to clipboard:', e) 45 | } 46 | finally { 47 | if(after) after() 48 | } 49 | } 50 | 51 | const hrefWithoutCurrentlyUsedAnchor = () => window.location.href.split('#')[0] 52 | 53 | -------------------------------------------------------------------------------- /docs/api/scripts/sourceset_dependencies.js: -------------------------------------------------------------------------------- 1 | sourceset_dependencies='{":net:dokkaHtml/androidTestRelease":[],":net:dokkaHtml/debug":[],":net:dokkaHtml/main":[],":net:dokkaHtml/release":[],":net:dokkaHtml/testFixtures":[],":net:dokkaHtml/testFixturesDebug":[],":net:dokkaHtml/testFixturesRelease":[]}' 2 | -------------------------------------------------------------------------------- /docs/api/styles/jetbrains-mono.css: -------------------------------------------------------------------------------- 1 | @font-face{ 2 | font-family: 'JetBrains Mono'; 3 | src: local('Iosevka Curly Medium'), 4 | url('https://raw.githubusercontent.com/liangjingkanji/liangjingkanji/master/font/iosevka-curly/iosevka-curly-medium.woff2') format('woff2'); 5 | font-display: swap; 6 | font-weight: normal; 7 | font-style: normal; 8 | } 9 | @font-face{ 10 | font-family: 'JetBrains Mono'; 11 | src: local('Iosevka Curly Bold'), 12 | url('https://raw.githubusercontent.com/liangjingkanji/liangjingkanji/master/font/iosevka-curly/iosevka-curly-bold.woff2') format('woff2'); 13 | font-display: swap; 14 | font-weight: bold; 15 | font-style: normal; 16 | } 17 | @font-face{ 18 | font-family: 'HYZhengYuan'; 19 | src: local('HYYouYuan-55W'), 20 | url('https://raw.githubusercontent.com/liangjingkanji/liangjingkanji/master/font/HYYouYuan/HYYouYuan-55W.ttf') format('truetype'); 21 | font-display: swap; 22 | font-weight: normal; 23 | font-style: normal; 24 | } 25 | @font-face{ 26 | font-family: 'HYZhengYuan'; 27 | src: local('HYYouYuan-75W'), 28 | url('https://raw.githubusercontent.com/liangjingkanji/liangjingkanji/master/font/HYYouYuan/HYYouYuan-75W.ttf') format('truetype'); 29 | font-display: swap; 30 | font-weight: bold; 31 | font-style: normal; 32 | } -------------------------------------------------------------------------------- /docs/api/styles/logo-styles.css: -------------------------------------------------------------------------------- 1 | #logo { 2 | background-image: url(../images/docs_logo.svg); 3 | } -------------------------------------------------------------------------------- /docs/auto-pull.md: -------------------------------------------------------------------------------- 1 | 首先请阅读上章节 [自动下拉刷新](auto-refresh.md), 已提及内容不再重复 2 | 3 | 4 | ## 自动分页 5 | 6 | 提供`addData()`来简化分页, 开发者可以借鉴实现 7 | 8 | ```kotlin hl_lines="2" 9 | page.onRefresh { 10 | scope { 11 | val data = Get(Api.PATH) { 12 | param("page", index) 13 | }.await().data 14 | addData(data.list) { 15 | index < data.total 16 | } 17 | } 18 | }.autoRefresh() 19 | ``` 20 | 21 | ## 索引自增 22 | `index` 每次上拉加载自动++1, 刷新列表重置为`PageRefreshLayout.startIndex` 23 | 24 | ## 有更多页 25 | 26 | 根据`hasMore`返回结果是否关闭上拉加载, `isEmpty`决定是否显示`空数据`缺省页 27 | 28 | ```kotlin 29 | fun addData( 30 | data: List?, 31 | adapter: BindingAdapter? = null, 32 | isEmpty: () -> Boolean = { data.isNullOrEmpty() }, 33 | hasMore: BindingAdapter.() -> Boolean = { true } 34 | ) 35 | ``` 36 | 37 |
38 | 1. [PageRefreshLayout 下拉刷新/上拉加载](https://liangjingkanji.github.io/BRV/refresh.html) -------------------------------------------------------------------------------- /docs/auto-refresh.md: -------------------------------------------------------------------------------- 1 | !!! success "模块化依赖" 2 | 如果自己处理下拉刷新可跳过本章, Net可以仅仅作为简单的网络框架存在 3 | 4 |
5 | Net可依赖三方库 [BRV](https://github.com/liangjingkanji/BRV) 实现自动处理下拉刷新 6 | 7 | 8 | 9 | ```groovy 10 | implementation 'com.github.liangjingkanji:BRV:+' // 使用固定版本号替换+符号 11 | ``` 12 | 13 | ## PageRefreshLayout 14 | 15 | ```xml 16 | 23 | 24 | 28 | 29 | 30 | ``` 31 | 32 | ## 创建列表 33 | 34 | ```kotlin 35 | rv_push.linear().setup { 36 | addType(R.layout.item_list) 37 | } 38 | ``` 39 | 40 | ## 网络请求 41 | 42 | 1. 请求开始, 显示下拉刷新动画 43 | 2. 请求成功, 显示`内容`缺省页 44 | 3. 请求失败, 显示`错误`缺省页 45 | 46 | ```kotlin hl_lines="2" 47 | page.onRefresh { 48 | scope { 49 | // 请求到数据设置到RecyclerView 50 | rv_push.models = Get(Api.PATH).await().data.list 51 | } 52 | }.autoRefresh() 53 | ``` 54 | 55 | ## 生命周期 56 | 57 | | 生命周期 | 描述 | 58 | | -------- | -------------------------------------------------- | 59 | | 开始 | `showLoading/autoRefresh`触发`onRefresh`, 开始请求 | 60 | | 结束 | PageRefreshLayout被销毁, 请求自动取消 | -------------------------------------------------------------------------------- /docs/callback.md: -------------------------------------------------------------------------------- 1 | Net支持OkHttp的原有的队列请求`Callback` 2 | 3 | !!! Failure "不推荐" 4 | Callback属于接口回调, 其代码冗余, 且无法支持并发请求 5 | 6 | 7 | ```kotlin 8 | Net.post(Api.PATH).enqueue(object : Callback { 9 | override fun onFailure(call: Call, e: IOException) { 10 | } 11 | 12 | override fun onResponse(call: Call, response: Response) { 13 | // 此处为子线程 14 | val body = response.body?.string() ?: "无数据" 15 | runMain { 16 | // 此处为主线程 17 | tv.text = body 18 | } 19 | } 20 | }) 21 | ``` -------------------------------------------------------------------------------- /docs/cancel.md: -------------------------------------------------------------------------------- 1 | 部分场景开发者想手动取消请求 2 | 3 | ```kotlin 4 | downloadScope = scopeNetLife { 5 | // 下载文件 6 | val file = Get(Api.DOWNLOAD).await() 7 | } 8 | 9 | downloadScope.cancel() // 取消下载 10 | ``` 11 | 12 | ## 任意位置取消 13 | 发起请求时指定`Id` 14 | 15 | ```kotlin 16 | scopeNetLife { 17 | tv.text = Get(Api.DOWNLOAD){ 18 | setId("请求用户信息") 19 | }.await() 20 | } 21 | ``` 22 | 23 | === "根据ID取消" 24 | ``` kotlin 25 | Net.cancelId("请求用户信息") 26 | ``` 27 | === "根据Group取消" 28 | ``` kotlin 29 | Net.cancelGroup("请求分组名称") 30 | ``` 31 | 32 | ## Group和Id区别 33 | 34 | | 函数 | 描述 | 35 | |-|-| 36 | | id | 请求唯一Id, 实际上重复也行, 但是取消请求时遍历到指定Id就会结束遍历 | 37 | | group | 允许多个请求使用相同group, 在取消请求时会遍历所有分组的请求
| 38 | 39 | !!! warning "作用域结束请求自动取消" 40 | 在`scopeXX()`作用域中发起请求时会默认使用当前协程错误处理器作为Group 41 | ```kotlin 42 | setGroup(coroutineContext[CoroutineExceptionHandler]) 43 | ``` 44 | 在作用域结束时 会`cancelGroup`, 所以如果你手动指定分组会导致无法自动取消 -------------------------------------------------------------------------------- /docs/converter.md: -------------------------------------------------------------------------------- 1 | Net支持请求返回的数据类型取决于你的转换器实现 2 | 3 | # Get<任何对象>(Api.PATH).await() 4 | 5 | 默认转换器支持返回以下数据类型 6 | 7 | | 函数 | 描述 | 8 | |-|-| 9 | | String | 字符串 | 10 | | ByteArray | 字节数组 | 11 | | ByteString | 更多功能的字符串对象 | 12 | | File | 文件对象, 这种情况其实应当称为[下载文件](download-file.md) | 13 | | Response | 所有响应信息(响应体/响应头/请求信息等) | 14 | 15 | 使用示例 16 | 17 | ```kotlin 18 | scopeNetLife { 19 | Get(Api.PATH).await().headers("响应头名称") // 返回响应头 20 | } 21 | ``` 22 | 23 | ??? example "转换器实现非常简单" 24 | ```kotlin title="NetConverter.kt" linenums="1" 25 | interface NetConverter { 26 | 27 | @Throws(Throwable::class) 28 | fun onConvert(succeed: Type, response: Response): R? 29 | 30 | companion object DEFAULT : NetConverter { 31 | /** 32 | * 返回结果应当等于泛型对象, 可空 33 | * @param succeed 请求要求返回的泛型类型 34 | * @param response 请求响应对象 35 | */ 36 | override fun onConvert(succeed: Type, response: Response): R? { 37 | return when { 38 | succeed === String::class.java && response.isSuccessful -> response.body?.string() as R 39 | succeed === ByteString::class.java && response.isSuccessful -> response.body?.byteString() as R 40 | succeed is GenericArrayType && succeed.genericComponentType === Byte::class.java && response.isSuccessful -> response.body?.bytes() as R 41 | succeed === File::class.java && response.isSuccessful -> response.file() as R 42 | succeed === Response::class.java -> response as R 43 | else -> throw ConvertException(response, "An exception occurred while converting the NetConverter.DEFAULT") 44 | } 45 | } 46 | } 47 | } 48 | ``` 49 | 50 | 假设不支持你需要的数据类型, 例如JSON/ProtoBuf/Bitmap等请[自定义转换器](converter-customize.md#_3) -------------------------------------------------------------------------------- /docs/cookie.md: -------------------------------------------------------------------------------- 1 | 可以实现OkHttp的`CookieJar`, 且提供持久化的[PersistentCookieJar](https://github.com/liangjingkanji/Net/blob/2abf07e1d003ef44574278fd2010f3375225d964/net/src/main/java/com/drake/net/cookie/PersistentCookieJar.kt) 2 | 3 | ```kotlin 4 | NetConfig.initialize(Api.HOST, this) { 5 | // 添加持久化Cookie 6 | cookieJar(PersistentCookieJar(this@App)) 7 | } 8 | ``` 9 | 10 | 可以手动增删Cookie 11 | 12 | | PersistentCookieJar | 描述 | 13 | |-|-| 14 | | addAll | 添加Cookie | 15 | | getAll | 获取某个域名的所有Cookie | 16 | | remove | 删除某个域名下所有或者指定名称的Cookie | 17 | | clear | 删除客户端全部Cookie | 18 | 19 | 20 | 可通过客户端获取到已配置cookieJar 21 | ```kotlin 22 | (NetConfig.okHttpClient.cookieJar as? PersistentCookieJar)?.clear() 23 | ``` 24 | 25 | !!! note "隔绝Cookies共享" 26 | 为`PersistentCookieJar`指定不同`dbName`阻止不同的客户端共享Cookies -------------------------------------------------------------------------------- /docs/coroutine-request.md: -------------------------------------------------------------------------------- 1 | Net的协程作用域会自动处理协程生命周期 2 | 3 | 在上章节已经介绍如何发起并发网络请求, 这里演示同时(并发)请求`一万次`, 然后取消全部 4 | 5 | ```kotlin 6 | val job = scopeNetLife { 7 | repeat(10000) { 8 | // 这里将返回的数据显示在TextView上 9 | launch { 10 | tv.text = Get(Api.PATH).await() 11 | } 12 | } 13 | } 14 | ``` 15 | 16 | 17 | 等待五秒后取消请求 18 | ```kotlin 19 | thread { 20 | Thread.sleep(5000) // 等待五秒 21 | job.cancel() 22 | } 23 | ``` 24 | 25 |
26 | 27 | Net主要使用协程请求, 但也支持其他方式发起请求 28 | 29 | - [同步请求](sync-request.md) 30 | - [回调请求](callback.md) 31 | - 协程请求 32 | -------------------------------------------------------------------------------- /docs/css/extra.css: -------------------------------------------------------------------------------- 1 | @font-face{ 2 | font-family: 'Iosevka Curly'; 3 | src: local('Iosevka Curly Medium'), 4 | url('https://raw.githubusercontent.com/liangjingkanji/liangjingkanji/master/font/iosevka-curly/iosevka-curly-medium.woff2') format('woff2'); 5 | font-display: swap; 6 | font-weight: normal; 7 | font-style: normal; 8 | } 9 | @font-face{ 10 | font-family: 'Iosevka Curly'; 11 | src: local('Iosevka Curly Bold'), 12 | url('https://raw.githubusercontent.com/liangjingkanji/liangjingkanji/master/font/iosevka-curly/iosevka-curly-bold.woff2') format('woff2'); 13 | font-display: swap; 14 | font-weight: bold; 15 | font-style: normal; 16 | } 17 | @font-face{ 18 | font-family: 'HYYouYuan'; 19 | src: local('HYYouYuan-55W'), 20 | url('https://raw.githubusercontent.com/liangjingkanji/liangjingkanji/master/font/HYYouYuan/HYYouYuan-55W.ttf') format('truetype'); 21 | font-display: swap; 22 | font-weight: normal; 23 | font-style: normal; 24 | } 25 | @font-face{ 26 | font-family: 'HYYouYuan'; 27 | src: local('HYYouYuan-75W'), 28 | url('https://raw.githubusercontent.com/liangjingkanji/liangjingkanji/master/font/HYYouYuan/HYYouYuan-75W.ttf') format('truetype'); 29 | font-display: swap; 30 | font-weight: bold; 31 | font-style: normal; 32 | } 33 | 34 | * { 35 | -webkit-font-feature-settings: "liga" on, "calt" on; 36 | -webkit-font-smoothing: subpixel-antialiased; 37 | -moz-osx-font-smoothing: auto; 38 | text-rendering: optimizeLegibility; 39 | font-family: "Iosevka Curly", HYYouYuan !important; 40 | } 41 | 42 | code, 43 | .md-nav, 44 | .md-typeset code, 45 | .md-typeset { 46 | font-size: 14px !important; 47 | } 48 | 49 | .highlight span.filename, 50 | .md-typeset .admonition-title, 51 | .md-typeset summary { 52 | font-weight: normal; 53 | } -------------------------------------------------------------------------------- /docs/debounce.md: -------------------------------------------------------------------------------- 1 | !!! question "节流" 2 | 在一定时间间隔内,只执行最后一次请求, 忽略其他多余的请求 3 | 4 | 搜索输入框一般都是输入完关键词后自动开始搜索 5 | 6 | 这个过程涉及到 7 | 8 | 1. 每次变化都搜索会导致服务器压力, 应在停止输入满足一定时间后自动搜索 9 | 2. 当产生新的搜索请求后应取消旧请求, 以防止旧数据覆盖新数据 10 | 3. 当输入内容没有变化(例复制粘贴重复内容到搜索框)不会发起搜索请求 11 | 12 |
13 | 14 | 15 | 16 |
17 | 18 | ```kotlin 19 | var scope: CoroutineScope? = null 20 | 21 | // distinctUntilChanged 表示过滤掉重复结果 22 | binding.etInput.debounce().distinctUntilChanged().launchIn(this) { 23 | scope?.cancel() // 发起新的请求前取消旧的请求, 避免旧数据覆盖新数据 24 | scope = scopeNetLife { // 保存旧的请求到一个变量中 25 | tv.text = "请求中" 26 | tv.text = Get(Api.TIME).await() 27 | } 28 | } 29 | ``` 30 | 31 | 指定参数设置节流阀超时时间 32 | ```kotlin 33 | fun EditText.debounce(timeoutMillis: Long = 800) 34 | ``` 35 |
36 | 1. [示例-自动搜索分页列表](https://github.com/liangjingkanji/Net/blob/a78d46118666a3509d3fcc79c1d28ad81e2d5a57/sample/src/main/java/com/drake/net/sample/ui/fragment/EditDebounceFragment.kt) -------------------------------------------------------------------------------- /docs/download-file.md: -------------------------------------------------------------------------------- 1 | 泛型改为`File`即可 2 | 3 | ```kotlin 4 | scopeNetLife { 5 | val file = Get(Api.DOWNLOAD).await() 6 | } 7 | ``` 8 | 9 | ## 下载选项 10 | 11 | 丰富的下载定制方案, 并且在不断更新 12 | 13 | ```kotlin 14 | scopeNetLife { 15 | val file = 16 | Get("https://github.com/liangjingkanji/Net/releases/latest/download/net-sample.apk") { 17 | setDownloadFileName("net.apk") 18 | setDownloadDir(requireContext().filesDir) 19 | setDownloadMd5Verify() 20 | }.await() 21 | } 22 | ``` 23 | 24 | | 配置选项 | 描述 | 25 | | --------------------------- | ------------------------------------------------------------ | 26 | | addDownloadListener | [下载进度监听器](progress.md) | 27 | | setDownloadFileName | 下载文件名 | 28 | | setDownloadDir | 下载目录 | 29 | | setDownloadMd5Verify | 下载文件MD5校验 | 30 | | setDownloadFileNameConflict | 下载文件同名冲突解决 | 31 | | setDownloadFileNameDecode | 文件名Url解码中文 | 32 | | setDownloadTempFile | 临时文件名 | 33 | 34 | ## 重复下载 35 | 36 | 防止重复下载有以下方式 37 | 38 | | 函数 | 描述 | 39 | | -------- | --------------------------------------------------------- | 40 | | 文件判断 | 判断本地是否存在同名文件 | 41 | | 缓存模式 | 开启缓存, 占用设备两份空间(缓存/下载成功文件都占空间) | 42 | | MD5校验 | 服务器返回`Content-MD5`, 客户端开启`setDownloadMd5Verify` | -------------------------------------------------------------------------------- /docs/error-global.md: -------------------------------------------------------------------------------- 1 | 可实现`NetErrorHandler`接口来监听全局错误处理 2 | 3 | === "创建" 4 | ```kotlin 5 | class NetworkingErrorHandler : NetErrorHandler { 6 | override fun onError(e: Throwable) { 7 | // .... 其他错误 8 | if (e is ResponseException && e.tag == 401) { // 判断异常为token失效 9 | // 打开登录界面或者弹登录失效对话框 10 | } 11 | } 12 | } 13 | ``` 14 | === "配置" 15 | ```kotlin 16 | NetConfig.initialize(Api.HOST, this) { 17 | setErrorHandler(NetworkingErrorHandler)) 18 | } 19 | ``` 20 | 21 | |NetErrorHandler|使用场景|触发位置| 22 | |-|-|-| 23 | |`onError`| 吐司错误信息 | `scopeNetLife/scopeDialog` | 24 | |`onStateError` | 要求错误显示在缺省页 |`PageRefreshLayout.scope/StateLayout.scope`| 25 | 26 | 27 | !!! warning "以下情况全局错误处理无效" 28 | 29 | 1. 异步任务作用域(`scope/scopeLife`)发生的错误 30 | 2. 使用[单例错误处理](error-single.md)处理的错误 -------------------------------------------------------------------------------- /docs/error-single.md: -------------------------------------------------------------------------------- 1 | 捕获当前请求/作用域错误, 将不会再被全局错误处理 2 | 3 | ## 捕获请求 4 | 5 | 一个请求发生错误会取消当前作用域内所有请求, 但单独捕获后不会再影响其他请求 6 | 7 | 例如 8 | ```kotlin 9 | scopeNetLife { 10 | Get("path").await() // 失败 11 | Get("path2").await() // 上面失败, 此处也不会执行 12 | } 13 | ``` 14 | 15 | 捕获第一个协程避免终止后续执行 16 | ```kotlin 17 | scopeNetLife { 18 | try { 19 | Get("path").await() // 失败 20 | } catch(e:Exception) { 21 | } 22 | Get("path2").await() // 上面失败, 此处继续执行 23 | } 24 | ``` 25 | 当然如果创建不同的作用域分别请求那是互不影响的 26 | ```kotlin 27 | scopeNetLife { 28 | Get("path").await() // 失败 29 | } 30 | scopeNetLife { 31 | Get("path2").await() // 上面失败, 此处完全不受影响 32 | } 33 | ``` 34 | 35 |
36 | 37 | ## 捕获作用域 38 | 39 | ```kotlin 40 | scope { 41 | val data = Get("http://www.error.com/").await() 42 | }.catch { 43 | // 协程内发生错误回调, it为异常对象 44 | }.finally { 45 | // 协程执行完毕回调(不论成败), it为异常对象 46 | } 47 | ``` 48 | 49 | | 函数 | 区别 | 50 | |-|-| 51 | | catch | 发生错误后回调, 取消不回调
不会再执行全局错误处理, 可使用`handleError`再次传递给全局 | 52 | | finally | 执行完毕回调(不论成败), 取消作用域时`it`为`CancellationException`
执行全局错误处理 | -------------------------------------------------------------------------------- /docs/error.md: -------------------------------------------------------------------------------- 1 | Net有完善的错误处理机制, 具备捕获异常/取消请求/错误提示/追踪链路 2 | 3 | !!! success "收集网络日志" 4 | 在Net作用域内发生的异常都会被全局错误处理捕获, 可以将其筛选上传日志 5 | 6 |
7 | 以下位置抛出异常会被捕获 8 | 9 | | 函数 | 描述 | 10 | |-|-| 11 | | 作用域 | `scopeXX`代码块中 | 12 | | 拦截器 | `Interceptor/RequestInterceptor` | 13 | | 转换器 | `NetConverter` | 14 | 15 | 如果捕获到错误默认会执行以下操作 16 | 17 | 1. `Logcat`输出异常堆栈信息, [自定义异常抛出](error-throws.md) 18 | 2. `Toast`显示错误文本, [自定义错误提示](error-tip.md) 19 | 20 |
21 | !!! failure "捕获不到异常" 22 | 如果请求未执行`await()`, 那么即使发生错误也不会被捕获到 23 | 24 |
25 | 自定义请阅读[全局错误处理](error-global.md) -------------------------------------------------------------------------------- /docs/https.md: -------------------------------------------------------------------------------- 1 | Net可快速配置Https证书, 或者使用OkHttp的方式 2 | 3 | ## 使用CA证书 4 | 5 | Https如果使用的CA证书, 不需要任何配置可以直接访问 6 | 7 | ```kotlin 8 | scopeNetLife { 9 | tv.text = Get(Api.PATH).await() 10 | } 11 | ``` 12 | 13 | ## 信任所有证书 14 | 15 | 信任所有证书可以解决无法访问私有证书的Https地址问题 16 | 17 | === "全局配置" 18 | 19 | ```kotlin 20 | NetConfig.initialize(Api.HOST, this){ 21 | trustSSLCertificate() // 信任所有证书 22 | } 23 | ``` 24 | === "单例配置" 25 | 26 | ```kotlin 27 | scopeNetLife { 28 | Get(Api.PATH){ 29 | setClient { 30 | trustSSLCertificate() 31 | } 32 | }.await() 33 | } 34 | ``` 35 | 36 | ## 导入证书 37 | 38 | 私有证书可以放到任何位置, 只要读取到`InputStream`流对象即可 39 | 40 | === "全局配置" 41 | 42 | ```kotlin 43 | NetConfig.initialize(Api.HOST, this) { 44 | val privateCertificate = resources.assets.open("https.certificate") 45 | setSSLCertificate(privateCertificate) 46 | } 47 | ``` 48 | 49 | === "单例配置" 50 | 51 | ```kotlin 52 | scopeNetLife { 53 | Get(Api.PATH) { 54 | setClient { 55 | val privateCertificate = resources.assets.open("https.certificate") // 这里的证书是放到应用的资产目录下 56 | setSSLCertificate(privateCertificate) 57 | } 58 | }.await() 59 | } 60 | ``` 61 | 62 | 63 | 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /docs/img/book-open.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/img/code-preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liangjingkanji/Net/0d2ab60f67df6442c613d8055c59f3a60c524022/docs/img/code-preview.png -------------------------------------------------------------------------------- /docs/img/discussesions.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 编组备份 5 | 6 | 7 | 8 | 11 | 14 | 16 | 讨论 17 | 18 | 20 | Discussions 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /docs/img/issues.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 编组 5 | 6 | 7 | 8 | 9 | 12 | 14 | 常见问题 15 | 16 | 17 | 18 | 21 | 23 | Issues 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /docs/img/logo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liangjingkanji/Net/0d2ab60f67df6442c613d8055c59f3a60c524022/docs/img/logo.gif -------------------------------------------------------------------------------- /docs/img/preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liangjingkanji/Net/0d2ab60f67df6442c613d8055c59f3a60c524022/docs/img/preview.png -------------------------------------------------------------------------------- /docs/interceptor.md: -------------------------------------------------------------------------------- 1 | 根据使用场景选择 2 | 3 | 1. `Interceptor`: 支持三方OkHttp拦截器组件, 允许修改请求/响应信息, 可以转发请求 4 | 2. `RequestInterceptor`: Net独有的轻量级拦截器, 允许修改全局请求头/请求参数, 无法转发请求 5 |
6 | 7 | !!! Failure "禁止无效封装" 8 | 不应为全局参数/加密等封装请求方法, 应自定义拦截器/转换器来实现, [常见拦截器示例](https://github.com/liangjingkanji/Net/tree/master/sample/src/main/java/com/drake/net/sample/interceptor) 9 | 10 | 11 | 12 | ## 拦截器 13 | 14 | 添加拦截器 15 | 16 | ```kotlin 17 | NetConfig.initialize(Api.HOST, this) { 18 | addInterceptor(RefreshTokenInterceptor()) 19 | } 20 | ``` 21 | 22 | 客户端token自动续期示例 23 | 24 | ```kotlin 25 | class RefreshTokenInterceptor : Interceptor { 26 | override fun intercept(chain: Interceptor.Chain): Response { 27 | val request = chain.request() 28 | val response = chain.proceed(request) 29 | return synchronized(RefreshTokenInterceptor::class.java) { 30 | if (response.code == 401 && UserConfig.isLogin && !request.url.encodedPath.contains(Api.Token)) { 31 | val tokenInfo = Net.get(Api.Token).execute() // 同步请求token 32 | if (tokenInfo.isExpired) { 33 | // token过期抛出异常, 由全局错误处理器处理, 在其中可以跳转到登陆界面提示用户重新登陆 34 | throw ResponseException(response, "登录状态失效") 35 | } else { 36 | UserConfig.token = tokenInfo.token 37 | } 38 | chain.proceed(request) 39 | } else { 40 | response 41 | } 42 | } 43 | } 44 | } 45 | ``` 46 | 47 | ## 请求拦截器 48 | 49 | 轻量级拦截器(`RequestInterceptor`), 其Api更适合添加全局请求头/参数 50 | 51 | ```kotlin 52 | NetConfig.initialize(Api.HOST, this) { 53 | setRequestInterceptor(object : RequestInterceptor { 54 | override fun interceptor(request: BaseRequest) { 55 | request.param("client", "Net") 56 | request.setHeader("token", "123456") 57 | } 58 | }) 59 | } 60 | ``` 61 | 62 | 可以看出`setRequestInterceptor`仅支持一个, `addInterceptor`支持多个拦截器 -------------------------------------------------------------------------------- /docs/interval.md: -------------------------------------------------------------------------------- 1 | Net自带轮询器(计时器), 包含以下特性 2 | 3 | - 正计时 4 | - 倒计时 5 | - 指定次数 6 | - 支持开始/停止/暂停/继续/重置/切换开关 7 | - 仅页面显示时计时 8 | - 页面销毁自动取消 9 | 10 |
11 | === "限制次数/间隔" 12 | ```kotlin 13 | interval = Interval(100, 1, TimeUnit.SECONDS).life(this) // 自定义计数器个数的轮询器 14 | ``` 15 | === "无限执行" 16 | ```kotlin 17 | interval = Interval(1, TimeUnit.SECONDS) // 每秒回调一次, 不会自动结束 18 | ``` 19 | === "倒计时" 20 | ```kotlin 21 | // 倒计时轮询器, 当[start]]比[end]值大, 且end不等于-1时, 即为倒计时 22 | interval = Interval(1, 1, TimeUnit.SECONDS, 5).life(this) 23 | ``` 24 | 25 | 监听轮询器 26 | ```kotlin 27 | interval.subscribe { 28 | tv.text = it.toString() 29 | }.finish { 30 | tv.text = "计时完成" // 最后一位数时同时回调 subscribe/finish 31 | }.start() 32 | ``` 33 | 34 | | Interval | 描述 | 35 | |-|-| 36 | | start | 开始轮询器 | 37 | | stop | 停止 | 38 | | cancel | 取消, 区别于stop, 此函数执行后并不会回调finish | 39 | | pause | 暂停 | 40 | | resume | 继续 | 41 | | reset | 重置 | 42 | | switch | 切换开关 | 43 | | subscribe | 每次计时都会执行该回调 | 44 | | finish | 当计时器完成时执行该回调, 执行stop后也会回调 | 45 | | life | 指定生命周期自动取消轮询器 | 46 | | onlyResumed | 当界面不可见时暂停, 当界面可见时继续 | 47 | 48 | !!! failure "后台运行" 49 | 由于系统限制, 本工具无法保证一直后台运行 -------------------------------------------------------------------------------- /docs/issues.md: -------------------------------------------------------------------------------- 1 | Net最大的特点在于完美支持OkHttp的所有功能组件, 而OkHttp是Android主流网络解决方案 2 | 3 | 所以如果存在Net没有实现的功能可以谷歌搜索`"OkHttp如何实现XX"`, 然后可以很容易在Net中使用 4 | 5 | ## 低版本兼容 6 | 7 | 如果开发要求低至 Android 4.4 (API level 19) 8 | 请使用兼容版本: [Net-okhttp3](https://github.com/liangjingkanji/Net-okhttp3) 9 | 10 | 如果需更低版本支持请拉取仓库修改`minSdkVersion`后编译成aar使用 11 | 12 | ## 开发者交流 13 | 14 | - [反馈问题](https://github.com/liangjingkanji/Net/issues) 15 | - [其他开发者提及问题](https://github.com/liangjingkanji/Net/issues) 16 | - [交流社区](https://github.com/liangjingkanji/Net/discussions) 17 | - 18 | -------------------------------------------------------------------------------- /docs/log-notice.md: -------------------------------------------------------------------------------- 1 | 使用三方库 [Chucker](https://github.com/ChuckerTeam/chucker) 在通知栏记录网络请求日志 2 | 3 |
4 | ![](https://github.com/ChuckerTeam/chucker/raw/main/assets/chucker-http.gif){ width="300" } 5 |
Chucker
6 |
7 | 8 | 依赖 9 | 10 | ```groovy 11 | implementation "com.github.chuckerteam.chucker:library:3.5.2" 12 | ``` 13 | 14 | 添加拦截器 15 | 16 | ```kotlin 17 | NetConfig.initialize(Api.HOST, this) { 18 | // ... 19 | if (BuildConfig.DEBUG) { 20 | addInterceptor( 21 | ChuckerInterceptor.Builder(this@App) 22 | .collector(ChuckerCollector(this@App)) 23 | .maxContentLength(250000L) 24 | .redactHeaders(emptySet()) 25 | .alwaysReadResponseBody(false) 26 | .build() 27 | ) 28 | } 29 | } 30 | ``` 31 | 32 | 自定义功能浏览 [Chucker](https://github.com/ChuckerTeam/chucker) 官网 33 | -------------------------------------------------------------------------------- /docs/model-generate.md: -------------------------------------------------------------------------------- 1 | 应该没人会完全手写数据模型吧? 建议使用`JSON To Kotlin Class` 插件完成 2 | 3 | ## 安装插件 4 | 5 | 在Plugins里面搜索kotlin关键词下载安装 6 | 7 | 8 | 9 | ## 使用插件 10 | 11 | 选中分包后, 使用快捷键或者鼠标右键打开填写Json界面 12 | 13 | 14 | 15 | 添加Json然后点击`Generate`生成 16 | 17 | 18 | 19 | 然后就会在选中的分包下生成一个数据模型类了 20 | 21 | !!! Warning "不要生成数组" 22 | 不要输入JSON数组生成(否则生成的类会继承ArrayList), 输入JSON对象, 请求使用`Get>` 23 | 24 | ## 高级设置 25 | 26 | 点击`Advanced`打开设置界面 27 | 28 | ### 可空选项 29 | 30 | 截图即为我的推荐配置 31 | 32 | 33 | 34 | 1. Keyword 创建的数据模型的字段是 Val还是Var 35 | 2. Type 字段是否是可空类型, 最后选项表示根据Json的值判断是否可空 36 | 3. 默认值的策略, 无法选择不介绍 37 | 38 | ### 使用的框架 39 | 40 | 生成数据模型时会兼容你使用的框架, 例如Moshi和ks可能需要注解, 然后会自动生成SerialName这些名称注解 41 | 42 | 43 | 44 | ### 其他 45 | 46 | 截图即为我的推荐配置 47 | 48 | 49 | 50 | 1. 是否使用注释, 既会将Json字符串作为注释保留在数据模型类中 51 | 2. 根据字母排序数据模型的字段 52 | 3. 使用内部类. 例如Json中的Json对象会作为内部类被创建在数据模型类中 (推荐开启, 保持高内聚低耦合) 53 | 4. 如果Json对象的字段都是原始类型则使用Map来表示 54 | 5. 只在需要时创建注解 55 | 6. 自动验证Json正确性(在填写时) 56 | 7. Json格式化时使用的空格数量 57 | 8. 指定一个类作为父类模板(既创建的数据模型都会继承该类) 58 | 59 | ### 扩展 60 | 61 | 截图即为我的推荐配置 62 | 63 | 64 | 65 | 1. 添加@Keep注解, 为防止被代码混淆 66 | 2. 注解和字段处于同一行, 便于美观 67 | 3. 使用Parcelable序列化 68 | 4. 为字段添加前缀/后缀 69 | 5. 为数据模型类添加前缀/后缀 -------------------------------------------------------------------------------- /docs/okhttp-client.md: -------------------------------------------------------------------------------- 1 | Net全局持有一个`OkHttpClient`对象发起请求 2 | 3 | ```kotlin 4 | object NetConfig { 5 | var okHttpClient: OkHttpClient 6 | } 7 | ``` 8 | 9 | ## 全局 10 | 11 | ```kotlin 12 | NetConfig.initialize(Api.HOST, this) { 13 | // 此处this即为OkHttpClient.Builder 14 | } 15 | ``` 16 | 17 | 18 | ## 单例 19 | 20 | 单独指定当前请求客户端 21 | 22 | === "修改全局客户端" 23 | ```kotlin 24 | scopeNetLife { 25 | tv_response.text = Get(Api.PATH) { 26 | setClient { 27 | // 此处this即为OkHttpClient.Builder 28 | trustCertificate() 29 | } 30 | }.await() 31 | } 32 | ``` 33 | 在全局`OkHttpClient`基础上修改 34 | 35 | === "创建新客户端" 36 | ```kotlin 37 | scopeNetLife { 38 | tv_response.text = Get(Api.PATH) { 39 | okHttpClient = OkHttpClient.Builder().build() 40 | }.await() 41 | } 42 | ``` 43 | 创建新的OkHttpClient, 一般不使用, 因为新OkHttpClient会重新创建线程池/连接池等造成内存消耗 -------------------------------------------------------------------------------- /docs/repeat-request.md: -------------------------------------------------------------------------------- 1 | 常用于筛选列表请求, 选择新的筛选条件时应将上次未完成的取消后再发起请求 2 | 3 | 在Net禁止重复请求仅2行代码 4 | 5 | ```kotlin hl_lines="4" 6 | var scope: AndroidScope? = null 7 | 8 | btnFilter.setOnClickListener { 9 | scope?.cancel() 10 | 11 | scope = scopeNetLife { 12 | val result = Post(Api.PATH).await() 13 | } 14 | } 15 | ``` 16 | 17 | 当`scope`不为空时表示存在旧请求, 无论旧请求是否完成都可以调用`cancel()`保证取消即可 18 | 19 | 1. [取消请求](cancel.md)
20 | 2. 限制时间强制使用缓存, [配置缓存有效期](cache.md#_3) -------------------------------------------------------------------------------- /docs/sync-request.md: -------------------------------------------------------------------------------- 1 | Net支持在当前线程执行阻塞线程的同步请求 2 | 3 | !!! question "什么是同步请求" 4 | 即上个请求结束才会发起下个请求, 实际上协程也可以实现但是他不会阻塞线程 5 | 6 | 同步请求应用场景一般是在拦截器(执行在子线程)中使用 7 | 8 | 因为Android主线程不允许发起网络请求, 这里创建一个子线程来演示 9 | 10 | === "返回数据" 11 | 12 | ```kotlin 13 | thread { 14 | val result = Net.post(Api.PATH).execute() // 网络请求不允许在主线程 15 | tv?.post { 16 | tv?.text = result // view要求在主线程更新 17 | } 18 | } 19 | ``` 20 | 21 | === "返回Result" 22 | 23 | ```kotlin 24 | thread { 25 | val result = Net.post(Api.PATH).toResult().getOrDefault("请求发生错误, 我是默认值") 26 | tv?.post { 27 | tv?.text = result // view要求在主线程更新 28 | } 29 | } 30 | ``` 31 | 32 | 1. `execute`在请求错误时会直接抛出异常 33 | 2. `toResult`不会抛出异常, 可`getOrThrow/exceptionOrNull`等返回异常对象 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /docs/timing.md: -------------------------------------------------------------------------------- 1 | 以下为提供实现定时/限时请求思路, 并不限定只有以下方式 2 | 3 | 4 | ## 限时请求 5 | 6 | 即超过指定时间后立即取消请求 7 | 8 | ```kotlin 9 | scopeDialog { 10 | // 当接口请求在100毫秒内没有完成会抛出异常TimeoutCancellationException 11 | withTimeout(100) { 12 | Get(Api.PATH).await() 13 | } 14 | }.catch { 15 | Log.e("日志", "请求错误", it) // catch无法接收到CancellationException异常 16 | }.finally { 17 | Log.e("日志", "请求完成", it) // TimeoutCancellationException属于CancellationException子类故只会被finally接收到 18 | if (it is TimeoutCancellationException) { 19 | toast("由于未在指定时间完成请求则取消请求") 20 | } 21 | } 22 | ``` 23 | 24 | ## 定时请求 25 | 26 | 指定请求10次 27 | 28 | ```kotlin 29 | scopeNetLife { 30 | // 每秒请求一次, 总共执行10次 31 | repeat(20) { 32 | delay(1000) 33 | val data = Get(Api.PATH).await() 34 | if(it = 10) { 35 | return@repeat 36 | } 37 | } 38 | } 39 | ``` 40 | 41 | 每秒无限循环请求, 根据某个条件`break`退出 42 | 43 | ```kotlin 44 | scopeNetLife { 45 | while (true) { 46 | delay(1.toDuration(DurationUnit.SECONDS)) 47 | val data = Get(Api.PATH).await() 48 | if(data.type = 3) { 49 | break 50 | } 51 | } 52 | } 53 | ``` -------------------------------------------------------------------------------- /docs/track.md: -------------------------------------------------------------------------------- 1 | Net中网络请求异常会LogCat输出, 除非开发者修改全局错误处理 2 | 3 | 4 | 演示访问一个不存在的请求路径 5 | ```kotlin 6 | scopeNetLife { 7 | tv.text = Get("https://error.com/net/").await() 8 | } 9 | ``` 10 | 11 | LogCat可以看到异常堆栈信息, 包含具体Url和代码位置 12 | 13 | 14 | 15 | 16 | ### 关闭日志 17 | 18 | 可以关闭日志打印 19 | 20 | ```kotlin 21 | NetConfig.initialize(Api.HOST, this) { 22 | setDebug(false) // 关闭日志, 一般使用 BuildConfig.DEBUG 23 | } 24 | ``` 25 | -------------------------------------------------------------------------------- /docs/upload-file.md: -------------------------------------------------------------------------------- 1 | ```kotlin 2 | scopeNetLife { 3 | Post(Api.UPLOAD) { 4 | param("fileName", assetsFile()) 5 | }.await() 6 | } 7 | ``` 8 | 9 | 使用`addUploadListener`添加上传进度监听器, 阅读[进度监听](progress.md)章节 10 | 11 | ## 指定类型 12 | 13 | 默认根据文件后缀名生成`MediaType`, 如果想自定义MediaType可以直接创建`RequestBody` 14 | 15 | ```kotlin 16 | scopeNetLife { 17 | Post(Api.UPLOAD) { 18 | param("file", assetsFile().toRequestBody("image/webp".toMediaType())) 19 | }.await() 20 | } 21 | ``` 22 | 23 | ## 上传类型 24 | 25 | 自定义RequestBody可以实现任何数据类型的上传 26 | 27 | ```kotlin 28 | scopeNetLife { 29 | Post(Api.UPLOAD) { 30 | // 表单上传 31 | param("file", Uri) 32 | param("file", File) 33 | }.await() 34 | } 35 | ``` 36 | 37 | 直接上传`InputStream`可能内存泄露, 建议你保存到文件后上传 38 | 39 | 1. [使用文件流上传文件](https://github.com/liangjingkanji/Net/discussions/190) 40 | 41 | 42 | -------------------------------------------------------------------------------- /docs/view-model.md: -------------------------------------------------------------------------------- 1 | Net支持在ViewModel中创建网络请求/异步任务 2 | 3 | !!! Warning "不推荐" 4 | 1. 网络请求不一定要写在ViewModel
5 | 2. 网络请求不要写接口回调 6 | 3. 可以在Activity中直接返回请求结果 7 | 8 | 9 | ## 自动生命周期 10 | 11 | - [示例代码](https://github.com/liangjingkanji/Net/blob/c4d7c4cde6a34b9fa97a75cb357276b75432f8d1/sample/src/main/java/com/drake/net/sample/ui/fragment/ViewModelRequestFragment.kt) 12 | 13 | 使用`scopeXXLife()`创建作用域, 在ViewModel被销毁时自动取消请求 14 | 15 | ```kotlin 16 | class UserViewModel : ViewModel() { 17 | 18 | // 用户信息 19 | var userInfo: MutableLiveData = MutableLiveData() 20 | 21 | /** 22 | * 使用LiveData接受请求结果, 将该liveData直接使用DataBinding绑定到页面上, 会在请求成功自动更新视图 23 | */ 24 | fun fetchUserInfo() = scopeNetLife { 25 | userInfo.value = Get(Api.GAME).await() 26 | } 27 | 28 | /** 29 | * 开始非阻塞异步任务 30 | * 返回Deferred, 调用await()才会返回结果 31 | */ 32 | fun fetchList(scope: CoroutineScope) = scope.Get(Api.TEST) 33 | 34 | /** 35 | * 开始阻塞异步任务 36 | * 直接返回结果 37 | */ 38 | suspend fun fetchPrecessData() = coroutineScope { 39 | val response = Get(Api.TEST).await() 40 | response + "处理数据" 41 | } 42 | } 43 | ``` 44 | 45 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | ## For more details on how to configure your build environment visit 2 | # http://www.gradle.org/docs/current/userguide/build_environment.html 3 | # 4 | # Specifies the JVM arguments used for the daemon process. 5 | # The setting is particularly useful for tweaking memory settings. 6 | # Default value: -Xmx1024m -XX:MaxPermSize=256m 7 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 8 | # 9 | # When configured, Gradle will run in incubating parallel mode. 10 | # This option should only be used with decoupled projects. More details, visit 11 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 12 | # org.gradle.parallel=true 13 | #Fri Mar 18 17:49:14 CST 2022 14 | kotlin.code.style=official 15 | kotlin.incremental=false 16 | org.gradle.jvmargs=-Xmx2048M -Dkotlin.daemon.jvm.options\="-Xmx2048M" 17 | android.useAndroidX=true 18 | android.enableJetifier=true 19 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liangjingkanji/Net/0d2ab60f67df6442c613d8055c59f3a60c524022/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Thu Jul 28 17:18:47 CST 2022 2 | distributionBase=GRADLE_USER_HOME 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip 4 | distributionPath=wrapper/dists 5 | zipStorePath=wrapper/dists 6 | zipStoreBase=GRADLE_USER_HOME 7 | -------------------------------------------------------------------------------- /net/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /net/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: "com.android.library" 2 | apply plugin: "kotlin-android" 3 | apply plugin: 'org.jetbrains.dokka' 4 | 5 | android { 6 | compileSdkVersion 30 7 | defaultConfig { 8 | minSdkVersion 19 9 | targetSdkVersion 30 10 | versionCode 1 11 | versionName "1.0" 12 | 13 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 14 | consumerProguardFiles "consumer-rules.pro" 15 | } 16 | 17 | kotlinOptions { 18 | freeCompilerArgs = ["-Xinline-classes", "-Xallow-result-return-type"] 19 | } 20 | 21 | dokkaHtml { 22 | outputDirectory.set(file("$rootDir/docs/api")) 23 | suppressInheritedMembers.set(true) 24 | moduleName.set("Net") 25 | } 26 | } 27 | 28 | 29 | dependencies { 30 | implementation 'androidx.appcompat:appcompat:1.3.1' 31 | implementation 'androidx.startup:startup-runtime:1.0.0' 32 | implementation 'androidx.documentfile:documentfile:1.0.1' 33 | implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.6.2" 34 | 35 | compileOnly "com.squareup.okhttp3:okhttp:$okhttp_version" 36 | 37 | compileOnly "com.github.liangjingkanji:BRV:$brv_version" 38 | compileOnly "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutine_version" 39 | compileOnly "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutine_version" 40 | } 41 | -------------------------------------------------------------------------------- /net/consumer-rules.pro: -------------------------------------------------------------------------------- 1 | -keep class okhttp3.** { *; } 2 | -keep class com.drake.net.exception.** { *; } -------------------------------------------------------------------------------- /net/src/main/java/com/drake/net/cache/CacheMode.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2023 劉強東 https://github.com/liangjingkanji 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | package com.drake.net.cache 26 | 27 | enum class CacheMode { 28 | 29 | /** 只读取缓存, 本操作并不会请求网络故不存在写入缓存 */ 30 | READ, 31 | 32 | /** 只请求网络, 强制写入缓存 */ 33 | WRITE, 34 | 35 | /** 先从缓存读取,如果失败再从网络读取, 强制写入缓存 */ 36 | READ_THEN_REQUEST, 37 | 38 | /** 先从网络读取,如果失败再从缓存读取, 强制写入缓存 */ 39 | REQUEST_THEN_READ, 40 | } -------------------------------------------------------------------------------- /net/src/main/java/com/drake/net/exception/ConvertException.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2023 劉強東 https://github.com/liangjingkanji 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | package com.drake.net.exception 25 | 26 | import okhttp3.Response 27 | 28 | 29 | /** 30 | * 转换数据异常 31 | * @param response 响应信息 32 | * @param message 错误描述信息 33 | * @param cause 错误原因 34 | * @param tag 可携带任意对象, 一般用于在转换器/拦截器中添加然后传递给错误处理器去使用判断 35 | */ 36 | class ConvertException( 37 | response: Response, 38 | message: String? = null, 39 | cause: Throwable? = null, 40 | var tag: Any? = null 41 | ) : HttpResponseException(response, message, cause) -------------------------------------------------------------------------------- /net/src/main/java/com/drake/net/exception/DownloadFileException.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2023 劉強東 https://github.com/liangjingkanji 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | package com.drake.net.exception 26 | 27 | import okhttp3.Response 28 | 29 | /** 30 | * 下载文件异常 31 | * @param response 响应信息 32 | * @param message 错误描述信息 33 | * @param cause 错误原因 34 | * @param tag 可携带任意对象, 一般用于在转换器/拦截器中添加然后传递给错误处理器去使用判断 35 | */ 36 | class DownloadFileException( 37 | response: Response, 38 | message: String? = null, 39 | cause: Throwable? = null, 40 | var tag: Any? = null 41 | ) : HttpResponseException(response, message, cause) -------------------------------------------------------------------------------- /net/src/main/java/com/drake/net/exception/HttpFailureException.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2023 劉強東 https://github.com/liangjingkanji 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | package com.drake.net.exception 26 | 27 | import okhttp3.Request 28 | 29 | /** 30 | * 该类表示Http请求在服务器响应之前失败 31 | * @param request 请求信息 32 | * @param message 错误描述信息 33 | * @param cause 错误原因 34 | */ 35 | open class HttpFailureException( 36 | request: Request, 37 | message: String? = null, 38 | cause: Throwable? = null 39 | ) : NetException(request, message, cause) -------------------------------------------------------------------------------- /net/src/main/java/com/drake/net/exception/HttpResponseException.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2023 劉強東 https://github.com/liangjingkanji 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | package com.drake.net.exception 26 | 27 | import okhttp3.Response 28 | 29 | /** 30 | * 该类表示Http请求在服务器响应成功后失败 31 | * @param response 响应信息 32 | * @param message 错误描述信息 33 | * @param cause 错误原因 34 | * 35 | * @see ResponseException HttpStatusCode 200...299 36 | * @see RequestParamsException HttpStatusCode 400...499 37 | * @see ServerResponseException HttpStatusCode 500...599 38 | */ 39 | open class HttpResponseException( 40 | open val response: Response, 41 | message: String? = null, 42 | cause: Throwable? = null 43 | ) : NetException(response.request, message, cause) -------------------------------------------------------------------------------- /net/src/main/java/com/drake/net/exception/NetConnectException.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2023 劉強東 https://github.com/liangjingkanji 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | package com.drake.net.exception 26 | 27 | import okhttp3.Request 28 | 29 | /** 30 | * 连接错误 31 | * @param request 请求信息 32 | * @param message 错误描述信息 33 | * @param cause 错误原因 34 | */ 35 | class NetConnectException( 36 | request: Request, 37 | message: String? = null, 38 | cause: Throwable? = null 39 | ) : HttpFailureException(request, message, cause) -------------------------------------------------------------------------------- /net/src/main/java/com/drake/net/exception/NetSocketTimeoutException.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2023 劉強東 https://github.com/liangjingkanji 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | package com.drake.net.exception 26 | 27 | import okhttp3.Request 28 | 29 | /** 30 | * 请求过程中读取或者写入超时 31 | * @param request 请求信息 32 | * @param message 错误描述信息 33 | * @param cause 错误原因 34 | */ 35 | class NetSocketTimeoutException( 36 | request: Request, 37 | message: String? = null, 38 | cause: Throwable? = null 39 | ) : HttpFailureException(request, message, cause) -------------------------------------------------------------------------------- /net/src/main/java/com/drake/net/exception/NetUnknownHostException.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2023 劉強東 https://github.com/liangjingkanji 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | package com.drake.net.exception 26 | 27 | import okhttp3.Request 28 | 29 | /** 30 | * 主机域名无法访问 31 | * @param request 请求信息 32 | * @param message 错误描述信息 33 | * @param cause 错误原因 34 | */ 35 | class NetUnknownHostException( 36 | request: Request, 37 | message: String? = null, 38 | cause: Throwable? = null 39 | ) : HttpFailureException(request, message, cause) -------------------------------------------------------------------------------- /net/src/main/java/com/drake/net/exception/NetworkingException.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2023 劉強東 https://github.com/liangjingkanji 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | package com.drake.net.exception 26 | 27 | import okhttp3.Request 28 | 29 | /** 30 | * 无网络情况 31 | * @param request 请求信息 32 | * @param message 错误描述信息 33 | * @param cause 错误原因 34 | */ 35 | class NetworkingException( 36 | request: Request, 37 | message: String? = null, 38 | cause: Throwable? = null 39 | ) : HttpFailureException(request, message, cause) -------------------------------------------------------------------------------- /net/src/main/java/com/drake/net/exception/NoCacheException.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2023 劉強東 https://github.com/liangjingkanji 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | package com.drake.net.exception 26 | 27 | import com.drake.net.cache.ForceCache 28 | import okhttp3.Request 29 | 30 | /** 31 | * 读取缓存失败 32 | * 仅当设置强制缓存模式[com.drake.net.cache.CacheMode.READ]和[com.drake.net.cache.CacheMode.REQUEST_THEN_READ]才会发生此异常 33 | * @param request 请求信息 34 | * @param message 错误描述信息 35 | * @param cause 错误原因 36 | */ 37 | class NoCacheException( 38 | request: Request, 39 | message: String? = null, 40 | cause: Throwable? = null 41 | ) : NetException(request, message, cause) { 42 | 43 | override fun getLocalizedMessage(): String { 44 | return "cacheKey = " + ForceCache.key(request) + " " + super.getLocalizedMessage() 45 | } 46 | } -------------------------------------------------------------------------------- /net/src/main/java/com/drake/net/exception/RequestParamsException.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2023 劉強東 https://github.com/liangjingkanji 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | package com.drake.net.exception 26 | 27 | import okhttp3.Response 28 | 29 | /** 30 | * 400 - 499 客户端请求异常 31 | * @param response 响应信息 32 | * @param message 错误描述信息 33 | * @param cause 错误原因 34 | * @param tag 可携带任意对象, 一般用于在转换器/拦截器中添加然后传递给错误处理器去使用判断 35 | */ 36 | class RequestParamsException( 37 | response: Response, 38 | message: String? = null, 39 | cause: Throwable? = null, 40 | var tag: Any? = null 41 | ) : HttpResponseException(response, message, cause) -------------------------------------------------------------------------------- /net/src/main/java/com/drake/net/exception/ResponseException.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2023 劉強東 https://github.com/liangjingkanji 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | @file:Suppress("MemberVisibilityCanBePrivate") 26 | 27 | package com.drake.net.exception 28 | 29 | import okhttp3.Response 30 | 31 | /** 32 | * 状态码在200..299, 但是返回数据不符合业务要求可以抛出该异常 33 | * @param response 响应信息 34 | * @param message 错误描述信息 35 | * @param cause 错误原因 36 | * @param tag 可携带任意对象, 一般用于在转换器/拦截器中添加然后传递给错误处理器去使用判断 37 | */ 38 | class ResponseException( 39 | response: Response, 40 | message: String? = null, 41 | cause: Throwable? = null, 42 | var tag: Any? = null 43 | ) : HttpResponseException(response, message, cause) -------------------------------------------------------------------------------- /net/src/main/java/com/drake/net/exception/ServerResponseException.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2023 劉強東 https://github.com/liangjingkanji 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | package com.drake.net.exception 26 | 27 | import okhttp3.Response 28 | 29 | 30 | /** 31 | * >= 500 服务器异常 32 | * @param response 响应信息 33 | * @param message 错误描述信息 34 | * @param cause 错误原因 35 | * @param tag 可携带任意对象, 一般用于在转换器/拦截器中添加然后传递给错误处理器去使用判断 36 | */ 37 | class ServerResponseException( 38 | response: Response, 39 | message: String? = null, 40 | cause: Throwable? = null, 41 | var tag: Any? = null 42 | ) : HttpResponseException(response, message, cause) -------------------------------------------------------------------------------- /net/src/main/java/com/drake/net/exception/URLParseException.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2023 劉強東 https://github.com/liangjingkanji 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | package com.drake.net.exception 26 | 27 | /** 28 | * URL地址错误 29 | * 30 | * @param message 错误描述信息 31 | * @param cause 错误原因 32 | */ 33 | open class URLParseException( 34 | message: String? = null, 35 | cause: Throwable? = null, 36 | ) : Exception(message, cause) { 37 | 38 | var occurred: String = "" 39 | 40 | override fun getLocalizedMessage(): String? { 41 | return super.getLocalizedMessage() + occurred 42 | } 43 | } -------------------------------------------------------------------------------- /net/src/main/java/com/drake/net/interceptor/RequestInterceptor.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2023 劉強東 https://github.com/liangjingkanji 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | package com.drake.net.interceptor 26 | 27 | import com.drake.net.request.BaseRequest 28 | 29 | interface RequestInterceptor { 30 | 31 | /** 32 | * 当你使用Net发起请求的时候就会触发该拦截器 33 | * 该拦截器属于轻量级不具备重发的功能, 一般用于请求参数的修改 34 | * 请勿在这里进行请求重发可能会导致死循环 35 | */ 36 | fun interceptor(request: BaseRequest) 37 | } -------------------------------------------------------------------------------- /net/src/main/java/com/drake/net/interfaces/ProgressListener.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2023 劉強東 https://github.com/liangjingkanji 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | package com.drake.net.interfaces 25 | 26 | import com.drake.net.component.Progress 27 | 28 | /** 29 | * 进度监听器, 可能为下载或上传 30 | * 31 | * @param interval 进度监听器刷新的间隔时间, 单位为毫秒, 默认值为500ms 32 | */ 33 | abstract class ProgressListener(var interval: Long = 500) { 34 | // 上次进度变化的的开机时间 35 | var elapsedTime = 0L 36 | 37 | // 距离上次进度变化的新增字节数 38 | var intervalByteCount = 0L 39 | 40 | /** 41 | * 监听上传/下载进度回调函数 42 | * @param p 上传或者下载进度 43 | */ 44 | abstract fun onProgress(p: Progress) 45 | } -------------------------------------------------------------------------------- /net/src/main/java/com/drake/net/internal/NetInitializer.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2023 劉強東 https://github.com/liangjingkanji 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | package com.drake.net.internal 26 | 27 | import android.content.Context 28 | import androidx.startup.Initializer 29 | import com.drake.net.NetConfig 30 | 31 | /** 32 | * 使用AppStartup默认初始化[NetConfig.app], 仅应用主进程下有效 33 | * 在其他进程使用Net请手动在Application中初始化[NetConfig.initialize] 34 | */ 35 | internal class NetInitializer : Initializer { 36 | override fun create(context: Context) { 37 | NetConfig.app = context 38 | } 39 | 40 | override fun dependencies(): MutableList>> { 41 | return mutableListOf() 42 | } 43 | } -------------------------------------------------------------------------------- /net/src/main/java/com/drake/net/log/MessageType.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2023 劉強東 https://github.com/liangjingkanji 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | package com.drake.net.log 26 | 27 | enum class MessageType(var type: String) { 28 | REQUEST_URL("RQU"), 29 | REQUEST_TIME("RQT"), 30 | REQUEST_METHOD("RQM"), 31 | REQUEST_HEADER("RQH"), 32 | REQUEST_BODY("RQB"), 33 | REQUEST_END("RQD"), 34 | RESPONSE_TIME("RST"), 35 | RESPONSE_STATUS("RSS"), 36 | RESPONSE_HEADER("RSH"), 37 | RESPONSE_BODY("RSB"), 38 | RESPONSE_END("RSD"), 39 | RESPONSE_ERROR("REE"), 40 | UNKNOWN("UNKNOWN"); 41 | } -------------------------------------------------------------------------------- /net/src/main/java/com/drake/net/okhttp/OkHttpExtension.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2023 劉強東 https://github.com/liangjingkanji 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | package com.drake.net.okhttp 26 | 27 | import com.drake.net.interceptor.NetOkHttpInterceptor 28 | import okhttp3.OkHttpClient 29 | 30 | /** 31 | * Net要求经过该函数处理创建特殊的OkHttpClient 32 | */ 33 | fun OkHttpClient.toNetOkhttp() = run { 34 | if (!interceptors.contains(NetOkHttpInterceptor)) { 35 | newBuilder().addInterceptor(NetOkHttpInterceptor).build() 36 | } else { 37 | this 38 | } 39 | } -------------------------------------------------------------------------------- /net/src/main/java/com/drake/net/reflect/TypeUtils.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2023 劉強東 https://github.com/liangjingkanji 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | package com.drake.net.reflect 26 | 27 | import kotlin.reflect.javaType 28 | import kotlin.reflect.typeOf 29 | 30 | @OptIn(ExperimentalStdlibApi::class) 31 | inline fun typeTokenOf() = typeOf().javaType -------------------------------------------------------------------------------- /net/src/main/java/com/drake/net/request/Method.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2023 劉強東 https://github.com/liangjingkanji 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | package com.drake.net.request 26 | 27 | enum class Method { 28 | GET, HEAD, OPTIONS, TRACE, // Url request 29 | POST, DELETE, PUT, PATCH // Body request 30 | } -------------------------------------------------------------------------------- /net/src/main/java/com/drake/net/time/IntervalStatus.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2023 劉強東 https://github.com/liangjingkanji 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | package com.drake.net.time 26 | 27 | /** 28 | * 计时器的状态 29 | */ 30 | enum class IntervalStatus { 31 | STATE_ACTIVE, STATE_IDLE, STATE_PAUSE 32 | } -------------------------------------------------------------------------------- /net/src/main/java/com/drake/net/transform/DeferredTransform.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2023 劉強東 https://github.com/liangjingkanji 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | package com.drake.net.transform 26 | 27 | import kotlinx.coroutines.Deferred 28 | 29 | /** 30 | * 可以将[Deferred]返回结果进行转换 31 | * [block]在[Deferred]执行成功返回结果时执行 32 | */ 33 | fun Deferred.transform(block: (T) -> R): DeferredTransform { 34 | return DeferredTransform(this, block) 35 | } 36 | 37 | data class DeferredTransform(val deferred: Deferred, val block: (T) -> R) -------------------------------------------------------------------------------- /net/src/main/java/com/drake/net/utils/TipUtils.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2023 劉強東 https://github.com/liangjingkanji 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | package com.drake.net.utils 26 | 27 | import android.annotation.SuppressLint 28 | import android.widget.Toast 29 | import com.drake.net.NetConfig 30 | 31 | object TipUtils { 32 | 33 | private var toast: Toast? = null 34 | 35 | /** 36 | * 重复显示不会覆盖, 可以在子线程显示 37 | * 本方法会导致报内存泄露, 这是因为为了避免吐司反复显示导致重叠会长期持有Toast引用来保持单例导致, 可以无视或者自己实现吐司 38 | */ 39 | @SuppressLint("ShowToast") 40 | @JvmStatic 41 | fun toast(message: String?) { 42 | message ?: return 43 | runMain { 44 | toast?.cancel() 45 | toast = Toast.makeText(NetConfig.app, message, Toast.LENGTH_SHORT) 46 | toast?.show() 47 | } 48 | } 49 | } -------------------------------------------------------------------------------- /net/src/main/res/drawable/ic_limited_time.xml: -------------------------------------------------------------------------------- 1 | 24 | 25 | 30 | 33 | 34 | -------------------------------------------------------------------------------- /net/src/main/res/drawable/ic_timing.xml: -------------------------------------------------------------------------------- 1 | 24 | 25 | 30 | 33 | 36 | 37 | -------------------------------------------------------------------------------- /net/src/main/res/xml/network_security_config.xml: -------------------------------------------------------------------------------- 1 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /sample/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | /release 3 | -------------------------------------------------------------------------------- /sample/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /sample/src/androidTest/java/com/drake/net/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.drake.net 2 | 3 | import androidx.test.ext.junit.runners.AndroidJUnit4 4 | import androidx.test.platform.app.InstrumentationRegistry 5 | import org.junit.Assert.assertEquals 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | /** 10 | * Instrumented test, which will execute on an Android device. 11 | * 12 | * See [testing documentation](http://d.android.com/tools/testing). 13 | */ 14 | @RunWith(AndroidJUnit4::class) 15 | class ExampleInstrumentedTest { 16 | @Test 17 | fun useAppContext() { 18 | // Context of the app under test. 19 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 20 | assertEquals("com.drake.net", appContext.packageName) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /sample/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 22 | 23 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /sample/src/main/java/com/drake/net/sample/constants/Api.kt: -------------------------------------------------------------------------------- 1 | package com.drake.net.sample.constants 2 | 3 | 4 | /* 5 | 建议请求路径都写在一个单例类中, 方便查找和替换 6 | */ 7 | object Api { 8 | const val HOST = "http://127.0.0.1:8091" 9 | 10 | const val TEXT = "/text" 11 | const val DELAY = "/delay" 12 | const val UPLOAD = "/upload" 13 | const val GAME = "/game" 14 | const val DATA = "/data" 15 | const val ARRAY = "/array" 16 | const val CONFIG = "/config" 17 | const val USER_INFO = "/userInfo" 18 | const val TIME = "/time" 19 | const val Token = "/token" 20 | } -------------------------------------------------------------------------------- /sample/src/main/java/com/drake/net/sample/constants/UserConfig.kt: -------------------------------------------------------------------------------- 1 | package com.drake.net.sample.constants 2 | 3 | import com.drake.serialize.serialize.annotation.SerializeConfig 4 | import com.drake.serialize.serialize.serialLazy 5 | 6 | 7 | /** 8 | * 本单例类使用 https://github.com/liangjingkanji/Serialize 为字段提供持久化存储 9 | */ 10 | @SerializeConfig(mmapID = "user_config") 11 | object UserConfig { 12 | 13 | var token by serialLazy(default = "6cad0ff06f5a214b9cfdf2a4a7c432339") 14 | 15 | var isLogin by serialLazy(default = false) 16 | } -------------------------------------------------------------------------------- /sample/src/main/java/com/drake/net/sample/contract/AlbumSelectContract.kt: -------------------------------------------------------------------------------- 1 | package com.drake.net.sample.contract 2 | 3 | import android.content.Context 4 | import android.content.Intent 5 | import android.net.Uri 6 | import android.provider.MediaStore 7 | import androidx.activity.result.contract.ActivityResultContract 8 | 9 | class AlbumSelectContract : ActivityResultContract() { 10 | 11 | override fun createIntent(context: Context, input: Unit?): Intent { 12 | val intent = Intent(Intent.ACTION_PICK) 13 | intent.setDataAndType(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, "image/*") 14 | return intent 15 | } 16 | 17 | class AlbumSelectResult(val code: Int, val uri: Uri?) 18 | 19 | override fun parseResult(resultCode: Int, intent: Intent?): AlbumSelectResult { 20 | return AlbumSelectResult(resultCode, intent?.data) 21 | } 22 | } -------------------------------------------------------------------------------- /sample/src/main/java/com/drake/net/sample/converter/FastJsonConverter.kt: -------------------------------------------------------------------------------- 1 | package com.drake.net.sample.converter 2 | 3 | import com.alibaba.fastjson.JSON 4 | import com.drake.net.convert.JSONConvert 5 | import org.json.JSONObject 6 | import java.lang.reflect.Type 7 | 8 | class FastJsonConverter : JSONConvert(code = "errorCode", message = "errorMsg", success = "0") { 9 | 10 | override fun String.parseBody(succeed: Type): R? { 11 | val string = try { 12 | JSONObject(this).getString("data") 13 | } catch (e: Exception) { 14 | this 15 | } 16 | return JSON.parseObject(string, succeed) 17 | } 18 | } -------------------------------------------------------------------------------- /sample/src/main/java/com/drake/net/sample/converter/GsonConverter.kt: -------------------------------------------------------------------------------- 1 | package com.drake.net.sample.converter 2 | 3 | import com.drake.net.convert.JSONConvert 4 | import com.google.gson.GsonBuilder 5 | import org.json.JSONObject 6 | import java.lang.reflect.Type 7 | 8 | class GsonConverter : JSONConvert(code = "errorCode", message = "errorMsg") { 9 | companion object { 10 | private val gson = GsonBuilder().serializeNulls().create() 11 | } 12 | 13 | override fun String.parseBody(succeed: Type): R? { 14 | val string = try { 15 | JSONObject(this).getString("data") 16 | } catch (e: Exception) { 17 | this 18 | } 19 | return gson.fromJson(string, succeed) 20 | } 21 | } -------------------------------------------------------------------------------- /sample/src/main/java/com/drake/net/sample/converter/MoshiConverter.kt: -------------------------------------------------------------------------------- 1 | package com.drake.net.sample.converter 2 | 3 | import com.drake.net.convert.JSONConvert 4 | import com.squareup.moshi.Moshi 5 | import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory 6 | import org.json.JSONObject 7 | import java.lang.reflect.Type 8 | 9 | class MoshiConverter : JSONConvert(code = "errorCode", message = "errorMsg", success = "0") { 10 | 11 | companion object { 12 | private val moshi = Moshi.Builder().add(KotlinJsonAdapterFactory()).build() 13 | } 14 | 15 | override fun String.parseBody(succeed: Type): R? { 16 | val string = try { 17 | JSONObject(this).getString("data") 18 | } catch (e: Exception) { 19 | this 20 | } 21 | return moshi.adapter(succeed).fromJson(string) 22 | } 23 | } -------------------------------------------------------------------------------- /sample/src/main/java/com/drake/net/sample/converter/ProtobufConverter.kt: -------------------------------------------------------------------------------- 1 | @file:Suppress("UNCHECKED_CAST", "MemberVisibilityCanBePrivate") 2 | 3 | package com.drake.net.sample.converter 4 | 5 | import com.drake.net.convert.NetConverter 6 | import com.drake.net.exception.ConvertException 7 | import com.drake.net.exception.RequestParamsException 8 | import com.drake.net.exception.ServerResponseException 9 | import com.drake.net.request.kType 10 | import kotlinx.serialization.protobuf.ProtoBuf 11 | import kotlinx.serialization.serializer 12 | import okhttp3.Response 13 | import java.lang.reflect.Type 14 | 15 | class ProtobufConverter : NetConverter { 16 | 17 | override fun onConvert(succeed: Type, response: Response): R? { 18 | try { 19 | return NetConverter.onConvert(succeed, response) 20 | } catch (e: ConvertException) { 21 | val code = response.code 22 | when { 23 | code in 200..299 -> { // 请求成功 24 | val bytes = response.body?.bytes() ?: return null 25 | val kType = response.request.kType ?: throw ConvertException(response, "Request does not contain KType") 26 | return ProtoBuf.decodeFromByteArray(ProtoBuf.serializersModule.serializer(kType), bytes) as R 27 | } 28 | code in 400..499 -> throw RequestParamsException(response, code.toString()) // 请求参数错误 29 | code >= 500 -> throw ServerResponseException(response, code.toString()) // 服务器异常错误 30 | else -> throw ConvertException(response, message = "Http status code not within range") 31 | } 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /sample/src/main/java/com/drake/net/sample/interceptor/EncryptDataInterceptor.kt: -------------------------------------------------------------------------------- 1 | package com.drake.net.sample.interceptor 2 | 3 | import com.drake.net.request.MediaConst 4 | import com.drake.net.sample.utils.AESUtils 5 | import okhttp3.Interceptor 6 | import okhttp3.RequestBody.Companion.toRequestBody 7 | import okhttp3.Response 8 | import okhttp3.ResponseBody.Companion.toResponseBody 9 | import okio.Buffer 10 | 11 | /** 12 | * 演示如何加密请求参数/解密响应数据 13 | */ 14 | class EncryptDataInterceptor : Interceptor { 15 | override fun intercept(chain: Interceptor.Chain): Response { 16 | var request = chain.request() 17 | 18 | // 加密, 仅加密 POST请求/JSON参数类型 19 | if (request.method == "POST" && request.header("Content-Type") == MediaConst.JSON.toString()) { 20 | val body = request.body 21 | if (body != null) { 22 | val buff = Buffer() 23 | body.writeTo(buff) 24 | val json = buff.readUtf8() 25 | if (json.isNotBlank()) { 26 | val encryptJson = AESUtils.encrypt(json) 27 | val newBody = encryptJson.toRequestBody(MediaConst.JSON) 28 | request = request.newBuilder().post(newBody).build() 29 | } 30 | } 31 | } 32 | 33 | var response = chain.proceed(request) 34 | 35 | // 解密, 仅解密JSON响应类型 36 | if (response.header("Content-Type") == MediaConst.JSON.toString()) { 37 | val body = response.body 38 | if (body != null) { 39 | val json = body.string() 40 | if (json.isNotBlank()) { 41 | val decryptJson = AESUtils.decrypt(json) 42 | val requestBody = decryptJson.toResponseBody(MediaConst.JSON) 43 | response = response.newBuilder().body(requestBody).build() 44 | } 45 | } 46 | } 47 | 48 | return response 49 | } 50 | } -------------------------------------------------------------------------------- /sample/src/main/java/com/drake/net/sample/interceptor/GlobalHeaderInterceptor.kt: -------------------------------------------------------------------------------- 1 | package com.drake.net.sample.interceptor 2 | 3 | import com.drake.net.interceptor.RequestInterceptor 4 | import com.drake.net.request.BaseRequest 5 | import com.drake.net.sample.constants.UserConfig 6 | 7 | 8 | /** 演示添加全局请求头/参数 */ 9 | class GlobalHeaderInterceptor : RequestInterceptor { 10 | 11 | /** 本方法每次请求发起都会调用, 这里添加的参数可以是动态参数 */ 12 | override fun interceptor(request: BaseRequest) { 13 | request.setHeader("client", "Android") 14 | request.setHeader("token", UserConfig.token) 15 | } 16 | } -------------------------------------------------------------------------------- /sample/src/main/java/com/drake/net/sample/interceptor/RefreshTokenInterceptor.kt: -------------------------------------------------------------------------------- 1 | package com.drake.net.sample.interceptor 2 | 3 | import com.drake.net.Net 4 | import com.drake.net.exception.ResponseException 5 | import com.drake.net.sample.constants.Api 6 | import com.drake.net.sample.constants.UserConfig 7 | import com.drake.net.sample.model.TokenModel 8 | import okhttp3.Interceptor 9 | import okhttp3.Response 10 | 11 | 12 | /** 13 | * 客户端token自动续期示例 14 | */ 15 | class RefreshTokenInterceptor : Interceptor { 16 | override fun intercept(chain: Interceptor.Chain): Response { 17 | val request = chain.request() 18 | val response = chain.proceed(request) 19 | return synchronized(RefreshTokenInterceptor::class.java) { 20 | if (response.code == 401 && UserConfig.isLogin && !request.url.encodedPath.contains(Api.Token)) { 21 | val tokenInfo = Net.get(Api.Token).execute() // 同步请求token 22 | if (tokenInfo.isExpired) { 23 | // token过期抛出异常, 由全局错误处理器处理, 在其中可以跳转到登陆界面提示用户重新登陆 24 | throw ResponseException(response, "登录状态失效") 25 | } else { 26 | UserConfig.token = tokenInfo.token 27 | } 28 | chain.proceed(request) 29 | } else { 30 | response 31 | } 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /sample/src/main/java/com/drake/net/sample/model/ArrayData.kt: -------------------------------------------------------------------------------- 1 | package com.drake.net.sample.model 2 | 3 | class ArrayData { 4 | var title: String = "" 5 | var name: String = "" 6 | } -------------------------------------------------------------------------------- /sample/src/main/java/com/drake/net/sample/model/BasicData.kt: -------------------------------------------------------------------------------- 1 | package com.drake.net.sample.model 2 | 3 | open class BasicData { 4 | val code: String = "" 5 | val msg: String = "" 6 | val data: T? = null 7 | } 8 | -------------------------------------------------------------------------------- /sample/src/main/java/com/drake/net/sample/model/ConfigModel.kt: -------------------------------------------------------------------------------- 1 | package com.drake.net.sample.model 2 | 3 | 4 | import kotlinx.serialization.Serializable 5 | 6 | @Serializable 7 | data class ConfigModel( 8 | var maintain: Boolean = false 9 | ) -------------------------------------------------------------------------------- /sample/src/main/java/com/drake/net/sample/model/GameModel.kt: -------------------------------------------------------------------------------- 1 | package com.drake.net.sample.model 2 | 3 | @kotlinx.serialization.Serializable 4 | data class GameModel( 5 | var total: Int = 0, 6 | var list: List = listOf() 7 | ) { 8 | 9 | @kotlinx.serialization.Serializable 10 | data class Data( 11 | var id: Int = 0, 12 | var img: String = "", 13 | var name: String = "", 14 | var label: List = listOf(), 15 | var price: String = "", 16 | var initialPrice: String = "", 17 | var grade: Int = 0, 18 | var discount: Double = 0.0, 19 | var endTime: Int = 0 20 | ) 21 | } -------------------------------------------------------------------------------- /sample/src/main/java/com/drake/net/sample/model/SubData.kt: -------------------------------------------------------------------------------- 1 | package com.drake.net.sample.model 2 | 3 | class SubData : BasicData() { 4 | var title: String = "" 5 | var name: String = "" 6 | } -------------------------------------------------------------------------------- /sample/src/main/java/com/drake/net/sample/model/TokenModel.kt: -------------------------------------------------------------------------------- 1 | package com.drake.net.sample.model 2 | 3 | import kotlinx.serialization.Serializable 4 | 5 | @Serializable 6 | data class TokenModel( 7 | var token: String = "", 8 | var isExpired: Boolean = false 9 | ) -------------------------------------------------------------------------------- /sample/src/main/java/com/drake/net/sample/model/UserInfoModel.kt: -------------------------------------------------------------------------------- 1 | package com.drake.net.sample.model 2 | 3 | 4 | import kotlinx.serialization.Serializable 5 | 6 | @Serializable 7 | data class UserInfoModel( 8 | var userId: Int = 0, 9 | var username: String = "", 10 | var age: Int = 0, 11 | var balance: String = "" 12 | ) -------------------------------------------------------------------------------- /sample/src/main/java/com/drake/net/sample/ui/activity/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.drake.net.sample.ui.activity 2 | 3 | import androidx.core.view.GravityCompat 4 | import androidx.navigation.findNavController 5 | import androidx.navigation.fragment.FragmentNavigator 6 | import androidx.navigation.ui.AppBarConfiguration 7 | import androidx.navigation.ui.setupWithNavController 8 | import com.bumptech.glide.Glide 9 | import com.drake.engine.base.EngineActivity 10 | import com.drake.net.sample.R 11 | import com.drake.net.sample.databinding.ActivityMainBinding 12 | import com.drake.statusbar.immersive 13 | 14 | /** 15 | * 以下代码设置导航, 和框架本身无关无需关心, 请查看[com.drake.net.sample.ui.fragment]内的Fragment 16 | */ 17 | class MainActivity : EngineActivity(R.layout.activity_main) { 18 | 19 | override fun initView() { 20 | immersive(binding.toolbar, true) 21 | setSupportActionBar(binding.toolbar) 22 | val navController = findNavController(R.id.nav) 23 | 24 | Glide.with(this) 25 | .load("https://avatars.githubusercontent.com/u/21078112?v=4") 26 | .circleCrop() 27 | .into(binding.drawerNav.getHeaderView(0).findViewById(R.id.iv)) 28 | 29 | binding.toolbar.setupWithNavController( 30 | navController, 31 | AppBarConfiguration(binding.drawerNav.menu, binding.drawer) 32 | ) 33 | navController.addOnDestinationChangedListener { _, destination, _ -> 34 | binding.toolbar.subtitle = 35 | (destination as FragmentNavigator.Destination).className.substringAfterLast('.') 36 | } 37 | binding.drawerNav.setupWithNavController(navController) 38 | } 39 | 40 | override fun initData() { 41 | } 42 | 43 | override fun onBackPressed() { 44 | if (binding.drawer.isDrawerOpen(GravityCompat.START)) binding.drawer.closeDrawers() else super.onBackPressed() 45 | } 46 | } 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /sample/src/main/java/com/drake/net/sample/ui/fragment/AsyncTaskFragment.kt: -------------------------------------------------------------------------------- 1 | package com.drake.net.sample.ui.fragment 2 | 3 | import com.drake.engine.base.EngineFragment 4 | import com.drake.net.sample.R 5 | import com.drake.net.sample.databinding.FragmentAsyncTaskBinding 6 | import com.drake.net.utils.scope 7 | import kotlinx.coroutines.* 8 | 9 | class AsyncTaskFragment : EngineFragment(R.layout.fragment_async_task) { 10 | 11 | override fun initView() { 12 | scope { 13 | binding.tvFragment.text = withContext(Dispatchers.IO) { 14 | delay(2000) 15 | "结果" 16 | } 17 | } 18 | } 19 | 20 | override fun initData() { 21 | } 22 | 23 | /** 24 | * 抽出异步任务为一个函数 25 | */ 26 | private suspend fun withDownloadFile() = withContext(Dispatchers.IO) { 27 | delay(200) 28 | "结果" 29 | } 30 | 31 | private fun CoroutineScope.asyncDownloadFile() = async { 32 | "结果" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /sample/src/main/java/com/drake/net/sample/ui/fragment/AutoDialogFragment.kt: -------------------------------------------------------------------------------- 1 | package com.drake.net.sample.ui.fragment 2 | 3 | import com.drake.engine.base.EngineFragment 4 | import com.drake.net.Post 5 | import com.drake.net.sample.R 6 | import com.drake.net.sample.constants.Api 7 | import com.drake.net.sample.databinding.FragmentAutoDialogBinding 8 | import com.drake.net.utils.scopeDialog 9 | import com.drake.tooltip.toast 10 | import kotlinx.coroutines.CancellationException 11 | 12 | 13 | class AutoDialogFragment : 14 | EngineFragment(R.layout.fragment_auto_dialog) { 15 | 16 | override fun initView() { 17 | scopeDialog { 18 | binding.tvFragment.text = Post(Api.DELAY) { 19 | param("username", "你的账号") 20 | param("password", "123456") 21 | }.await() 22 | }.finally { 23 | // 关闭对话框后执行的异常 24 | if (it is CancellationException) { 25 | toast("对话框被关闭, 网络请求自动取消") // 这里存在Handler吐司崩溃, 如果不想处理就直接使用我的吐司库 https://github.com/liangjingkanji/Tooltip 26 | } 27 | } 28 | } 29 | 30 | override fun initData() { 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /sample/src/main/java/com/drake/net/sample/ui/fragment/CallbackRequestFragment.kt: -------------------------------------------------------------------------------- 1 | package com.drake.net.sample.ui.fragment 2 | 3 | import com.drake.engine.base.EngineFragment 4 | import com.drake.net.Net 5 | import com.drake.net.sample.R 6 | import com.drake.net.sample.constants.Api 7 | import com.drake.net.sample.databinding.FragmentCallbackRequestBinding 8 | import com.drake.net.utils.runMain 9 | import okhttp3.Call 10 | import okhttp3.Callback 11 | import okhttp3.Response 12 | import java.io.IOException 13 | 14 | class CallbackRequestFragment : 15 | EngineFragment(R.layout.fragment_callback_request) { 16 | 17 | override fun initData() { 18 | } 19 | 20 | override fun initView() { 21 | // Net同样支持OkHttp原始的队列任务 22 | Net.post(Api.TEXT).enqueue(object : Callback { 23 | override fun onFailure(call: Call, e: IOException) { 24 | } 25 | 26 | override fun onResponse(call: Call, response: Response) { 27 | // 此处为子线程 28 | val body = response.body?.string() ?: "无数据" 29 | runMain { 30 | // 此处为主线程 31 | binding.tvFragment.text = body 32 | } 33 | } 34 | }) 35 | } 36 | } -------------------------------------------------------------------------------- /sample/src/main/java/com/drake/net/sample/ui/fragment/CoroutineScopeFragment.kt: -------------------------------------------------------------------------------- 1 | package com.drake.net.sample.ui.fragment 2 | 3 | import androidx.lifecycle.Lifecycle 4 | import com.drake.engine.base.EngineFragment 5 | import com.drake.net.sample.R 6 | import com.drake.net.sample.databinding.FragmentCoroutineScopeBinding 7 | import com.drake.net.utils.scope 8 | import com.drake.net.utils.scopeLife 9 | import com.drake.net.utils.scopeNet 10 | import com.drake.net.utils.scopeNetLife 11 | import kotlinx.coroutines.delay 12 | 13 | 14 | class CoroutineScopeFragment : 15 | EngineFragment(R.layout.fragment_coroutine_scope) { 16 | override fun initData() { 17 | // 其作用域在应用进程销毁时才会被动取消 18 | scope { 19 | 20 | } 21 | 22 | // 其作用域在Activity或者Fragment销毁(onDestroy)时被动取消 [scopeNetLife] 23 | scopeLife { 24 | delay(2000) 25 | binding.tvFragment.text = "任务结束" 26 | } 27 | 28 | // 自定义取消跟随的生命周期, 失去焦点时立即取消作用域 29 | scopeLife(Lifecycle.Event.ON_PAUSE) { 30 | 31 | } 32 | 33 | // 此作用域会捕捉发生的异常, 如果是网络异常会进入网络异常的全局处理函数, 例如自动弹出吐司 [NetConfig.onError] 34 | scopeNet { 35 | 36 | } 37 | 38 | // 自动网络处理 + 生命周期管理 39 | scopeNetLife { 40 | 41 | } 42 | } 43 | 44 | override fun initView() { 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /sample/src/main/java/com/drake/net/sample/ui/fragment/ErrorHandlerFragment.kt: -------------------------------------------------------------------------------- 1 | package com.drake.net.sample.ui.fragment 2 | 3 | import com.drake.engine.base.EngineFragment 4 | import com.drake.net.Get 5 | import com.drake.net.sample.R 6 | import com.drake.net.sample.databinding.FragmentErrorHandlerBinding 7 | import com.drake.net.utils.scopeNetLife 8 | 9 | 10 | class ErrorHandlerFragment : 11 | EngineFragment(R.layout.fragment_error_handler) { 12 | 13 | override fun initView() { 14 | scopeNetLife { 15 | // 该请求是错误的路径会在控制台打印出错误信息 16 | Get("error").await() 17 | }.catch { 18 | // 重写该函数后, 错误不会流到[NetConfig.onError]中的全局错误处理, 在App.kt中可以自定义该全局处理, 同时包含onStateError 19 | binding.tvFragment.text = it.message 20 | } 21 | } 22 | 23 | override fun initData() { 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /sample/src/main/java/com/drake/net/sample/ui/fragment/ExceptionTraceFragment.kt: -------------------------------------------------------------------------------- 1 | package com.drake.net.sample.ui.fragment 2 | 3 | import com.drake.engine.base.EngineFragment 4 | import com.drake.net.Get 5 | import com.drake.net.sample.R 6 | import com.drake.net.sample.databinding.FragmentExceptionTraceBinding 7 | import com.drake.net.utils.scopeNetLife 8 | 9 | 10 | class ExceptionTraceFragment : 11 | EngineFragment(R.layout.fragment_exception_trace) { 12 | 13 | override fun initView() { 14 | scopeNetLife { 15 | // 这是一个错误的地址, 请查看LogCat的错误信息, 在[initNet]函数中的[onError]回调中你也可以进行自定义错误信息打印 16 | binding.tvFragment.text = 17 | Get("https://githuberror.com/liangjingkanji/Net/").await() 18 | } 19 | } 20 | 21 | override fun initData() { 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /sample/src/main/java/com/drake/net/sample/ui/fragment/HttpsCertificateFragment.kt: -------------------------------------------------------------------------------- 1 | package com.drake.net.sample.ui.fragment 2 | 3 | import android.view.View 4 | import com.drake.engine.base.EngineFragment 5 | import com.drake.net.Get 6 | import com.drake.net.okhttp.setSSLCertificate 7 | import com.drake.net.okhttp.trustSSLCertificate 8 | import com.drake.net.sample.R 9 | import com.drake.net.sample.databinding.FragmentHttpsCertificateBinding 10 | import com.drake.net.utils.scopeNetLife 11 | import com.drake.tooltip.toast 12 | import okhttp3.OkHttpClient 13 | 14 | class HttpsCertificateFragment : 15 | EngineFragment(R.layout.fragment_https_certificate) { 16 | 17 | override fun initView() { 18 | binding.btnTrustCertificate.setOnClickListener(this::trustAllCertificate) 19 | binding.btnImportCertificate.setOnClickListener(this::importCertificate) 20 | } 21 | 22 | override fun initData() { 23 | } 24 | 25 | /** 26 | * 信任全部证书 27 | * 大部分情况下还是建议在Application中配置一次全局的证书 28 | */ 29 | private fun trustAllCertificate(view: View) { 30 | scopeNetLife { 31 | binding.tvResponse.text = Get("https://github.com/liangjingkanji/Net/") { 32 | // 构建新的客户端 33 | okHttpClient = OkHttpClient.Builder().trustSSLCertificate().build() 34 | }.await() 35 | } 36 | } 37 | 38 | /** 39 | * 导入私有证书 40 | */ 41 | private fun importCertificate(view: View) { 42 | scopeNetLife { 43 | Get("https://github.com/liangjingkanji/Net/") { 44 | // 使用现在客户端 45 | setClient { 46 | val privateCertificate = resources.assets.open("https.certificate") 47 | setSSLCertificate(privateCertificate) 48 | } 49 | }.await() 50 | }.catch { 51 | toast("作者没有证书, 只是演示代码, O(∩_∩)O哈哈~") 52 | } 53 | } 54 | 55 | } -------------------------------------------------------------------------------- /sample/src/main/java/com/drake/net/sample/ui/fragment/InterceptorFragment.kt: -------------------------------------------------------------------------------- 1 | package com.drake.net.sample.ui.fragment 2 | 3 | import com.drake.engine.base.EngineFragment 4 | import com.drake.net.Get 5 | import com.drake.net.sample.R 6 | import com.drake.net.sample.constants.Api 7 | import com.drake.net.sample.databinding.FragmentInterceptorBinding 8 | import com.drake.net.utils.scopeNetLife 9 | 10 | 11 | class InterceptorFragment : 12 | EngineFragment(R.layout.fragment_interceptor) { 13 | 14 | override fun initView() { 15 | scopeNetLife { 16 | binding.tvFragment.text = Get(Api.TEXT) { 17 | // 拦截器只支持全局, 无法单例, 请查看[com.drake.net.sample.interceptor.NetInterceptor] 18 | }.await() 19 | } 20 | } 21 | 22 | override fun initData() { 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /sample/src/main/java/com/drake/net/sample/ui/fragment/LimitedTimeFragment.kt: -------------------------------------------------------------------------------- 1 | package com.drake.net.sample.ui.fragment 2 | 3 | import android.util.Log 4 | import android.view.View 5 | import com.drake.engine.base.EngineFragment 6 | import com.drake.net.Get 7 | import com.drake.net.sample.R 8 | import com.drake.net.sample.constants.Api 9 | import com.drake.net.sample.databinding.FragmentLimitedTimeBinding 10 | import com.drake.net.utils.scopeDialog 11 | import com.drake.tooltip.toast 12 | import kotlinx.coroutines.TimeoutCancellationException 13 | import kotlinx.coroutines.withTimeout 14 | 15 | class LimitedTimeFragment : EngineFragment(R.layout.fragment_limited_time) { 16 | override fun initView() { 17 | binding.v = this 18 | } 19 | 20 | override fun initData() { 21 | } 22 | 23 | override fun onClick(v: View) { 24 | scopeDialog { 25 | // 当接口请求在100毫秒内没有完成会抛出异常TimeoutCancellationException 26 | withTimeout(100) { 27 | Get(Api.TEXT).await() 28 | } 29 | }.catch { 30 | Log.e("日志", "catch", it) // catch无法接收到CancellationException异常 31 | }.finally { 32 | Log.e("日志", "finally", it) // TimeoutCancellationException属于CancellationException子类故只会被finally接收到 33 | if (it is TimeoutCancellationException) { 34 | toast("由于未在指定时间完成请求则取消请求") 35 | } 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /sample/src/main/java/com/drake/net/sample/ui/fragment/ParallelNetworkFragment.kt: -------------------------------------------------------------------------------- 1 | package com.drake.net.sample.ui.fragment 2 | 3 | import com.drake.engine.base.EngineFragment 4 | import com.drake.net.Get 5 | import com.drake.net.Post 6 | import com.drake.net.Trace 7 | import com.drake.net.sample.R 8 | import com.drake.net.sample.constants.Api 9 | import com.drake.net.sample.databinding.FragmentParallelNetworkBinding 10 | import com.drake.net.utils.scopeNetLife 11 | 12 | 13 | class ParallelNetworkFragment : 14 | EngineFragment(R.layout.fragment_parallel_network) { 15 | 16 | override fun initView() { 17 | scopeNetLife { 18 | 19 | // 同时发起三个请求 20 | val deferred = Get(Api.TEXT) 21 | val deferred1 = Post(Api.TEXT) 22 | val deferred2 = Trace(Api.TEXT) 23 | 24 | // 同时接收三个请求数据 25 | deferred.await() 26 | deferred1.await() 27 | deferred2.await() 28 | } 29 | } 30 | 31 | override fun initData() { 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /sample/src/main/java/com/drake/net/sample/ui/fragment/PreviewCacheFragment.kt: -------------------------------------------------------------------------------- 1 | package com.drake.net.sample.ui.fragment 2 | 3 | import android.util.Log 4 | import com.drake.engine.base.EngineFragment 5 | import com.drake.net.Get 6 | import com.drake.net.cache.CacheMode 7 | import com.drake.net.sample.R 8 | import com.drake.net.sample.constants.Api 9 | import com.drake.net.sample.databinding.FragmentReadCacheBinding 10 | import com.drake.net.utils.scopeNetLife 11 | 12 | 13 | /** 预览缓存. 其实不仅仅是双重加载缓存/网络也可以用于回退请求, 可以执行两次作用域并且忽略preview{}内的所有错误 */ 14 | class PreviewCacheFragment : EngineFragment(R.layout.fragment_read_cache) { 15 | 16 | override fun initView() { 17 | 18 | // 一般用于秒开首页或者回退加载数据. 我们可以在preview{}只加载缓存. 然后再执行scopeNetLife来请求网络, 做到缓存+网络双重加载的效果 19 | 20 | scopeNetLife { 21 | // 然后执行这里(网络请求) 22 | binding.tvFragment.text = Get(Api.TEXT) { 23 | setCacheMode(CacheMode.WRITE) 24 | }.await() 25 | Log.d("日志", "网络请求") 26 | }.preview(true) { 27 | // 先执行这里(仅读缓存), 任何异常都视为读取缓存失败 28 | binding.tvFragment.text = Get(Api.TEXT) { 29 | setCacheMode(CacheMode.READ) 30 | }.await() 31 | Log.d("日志", "读取缓存") 32 | } 33 | } 34 | 35 | override fun initData() { 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /sample/src/main/java/com/drake/net/sample/ui/fragment/PullRefreshFragment.kt: -------------------------------------------------------------------------------- 1 | package com.drake.net.sample.ui.fragment 2 | 3 | import com.drake.brv.utils.linear 4 | import com.drake.brv.utils.setup 5 | import com.drake.engine.base.EngineFragment 6 | import com.drake.net.Get 7 | import com.drake.net.sample.R 8 | import com.drake.net.sample.constants.Api 9 | import com.drake.net.sample.databinding.FragmentPullRefreshBinding 10 | import com.drake.net.sample.model.GameModel 11 | import com.drake.net.utils.scope 12 | 13 | 14 | class PullRefreshFragment : 15 | EngineFragment(R.layout.fragment_pull_refresh) { 16 | 17 | override fun initView() { 18 | binding.rv.linear().setup { 19 | addType(R.layout.item_pull_list) 20 | } 21 | 22 | binding.page.onRefresh { 23 | scope { 24 | val response = Get(String.format(Api.GAME, index)).await() 25 | addData(response.list) { 26 | itemCount < response.total 27 | } 28 | } 29 | }.autoRefresh() 30 | } 31 | 32 | override fun initData() { 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /sample/src/main/java/com/drake/net/sample/ui/fragment/PushRefreshFragment.kt: -------------------------------------------------------------------------------- 1 | package com.drake.net.sample.ui.fragment 2 | 3 | import com.drake.brv.utils.linear 4 | import com.drake.brv.utils.models 5 | import com.drake.brv.utils.setup 6 | import com.drake.engine.base.EngineFragment 7 | import com.drake.net.Get 8 | import com.drake.net.sample.R 9 | import com.drake.net.sample.constants.Api 10 | import com.drake.net.sample.databinding.FragmentPushRefreshBinding 11 | import com.drake.net.sample.model.GameModel 12 | import com.drake.net.utils.scope 13 | 14 | /** 本页面已禁用上拉加载(添加xml属性app:srlEnableLoadMore="false"), 只允许下拉刷新 */ 15 | class PushRefreshFragment : 16 | EngineFragment(R.layout.fragment_push_refresh) { 17 | 18 | override fun initView() { 19 | binding.rv.linear().setup { 20 | addType(R.layout.item_game) 21 | } 22 | 23 | binding.page.onRefresh { 24 | scope { 25 | binding.rv.models = Get(Api.GAME).await().list 26 | } 27 | // }.autoRefresh() // 首次下拉刷新 28 | }.showLoading() // 首次加载缺省页 29 | } 30 | 31 | override fun initData() { 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /sample/src/main/java/com/drake/net/sample/ui/fragment/ReadCacheFragment.kt: -------------------------------------------------------------------------------- 1 | package com.drake.net.sample.ui.fragment 2 | 3 | import com.drake.engine.base.EngineFragment 4 | import com.drake.net.Post 5 | import com.drake.net.cache.CacheMode 6 | import com.drake.net.sample.R 7 | import com.drake.net.sample.constants.Api 8 | import com.drake.net.sample.databinding.FragmentReadCacheBinding 9 | import com.drake.net.utils.scopeNetLife 10 | 11 | 12 | /** 13 | * 默认支持Http标准缓存协议 14 | * 如果需要自定义缓存模式来强制读写缓存,可以使用[CacheMode], 这会覆盖默认的Http标准缓存协议. 15 | * 可以缓存任何数据, 包括文件. 并且遵守LRU缓存策略限制最大缓存空间 16 | */ 17 | class ReadCacheFragment : EngineFragment(R.layout.fragment_read_cache) { 18 | 19 | override fun initView() { 20 | scopeNetLife { 21 | binding.tvFragment.text = 22 | Post(Api.TEXT) { 23 | setCacheMode(CacheMode.REQUEST_THEN_READ) // 请求网络失败会读取缓存, 请断网测试 24 | // setCacheKey("自定义缓存KEY") 25 | }.await() 26 | } 27 | } 28 | 29 | override fun initData() { 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /sample/src/main/java/com/drake/net/sample/ui/fragment/StateLayoutFragment.kt: -------------------------------------------------------------------------------- 1 | package com.drake.net.sample.ui.fragment 2 | 3 | import com.drake.engine.base.EngineFragment 4 | import com.drake.net.Get 5 | import com.drake.net.sample.R 6 | import com.drake.net.sample.constants.Api 7 | import com.drake.net.sample.databinding.FragmentStateLayoutBinding 8 | import com.drake.net.utils.scope 9 | 10 | 11 | class StateLayoutFragment : 12 | EngineFragment(R.layout.fragment_state_layout) { 13 | 14 | override fun initData() { 15 | } 16 | 17 | override fun initView() { 18 | binding.state.onRefresh { 19 | scope { 20 | binding.tvFragment.text = Get(Api.TEXT).await() 21 | } 22 | }.showLoading() 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /sample/src/main/java/com/drake/net/sample/ui/fragment/SuperIntervalFragment.kt: -------------------------------------------------------------------------------- 1 | package com.drake.net.sample.ui.fragment 2 | 3 | import android.view.Menu 4 | import android.view.MenuInflater 5 | import android.view.MenuItem 6 | import com.drake.engine.base.EngineFragment 7 | import com.drake.net.sample.R 8 | import com.drake.net.sample.databinding.FragmentSuperIntervalBinding 9 | import com.drake.net.time.Interval 10 | import java.util.concurrent.TimeUnit 11 | 12 | 13 | class SuperIntervalFragment : 14 | EngineFragment(R.layout.fragment_super_interval) { 15 | 16 | private lateinit var interval: Interval // 轮询器 17 | 18 | override fun initView() { 19 | setHasOptionsMenu(true) 20 | // 自定义计数器个数的轮询器, 当[start]]比[end]值大, 且end不等于-1时, 即为倒计时 21 | interval = Interval(0, 1, TimeUnit.SECONDS, 10).life(this) 22 | // interval = Interval(1, TimeUnit.SECONDS) // 每秒回调一次, 不会自动结束 23 | interval.subscribe { 24 | binding.tvFragment.text = it.toString() 25 | }.finish { 26 | binding.tvFragment.text = "计时完成" 27 | }.start() 28 | } 29 | 30 | override fun initData() { 31 | } 32 | 33 | override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { 34 | super.onCreateOptionsMenu(menu, inflater) 35 | inflater.inflate(R.menu.menu_interval, menu) 36 | } 37 | 38 | override fun onOptionsItemSelected(item: MenuItem): Boolean { 39 | when (item.itemId) { 40 | R.id.start -> interval.start() 41 | R.id.pause -> interval.pause() 42 | R.id.resume -> interval.resume() 43 | R.id.reset -> interval.reset() 44 | R.id.switch_interval -> interval.switch() 45 | R.id.stop -> interval.stop() 46 | R.id.cancel -> interval.cancel() 47 | } 48 | return super.onOptionsItemSelected(item) 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /sample/src/main/java/com/drake/net/sample/ui/fragment/SwitchDispatcherFragment.kt: -------------------------------------------------------------------------------- 1 | package com.drake.net.sample.ui.fragment 2 | 3 | import com.drake.engine.base.EngineFragment 4 | import com.drake.net.sample.R 5 | import com.drake.net.sample.databinding.FragmentSwitchDispatcherBinding 6 | import com.drake.net.utils.scopeLife 7 | import com.drake.net.utils.withIO 8 | import com.drake.net.utils.withMain 9 | import kotlinx.coroutines.launch 10 | 11 | 12 | class SwitchDispatcherFragment : 13 | EngineFragment(R.layout.fragment_switch_dispatcher) { 14 | 15 | override fun initView() { 16 | scopeLife { 17 | 18 | // 点击函数名查看更多相关函数 19 | launch { 20 | val data = withMain { 21 | "异步调度器切换到主线程" 22 | } 23 | } 24 | 25 | val data = withIO { 26 | "主线程切换到IO调度器" 27 | } 28 | } 29 | } 30 | 31 | override fun initData() { 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /sample/src/main/java/com/drake/net/sample/ui/fragment/SyncRequestFragment.kt: -------------------------------------------------------------------------------- 1 | package com.drake.net.sample.ui.fragment 2 | 3 | import com.drake.engine.base.EngineFragment 4 | import com.drake.net.Net 5 | import com.drake.net.sample.R 6 | import com.drake.net.sample.constants.Api 7 | import com.drake.net.sample.databinding.FragmentSyncRequestBinding 8 | import kotlin.concurrent.thread 9 | 10 | class SyncRequestFragment : 11 | EngineFragment(R.layout.fragment_sync_request) { 12 | 13 | override fun initView() { 14 | thread { // 网络请求不允许在主线程 15 | val result = try { 16 | Net.post(Api.TEXT).execute() 17 | } catch (e: Exception) { // 同步请求失败会导致崩溃要求捕获异常 18 | "请求错误 = ${e.message}" 19 | } 20 | 21 | // val result = Net.post(Api.BANNER).toResult().getOrDefault("请求发生错误, 我这是默认值") 22 | 23 | binding.tvFragment?.post { // 这里用?号是避免界面被销毁依然赋值 24 | binding.tvFragment?.text = result // view要求在主线程更新 25 | } 26 | } 27 | } 28 | 29 | override fun initData() { 30 | } 31 | } -------------------------------------------------------------------------------- /sample/src/main/java/com/drake/net/sample/ui/fragment/UniqueRequestFragment.kt: -------------------------------------------------------------------------------- 1 | package com.drake.net.sample.ui.fragment 2 | 3 | import android.util.Log 4 | import com.drake.engine.base.EngineFragment 5 | import com.drake.net.Post 6 | import com.drake.net.sample.R 7 | import com.drake.net.sample.constants.Api 8 | import com.drake.net.sample.databinding.FragmentUniqueRequestBinding 9 | import com.drake.net.scope.AndroidScope 10 | import com.drake.net.utils.scopeNetLife 11 | 12 | class UniqueRequestFragment : 13 | EngineFragment(R.layout.fragment_unique_request) { 14 | 15 | private var scope: AndroidScope? = null 16 | 17 | override fun initView() { 18 | binding.btnRequest.setOnClickListener { 19 | binding.tvResult.text = "请求中" 20 | scope?.cancel() // 如果存在则取消 21 | 22 | scope = scopeNetLife { 23 | val result = Post(Api.TEXT).await() 24 | Log.d("日志", "请求到结果") // 你一直重复点击"发起请求"按钮会发现永远无法拿到请求结果, 因为每次发起新的请求会取消未完成的 25 | binding.tvResult.text = result 26 | } 27 | } 28 | } 29 | 30 | override fun initData() { 31 | } 32 | } -------------------------------------------------------------------------------- /sample/src/main/java/com/drake/net/sample/ui/fragment/ViewModelRequestFragment.kt: -------------------------------------------------------------------------------- 1 | package com.drake.net.sample.ui.fragment 2 | 3 | import androidx.fragment.app.viewModels 4 | import com.drake.engine.base.EngineFragment 5 | import com.drake.net.sample.R 6 | import com.drake.net.sample.databinding.FragmentViewModelRequestBinding 7 | import com.drake.net.sample.utils.HttpUtils 8 | import com.drake.net.sample.vm.UserViewModel 9 | import com.drake.net.utils.scopeNetLife 10 | 11 | class ViewModelRequestFragment : 12 | EngineFragment(R.layout.fragment_view_model_request) { 13 | 14 | private val userViewModel: UserViewModel by viewModels() // 创建ViewModel 15 | 16 | override fun initView() { 17 | 18 | // 直接将用户信息绑定到视图上 19 | binding.lifecycleOwner = this 20 | binding.m = userViewModel 21 | 22 | // 动作开始拉取服务器数据 23 | binding.btnFetchUserinfo.setOnClickListener { 24 | userViewModel.fetchUserInfo() 25 | } 26 | } 27 | 28 | override fun initData() { 29 | 30 | scopeNetLife { 31 | val configAsync = HttpUtils.getConfigAsync(this) 32 | // 经常使用的请求可以封装函数 33 | val userInfo = HttpUtils.getUser() 34 | configAsync.await() // 实际上在getUser之前就发起请求, 此处只是等待结果, 这就是并发请求 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /sample/src/main/java/com/drake/net/sample/ui/fragment/converter/BaseConvertFragment.kt: -------------------------------------------------------------------------------- 1 | package com.drake.net.sample.ui.fragment.converter 2 | 3 | import android.os.Bundle 4 | import android.view.Menu 5 | import android.view.MenuInflater 6 | import android.view.MenuItem 7 | import androidx.annotation.LayoutRes 8 | import androidx.databinding.ViewDataBinding 9 | import androidx.navigation.fragment.findNavController 10 | import androidx.navigation.ui.onNavDestinationSelected 11 | import com.drake.engine.base.EngineFragment 12 | import com.drake.net.sample.R 13 | 14 | abstract class BaseConvertFragment(@LayoutRes contentLayoutId: Int = 0) : 15 | EngineFragment(contentLayoutId) { 16 | 17 | override fun onCreate(savedInstanceState: Bundle?) { 18 | super.onCreate(savedInstanceState) 19 | setHasOptionsMenu(true) 20 | } 21 | 22 | override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { 23 | super.onCreateOptionsMenu(menu, inflater) 24 | inflater.inflate(R.menu.menu_converter, menu) 25 | } 26 | 27 | override fun onOptionsItemSelected(item: MenuItem): Boolean { 28 | item.onNavDestinationSelected(findNavController()) 29 | return true 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /sample/src/main/java/com/drake/net/sample/ui/fragment/converter/FastJsonConvertFragment.kt: -------------------------------------------------------------------------------- 1 | package com.drake.net.sample.ui.fragment.converter 2 | 3 | import com.drake.net.Get 4 | import com.drake.net.sample.R 5 | import com.drake.net.sample.constants.Api 6 | import com.drake.net.sample.converter.FastJsonConverter 7 | import com.drake.net.sample.databinding.FragmentCustomConvertBinding 8 | import com.drake.net.sample.model.GameModel 9 | import com.drake.net.utils.scopeNetLife 10 | 11 | 12 | class FastJsonConvertFragment : 13 | BaseConvertFragment(R.layout.fragment_custom_convert) { 14 | 15 | override fun initView() { 16 | binding.tvConvertTip.text = """ 17 | 1. 阿里巴巴出品的Json解析库 18 | 2. 引入kotlin-reflect库可以支持kotlin默认值 19 | """.trimIndent() 20 | 21 | scopeNetLife { 22 | binding.tvFragment.text = Get(Api.GAME) { 23 | converter = FastJsonConverter() // 单例转换器, 此时会忽略全局转换器 24 | }.await().list[0].name 25 | } 26 | } 27 | 28 | override fun initData() { 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /sample/src/main/java/com/drake/net/sample/ui/fragment/converter/GsonConvertFragment.kt: -------------------------------------------------------------------------------- 1 | package com.drake.net.sample.ui.fragment.converter 2 | 3 | import com.drake.net.Get 4 | import com.drake.net.sample.R 5 | import com.drake.net.sample.constants.Api 6 | import com.drake.net.sample.converter.GsonConverter 7 | import com.drake.net.sample.databinding.FragmentCustomConvertBinding 8 | import com.drake.net.sample.model.GameModel 9 | import com.drake.net.utils.scopeNetLife 10 | 11 | 12 | class GsonConvertFragment : 13 | BaseConvertFragment(R.layout.fragment_custom_convert) { 14 | 15 | override fun initView() { 16 | binding.tvConvertTip.text = """ 17 | 1. Google官方出品 18 | 2. Json解析库Java上的老牌解析库 19 | 3. 不支持Kotlin构造参数默认值 20 | 4. 支持动态解析 21 | """.trimIndent() 22 | 23 | scopeNetLife { 24 | binding.tvFragment.text = Get(Api.GAME) { 25 | converter = GsonConverter() // 单例转换器, 此时会忽略全局转换器, 在Net中可以直接解析List等嵌套泛型数据 26 | }.await().list[0].name 27 | } 28 | } 29 | 30 | override fun initData() { 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /sample/src/main/java/com/drake/net/sample/ui/fragment/converter/MoshiConvertFragment.kt: -------------------------------------------------------------------------------- 1 | package com.drake.net.sample.ui.fragment.converter 2 | 3 | import com.drake.net.Get 4 | import com.drake.net.sample.R 5 | import com.drake.net.sample.constants.Api 6 | import com.drake.net.sample.converter.MoshiConverter 7 | import com.drake.net.sample.databinding.FragmentCustomConvertBinding 8 | import com.drake.net.sample.model.GameModel 9 | import com.drake.net.utils.scopeNetLife 10 | 11 | 12 | class MoshiConvertFragment : 13 | BaseConvertFragment(R.layout.fragment_custom_convert) { 14 | 15 | override fun initView() { 16 | binding.tvConvertTip.text = """ 17 | 1. Square出品的JSON解析库 18 | 2. 支持Kotlin构造默认值 19 | 3. 具备注解和反射两种使用方式 20 | 4. 非可选类型反序列化时赋值Null会抛出异常 21 | 5, 不支持动态解析 22 | """.trimIndent() 23 | 24 | scopeNetLife { 25 | binding.tvFragment.text = Get(Api.GAME) { 26 | converter = MoshiConverter() // 单例转换器, 此时会忽略全局转换器 27 | }.await().list[0].name 28 | } 29 | } 30 | 31 | override fun initData() { 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /sample/src/main/java/com/drake/net/sample/ui/fragment/converter/SerializationConvertFragment.kt: -------------------------------------------------------------------------------- 1 | package com.drake.net.sample.ui.fragment.converter 2 | 3 | import com.drake.net.Get 4 | import com.drake.net.sample.R 5 | import com.drake.net.sample.constants.Api 6 | import com.drake.net.sample.converter.SerializationConverter 7 | import com.drake.net.sample.databinding.FragmentCustomConvertBinding 8 | import com.drake.net.sample.model.GameModel 9 | import com.drake.net.utils.scopeNetLife 10 | 11 | class SerializationConvertFragment : 12 | BaseConvertFragment(R.layout.fragment_custom_convert) { 13 | 14 | override fun initView() { 15 | binding.tvConvertTip.text = """ 16 | 1. kotlin官方出品, 推荐使用 17 | 2. kotlinx.serialization 是Kotlin上是最完美的序列化工具 18 | 3. 支持多种反序列化数据类型Pair/枚举/Map... 19 | 4. 多配置选项 20 | 5. 支持动态解析 21 | 6. 支持ProtoBuf/CBOR/JSON等数据 22 | """.trimIndent() 23 | 24 | scopeNetLife { 25 | val data = Get(Api.GAME) { 26 | // 该转换器直接解析JSON中的data字段, 而非返回的整个JSON字符串 27 | converter = SerializationConverter() // 单例转换器, 此时会忽略全局转换器 28 | }.await() 29 | 30 | binding.tvFragment.text = data.list[0].name 31 | } 32 | } 33 | 34 | override fun initData() { 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /sample/src/main/java/com/drake/net/sample/utils/AESUtils.kt: -------------------------------------------------------------------------------- 1 | package com.drake.net.sample.utils 2 | 3 | import okio.ByteString 4 | import okio.ByteString.Companion.decodeHex 5 | import javax.crypto.Cipher 6 | import javax.crypto.spec.SecretKeySpec 7 | 8 | object AESUtils { 9 | 10 | private const val KEY = "123456789" 11 | private const val IV = "123456789" 12 | 13 | fun encrypt(data: String): String { 14 | val key = KEY.decodeHex() 15 | val iv = IV.decodeHex() 16 | val cipher = Cipher.getInstance("AES/CBC/PKCS5Padding") 17 | val keySpec = SecretKeySpec(key.toByteArray(), "AES") 18 | val ivSpec = javax.crypto.spec.IvParameterSpec(iv.toByteArray()) 19 | cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec) 20 | val encrypted = cipher.doFinal(data.toByteArray()) 21 | return ByteString.of(*encrypted).hex() 22 | } 23 | 24 | fun decrypt(data: String): String { 25 | val key = KEY.decodeHex() 26 | val iv = IV.decodeHex() 27 | val cipher = Cipher.getInstance("AES/CBC/PKCS5Padding") 28 | val keySpec = SecretKeySpec(key.toByteArray(), "AES") 29 | val ivSpec = javax.crypto.spec.IvParameterSpec(iv.toByteArray()) 30 | cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec) 31 | val encrypted = cipher.doFinal(data.decodeHex().toByteArray()) 32 | return String(encrypted) 33 | } 34 | } -------------------------------------------------------------------------------- /sample/src/main/java/com/drake/net/sample/utils/HttpUtils.kt: -------------------------------------------------------------------------------- 1 | package com.drake.net.sample.utils 2 | 3 | import com.drake.net.Get 4 | import com.drake.net.sample.constants.Api 5 | import com.drake.net.sample.model.ConfigModel 6 | import com.drake.net.sample.model.UserInfoModel 7 | import kotlinx.coroutines.CoroutineScope 8 | import kotlinx.coroutines.coroutineScope 9 | 10 | 11 | /** 12 | * 常用的请求方法建议写到一个工具类中 13 | */ 14 | object HttpUtils { 15 | 16 | /** 17 | * 获取配置信息 18 | * 19 | * 本方法需要再调用await()才会返回结果, 属于异步方法 20 | */ 21 | fun getConfigAsync(scope: CoroutineScope) = scope.Get(Api.CONFIG) 22 | 23 | /** 24 | * 获取用户信息 25 | * 阻塞返回可直接返回结果 26 | * 27 | * @param userId 如果为空表示请求自身用户信息 28 | */ 29 | suspend fun getUser(userId: String? = null) = coroutineScope { 30 | Get(Api.USER_INFO) { 31 | param("userId", userId) 32 | }.await() 33 | } 34 | 35 | } -------------------------------------------------------------------------------- /sample/src/main/java/com/drake/net/sample/vm/UserViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.drake.net.sample.vm 2 | 3 | import androidx.lifecycle.MutableLiveData 4 | import androidx.lifecycle.ViewModel 5 | import androidx.lifecycle.scopeNetLife 6 | import com.drake.net.Get 7 | import com.drake.net.sample.constants.Api 8 | import kotlinx.coroutines.CoroutineScope 9 | import kotlinx.coroutines.coroutineScope 10 | 11 | 12 | /** 13 | * 不要将请求结果抛来抛去, 增加代码复杂度 14 | */ 15 | class UserViewModel : ViewModel() { 16 | 17 | // 用户信息 18 | var userInfo: MutableLiveData = MutableLiveData() 19 | 20 | /** 21 | * 使用LiveData接受请求结果, 将该liveData直接使用DataBinding绑定到页面上, 会在请求成功自动更新视图 22 | */ 23 | fun fetchUserInfo() = scopeNetLife { 24 | userInfo.value = Get(Api.GAME).await() 25 | } 26 | 27 | /** 28 | * 开始非阻塞异步任务 29 | * 返回Deferred, 调用await()才会返回结果 30 | */ 31 | fun fetchList(scope: CoroutineScope) = scope.Get(Api.TEXT) 32 | 33 | /** 34 | * 开始阻塞异步任务 35 | * 直接返回结果 36 | */ 37 | suspend fun fetchPrecessData() = coroutineScope { 38 | val response = Get(Api.TEXT).await() 39 | response + "处理数据" 40 | } 41 | } -------------------------------------------------------------------------------- /sample/src/main/res/drawable/bg_card.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /sample/src/main/res/drawable/bg_empty.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liangjingkanji/Net/0d2ab60f67df6442c613d8055c59f3a60c524022/sample/src/main/res/drawable/bg_empty.webp -------------------------------------------------------------------------------- /sample/src/main/res/drawable/bg_error.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liangjingkanji/Net/0d2ab60f67df6442c613d8055c59f3a60c524022/sample/src/main/res/drawable/bg_error.webp -------------------------------------------------------------------------------- /sample/src/main/res/drawable/bg_input.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /sample/src/main/res/drawable/ic_async_task.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /sample/src/main/res/drawable/ic_callback_request.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /sample/src/main/res/drawable/ic_config_dialog.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /sample/src/main/res/drawable/ic_convert.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /sample/src/main/res/drawable/ic_debounce.xml: -------------------------------------------------------------------------------- 1 | 8 | 11 | 12 | -------------------------------------------------------------------------------- /sample/src/main/res/drawable/ic_download_file.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /sample/src/main/res/drawable/ic_error_handler.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /sample/src/main/res/drawable/ic_exception_trace.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /sample/src/main/res/drawable/ic_fastest.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /sample/src/main/res/drawable/ic_https.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /sample/src/main/res/drawable/ic_interceptor.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /sample/src/main/res/drawable/ic_interval.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /sample/src/main/res/drawable/ic_menu.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /sample/src/main/res/drawable/ic_parallel_network.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /sample/src/main/res/drawable/ic_pull_refresh.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /sample/src/main/res/drawable/ic_push_refresh.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /sample/src/main/res/drawable/ic_read_cache.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /sample/src/main/res/drawable/ic_scope.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /sample/src/main/res/drawable/ic_simple_request.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /sample/src/main/res/drawable/ic_state_layout.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /sample/src/main/res/drawable/ic_switch_dispatcher.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /sample/src/main/res/drawable/ic_sync_request.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /sample/src/main/res/drawable/ic_unique.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /sample/src/main/res/drawable/ic_upload_file.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /sample/src/main/res/drawable/ic_view_model.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /sample/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 11 | 12 | 13 | 17 | 18 | 25 | 26 | 33 | 34 | 35 | 36 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /sample/src/main/res/layout/fragment_async_task.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 11 | 12 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /sample/src/main/res/layout/fragment_auto_dialog.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 11 | 12 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /sample/src/main/res/layout/fragment_callback_request.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 10 | 11 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /sample/src/main/res/layout/fragment_coroutine_scope.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 11 | 12 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /sample/src/main/res/layout/fragment_custom_convert.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 11 | 12 | 21 | 22 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /sample/src/main/res/layout/fragment_download_file.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 12 | 13 | 27 | 28 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /sample/src/main/res/layout/fragment_edit_debounce.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 12 | 13 | 20 | 21 | 22 | 27 | 28 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /sample/src/main/res/layout/fragment_error_handler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 11 | 12 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /sample/src/main/res/layout/fragment_exception_trace.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 11 | 12 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /sample/src/main/res/layout/fragment_fastest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 11 | 12 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /sample/src/main/res/layout/fragment_https_certificate.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 12 | 13 |